|
|
@@ -0,0 +1,579 @@
|
|
|
+/*
|
|
|
+ * 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 "SDWebImageImageIOCoder.h"
|
|
|
+#import "NSImage+WebCache.h"
|
|
|
+#import <ImageIO/ImageIO.h>
|
|
|
+#import "NSData+ImageContentType.h"
|
|
|
+
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+static const size_t kBytesPerPixel = 4;
|
|
|
+static const size_t kBitsPerComponent = 8;
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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.
|
|
|
+#endif
|
|
|
+
|
|
|
+@implementation SDWebImageImageIOCoder {
|
|
|
+ size_t _width, _height;
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ UIImageOrientation _orientation;
|
|
|
+#endif
|
|
|
+ CGImageSourceRef _imageSource;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dealloc {
|
|
|
+ if (_imageSource) {
|
|
|
+ CFRelease(_imageSource);
|
|
|
+ _imageSource = NULL;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
++ (instancetype)sharedCoder {
|
|
|
+ static SDWebImageImageIOCoder *coder;
|
|
|
+ static dispatch_once_t onceToken;
|
|
|
+ dispatch_once(&onceToken, ^{
|
|
|
+ coder = [[SDWebImageImageIOCoder alloc] init];
|
|
|
+ });
|
|
|
+ return coder;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Decode
|
|
|
+- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
|
|
+ switch ([NSData sd_imageFormatForImageData:data]) {
|
|
|
+ // Do not support GIF and WebP decoding
|
|
|
+ case SDImageFormatGIF:
|
|
|
+ case SDImageFormatWebP:
|
|
|
+ return NO;
|
|
|
+ default:
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
|
|
|
+ switch ([NSData sd_imageFormatForImageData:data]) {
|
|
|
+ // Support static GIF progressive decoding
|
|
|
+ case SDImageFormatWebP:
|
|
|
+ return NO;
|
|
|
+ default:
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (UIImage *)decodedImageWithData:(NSData *)data {
|
|
|
+ if (!data) {
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+
|
|
|
+ UIImage *image = [[UIImage alloc] initWithData:data];
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ UIImageOrientation orientation = [[self class] sd_imageOrientationFromImageData:data];
|
|
|
+ if (orientation != UIImageOrientationUp) {
|
|
|
+ image = [UIImage imageWithCGImage:image.CGImage
|
|
|
+ scale:image.scale
|
|
|
+ orientation:orientation];
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ return image;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
|
|
|
+ if (!_imageSource) {
|
|
|
+ _imageSource = CGImageSourceCreateIncremental(NULL);
|
|
|
+ }
|
|
|
+ UIImage *image;
|
|
|
+
|
|
|
+ // 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.
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ _orientation = [[self class] sd_imageOrientationFromEXIFOrientation:orientationValue];
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_width + _height > 0) {
|
|
|
+ // Create the image
|
|
|
+ CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
|
|
|
+
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ // Workaround for iOS anamorphic image
|
|
|
+ if (partialImageRef) {
|
|
|
+ const size_t partialHeight = CGImageGetHeight(partialImageRef);
|
|
|
+ CGColorSpaceRef colorSpace = SDCGColorSpaceGetDeviceRGB();
|
|
|
+ CGContextRef bmContext = CGBitmapContextCreate(NULL, _width, _height, 8, _width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
|
|
|
+ if (bmContext) {
|
|
|
+ CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = _width, .size.height = partialHeight}, partialImageRef);
|
|
|
+ CGImageRelease(partialImageRef);
|
|
|
+ partialImageRef = CGBitmapContextCreateImage(bmContext);
|
|
|
+ CGContextRelease(bmContext);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ CGImageRelease(partialImageRef);
|
|
|
+ partialImageRef = nil;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ if (partialImageRef) {
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:_orientation];
|
|
|
+#elif SD_MAC
|
|
|
+ image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
|
|
|
+#endif
|
|
|
+ CGImageRelease(partialImageRef);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (finished) {
|
|
|
+ if (_imageSource) {
|
|
|
+ CFRelease(_imageSource);
|
|
|
+ _imageSource = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return image;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIImage *)decompressedImageWithImage:(UIImage *)image
|
|
|
+ data:(NSData *__autoreleasing _Nullable *)data
|
|
|
+ options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict {
|
|
|
+#if SD_MAC
|
|
|
+ return image;
|
|
|
+#endif
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+ BOOL shouldScaleDown = NO;
|
|
|
+ if (optionsDict != nil) {
|
|
|
+ NSNumber *scaleDownLargeImagesOption = nil;
|
|
|
+ if ([optionsDict[SDWebImageCoderScaleDownLargeImagesKey] isKindOfClass:[NSNumber class]]) {
|
|
|
+ scaleDownLargeImagesOption = (NSNumber *)optionsDict[SDWebImageCoderScaleDownLargeImagesKey];
|
|
|
+ }
|
|
|
+ if (scaleDownLargeImagesOption != nil) {
|
|
|
+ shouldScaleDown = [scaleDownLargeImagesOption boolValue];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!shouldScaleDown) {
|
|
|
+ return [self sd_decompressedImageWithImage:image];
|
|
|
+ } else {
|
|
|
+ return [self sd_decompressedAndScaledDownImageWithImage:image];
|
|
|
+ }
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+- (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
|
|
|
+ if (![[self class] 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 = [[self class] 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;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (nullable UIImage *)sd_decompressedAndScaledDownImageWithImage:(nullable UIImage *)image {
|
|
|
+ if (![[self class] shouldDecodeImage:image]) {
|
|
|
+ return image;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (![[self class] shouldScaleDownImage:image]) {
|
|
|
+ return [self sd_decompressedImageWithImage: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 = [[self class] 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;
|
|
|
+ }
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+#pragma mark - Encode
|
|
|
+- (BOOL)canEncodeToFormat:(SDImageFormat)format {
|
|
|
+ switch (format) {
|
|
|
+ // Do not support GIF and WebP encoding
|
|
|
+ case SDImageFormatGIF:
|
|
|
+ case SDImageFormatWebP:
|
|
|
+ return NO;
|
|
|
+ default:
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (NSData *)encodedDataWithImage:(UIImage *)image format:(SDImageFormat)format {
|
|
|
+ if (!image) {
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (format == SDImageFormatUndefined) {
|
|
|
+ BOOL hasAlpha = SDCGImageRefContainsAlpha(image.CGImage);
|
|
|
+ if (hasAlpha) {
|
|
|
+ format = SDImageFormatPNG;
|
|
|
+ } else {
|
|
|
+ format = SDImageFormatJPEG;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ NSMutableData *imageData = [NSMutableData data];
|
|
|
+ CFStringRef imageUTType = [NSData sd_UTTypeFromSDImageFormat: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
|
|
|
+ NSInteger exifOrientation = [[self class] sd_exifOrientationFromImageOrientation:image.imageOrientation];
|
|
|
+ [properties setValue:@(exifOrientation) forKey:(__bridge_transfer NSString *)kCGImagePropertyOrientation];
|
|
|
+#endif
|
|
|
+
|
|
|
+ // Add your image to the destination.
|
|
|
+ CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
|
|
|
+
|
|
|
+ // Finalize the destination.
|
|
|
+ if (CGImageDestinationFinalize(imageDestination) == NO) {
|
|
|
+ // Handle failure.
|
|
|
+ imageData = nil;
|
|
|
+ }
|
|
|
+
|
|
|
+ CFRelease(imageDestination);
|
|
|
+
|
|
|
+ return [imageData copy];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Helper
|
|
|
++ (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;
|
|
|
+
|
|
|
+ BOOL hasAlpha = SDCGImageRefContainsAlpha(imageRef);
|
|
|
+ // do not decode images with alpha
|
|
|
+ if (hasAlpha) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
+#pragma mark EXIF orientation tag converter
|
|
|
++ (UIImageOrientation)sd_imageOrientationFromImageData:(nonnull NSData *)imageData {
|
|
|
+ UIImageOrientation result = UIImageOrientationUp;
|
|
|
+ CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
|
|
|
+ if (imageSource) {
|
|
|
+ CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
|
|
|
+ if (properties) {
|
|
|
+ CFTypeRef val;
|
|
|
+ NSInteger exifOrientation;
|
|
|
+ val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
|
|
|
+ if (val) {
|
|
|
+ CFNumberGetValue(val, kCFNumberNSIntegerType, &exifOrientation);
|
|
|
+ result = [self sd_imageOrientationFromEXIFOrientation:exifOrientation];
|
|
|
+ } // else - if it's not set it remains at up
|
|
|
+ CFRelease((CFTypeRef) properties);
|
|
|
+ } else {
|
|
|
+ //NSLog(@"NO PROPERTIES, FAIL");
|
|
|
+ }
|
|
|
+ CFRelease(imageSource);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+// Convert an EXIF image orientation to an iOS one.
|
|
|
++ (UIImageOrientation)sd_imageOrientationFromEXIFOrientation:(NSInteger)exifOrientation {
|
|
|
+ UIImageOrientation imageOrientation = UIImageOrientationUp;
|
|
|
+ switch (exifOrientation) {
|
|
|
+ case kCGImagePropertyOrientationUp:
|
|
|
+ imageOrientation = UIImageOrientationUp;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationDown:
|
|
|
+ imageOrientation = UIImageOrientationDown;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationLeft:
|
|
|
+ imageOrientation = UIImageOrientationLeft;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationRight:
|
|
|
+ imageOrientation = UIImageOrientationRight;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationUpMirrored:
|
|
|
+ imageOrientation = UIImageOrientationUpMirrored;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationDownMirrored:
|
|
|
+ imageOrientation = UIImageOrientationDownMirrored;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationLeftMirrored:
|
|
|
+ imageOrientation = UIImageOrientationLeftMirrored;
|
|
|
+ break;
|
|
|
+ case kCGImagePropertyOrientationRightMirrored:
|
|
|
+ imageOrientation = UIImageOrientationRightMirrored;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return imageOrientation;
|
|
|
+}
|
|
|
+
|
|
|
+// Convert an iOS orientation to an EXIF image orientation.
|
|
|
++ (NSInteger)sd_exifOrientationFromImageOrientation:(UIImageOrientation)imageOrientation {
|
|
|
+ NSInteger exifOrientation = kCGImagePropertyOrientationUp;
|
|
|
+ switch (imageOrientation) {
|
|
|
+ case UIImageOrientationUp:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationUp;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationDown:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationDown;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationLeft:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationLeft;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationRight:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationRight;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationUpMirrored:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationUpMirrored;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationDownMirrored:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationDownMirrored;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationLeftMirrored:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationLeftMirrored;
|
|
|
+ break;
|
|
|
+ case UIImageOrientationRightMirrored:
|
|
|
+ exifOrientation = kCGImagePropertyOrientationRightMirrored;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return exifOrientation;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+#if SD_UIKIT || SD_WATCH
|
|
|
++ (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 = SDCGColorSpaceGetDeviceRGB();
|
|
|
+ }
|
|
|
+ return colorspaceRef;
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+@end
|