Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added hooks for additional image formats (including GIFs) #81

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions Haneke/HNKCache.h 100644 → 100755
Expand Up @@ -234,6 +234,19 @@ typedef NS_ENUM(NSInteger, HNKPreloadPolicy)
*/
@property (nonatomic, copy) UIImage* (^postResizeBlock)(NSString *key, UIImage *image);

/**
This block alows the custom serialization for images. This is useful if you want to use none native image formates.
@warning The block will impacat the performace of the cache.
@warning The block will be called when saving images to disk.
*/
@property (nonatomic, copy) NSData* (^serializeImageBlock)(NSString *key, UIImage *image);

/**
This block allows the option of deserializing none standard image formatsm like GIFs.
@warning The block will only be run when deserializing data rom disk. To uport custion formats you will also need a fetcher that can handle the format.
*/
@property (nonatomic, copy) UIImage* (^deserializeImageBlock)(NSString *key, NSData *data);

/** Initializes a format with the given name.
@param name Name of the format.
*/
Expand Down
163 changes: 92 additions & 71 deletions Haneke/HNKCache.m
Expand Up @@ -169,7 +169,7 @@ - (BOOL)fetchImageForKey:(NSString*)key formatName:(NSString *)formatName succes

[format.diskCache fetchDataForKey:key success:^(NSData *data) {
HanekeLog(@"Disk cache hit: %@/%@", formatName, key.lastPathComponent);
UIImage *image = [UIImage imageWithData:data];
UIImage *image = format.deserializeImageBlock ? format.deserializeImageBlock(key, data) : [UIImage imageWithData:data];
if (image)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Expand Down Expand Up @@ -338,7 +338,7 @@ - (void)enumeratePreloadImagesOfFormat:(HNKCacheFormat*)format usingBlock:(void(
}

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:data];
UIImage *image = format.deserializeImageBlock ? format.deserializeImageBlock(key, data) : [UIImage imageWithData:data];
if (!image) return;
image = [image hnk_decompressedImage];
dispatch_async(dispatch_get_main_queue(), ^{
Expand All @@ -354,7 +354,7 @@ - (void)setDiskImage:(UIImage*)image forKey:(NSString*)key format:(HNKCacheForma
{
if (format.diskCapacity == 0) return;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [image hnk_dataWithCompressionQuality:format.compressionQuality];
NSData *data = format.serializeImageBlock ? format.serializeImageBlock(key, image) : [image hnk_dataWithCompressionQuality:format.compressionQuality];
dispatch_async(dispatch_get_main_queue(), ^{
[format.diskCache setData:data forKey:key];
});
Expand All @@ -369,7 +369,7 @@ - (void)setDiskImage:(UIImage*)image forKey:(NSString*)key format:(HNKCacheForma
- (void)updateAccessDateOfImage:(UIImage*)image key:(NSString*)key format:(HNKCacheFormat*)format
{
[format.diskCache updateAccessDateForKey:key data:^NSData *{
NSData *data = [image hnk_dataWithCompressionQuality:format.compressionQuality];
NSData *data = format.serializeImageBlock ? format.serializeImageBlock(key, image) : [image hnk_dataWithCompressionQuality:format.compressionQuality];
return data;
}];
}
Expand Down Expand Up @@ -460,6 +460,22 @@ - (NSString*)directory

@implementation UIImage (hnk_utils)

- (UIImage *)hnk_alterSubimages:(UIImage *(^)(UIImage *original))modifierBlock
{
if (self.images.count > 1)
{
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:self.images.count];
for (UIImage *frame in self.images) {
[frames addObject:modifierBlock(frame)];
}
return [UIImage animatedImageWithImages:[frames copy] duration:self.duration];
}
else
{
return modifierBlock(self);
}
}

- (CGSize)hnk_aspectFillSizeForSize:(CGSize)size
{
const CGFloat scaleWidth = size.width / self.size.width;
Expand Down Expand Up @@ -496,68 +512,71 @@ - (CGSize)hnk_aspectFitSizeForSize:(CGSize)size

- (UIImage *)hnk_decompressedImage;
{
// Ideally we would simply use kCGImageSourceShouldCacheImmediately but as of iOS 7.1 it locks on copyImageBlockSetJPEG which makes it dangerous.
// const CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
// CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES});

CGImageRef originalImageRef = self.CGImage;
const CGBitmapInfo originalBitmapInfo = CGImageGetBitmapInfo(originalImageRef);

// See: http://stackoverflow.com/questions/23723564/which-cgimagealphainfo-should-we-use
const uint32_t alphaInfo = (originalBitmapInfo & kCGBitmapAlphaInfoMask);
CGBitmapInfo bitmapInfo = originalBitmapInfo;
switch (alphaInfo)
{
case kCGImageAlphaNone:
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
break;
case kCGImageAlphaPremultipliedFirst:
case kCGImageAlphaPremultipliedLast:
case kCGImageAlphaNoneSkipFirst:
case kCGImageAlphaNoneSkipLast:
break;
case kCGImageAlphaOnly:
case kCGImageAlphaLast:
case kCGImageAlphaFirst:
{ // Unsupported
return self;
return [self hnk_alterSubimages:^UIImage *(UIImage *original) {

// Ideally we would simply use kCGImageSourceShouldCacheImmediately but as of iOS 7.1 it locks on copyImageBlockSetJPEG which makes it dangerous.
// const CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
// CGImageRef imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)@{(id)kCGImageSourceShouldCacheImmediately: @YES});

CGImageRef originalImageRef = original.CGImage;
const CGBitmapInfo originalBitmapInfo = CGImageGetBitmapInfo(originalImageRef);

// See: http://stackoverflow.com/questions/23723564/which-cgimagealphainfo-should-we-use
const uint32_t alphaInfo = (originalBitmapInfo & kCGBitmapAlphaInfoMask);
CGBitmapInfo bitmapInfo = originalBitmapInfo;
switch (alphaInfo)
{
case kCGImageAlphaNone:
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
break;
case kCGImageAlphaPremultipliedFirst:
case kCGImageAlphaPremultipliedLast:
case kCGImageAlphaNoneSkipFirst:
case kCGImageAlphaNoneSkipLast:
break;
case kCGImageAlphaOnly:
case kCGImageAlphaLast:
case kCGImageAlphaFirst:
{ // Unsupported
return original;
}
break;
}
break;
}

const CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
const CGSize pixelSize = CGSizeMake(self.size.width * self.scale, self.size.height * self.scale);
const CGContextRef context = CGBitmapContextCreate(NULL,
pixelSize.width,
pixelSize.height,
CGImageGetBitsPerComponent(originalImageRef),
0,
colorSpace,
bitmapInfo);
CGColorSpaceRelease(colorSpace);

UIImage *image;
if (!context) return self;

const CGRect imageRect = CGRectMake(0, 0, pixelSize.width, pixelSize.height);
UIGraphicsPushContext(context);

// Flip coordinate system. See: http://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage
CGContextTranslateCTM(context, 0, pixelSize.height);
CGContextScaleCTM(context, 1.0, -1.0);

// UIImage and drawInRect takes into account image orientation, unlike CGContextDrawImage.
[self drawInRect:imageRect];
UIGraphicsPopContext();
const CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);

const CGFloat scale = [UIScreen mainScreen].scale;
image = [UIImage imageWithCGImage:decompressedImageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(decompressedImageRef);

return image;

const CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
const CGSize pixelSize = CGSizeMake(original.size.width * original.scale, original.size.height * original.scale);
const CGContextRef context = CGBitmapContextCreate(NULL,
pixelSize.width,
pixelSize.height,
CGImageGetBitsPerComponent(originalImageRef),
0,
colorSpace,
bitmapInfo);
CGColorSpaceRelease(colorSpace);

UIImage *image;
if (!context) return original;

const CGRect imageRect = CGRectMake(0, 0, pixelSize.width, pixelSize.height);
UIGraphicsPushContext(context);

// Flip coordinate system. See: http://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage
CGContextTranslateCTM(context, 0, pixelSize.height);
CGContextScaleCTM(context, 1.0, -1.0);

// UIImage and drawInRect takes into account image orientation, unlike CGContextDrawImage.
[original drawInRect:imageRect];
UIGraphicsPopContext();
const CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);

const CGFloat scale = [UIScreen mainScreen].scale;
image = [UIImage imageWithCGImage:decompressedImageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(decompressedImageRef);

return image;
}];
}

- (BOOL)hnk_hasAlpha
Expand All @@ -571,12 +590,14 @@ - (BOOL)hnk_hasAlpha

- (UIImage *)hnk_imageByScalingToSize:(CGSize)newSize
{
const BOOL hasAlpha = [self hnk_hasAlpha];
UIGraphicsBeginImageContextWithOptions(newSize, !hasAlpha, 0.0);
[self drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
return [self hnk_alterSubimages:^UIImage *(UIImage *original) {
const BOOL hasAlpha = [original hnk_hasAlpha];
UIGraphicsBeginImageContextWithOptions(newSize, !hasAlpha, 0.0);
[original drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}];
}

@end
7 changes: 7 additions & 0 deletions Haneke/HNKNetworkFetcher.h
Expand Up @@ -62,4 +62,11 @@ enum
*/
@property (nonatomic, readonly) NSURLSession *URLSession;

/**
Used to suport addional imagetypes.
@param data Image data
@return A IIImage or nil
*/
- (UIImage *)imageFromData:(NSData *)data;

@end
7 changes: 6 additions & 1 deletion Haneke/HNKNetworkFetcher.m
Expand Up @@ -92,7 +92,7 @@ - (void)fetchImageWithSuccess:(void (^)(UIImage *image))successBlock failure:(vo
}
}

UIImage *image = [UIImage imageWithData:data];
UIImage *image = [self imageFromData:data];

if (!image)
{
Expand Down Expand Up @@ -143,4 +143,9 @@ - (NSURLSession*)URLSession
return [NSURLSession sharedSession];
}

- (UIImage *)imageFromData:(NSData *)data
{
return [UIImage imageWithData:data];
}

@end