Update for #787 Add an option to scale down large images on iOS - code cleanup
This commit is contained in:
parent
959d965248
commit
00bf467eb7
|
@ -9,56 +9,28 @@
|
|||
|
||||
#import "SDWebImageDecoder.h"
|
||||
|
||||
@implementation UIImage (ForceDecode)
|
||||
|
||||
#if SD_UIKIT || SD_WATCH
|
||||
static const size_t kBytesPerPixel = 4;
|
||||
static const size_t kBitsPerComponent = 8;
|
||||
#endif
|
||||
|
||||
|
||||
@implementation UIImage (ForceDecode)
|
||||
|
||||
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
|
||||
if (image == nil) { return nil; } // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
|
||||
if (![UIImage shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
#if SD_MAC
|
||||
return image;
|
||||
#elif SD_UIKIT || SD_WATCH
|
||||
|
||||
// while downloading huge amount of images
|
||||
// autorelease the bitmap context
|
||||
// and all vars to help system to free memory
|
||||
// when there are memory warning.
|
||||
// on iOS7, do not forget to call
|
||||
// [[SDImageCache sharedImageCache] clearMemory];
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool{
|
||||
if (image.images != nil) { return image; } // do not decode animated images
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
|
||||
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
|
||||
alpha == kCGImageAlphaLast ||
|
||||
alpha == kCGImageAlphaPremultipliedFirst ||
|
||||
alpha == kCGImageAlphaPremultipliedLast);
|
||||
if (anyAlpha) { return image; } // do not decode images with alpha
|
||||
|
||||
// current
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
|
||||
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
|
||||
|
||||
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
|
||||
if (unsupportedColorSpace) {
|
||||
colorspaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
}
|
||||
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
|
||||
|
||||
size_t width = CGImageGetWidth(imageRef);
|
||||
size_t height = CGImageGetHeight(imageRef);
|
||||
size_t bytesPerRow = kBytesPerPixel * width;
|
||||
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
|
@ -77,16 +49,11 @@ static const size_t kBitsPerComponent = 8;
|
|||
scale:image.scale
|
||||
orientation:image.imageOrientation];
|
||||
|
||||
if (unsupportedColorSpace) {
|
||||
CGColorSpaceRelease(colorspaceRef);
|
||||
}
|
||||
|
||||
CGContextRelease(context);
|
||||
CGImageRelease(imageRefWithoutAlpha);
|
||||
|
||||
return imageWithoutAlpha;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -95,7 +62,7 @@ static const size_t kBitsPerComponent = 8;
|
|||
* Suggested value for iPad2 and iPhone 4: 120.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
|
||||
*/
|
||||
#define kDestImageSizeMB 60.0f
|
||||
static const CGFloat kDestImageSizeMB = 60.0f;
|
||||
|
||||
/*
|
||||
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
|
||||
|
@ -103,47 +70,29 @@ static const size_t kBitsPerComponent = 8;
|
|||
* Suggested value for iPad2 and iPhone 4: 40.
|
||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
|
||||
*/
|
||||
#define kSourceImageTileSizeMB 20.0f
|
||||
static const CGFloat kSourceImageTileSizeMB = 20.0f;
|
||||
|
||||
#define bytesPerMB 1048576.0f
|
||||
#define pixelsPerMB ( bytesPerMB / kBytesPerPixel )
|
||||
#define destTotalPixels kDestImageSizeMB * pixelsPerMB
|
||||
#define tileTotalPixels kSourceImageTileSizeMB * pixelsPerMB
|
||||
#define destSeemOverlap 2.0f // the numbers of pixels to overlap the seems where tiles meet.
|
||||
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
|
||||
static const CGFloat kPixelsPerMB = kBytesPerMB * kBytesPerPixel;
|
||||
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
|
||||
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
|
||||
|
||||
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
|
||||
|
||||
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
||||
if (image == nil) { return nil; } // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
|
||||
if (![UIImage shouldDecodeImage:image]) {
|
||||
return image;
|
||||
}
|
||||
|
||||
#if SD_MAC
|
||||
return image;
|
||||
#elif SD_UIKIT || SD_WATCH
|
||||
|
||||
BOOL shouldDownSize = ({
|
||||
BOOL shouldDoIt = YES;
|
||||
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
float imageScale = destTotalPixels / sourceTotalPixels;
|
||||
if (imageScale < 1) {
|
||||
shouldDoIt = YES;
|
||||
} else {
|
||||
shouldDoIt = NO;
|
||||
}
|
||||
shouldDoIt;
|
||||
});
|
||||
|
||||
if (!shouldDownSize) {
|
||||
if (![UIImage shouldScaleDownImage:image]) {
|
||||
return [UIImage decodedImageWithImage:image];
|
||||
}
|
||||
|
||||
CGContextRef destContext;
|
||||
|
||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||
// on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
|
||||
@autoreleasepool {
|
||||
if (image.images != nil) { return image; } // do not decode animated images
|
||||
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
|
@ -153,42 +102,22 @@ static const size_t kBitsPerComponent = 8;
|
|||
// Determine the scale ratio to apply to the input image
|
||||
// that results in an output image of the defined size.
|
||||
// see kDestImageSizeMB, and how it relates to destTotalPixels.
|
||||
float imageScale = destTotalPixels / sourceTotalPixels;
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
CGSize destResolution = CGSizeZero;
|
||||
destResolution.width = (int)(sourceResolution.width*imageScale);
|
||||
destResolution.height = (int)(sourceResolution.height*imageScale);
|
||||
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(sourceImageRef);
|
||||
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
|
||||
alpha == kCGImageAlphaLast ||
|
||||
alpha == kCGImageAlphaPremultipliedFirst ||
|
||||
alpha == kCGImageAlphaPremultipliedLast);
|
||||
if (anyAlpha) { return image; } // do not decode images with alpha
|
||||
|
||||
// current color space
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(sourceImageRef));
|
||||
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(sourceImageRef);
|
||||
|
||||
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
|
||||
if (unsupportedColorSpace) {
|
||||
colorspaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
}
|
||||
CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
|
||||
|
||||
size_t bytesPerRow = kBytesPerPixel * destResolution.width;
|
||||
|
||||
// Allocate enough pixel data to hold the output image.
|
||||
void* destBitmapData = malloc( bytesPerRow * destResolution.height );
|
||||
if (destBitmapData == NULL) {
|
||||
if (unsupportedColorSpace) {
|
||||
CGColorSpaceRelease(colorspaceRef);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
|
||||
// Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
|
||||
// to create bitmap graphics contexts without alpha info.
|
||||
|
@ -200,10 +129,6 @@ static const size_t kBitsPerComponent = 8;
|
|||
colorspaceRef,
|
||||
kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
|
||||
|
||||
if (unsupportedColorSpace) {
|
||||
CGColorSpaceRelease(colorspaceRef);
|
||||
}
|
||||
|
||||
if (destContext == NULL) {
|
||||
free( destBitmapData );
|
||||
return image;
|
||||
|
@ -224,7 +149,7 @@ static const size_t kBitsPerComponent = 8;
|
|||
// The source tile height is dynamic. Since we specified the size
|
||||
// of the source tile in MB, see how many rows of pixels high it
|
||||
// can be given the input image width.
|
||||
sourceTile.size.height = (int)( tileTotalPixels / sourceTile.size.width );
|
||||
sourceTile.size.height = (int)(kTileTotalPixels / sourceTile.size.width );
|
||||
sourceTile.origin.x = 0.0f;
|
||||
// The output tile is the same proportions as the input tile, but
|
||||
// scaled to image scale.
|
||||
|
@ -234,7 +159,7 @@ static const size_t kBitsPerComponent = 8;
|
|||
destTile.origin.x = 0.0f;
|
||||
// The source seem overlap is proportionate to the destination seem overlap.
|
||||
// this is the amount of pixels to overlap each tile as we assemble the ouput image.
|
||||
float sourceSeemOverlap = (int)((destSeemOverlap/destResolution.height)*sourceResolution.height);
|
||||
float sourceSeemOverlap = (int)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
|
||||
CGImageRef sourceTileImageRef;
|
||||
// calculate the number of read/write operations required to assemble the
|
||||
// output image.
|
||||
|
@ -248,11 +173,11 @@ static const size_t kBitsPerComponent = 8;
|
|||
// Add seem overlaps to the tiles, but save the original tile height for y coordinate calculations.
|
||||
float sourceTileHeightMinusOverlap = sourceTile.size.height;
|
||||
sourceTile.size.height += sourceSeemOverlap;
|
||||
destTile.size.height += destSeemOverlap;
|
||||
destTile.size.height += kDestSeemOverlap;
|
||||
for( int y = 0; y < iterations; ++y ) {
|
||||
@autoreleasepool {
|
||||
sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
|
||||
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + destSeemOverlap);
|
||||
destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
|
||||
sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
|
||||
if( y == iterations - 1 && remainder ) {
|
||||
float dify = destTile.size.height;
|
||||
|
@ -277,8 +202,75 @@ static const size_t kBitsPerComponent = 8;
|
|||
}
|
||||
return destImage;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
|
||||
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
|
||||
if (image == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// do not decode animated images
|
||||
if (image.images != nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
CGImageRef imageRef = image.CGImage;
|
||||
|
||||
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
|
||||
BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
|
||||
alpha == kCGImageAlphaLast ||
|
||||
alpha == kCGImageAlphaPremultipliedFirst ||
|
||||
alpha == kCGImageAlphaPremultipliedLast);
|
||||
// do not decode images with alpha
|
||||
if (anyAlpha) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)shouldScaleDownImage:(nonnull UIImage *)image {
|
||||
BOOL shouldScaleDown = YES;
|
||||
|
||||
CGImageRef sourceImageRef = image.CGImage;
|
||||
CGSize sourceResolution = CGSizeZero;
|
||||
sourceResolution.width = CGImageGetWidth(sourceImageRef);
|
||||
sourceResolution.height = CGImageGetHeight(sourceImageRef);
|
||||
float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
|
||||
float imageScale = kDestTotalPixels / sourceTotalPixels;
|
||||
if (imageScale < 1) {
|
||||
shouldScaleDown = YES;
|
||||
} else {
|
||||
shouldScaleDown = NO;
|
||||
}
|
||||
|
||||
return shouldScaleDown;
|
||||
}
|
||||
|
||||
+ (CGColorSpaceRef)colorSpaceForImageRef:(CGImageRef)imageRef {
|
||||
// current
|
||||
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
|
||||
CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
|
||||
|
||||
BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelCMYK ||
|
||||
imageColorSpaceModel == kCGColorSpaceModelIndexed);
|
||||
if (unsupportedColorSpace) {
|
||||
colorspaceRef = CGColorSpaceCreateDeviceRGB();
|
||||
CFAutorelease(colorspaceRef);
|
||||
}
|
||||
return colorspaceRef;
|
||||
}
|
||||
#elif SD_MAC
|
||||
+ (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (nullable UIImage *)decodedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
||||
return image;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in New Issue