From 92d7a01a526c19f19240827df06b87c0ddca4747 Mon Sep 17 00:00:00 2001 From: Olivier Poitrey Date: Tue, 4 Oct 2011 13:07:26 +0200 Subject: [PATCH] Perform image decoding/optimization in the IO thread instead of main thread for better responsiveness (fix #18) This new optimization is currently disabled by default so you can test it and give us feedback. To enable it, add #define ENABLE_SDWEBIMAGE_DECODER and to not forget to add SDWebImageDecoder class to your projet. Thanks to Adam Jernst (https://github.com/adamjernst) and James Tang (https://github.com/mystcolor) for this great optimization. See https://github.com/rs/SDWebImage/pull/18 for more info. --- SDImageCache.m | 12 ++++ SDWebImageDecoder.h | 35 ++++++++++++ SDWebImageDecoder.m | 124 +++++++++++++++++++++++++++++++++++++++++ SDWebImageDownloader.m | 20 +++++++ 4 files changed, 191 insertions(+) create mode 100644 SDWebImageDecoder.h create mode 100644 SDWebImageDecoder.m diff --git a/SDImageCache.m b/SDImageCache.m index 9dacb50..8d836b0 100644 --- a/SDImageCache.m +++ b/SDImageCache.m @@ -7,8 +7,13 @@ */ #import "SDImageCache.h" +#import "SDWebImageDecoder.h" #import +#ifdef ENABLE_SDWEBIMAGE_DECODER +#import "SDWebImageDecoder.h" +#endif + static NSInteger cacheMaxCacheAge = 60*60*24*7; // 1 week static SDImageCache *instance; @@ -173,6 +178,13 @@ static SDImageCache *instance; UIImage *image = [[[UIImage alloc] initWithContentsOfFile:[self cachePathForKey:key]] autorelease]; if (image) { +#ifdef ENABLE_SDWEBIMAGE_DECODER + UIImage *decodedImage = [UIImage decodedImageWithImage:image]; + if (decodedImage) + { + image = decodedImage; + } +#endif [mutableArguments setObject:image forKey:@"image"]; } diff --git a/SDWebImageDecoder.h b/SDWebImageDecoder.h new file mode 100644 index 0000000..026d60b --- /dev/null +++ b/SDWebImageDecoder.h @@ -0,0 +1,35 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * Created by james on 9/28/11. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import + +@protocol SDWebImageDecoderDelegate; + +@interface SDWebImageDecoder : NSObject +{ + NSOperationQueue *imageDecodingQueue; +} + ++ (SDWebImageDecoder *)sharedImageDecoder; +- (void)decodeImage:(UIImage *)image withDelegate:(id )delegate userInfo:(NSDictionary *)info; + +@end + +@protocol SDWebImageDecoderDelegate + +- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo; + +@end + +@interface UIImage (ForceDecode) + ++ (UIImage *)decodedImageWithImage:(UIImage *)image; + +@end \ No newline at end of file diff --git a/SDWebImageDecoder.m b/SDWebImageDecoder.m new file mode 100644 index 0000000..a9af798 --- /dev/null +++ b/SDWebImageDecoder.m @@ -0,0 +1,124 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * Created by james on 9/28/11. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageDecoder.h" + +#define DECOMPRESSED_IMAGE_KEY @"decompressedImage" +#define DECODE_INFO_KEY @"decodeInfo" + +#define IMAGE_KEY @"image" +#define DELEGATE_KEY @"delegate" +#define USER_INFO_KEY @"userInfo" + +@implementation SDWebImageDecoder +static SDWebImageDecoder *sharedInstance; + +- (void)notifyDelegateOnMainThreadWithInfo:(NSDictionary *)dict +{ + [dict retain]; + NSDictionary *decodeInfo = [dict objectForKey:DECODE_INFO_KEY]; + UIImage *decodedImage = [dict objectForKey:DECOMPRESSED_IMAGE_KEY]; + + id delegate = [decodeInfo objectForKey:DELEGATE_KEY]; + NSDictionary *userInfo = [decodeInfo objectForKey:USER_INFO_KEY]; + + [delegate imageDecoder:self didFinishDecodingImage:decodedImage userInfo:userInfo]; + [dict release]; +} + +- (void)decodeImageWithInfo:(NSDictionary *)decodeInfo +{ + UIImage *image = [decodeInfo objectForKey:IMAGE_KEY]; + + UIImage *decompressedImage = [UIImage decodedImageWithImage:image]; + if (!decompressedImage) + { + // If really have any error occurs, we use the original image at this moment + decompressedImage = image; + } + + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + decompressedImage, DECOMPRESSED_IMAGE_KEY, + decodeInfo, DECODE_INFO_KEY, nil]; + + [self performSelectorOnMainThread:@selector(notifyDelegateOnMainThreadWithInfo:) withObject:dict waitUntilDone:NO]; +} + +- (id)init +{ + if ((self = [super init])) + { + // Initialization code here. + imageDecodingQueue = [[NSOperationQueue alloc] init]; + } + + return self; +} + +- (void)decodeImage:(UIImage *)image withDelegate:(id)delegate userInfo:(NSDictionary *)info +{ + NSDictionary *decodeInfo = [NSDictionary dictionaryWithObjectsAndKeys: + image, IMAGE_KEY, + delegate, DELEGATE_KEY, + info, USER_INFO_KEY, nil]; + + NSOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(decodeImageWithInfo:) object:decodeInfo]; + [imageDecodingQueue addOperation:operation]; + [operation release]; +} + +- (void)dealloc +{ + [imageDecodingQueue release], imageDecodingQueue = nil; + [super dealloc]; +} + ++ (SDWebImageDecoder *)sharedImageDecoder +{ + if (!sharedInstance) + { + sharedInstance = [[SDWebImageDecoder alloc] init]; + } + return sharedInstance; +} + +@end + + +@implementation UIImage (ForceDecode) + ++ (UIImage *)decodedImageWithImage:(UIImage *)image +{ + CGImageRef imageRef = image.CGImage; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(NULL, + CGImageGetWidth(imageRef), + CGImageGetHeight(imageRef), + 8, + // Just always return width * 4 will be enough + CGImageGetWidth(imageRef) * 4, + // System only supports RGB, set explicitly + colorSpace, + // Makes system don't need to do extra conversion when displayed. + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); + CGColorSpaceRelease(colorSpace); + if (!context) return nil; + + CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}; + CGContextDrawImage(context, rect, imageRef); + CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); + CGContextRelease(context); + + UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef]; + CGImageRelease(decompressedImageRef); + return [decompressedImage autorelease]; +} + +@end diff --git a/SDWebImageDownloader.m b/SDWebImageDownloader.m index f6740cf..52d599c 100644 --- a/SDWebImageDownloader.m +++ b/SDWebImageDownloader.m @@ -8,6 +8,12 @@ #import "SDWebImageDownloader.h" +#ifdef ENABLE_SDWEBIMAGE_DECODER +#import "SDWebImageDecoder.h" +@interface SDWebImageDownloader (ImageDecoder) +@end +#endif + NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; @@ -120,7 +126,12 @@ NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNot if ([delegate respondsToSelector:@selector(imageDownloader:didFinishWithImage:)]) { UIImage *image = [[UIImage alloc] initWithData:imageData]; + +#ifdef ENABLE_SDWEBIMAGE_DECODER + [[SDWebImageDecoder sharedImageDecoder] decodeImage:image withDelegate:self userInfo:nil]; +#else [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image]; +#endif [image release]; } } @@ -138,6 +149,15 @@ NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNot self.imageData = nil; } +#pragma mark SDWebImageDecoderDelegate + +#ifdef ENABLE_SDWEBIMAGE_DECODER +- (void)imageDecoder:(SDWebImageDecoder *)decoder didFinishDecodingImage:(UIImage *)image userInfo:(NSDictionary *)userInfo +{ + [delegate performSelector:@selector(imageDownloader:didFinishWithImage:) withObject:self withObject:image]; +} +#endif + #pragma mark NSObject - (void)dealloc