From 855d457bcf265c35f6f5092861fdf4c17b866dbe Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 15 Jul 2016 14:51:58 -0700 Subject: [PATCH] Support varying pixel lengths (#213) * Add support for varying pixel lengths. On some devices the pixel length returned from CGImageCreate sadly isn't 4 bytes per pixel. Might be a good idea to support differing lengths. Encoding this into the format would be a good thing for sure. Anyway, that's what this does. * Add in methods for reading out bitsPerPixel too. --- Pod/Classes/PINAnimatedImage.h | 1 - Pod/Classes/PINAnimatedImage.m | 38 ++++++++++++++++----------- Pod/Classes/PINAnimatedImageManager.h | 2 ++ Pod/Classes/PINAnimatedImageManager.m | 19 ++++++++------ 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/Pod/Classes/PINAnimatedImage.h b/Pod/Classes/PINAnimatedImage.h index 3f8f23f..644a072 100644 --- a/Pod/Classes/PINAnimatedImage.h +++ b/Pod/Classes/PINAnimatedImage.h @@ -50,7 +50,6 @@ typedef NS_ENUM(NSUInteger, PINAnimatedImageStatus) { PINAnimatedImageStatusError, }; -extern const size_t kPINAnimatedImageComponentsPerPixel; extern const Float32 kPINAnimatedImageDefaultDuration; extern const Float32 kPINAnimatedImageMinimumDuration; extern const NSTimeInterval kPINAnimatedImageDisplayRefreshRate; diff --git a/Pod/Classes/PINAnimatedImage.m b/Pod/Classes/PINAnimatedImage.m index b6db6b0..8f9837e 100644 --- a/Pod/Classes/PINAnimatedImage.m +++ b/Pod/Classes/PINAnimatedImage.m @@ -16,7 +16,6 @@ NSString *kPINAnimatedImageErrorDomain = @"kPINAnimatedImageErrorDomain"; const Float32 kPINAnimatedImageDefaultDuration = 0.1; static const size_t kPINAnimatedImageBitsPerComponent = 8; -const size_t kPINAnimatedImageComponentsPerPixel = 4; const NSTimeInterval kPINAnimatedImageDisplayRefreshRate = 60.0; //http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser @@ -111,9 +110,9 @@ const Float32 kPINAnimatedImageMinimumDuration = 1 / kPINAnimatedImageDisplayRef }]; } -- (PINImage *)coverImageWithMemoryMap:(NSData *)memoryMap width:(UInt32)width height:(UInt32)height bitmapInfo:(CGBitmapInfo)bitmapInfo +- (PINImage *)coverImageWithMemoryMap:(NSData *)memoryMap width:(UInt32)width height:(UInt32)height bitsPerPixel:(UInt32)bitsPerPixel bitmapInfo:(CGBitmapInfo)bitmapInfo { - CGImageRef imageRef = [[self class] imageAtIndex:0 inMemoryMap:memoryMap width:width height:height bitmapInfo:bitmapInfo]; + CGImageRef imageRef = [[self class] imageAtIndex:0 inMemoryMap:memoryMap width:width height:height bitsPerPixel:bitsPerPixel bitmapInfo:bitmapInfo]; #if PIN_TARGET_IOS return [UIImage imageWithCGImage:imageRef]; #elif PIN_TARGET_MAC @@ -128,7 +127,7 @@ void releaseData(void *data, const void *imageData, size_t size) CFRelease(data); } -- (CGImageRef)imageAtIndex:(NSUInteger)index inSharedImageFiles:(NSArray *)imageFiles width:(UInt32)width height:(UInt32)height bitmapInfo:(CGBitmapInfo)bitmapInfo +- (CGImageRef)imageAtIndex:(NSUInteger)index inSharedImageFiles:(NSArray *)imageFiles width:(UInt32)width height:(UInt32)height bitsPerPixel:(UInt32)bitsPerPixel bitmapInfo:(CGBitmapInfo)bitmapInfo { if (self.status == PINAnimatedImageStatusError) { return nil; @@ -147,7 +146,7 @@ void releaseData(void *data, const void *imageData, size_t size) }]; }); }]; - return [[self class] imageAtIndex:index inMemoryMap:memoryMappedData width:width height:height bitmapInfo:bitmapInfo]; + return [[self class] imageAtIndex:index inMemoryMap:memoryMappedData width:width height:height bitsPerPixel:bitsPerPixel bitmapInfo:bitmapInfo]; } else { index -= imageFile.frameCount; } @@ -161,7 +160,7 @@ void releaseData(void *data, const void *imageData, size_t size) return self.durations[index]; } -+ (CGImageRef)imageAtIndex:(NSUInteger)index inMemoryMap:(NSData *)memoryMap width:(UInt32)width height:(UInt32)height bitmapInfo:(CGBitmapInfo)bitmapInfo ++ (CGImageRef)imageAtIndex:(NSUInteger)index inMemoryMap:(NSData *)memoryMap width:(UInt32)width height:(UInt32)height bitsPerPixel:(UInt32)bitsPerPixel bitmapInfo:(CGBitmapInfo)bitmapInfo { if (memoryMap == nil) { return nil; @@ -169,7 +168,7 @@ void releaseData(void *data, const void *imageData, size_t size) Float32 outDuration; - size_t imageLength = width * height * kPINAnimatedImageComponentsPerPixel; + const size_t imageLength = width * height * bitsPerPixel / 8; //frame duration + previous images NSUInteger offset = sizeof(UInt32) + (index * (imageLength + sizeof(outDuration))); @@ -183,13 +182,13 @@ void releaseData(void *data, const void *imageData, size_t size) //retain the memory map, it will be released when releaseData is called CFRetain((CFDataRef)memoryMap); - CGDataProviderRef dataProvider = CGDataProviderCreateWithData((void *)memoryMap, imageData, width * height * kPINAnimatedImageComponentsPerPixel, releaseData); + CGDataProviderRef dataProvider = CGDataProviderCreateWithData((void *)memoryMap, imageData, imageLength, releaseData); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGImageRef imageRef = CGImageCreate(width, height, kPINAnimatedImageBitsPerComponent, - kPINAnimatedImageBitsPerComponent * kPINAnimatedImageComponentsPerPixel, - kPINAnimatedImageComponentsPerPixel * width, + bitsPerPixel, + bitsPerPixel / 8 * width, colorSpace, bitmapInfo, dataProvider, @@ -221,17 +220,24 @@ void releaseData(void *data, const void *imageData, size_t size) return height; } ++ (UInt32)bitsPerPixelFromMemoryMap:(NSData *)memoryMap +{ + UInt32 bitsPerPixel; + [memoryMap getBytes:&bitsPerPixel range:NSMakeRange(10, sizeof(bitsPerPixel))]; + return bitsPerPixel; +} + + (UInt32)loopCountFromMemoryMap:(NSData *)memoryMap { UInt32 loopCount; - [memoryMap getBytes:&loopCount range:NSMakeRange(10, sizeof(loopCount))]; + [memoryMap getBytes:&loopCount range:NSMakeRange(14, sizeof(loopCount))]; return loopCount; } + (UInt32)frameCountFromMemoryMap:(NSData *)memoryMap { UInt32 frameCount; - [memoryMap getBytes:&frameCount range:NSMakeRange(14, sizeof(frameCount))]; + [memoryMap getBytes:&frameCount range:NSMakeRange(18, sizeof(frameCount))]; return frameCount; } @@ -239,7 +245,7 @@ void releaseData(void *data, const void *imageData, size_t size) + (Float32 *)createDurations:(Float32 *)durations fromMemoryMap:(NSData *)memoryMap frameCount:(UInt32)frameCount frameSize:(NSUInteger)frameSize totalDuration:(nonnull CFTimeInterval *)totalDuration { *totalDuration = 0; - [memoryMap getBytes:&durations range:NSMakeRange(18, sizeof(Float32) * frameCount)]; + [memoryMap getBytes:&durations range:NSMakeRange(22, sizeof(Float32) * frameCount)]; for (NSUInteger idx = 0; idx < frameCount; idx++) { *totalDuration += durations[idx]; @@ -303,6 +309,7 @@ void releaseData(void *data, const void *imageData, size_t size) inSharedImageFiles:self.sharedAnimatedImage.maps width:(UInt32)self.sharedAnimatedImage.width height:(UInt32)self.sharedAnimatedImage.height + bitsPerPixel:(UInt32)self.sharedAnimatedImage.bitsPerPixel bitmapInfo:self.sharedAnimatedImage.bitmapInfo]; } @@ -392,7 +399,7 @@ static NSUInteger gcd(NSUInteger a, NSUInteger b) return self; } -- (void)setInfoProcessedWithCoverImage:(PINImage *)coverImage UUID:(NSUUID *)UUID durations:(Float32 *)durations totalDuration:(CFTimeInterval)totalDuration loopCount:(size_t)loopCount frameCount:(size_t)frameCount width:(size_t)width height:(size_t)height bitmapInfo:(CGBitmapInfo)bitmapInfo +- (void)setInfoProcessedWithCoverImage:(PINImage *)coverImage UUID:(NSUUID *)UUID durations:(Float32 *)durations totalDuration:(CFTimeInterval)totalDuration loopCount:(size_t)loopCount frameCount:(size_t)frameCount width:(size_t)width height:(size_t)height bitsPerPixel:(size_t)bitsPerPixel bitmapInfo:(CGBitmapInfo)bitmapInfo { NSAssert(_status == PINAnimatedImageStatusUnprocessed, @"Status should be unprocessed."); [_coverImageLock lockWithBlock:^{ @@ -406,6 +413,7 @@ static NSUInteger gcd(NSUInteger a, NSUInteger b) _frameCount = frameCount; _width = width; _height = height; + _bitsPerPixel = bitsPerPixel; _bitmapInfo = bitmapInfo; _status = PINAnimatedImageStatusInfoProcessed; } @@ -438,7 +446,7 @@ static NSUInteger gcd(NSUInteger a, NSUInteger b) __block PINImage *coverImage = nil; [_coverImageLock lockWithBlock:^{ if (_coverImage == nil) { - CGImageRef imageRef = [PINAnimatedImage imageAtIndex:0 inMemoryMap:self.maps[0].memoryMappedData width:(UInt32)self.width height:(UInt32)self.height bitmapInfo:self.bitmapInfo]; + CGImageRef imageRef = [PINAnimatedImage imageAtIndex:0 inMemoryMap:self.maps[0].memoryMappedData width:(UInt32)self.width height:(UInt32)self.height bitsPerPixel:(UInt32)self.bitsPerPixel bitmapInfo:self.bitmapInfo]; #if PIN_TARGET_IOS coverImage = [UIImage imageWithCGImage:imageRef]; #elif PIN_TARGET_MAC diff --git a/Pod/Classes/PINAnimatedImageManager.h b/Pod/Classes/PINAnimatedImageManager.h index 1c7b7be..0e7cff3 100644 --- a/Pod/Classes/PINAnimatedImageManager.h +++ b/Pod/Classes/PINAnimatedImageManager.h @@ -53,6 +53,7 @@ typedef void(^PINAnimatedImageDecodedPath)(BOOL finished, NSString *path, NSErro frameCount:(size_t)frameCount width:(size_t)width height:(size_t)height + bitsPerPixel:(size_t)bitsPerPixel bitmapInfo:(CGBitmapInfo)bitmapInfo; @property (nonatomic, readonly) NSUUID *UUID; @@ -62,6 +63,7 @@ typedef void(^PINAnimatedImageDecodedPath)(BOOL finished, NSString *path, NSErro @property (nonatomic, readonly) size_t frameCount; @property (nonatomic, readonly) size_t width; @property (nonatomic, readonly) size_t height; +@property (nonatomic, readonly) size_t bitsPerPixel; @property (nonatomic, readonly) CGBitmapInfo bitmapInfo; @end diff --git a/Pod/Classes/PINAnimatedImageManager.m b/Pod/Classes/PINAnimatedImageManager.m index 55631bd..166df60 100644 --- a/Pod/Classes/PINAnimatedImageManager.m +++ b/Pod/Classes/PINAnimatedImageManager.m @@ -20,7 +20,7 @@ static const NSUInteger maxFileSize = 50000000; //max file size in bytes static const Float32 maxFileDuration = 1; //max duration of a file in seconds -typedef void(^PINAnimatedImageInfoProcessed)(PINImage *coverImage, NSUUID *UUID, Float32 *durations, CFTimeInterval totalDuration, size_t loopCount, size_t frameCount, size_t width, size_t height, UInt32 bitmapInfo); +typedef void(^PINAnimatedImageInfoProcessed)(PINImage *coverImage, NSUUID *UUID, Float32 *durations, CFTimeInterval totalDuration, size_t loopCount, size_t frameCount, size_t width, size_t height, size_t bitsPerPixel, UInt32 bitmapInfo); BOOL PINStatusCoverImageCompleted(PINAnimatedImageStatus status); BOOL PINStatusCoverImageCompleted(PINAnimatedImageStatus status) { @@ -166,12 +166,12 @@ static dispatch_once_t startupCleanupOnce; if (startProcessing) { dispatch_async(self.serialProcessingQueue, ^{ - [[self class] processAnimatedImage:animatedImageData temporaryDirectory:[PINAnimatedImageManager temporaryDirectory] infoCompletion:^(PINImage *coverImage, NSUUID *UUID, Float32 *durations, CFTimeInterval totalDuration, size_t loopCount, size_t frameCount, size_t width, size_t height, UInt32 bitmapInfo) { + [[self class] processAnimatedImage:animatedImageData temporaryDirectory:[PINAnimatedImageManager temporaryDirectory] infoCompletion:^(PINImage *coverImage, NSUUID *UUID, Float32 *durations, CFTimeInterval totalDuration, size_t loopCount, size_t frameCount, size_t width, size_t height, size_t bitsPerPixel, UInt32 bitmapInfo) { __block NSArray *infoCompletions = nil; __block PINSharedAnimatedImage *sharedAnimatedImage = nil; [_lock lockWithBlock:^{ sharedAnimatedImage = [self.animatedImages objectForKey:animatedImageData]; - [sharedAnimatedImage setInfoProcessedWithCoverImage:coverImage UUID:UUID durations:durations totalDuration:totalDuration loopCount:loopCount frameCount:frameCount width:width height:height bitmapInfo:bitmapInfo]; + [sharedAnimatedImage setInfoProcessedWithCoverImage:coverImage UUID:UUID durations:durations totalDuration:totalDuration loopCount:loopCount frameCount:frameCount width:width height:height bitsPerPixel:bitsPerPixel bitmapInfo:bitmapInfo]; infoCompletions = sharedAnimatedImage.infoCompletions; sharedAnimatedImage.infoCompletions = @[]; }]; @@ -252,6 +252,7 @@ ERROR;}) \ HANDLE_PROCESSING_ERROR(fileHandleError); UInt32 width; UInt32 height; + UInt32 bitsPerPixel; UInt32 bitmapInfo; NSUInteger fileCount = 0; UInt32 frameCountForFile = 0; @@ -295,6 +296,7 @@ ERROR;}) \ width = (UInt32)CGImageGetWidth(frameImage); height = (UInt32)CGImageGetHeight(frameImage); + bitsPerPixel = (UInt32)CGImageGetBitsPerPixel(frameImage); #if PIN_TARGET_IOS coverImage = [UIImage imageWithCGImage:frameImage]; @@ -312,13 +314,13 @@ ERROR;}) \ if (PROCESSING_ERROR == nil) { //Get size, write file header get coverImage dispatch_group_async(diskGroup, diskWriteQueue, ^{ - NSError *fileHeaderError = [self writeFileHeader:fileHandle width:width height:height loopCount:loopCount frameCount:frameCount bitmapInfo:bitmapInfo durations:durations]; + NSError *fileHeaderError = [self writeFileHeader:fileHandle width:width height:height bitsPerPixel:bitsPerPixel loopCount:loopCount frameCount:frameCount bitmapInfo:bitmapInfo durations:durations]; HANDLE_PROCESSING_ERROR(fileHeaderError); if (fileHeaderError == nil) { [fileHandle closeFile]; PINLog(@"notifying info"); - infoCompletion(coverImage, UUID, durations, totalDuration, loopCount, frameCount, width, height, bitmapInfo); + infoCompletion(coverImage, UUID, durations, totalDuration, loopCount, frameCount, width, height, bitsPerPixel, bitmapInfo); } }); fileCount = 1; @@ -396,7 +398,7 @@ ERROR;}) \ } NSData *frameData = (__bridge_transfer NSData *)CGDataProviderCopyData(CGImageGetDataProvider(frameImage)); - NSAssert(frameData.length == width * height * kPINAnimatedImageComponentsPerPixel, @"data should be width * height * 4 bytes"); + NSAssert(frameData.length == width * height * bitsPerPixel / 8, @"data should be width * height * bytes per pixel"); NSError *frameWriteError = [self writeFrameToFile:fileHandle duration:duration frameData:frameData]; HANDLE_PROCESSING_ERROR(frameWriteError); @@ -517,14 +519,15 @@ ERROR;}) \ */ -+ (NSError *)writeFileHeader:(NSFileHandle *)fileHandle width:(UInt32)width height:(UInt32)height loopCount:(UInt32)loopCount frameCount:(UInt32)frameCount bitmapInfo:(UInt32)bitmapInfo durations:(Float32*)durations ++ (NSError *)writeFileHeader:(NSFileHandle *)fileHandle width:(UInt32)width height:(UInt32)height bitsPerPixel:(UInt32)bitsPerPixel loopCount:(UInt32)loopCount frameCount:(UInt32)frameCount bitmapInfo:(UInt32)bitmapInfo durations:(Float32*)durations { NSError *error = nil; @try { - UInt16 version = 1; + UInt16 version = 2; [fileHandle writeData:[NSData dataWithBytes:&version length:sizeof(version)]]; [fileHandle writeData:[NSData dataWithBytes:&width length:sizeof(width)]]; [fileHandle writeData:[NSData dataWithBytes:&height length:sizeof(height)]]; + [fileHandle writeData:[NSData dataWithBytes:&bitsPerPixel length:sizeof(bitsPerPixel)]]; [fileHandle writeData:[NSData dataWithBytes:&loopCount length:sizeof(loopCount)]]; [fileHandle writeData:[NSData dataWithBytes:&frameCount length:sizeof(frameCount)]]; [fileHandle writeData:[NSData dataWithBytes:&bitmapInfo length:sizeof(bitmapInfo)]];