123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- /*
- * This file is part of the SDWebImage package.
- * (c) Olivier Poitrey <rs@dailymotion.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- #import "SDImageIOCoder.h"
- #import "SDImageCoderHelper.h"
- #import "NSImage+Compatibility.h"
- #import <ImageIO/ImageIO.h>
- #import "UIImage+Metadata.h"
- #import "SDImageHEICCoderInternal.h"
- #import "SDImageIOAnimatedCoderInternal.h"
- // Specify File Size for lossy format encoding, like JPEG
- static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
- @implementation SDImageIOCoder {
- size_t _width, _height;
- CGImagePropertyOrientation _orientation;
- CGImageSourceRef _imageSource;
- CGFloat _scale;
- BOOL _finished;
- BOOL _preserveAspectRatio;
- CGSize _thumbnailSize;
- }
- - (void)dealloc {
- if (_imageSource) {
- CFRelease(_imageSource);
- _imageSource = NULL;
- }
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- - (void)didReceiveMemoryWarning:(NSNotification *)notification
- {
- if (_imageSource) {
- CGImageSourceRemoveCacheAtIndex(_imageSource, 0);
- }
- }
- + (instancetype)sharedCoder {
- static SDImageIOCoder *coder;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- coder = [[SDImageIOCoder alloc] init];
- });
- return coder;
- }
- #pragma mark - Decode
- - (BOOL)canDecodeFromData:(nullable NSData *)data {
- switch ([NSData sd_imageFormatForImageData:data]) {
- case SDImageFormatWebP:
- // Do not support WebP decoding
- return NO;
- case SDImageFormatHEIC:
- // Check HEIC decoding compatibility
- return [SDImageHEICCoder canDecodeFromHEICFormat];
- case SDImageFormatHEIF:
- // Check HEIF decoding compatibility
- return [SDImageHEICCoder canDecodeFromHEIFFormat];
- default:
- return YES;
- }
- }
- - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderOptions *)options {
- if (!data) {
- return nil;
- }
- CGFloat scale = 1;
- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
- if (scaleFactor != nil) {
- scale = MAX([scaleFactor doubleValue], 1) ;
- }
-
- CGSize thumbnailSize = CGSizeZero;
- NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
- if (thumbnailSizeValue != nil) {
- #if SD_MAC
- thumbnailSize = thumbnailSizeValue.sizeValue;
- #else
- thumbnailSize = thumbnailSizeValue.CGSizeValue;
- #endif
- }
-
- BOOL preserveAspectRatio = YES;
- NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
- if (preserveAspectRatioValue != nil) {
- preserveAspectRatio = preserveAspectRatioValue.boolValue;
- }
-
- CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
- if (!source) {
- return nil;
- }
-
- UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
- CFRelease(source);
- if (!image) {
- return nil;
- }
-
- image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
- return image;
- }
- #pragma mark - Progressive Decode
- - (BOOL)canIncrementalDecodeFromData:(NSData *)data {
- return [self canDecodeFromData:data];
- }
- - (instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options {
- self = [super init];
- if (self) {
- _imageSource = CGImageSourceCreateIncremental(NULL);
- CGFloat scale = 1;
- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
- if (scaleFactor != nil) {
- scale = MAX([scaleFactor doubleValue], 1);
- }
- _scale = scale;
- CGSize thumbnailSize = CGSizeZero;
- NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
- if (thumbnailSizeValue != nil) {
- #if SD_MAC
- thumbnailSize = thumbnailSizeValue.sizeValue;
- #else
- thumbnailSize = thumbnailSizeValue.CGSizeValue;
- #endif
- }
- _thumbnailSize = thumbnailSize;
- BOOL preserveAspectRatio = YES;
- NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
- if (preserveAspectRatioValue != nil) {
- preserveAspectRatio = preserveAspectRatioValue.boolValue;
- }
- _preserveAspectRatio = preserveAspectRatio;
- #if SD_UIKIT
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
- #endif
- }
- return self;
- }
- - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
- if (_finished) {
- return;
- }
- _finished = finished;
-
- // The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
- // Thanks to the author @Nyx0uf
-
- // Update the data source, we must pass ALL the data, not just the new bytes
- CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
-
- if (_width + _height == 0) {
- CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
- if (properties) {
- NSInteger orientationValue = 1;
- CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
- if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
- val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
- if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
- val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
- if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
- CFRelease(properties);
-
- // When we draw to Core Graphics, we lose orientation information,
- // which means the image below born of initWithCGIImage will be
- // oriented incorrectly sometimes. (Unlike the image born of initWithData
- // in didCompleteWithError.) So save it here and pass it on later.
- _orientation = (CGImagePropertyOrientation)orientationValue;
- }
- }
- }
- - (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
- UIImage *image;
-
- if (_width + _height > 0) {
- // Create the image
- CGFloat scale = _scale;
- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
- if (scaleFactor != nil) {
- scale = MAX([scaleFactor doubleValue], 1);
- }
- image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
- if (image) {
- CFStringRef uttype = CGImageSourceGetType(_imageSource);
- image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
- }
- }
-
- return image;
- }
- #pragma mark - Encode
- - (BOOL)canEncodeToFormat:(SDImageFormat)format {
- switch (format) {
- case SDImageFormatWebP:
- // Do not support WebP encoding
- return NO;
- case SDImageFormatHEIC:
- // Check HEIC encoding compatibility
- return [SDImageHEICCoder canEncodeToHEICFormat];
- case SDImageFormatHEIF:
- // Check HEIF encoding compatibility
- return [SDImageHEICCoder canEncodeToHEIFFormat];
- default:
- return YES;
- }
- }
- - (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format options:(nullable SDImageCoderOptions *)options {
- if (!image) {
- return nil;
- }
- CGImageRef imageRef = image.CGImage;
- if (!imageRef) {
- // Earily return, supports CGImage only
- return nil;
- }
-
- if (format == SDImageFormatUndefined) {
- BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:imageRef];
- if (hasAlpha) {
- format = SDImageFormatPNG;
- } else {
- format = SDImageFormatJPEG;
- }
- }
-
- NSMutableData *imageData = [NSMutableData data];
- CFStringRef imageUTType = [NSData sd_UTTypeFromImageFormat:format];
-
- // Create an image destination.
- CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
- if (!imageDestination) {
- // Handle failure.
- return nil;
- }
-
- NSMutableDictionary *properties = [NSMutableDictionary dictionary];
- #if SD_UIKIT || SD_WATCH
- CGImagePropertyOrientation exifOrientation = [SDImageCoderHelper exifOrientationFromImageOrientation:image.imageOrientation];
- #else
- CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
- #endif
- properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
- // Encoding Options
- double compressionQuality = 1;
- if (options[SDImageCoderEncodeCompressionQuality]) {
- compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
- }
- properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
- CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
- if (backgroundColor) {
- properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
- }
- CGSize maxPixelSize = CGSizeZero;
- NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
- if (maxPixelSizeValue != nil) {
- #if SD_MAC
- maxPixelSize = maxPixelSizeValue.sizeValue;
- #else
- maxPixelSize = maxPixelSizeValue.CGSizeValue;
- #endif
- }
- NSUInteger pixelWidth = CGImageGetWidth(imageRef);
- NSUInteger pixelHeight = CGImageGetHeight(imageRef);
- if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
- CGFloat pixelRatio = pixelWidth / pixelHeight;
- CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
- CGFloat finalPixelSize;
- if (pixelRatio > maxPixelSizeRatio) {
- finalPixelSize = maxPixelSize.width;
- } else {
- finalPixelSize = maxPixelSize.height;
- }
- properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
- }
- NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
- if (maxFileSize > 0) {
- properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
- // Remove the quality if we have file size limit
- properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
- }
- BOOL embedThumbnail = NO;
- if (options[SDImageCoderEncodeEmbedThumbnail]) {
- embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
- }
- properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
-
- // Add your image to the destination.
- CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
-
- // Finalize the destination.
- if (CGImageDestinationFinalize(imageDestination) == NO) {
- // Handle failure.
- imageData = nil;
- }
-
- CFRelease(imageDestination);
-
- return [imageData copy];
- }
- @end
|