/* * This file is part of the SDWebImage package. * (c) Olivier Poitrey * (c) james * * 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