From b201e137d8a3d5568b57a78f8568f60384b613e4 Mon Sep 17 00:00:00 2001 From: Bogdan Poplauschi Date: Tue, 10 Dec 2013 15:40:19 +0200 Subject: [PATCH] Added PNG detection to be able to determine at runtime if a downloaded image should be saved as PNG or as JPEG --- SDWebImage/SDImageCache.h | 3 +- SDWebImage/SDImageCache.m | 88 +++++++++++++++++++++++++--------- SDWebImage/SDWebImageManager.m | 6 +-- 3 files changed, 70 insertions(+), 27 deletions(-) diff --git a/SDWebImage/SDImageCache.h b/SDWebImage/SDImageCache.h index 7a76ca1..4ed19f2 100644 --- a/SDWebImage/SDImageCache.h +++ b/SDWebImage/SDImageCache.h @@ -90,13 +90,14 @@ typedef enum SDImageCacheType SDImageCacheType; * Store an image into memory and optionally disk cache at the given key. * * @param image The image to store + * @param recalculate BOOL indicates if imageData can be used or a new data should be constructed from the UIImage * @param data The image data as returned by the server, this representation will be used for disk storage * instead of converting the given image object into a storable/compressed image format in order * to save quality and CPU * @param key The unique image cache key, usually it's image absolute URL * @param toDisk Store the image to disk cache if YES */ -- (void)storeImage:(UIImage *)image imageData:(NSData *)data forKey:(NSString *)key toDisk:(BOOL)toDisk; +- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk; /** * Query the disk cache asynchronously. diff --git a/SDWebImage/SDImageCache.m b/SDWebImage/SDImageCache.m index 8ffed7b..91e2ec6 100644 --- a/SDWebImage/SDImageCache.m +++ b/SDWebImage/SDImageCache.m @@ -14,6 +14,24 @@ #import static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week +// PNG signature bytes and data (below) +static unsigned char kPNGSignatureBytes[8] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; +static NSData *kPNGSignatureData = nil; + +BOOL ImageDataHasPNGPreffix(NSData *data); +BOOL ImageDataHasPNGPreffix(NSData *data) +{ + NSUInteger pngSignatureLength = [kPNGSignatureData length]; + if ([data length] >= pngSignatureLength) + { + if ([[data subdataWithRange:NSMakeRange(0, pngSignatureLength)] isEqualToData:kPNGSignatureData]) + { + return YES; + } + } + + return NO; +} @interface SDImageCache () @@ -25,7 +43,8 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week @end -@implementation SDImageCache { +@implementation SDImageCache +{ NSFileManager *_fileManager; } @@ -33,7 +52,11 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { static dispatch_once_t once; static id instance; - dispatch_once(&once, ^{instance = self.new;}); + dispatch_once(&once, ^ + { + instance = self.new; + kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8]; + }); return instance; } @@ -66,7 +89,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { _fileManager = NSFileManager.new; }); - + #if TARGET_OS_IPHONE // Subscribe to app events [[NSNotificationCenter defaultCenter] addObserver:self @@ -78,7 +101,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week selector:@selector(cleanDisk) name:UIApplicationWillTerminateNotification object:nil]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundCleanDisk) name:UIApplicationDidEnterBackgroundNotification @@ -138,7 +161,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week #pragma mark ImageCache -- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk +- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk { if (!image || !key) { @@ -153,16 +176,35 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { NSData *data = imageData; - if (!data) + if (image && (recalculate || !data)) { - if (image) - { #if TARGET_OS_IPHONE - data = UIImagePNGRepresentation(image); -#else - data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil]; -#endif + // We need to determine if the image is a PNG or a JPEG + // PNGs are easier to detect because they have a unique signature (http://www.w3.org/TR/PNG-Structure.html) + // The first eight bytes of a PNG file always contain the following (decimal) values: + // 137 80 78 71 13 10 26 10 + + // We assume the image is PNG, in case the imageData is nil (i.e. if trying to save a UIImage directly), + // we will consider it PNG to avoid loosing the transparency + BOOL imageIsPng = YES; + + // But if we have an image data, we will look at the preffix + if ([imageData length] >= [kPNGSignatureData length]) + { + imageIsPng = ImageDataHasPNGPreffix(imageData); } + + if (imageIsPng) + { + data = UIImagePNGRepresentation(image); + } + else + { + data = UIImageJPEGRepresentation(image, (CGFloat)1.0); + } +#else + data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil]; +#endif } if (data) @@ -183,12 +225,12 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week - (void)storeImage:(UIImage *)image forKey:(NSString *)key { - [self storeImage:image imageData:nil forKey:key toDisk:YES]; + [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:YES]; } - (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk { - [self storeImage:image imageData:nil forKey:key toDisk:toDisk]; + [self storeImage:image recalculateFromImage:YES imageData:nil forKey:key toDisk:toDisk]; } - (BOOL)diskImageExistsWithKey:(NSString *)key @@ -198,7 +240,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]]; }); - + return exists; } @@ -215,7 +257,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { return image; } - + // Second check the disk cache... UIImage *diskImage = [self diskImageForKey:key]; if (diskImage) @@ -223,7 +265,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week CGFloat cost = diskImage.size.height * diskImage.size.width * diskImage.scale; [self.memCache setObject:diskImage forKey:key cost:cost]; } - + return diskImage; } @@ -272,7 +314,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(void (^)(UIImage *image, SDImageCacheType cacheType))doneBlock { NSOperation *operation = NSOperation.new; - + if (!doneBlock) return nil; if (!key) @@ -295,7 +337,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { return; } - + @autoreleasepool { UIImage *diskImage = [self diskImageForKey:key]; @@ -311,7 +353,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week }); } }); - + return operation; } @@ -454,13 +496,13 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }]; - + // Start the long-running task and return immediately. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { // Do the work associated with the task, preferably in chunks. [self cleanDisk]; - + [application endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; }); @@ -487,7 +529,7 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week { count += 1; } - + return count; } diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 34193b6..e1ab9d1 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -199,8 +199,8 @@ if (transformedImage && finished) { - NSData *dataToStore = [transformedImage isEqual:downloadedImage] ? data : nil; - [self.imageCache storeImage:transformedImage imageData:dataToStore forKey:key toDisk:cacheOnDisk]; + BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage]; + [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:data forKey:key toDisk:cacheOnDisk]; } }); } @@ -213,7 +213,7 @@ if (downloadedImage && finished) { - [self.imageCache storeImage:downloadedImage imageData:data forKey:key toDisk:cacheOnDisk]; + [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk]; } } }