123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- * (c) james <https://github.com/mystcolor>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDWebImageDecoder.h"
- @implementation UIImage (ForceDecode)
- #if SD_UIKIT || SD_WATCH
- static const size_t kBytesPerPixel = 4;
- static const size_t kBitsPerComponent = 8;
- + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
- if (![UIImage shouldDecodeImage:image]) {
- return image;
- }
-
- // 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{
-
- CGImageRef imageRef = image.CGImage;
- 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.
- CGContextRef context = CGBitmapContextCreate(NULL,
- width,
- height,
- kBitsPerComponent,
- bytesPerRow,
- colorspaceRef,
- kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
- if (context == NULL) {
- return image;
- }
-
- // Draw the image into the context and retrieve the new bitmap image without alpha
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
- CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
- UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
- scale:image.scale
- orientation:image.imageOrientation];
-
- CGContextRelease(context);
- CGImageRelease(imageRefWithoutAlpha);
-
- return imageWithoutAlpha;
- }
- }
- /*
- * Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
- * Suggested value for iPad1 and iPhone 3GS: 60.
- * Suggested value for iPad2 and iPhone 4: 120.
- * Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
- */
- 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
- * Suggested value for iPad1 and iPhone 3GS: 20.
- * Suggested value for iPad2 and iPhone 4: 40.
- * Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
- */
- static const CGFloat kSourceImageTileSizeMB = 20.0f;
- 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 (![UIImage shouldDecodeImage:image]) {
- return image;
- }
-
- 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 {
- CGImageRef sourceImageRef = image.CGImage;
-
- CGSize sourceResolution = CGSizeZero;
- sourceResolution.width = CGImageGetWidth(sourceImageRef);
- sourceResolution.height = CGImageGetHeight(sourceImageRef);
- float sourceTotalPixels = sourceResolution.width * sourceResolution.height;
- // 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 = kDestTotalPixels / sourceTotalPixels;
- CGSize destResolution = CGSizeZero;
- destResolution.width = (int)(sourceResolution.width*imageScale);
- destResolution.height = (int)(sourceResolution.height*imageScale);
-
- // current color space
- CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:sourceImageRef];
-
- size_t bytesPerRow = kBytesPerPixel * destResolution.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.
- destContext = CGBitmapContextCreate(NULL,
- destResolution.width,
- destResolution.height,
- kBitsPerComponent,
- bytesPerRow,
- colorspaceRef,
- kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
-
- if (destContext == NULL) {
- return image;
- }
- CGContextSetInterpolationQuality(destContext, kCGInterpolationHigh);
-
- // Now define the size of the rectangle to be used for the
- // incremental blits from the input image to the output image.
- // we use a source tile width equal to the width of the source
- // image due to the way that iOS retrieves image data from disk.
- // iOS must decode an image from disk in full width 'bands', even
- // if current graphics context is clipped to a subrect within that
- // band. Therefore we fully utilize all of the pixel data that results
- // from a decoding opertion by achnoring our tile size to the full
- // width of the input image.
- CGRect sourceTile = CGRectZero;
- sourceTile.size.width = sourceResolution.width;
- // 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)(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.
- CGRect destTile;
- destTile.size.width = destResolution.width;
- destTile.size.height = sourceTile.size.height * imageScale;
- 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)((kDestSeemOverlap/destResolution.height)*sourceResolution.height);
- CGImageRef sourceTileImageRef;
- // calculate the number of read/write operations required to assemble the
- // output image.
- int iterations = (int)( sourceResolution.height / sourceTile.size.height );
- // If tile height doesn't divide the image height evenly, add another iteration
- // to account for the remaining pixels.
- int remainder = (int)sourceResolution.height % (int)sourceTile.size.height;
- if(remainder) {
- iterations++;
- }
- // 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 += kDestSeemOverlap;
- for( int y = 0; y < iterations; ++y ) {
- @autoreleasepool {
- sourceTile.origin.y = y * sourceTileHeightMinusOverlap + sourceSeemOverlap;
- destTile.origin.y = destResolution.height - (( y + 1 ) * sourceTileHeightMinusOverlap * imageScale + kDestSeemOverlap);
- sourceTileImageRef = CGImageCreateWithImageInRect( sourceImageRef, sourceTile );
- if( y == iterations - 1 && remainder ) {
- float dify = destTile.size.height;
- destTile.size.height = CGImageGetHeight( sourceTileImageRef ) * imageScale;
- dify -= destTile.size.height;
- destTile.origin.y += dify;
- }
- CGContextDrawImage( destContext, destTile, sourceTileImageRef );
- CGImageRelease( sourceTileImageRef );
- }
- }
-
- CGImageRef destImageRef = CGBitmapContextCreateImage(destContext);
- CGContextRelease(destContext);
- if (destImageRef == NULL) {
- return image;
- }
- UIImage *destImage = [UIImage imageWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
- CGImageRelease(destImageRef);
- if (destImage == nil) {
- return image;
- }
- return destImage;
- }
- }
- + (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
|