Added PNG detection to be able to determine at runtime if a downloaded image should be saved as PNG or as JPEG

This commit is contained in:
Bogdan Poplauschi 2013-12-10 15:40:19 +02:00 committed by Olivier Poitrey
parent 3380e56a7d
commit b201e137d8
3 changed files with 70 additions and 27 deletions

View File

@ -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.

View File

@ -14,6 +14,24 @@
#import <mach/mach_host.h>
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;
}

View File

@ -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];
}
}
}