From f0732d704cb315cc1a3e208cf5e71df3697e9251 Mon Sep 17 00:00:00 2001 From: gsempe Date: Wed, 18 Jun 2014 20:02:30 +0200 Subject: [PATCH] Add option to scale down large images on iOS Option is SDWebImageScaleDownLargeImage --- SDWebImage/SDWebImageDecoder.h | 2 + SDWebImage/SDWebImageDecoder.m | 57 +++++++++++++++++----- SDWebImage/SDWebImageDownloader.h | 6 ++- SDWebImage/SDWebImageDownloaderOperation.m | 8 +++ SDWebImage/SDWebImageManager.h | 10 +++- SDWebImage/SDWebImageManager.m | 5 ++ 6 files changed, 75 insertions(+), 13 deletions(-) diff --git a/SDWebImage/SDWebImageDecoder.h b/SDWebImage/SDWebImageDecoder.h index 0176a7ba..1ab5752e 100644 --- a/SDWebImage/SDWebImageDecoder.h +++ b/SDWebImage/SDWebImageDecoder.h @@ -15,4 +15,6 @@ + (UIImage *)decodedImageWithImage:(UIImage *)image; ++ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image; + @end diff --git a/SDWebImage/SDWebImageDecoder.m b/SDWebImage/SDWebImageDecoder.m index 79ddb30f..271c040e 100644 --- a/SDWebImage/SDWebImageDecoder.m +++ b/SDWebImage/SDWebImageDecoder.m @@ -10,9 +10,30 @@ #import "SDWebImageDecoder.h" +/* + Size in MB, compatible with all iOS devices. + */ +#define kSDWebImageDecoderMaxImageSizeMB 4.f + +#define SDWebImageDecoderMaxTotalPixels(bitsPerComponent) ((kSDWebImageDecoderMaxImageSizeMB*1024.*1024.*8.)/bitsPerComponent) + +inline static CGSize SDWebImageDecoderConstrainedSize(UIImage *image) { + CGImageRef imageRef = image.CGImage; + CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); + size_t imageBitsPerComponent = CGImageGetBitsPerComponent(imageRef); + CGFloat imageTotalPixels = imageSize.width * imageSize.height; + if (imageTotalPixels < SDWebImageDecoderMaxTotalPixels(imageBitsPerComponent)) { + return CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); + } + CGFloat ratio = SDWebImageDecoderMaxTotalPixels(imageBitsPerComponent) / imageTotalPixels; + CGFloat maxWidth = imageSize.width * ratio; + CGFloat maxHeight = imageSize.height *ratio; + return CGSizeMake(floorf(maxWidth), floorf(maxHeight)); +} + @implementation UIImage (ForceDecode) -+ (UIImage *)decodedImageWithImage:(UIImage *)image { ++ (UIImage *)decodedAndScaledDownImageToSize:(CGSize)size withImage:(UIImage *)image { if (image.images) { // Do not decode animated images return image; @@ -20,15 +41,20 @@ CGImageRef imageRef = image.CGImage; CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); - CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; + + if ((size.width < imageSize.width) && (size.height < imageSize.height)) { + imageSize = size; + } + CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); int infoMask = (bitmapInfo & kCGBitmapAlphaInfoMask); BOOL anyNonAlpha = (infoMask == kCGImageAlphaNone || - infoMask == kCGImageAlphaNoneSkipFirst || - infoMask == kCGImageAlphaNoneSkipLast); + infoMask == kCGImageAlphaNoneSkipFirst || + infoMask == kCGImageAlphaNoneSkipLast); // CGBitmapContextCreate doesn't support kCGImageAlphaNone with RGB. // https://developer.apple.com/library/mac/#qa/qa1037/_index.html @@ -39,7 +65,7 @@ // Set noneSkipFirst. bitmapInfo |= kCGImageAlphaNoneSkipFirst; } - // Some PNGs tell us they have alpha but only 3 components. Odd. + // Some PNGs tell us they have alpha but only 3 components. Odd. else if (!anyNonAlpha && CGColorSpaceGetNumberOfComponents(colorSpace) == 3) { // Unset the old alpha info. bitmapInfo &= ~kCGBitmapAlphaInfoMask; @@ -48,17 +74,18 @@ // It calculates the bytes-per-row based on the bitsPerComponent and width arguments. CGContextRef context = CGBitmapContextCreate(NULL, - imageSize.width, - imageSize.height, - CGImageGetBitsPerComponent(imageRef), - 0, - colorSpace, - bitmapInfo); + imageSize.width, + imageSize.height, + CGImageGetBitsPerComponent(imageRef), + 0, + colorSpace, + bitmapInfo); CGColorSpaceRelease(colorSpace); // If failed, return undecompressed image if (!context) return image; + CGContextSetInterpolationQuality(context, kCGInterpolationHigh); CGContextDrawImage(context, imageRect, imageRef); CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); @@ -69,4 +96,12 @@ return decompressedImage; } ++ (UIImage *)decodedImageWithImage:(UIImage *)image { + return [UIImage decodedAndScaledDownImageToSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) withImage:image]; +} + ++ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image { + return [UIImage decodedAndScaledDownImageToSize:SDWebImageDecoderConstrainedSize(image) withImage:image]; +} + @end diff --git a/SDWebImage/SDWebImageDownloader.h b/SDWebImage/SDWebImageDownloader.h index ffe5aa9d..1fae843b 100644 --- a/SDWebImage/SDWebImageDownloader.h +++ b/SDWebImage/SDWebImageDownloader.h @@ -50,7 +50,11 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { */ SDWebImageDownloaderHighPriority = 1 << 7, - + /** + * Scale down the image + */ + SDWebImageDownloaderScaleDownLargeImage = 1 << 8, + }; typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index a0b7db2f..39ebf43a 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -357,7 +357,15 @@ if (!image.images) // Do not force decod animated GIFs { +#ifdef TARGET_OS_IPHONE + if (self.options & SDWebImageDownloaderScaleDownLargeImage) { + image = [UIImage decodedAndScaledDownImageWithImage:image]; + } else { + image = [UIImage decodedImageWithImage:image]; + } +#else image = [UIImage decodedImageWithImage:image]; +#endif } if (CGSizeEqualToSize(image.size, CGSizeZero)) { diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index 6a1a17da..5a47c150 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -74,7 +74,15 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { * By default, placeholder images are loaded while the image is loading. This flag will delay the loading * of the placeholder image until after the image has finished loading. */ - SDWebImageDelayPlaceholder = 1 << 9 + SDWebImageDelayPlaceholder = 1 << 9, + + /** + * By default, images are decoded respecting their original size. On iOS, this flag will scale down the + * images to a size compatible with the constrained memory of devices. + * If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated. + */ + SDWebImageScaleDownLargeImage = 1 << 10 + }; typedef void(^SDWebImageCompletedBlock)(UIImage *image, NSError *error, SDImageCacheType cacheType); diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 7c256612..56b61340 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -134,12 +134,17 @@ if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; + if (options & SDWebImageScaleDownLargeImage) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImage; if (image && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; } + if (options & SDWebImageProgressiveDownload) { + // Progressive download deactivate scale down + downloaderOptions &= ~SDWebImageDownloaderScaleDownLargeImage; + } id subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) { if (weakOperation.isCancelled) { dispatch_main_sync_safe(^{