Merge pull request #2922 from dreampiggy/feature_thumbnail_decoding
Feature thumbnail image decoding
This commit is contained in:
commit
01b23e448c
|
@ -115,7 +115,8 @@
|
||||||
cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
|
cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
|
||||||
[cell.customImageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
|
[cell.customImageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
|
||||||
placeholderImage:placeholderImage
|
placeholderImage:placeholderImage
|
||||||
options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];
|
options:indexPath.row == 0 ? SDWebImageRefreshCached : 0
|
||||||
|
context:@{SDWebImageContextImageThumbnailPixelSize : @(CGSizeMake(180, 120))}];
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,25 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
|
||||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||||
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
||||||
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
|
||||||
if (context) {
|
NSValue *thumbnailSizeValue;
|
||||||
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
||||||
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
if (shouldScaleDown) {
|
||||||
coderOptions = [mutableCoderOptions copy];
|
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
|
||||||
|
CGFloat dimension = ceil(sqrt(thumbnailPixels));
|
||||||
|
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
|
||||||
}
|
}
|
||||||
|
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||||
|
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
|
||||||
|
mutableCoderOptions[SDImageCoderWebImageContext] = context;
|
||||||
|
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
|
||||||
|
|
||||||
if (!decodeFirstFrame) {
|
if (!decodeFirstFrame) {
|
||||||
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
|
Class animatedImageClass = context[SDWebImageContextAnimatedImageClass];
|
||||||
|
@ -56,14 +69,9 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
|
||||||
shouldDecode = NO;
|
shouldDecode = NO;
|
||||||
}
|
}
|
||||||
if (shouldDecode) {
|
if (shouldDecode) {
|
||||||
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
|
||||||
if (shouldScaleDown) {
|
|
||||||
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
|
|
||||||
} else {
|
|
||||||
image = [SDImageCoderHelper decodedImageWithImage:image];
|
image = [SDImageCoderHelper decodedImageWithImage:image];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,22 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrame
|
||||||
*/
|
*/
|
||||||
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor;
|
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
|
||||||
|
Defaults to YES.
|
||||||
|
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAspectRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A CGSize value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.preserveAspectRatio`) the value size.
|
||||||
|
Defaults to CGSizeZero, which means no thumbnail generation at all.
|
||||||
|
@note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
|
||||||
|
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize;
|
||||||
|
|
||||||
|
|
||||||
// These options are for image encoding
|
// These options are for image encoding
|
||||||
/**
|
/**
|
||||||
A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need.
|
A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need.
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
|
SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
|
||||||
SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
|
SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
|
||||||
|
SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio";
|
||||||
|
SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize";
|
||||||
|
|
||||||
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
|
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
|
||||||
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
|
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
|
||||||
|
|
|
@ -73,6 +73,16 @@
|
||||||
*/
|
*/
|
||||||
+ (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation CF_RETURNS_RETAINED;
|
+ (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation CF_RETURNS_RETAINED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage.
|
||||||
|
It will detect whether the image size matching the scale size, if not, stretch the image to the target size.
|
||||||
|
|
||||||
|
@param cgImage The CGImage
|
||||||
|
@param size The scale size in pixel.
|
||||||
|
@return A new created scaled image
|
||||||
|
*/
|
||||||
|
+ (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image
|
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image
|
||||||
@param image The image to be decoded
|
@param image The image to be decoded
|
||||||
|
@ -89,6 +99,12 @@
|
||||||
*/
|
*/
|
||||||
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
|
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Control the default limit bytes to scale down larget images.
|
||||||
|
This value must be larger than or equal to 1MB. Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
|
||||||
|
*/
|
||||||
|
@property (class, readwrite) NSUInteger defaultScaleDownLimitBytes;
|
||||||
|
|
||||||
#if SD_UIKIT || SD_WATCH
|
#if SD_UIKIT || SD_WATCH
|
||||||
/**
|
/**
|
||||||
Convert an EXIF image orientation to an iOS one.
|
Convert an EXIF image orientation to an iOS one.
|
||||||
|
|
|
@ -13,34 +13,34 @@
|
||||||
#import "SDAnimatedImageRep.h"
|
#import "SDAnimatedImageRep.h"
|
||||||
#import "UIImage+ForceDecode.h"
|
#import "UIImage+ForceDecode.h"
|
||||||
#import "SDAssociatedObject.h"
|
#import "SDAssociatedObject.h"
|
||||||
|
#import "UIImage+Metadata.h"
|
||||||
|
#import "SDInternalMacros.h"
|
||||||
|
#import <Accelerate/Accelerate.h>
|
||||||
|
|
||||||
|
static inline size_t SDByteAlign(size_t size, size_t alignment) {
|
||||||
|
return ((size + (alignment - 1)) / alignment) * alignment;
|
||||||
|
}
|
||||||
|
|
||||||
#if SD_UIKIT || SD_WATCH
|
|
||||||
static const size_t kBytesPerPixel = 4;
|
static const size_t kBytesPerPixel = 4;
|
||||||
static const size_t kBitsPerComponent = 8;
|
static const size_t kBitsPerComponent = 8;
|
||||||
|
|
||||||
|
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
|
||||||
|
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
|
||||||
/*
|
/*
|
||||||
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
|
* 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 iPad1 and iPhone 3GS: 60.
|
||||||
* Suggested value for iPad2 and iPhone 4: 120.
|
* Suggested value for iPad2 and iPhone 4: 120.
|
||||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
|
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
|
||||||
*/
|
*/
|
||||||
static const CGFloat kDestImageSizeMB = 60.f;
|
#if SD_MAC
|
||||||
|
static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB;
|
||||||
/*
|
#elif SD_UIKIT
|
||||||
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
|
static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
|
||||||
* Suggested value for iPad1 and iPhone 3GS: 20.
|
#elif SD_WATCH
|
||||||
* Suggested value for iPad2 and iPhone 4: 40.
|
static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB;
|
||||||
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
|
#endif
|
||||||
*/
|
|
||||||
static const CGFloat kSourceImageTileSizeMB = 20.f;
|
|
||||||
|
|
||||||
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.
|
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
|
||||||
#endif
|
|
||||||
|
|
||||||
@implementation SDImageCoderHelper
|
@implementation SDImageCoderHelper
|
||||||
|
|
||||||
|
@ -277,10 +277,54 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
return newImageRef;
|
return newImageRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size {
|
||||||
|
if (!cgImage) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
size_t width = CGImageGetWidth(cgImage);
|
||||||
|
size_t height = CGImageGetHeight(cgImage);
|
||||||
|
if (width == size.width && height == size.height) {
|
||||||
|
CGImageRetain(cgImage);
|
||||||
|
return cgImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
__block vImage_Buffer input_buffer = {}, output_buffer = {};
|
||||||
|
@onExit {
|
||||||
|
if (input_buffer.data) free(input_buffer.data);
|
||||||
|
if (output_buffer.data) free(output_buffer.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
vImage_CGImageFormat format = (vImage_CGImageFormat) {
|
||||||
|
.bitsPerComponent = 8,
|
||||||
|
.bitsPerPixel = 32,
|
||||||
|
.colorSpace = NULL,
|
||||||
|
.bitmapInfo = kCGImageAlphaFirst | kCGBitmapByteOrderDefault,
|
||||||
|
.version = 0,
|
||||||
|
.decode = NULL,
|
||||||
|
.renderingIntent = kCGRenderingIntentDefault,
|
||||||
|
};
|
||||||
|
|
||||||
|
vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
|
||||||
|
if (a_ret != kvImageNoError) return NULL;
|
||||||
|
output_buffer.width = MAX(size.width, 0);
|
||||||
|
output_buffer.height = MAX(size.height, 0);
|
||||||
|
output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64);
|
||||||
|
output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height);
|
||||||
|
if (!output_buffer.data) return NULL;
|
||||||
|
|
||||||
|
vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
|
||||||
|
if (ret != kvImageNoError) return NULL;
|
||||||
|
|
||||||
|
CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret);
|
||||||
|
if (ret != kvImageNoError) {
|
||||||
|
CGImageRelease(outputImage);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputImage;
|
||||||
|
}
|
||||||
|
|
||||||
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
|
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
|
||||||
#if SD_MAC
|
|
||||||
return image;
|
|
||||||
#else
|
|
||||||
if (![self shouldDecodeImage:image]) {
|
if (![self shouldDecodeImage:image]) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -289,18 +333,18 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
if (!imageRef) {
|
if (!imageRef) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
#if SD_MAC
|
||||||
|
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
|
||||||
|
#else
|
||||||
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
|
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
|
||||||
|
#endif
|
||||||
CGImageRelease(imageRef);
|
CGImageRelease(imageRef);
|
||||||
SDImageCopyAssociatedObject(image, decodedImage);
|
SDImageCopyAssociatedObject(image, decodedImage);
|
||||||
decodedImage.sd_isDecoded = YES;
|
decodedImage.sd_isDecoded = YES;
|
||||||
return decodedImage;
|
return decodedImage;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
|
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
|
||||||
#if SD_MAC
|
|
||||||
return image;
|
|
||||||
#else
|
|
||||||
if (![self shouldDecodeImage:image]) {
|
if (![self shouldDecodeImage:image]) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -311,13 +355,11 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
|
|
||||||
CGFloat destTotalPixels;
|
CGFloat destTotalPixels;
|
||||||
CGFloat tileTotalPixels;
|
CGFloat tileTotalPixels;
|
||||||
if (bytes > 0) {
|
if (bytes == 0) {
|
||||||
|
bytes = kDestImageLimitBytes;
|
||||||
|
}
|
||||||
destTotalPixels = bytes / kBytesPerPixel;
|
destTotalPixels = bytes / kBytesPerPixel;
|
||||||
tileTotalPixels = destTotalPixels / 3;
|
tileTotalPixels = destTotalPixels / 3;
|
||||||
} else {
|
|
||||||
destTotalPixels = kDestTotalPixels;
|
|
||||||
tileTotalPixels = kTileTotalPixels;
|
|
||||||
}
|
|
||||||
CGContextRef destContext;
|
CGContextRef destContext;
|
||||||
|
|
||||||
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
|
||||||
|
@ -420,7 +462,11 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
if (destImageRef == NULL) {
|
if (destImageRef == NULL) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
#if SD_MAC
|
||||||
|
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
|
||||||
|
#else
|
||||||
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
|
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
|
||||||
|
#endif
|
||||||
CGImageRelease(destImageRef);
|
CGImageRelease(destImageRef);
|
||||||
if (destImage == nil) {
|
if (destImage == nil) {
|
||||||
return image;
|
return image;
|
||||||
|
@ -429,7 +475,17 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
destImage.sd_isDecoded = YES;
|
destImage.sd_isDecoded = YES;
|
||||||
return destImage;
|
return destImage;
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
+ (NSUInteger)defaultScaleDownLimitBytes {
|
||||||
|
return kDestImageLimitBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
|
||||||
|
if (defaultScaleDownLimitBytes < kBytesPerMB) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kDestImageLimitBytes = defaultScaleDownLimitBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SD_UIKIT || SD_WATCH
|
#if SD_UIKIT || SD_WATCH
|
||||||
|
@ -503,7 +559,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#pragma mark - Helper Fuction
|
#pragma mark - Helper Fuction
|
||||||
#if SD_UIKIT || SD_WATCH
|
|
||||||
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
|
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
|
||||||
// Avoid extra decode
|
// Avoid extra decode
|
||||||
if (image.sd_isDecoded) {
|
if (image.sd_isDecoded) {
|
||||||
|
@ -514,7 +569,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
// do not decode animated images
|
// do not decode animated images
|
||||||
if (image.images != nil) {
|
if (image.sd_isAnimated) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -533,11 +588,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
CGFloat destTotalPixels;
|
CGFloat destTotalPixels;
|
||||||
if (bytes > 0) {
|
if (bytes == 0) {
|
||||||
destTotalPixels = bytes / kBytesPerPixel;
|
bytes = kDestImageLimitBytes;
|
||||||
} else {
|
|
||||||
destTotalPixels = kDestTotalPixels;
|
|
||||||
}
|
}
|
||||||
|
destTotalPixels = bytes / kBytesPerPixel;
|
||||||
if (destTotalPixels <= kPixelsPerMB) {
|
if (destTotalPixels <= kPixelsPerMB) {
|
||||||
// Too small to scale down
|
// Too small to scale down
|
||||||
return NO;
|
return NO;
|
||||||
|
@ -551,7 +605,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
||||||
|
|
||||||
return shouldScaleDown;
|
return shouldScaleDown;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
|
static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
|
||||||
// Inspiration from @libfeihu
|
// Inspiration from @libfeihu
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#import "NSData+ImageContentType.h"
|
#import "NSData+ImageContentType.h"
|
||||||
#import "SDImageCoderHelper.h"
|
#import "SDImageCoderHelper.h"
|
||||||
#import "SDAnimatedImageRep.h"
|
#import "SDAnimatedImageRep.h"
|
||||||
|
#import "UIImage+ForceDecode.h"
|
||||||
|
|
||||||
@interface SDImageIOCoderFrame : NSObject
|
@interface SDImageIOCoderFrame : NSObject
|
||||||
|
|
||||||
|
@ -32,6 +33,8 @@
|
||||||
NSUInteger _frameCount;
|
NSUInteger _frameCount;
|
||||||
NSArray<SDImageIOCoderFrame *> *_frames;
|
NSArray<SDImageIOCoderFrame *> *_frames;
|
||||||
BOOL _finished;
|
BOOL _finished;
|
||||||
|
BOOL _preserveAspectRatio;
|
||||||
|
CGSize _thumbnailSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
|
@ -145,6 +148,63 @@
|
||||||
return frameDuration;
|
return frameDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize {
|
||||||
|
// Parse the image properties
|
||||||
|
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
|
||||||
|
NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue];
|
||||||
|
NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue];
|
||||||
|
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
|
||||||
|
if (!exifOrientation) {
|
||||||
|
exifOrientation = kCGImagePropertyOrientationUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGImageRef imageRef;
|
||||||
|
if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
|
||||||
|
imageRef = CGImageSourceCreateImageAtIndex(source, index, NULL);
|
||||||
|
} else {
|
||||||
|
NSMutableDictionary *thumbnailOptions = [NSMutableDictionary dictionary];
|
||||||
|
thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
|
||||||
|
CGFloat maxPixelSize;
|
||||||
|
if (preserveAspectRatio) {
|
||||||
|
CGFloat pixelRatio = pixelWidth / pixelHeight;
|
||||||
|
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
|
||||||
|
if (pixelRatio > thumbnailRatio) {
|
||||||
|
maxPixelSize = thumbnailSize.width;
|
||||||
|
} else {
|
||||||
|
maxPixelSize = thumbnailSize.height;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
|
||||||
|
}
|
||||||
|
thumbnailOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
|
||||||
|
thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(YES);
|
||||||
|
imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)thumbnailOptions);
|
||||||
|
if (preserveAspectRatio) {
|
||||||
|
// kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
|
||||||
|
exifOrientation = kCGImagePropertyOrientationUp;
|
||||||
|
} else {
|
||||||
|
// `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size
|
||||||
|
if (imageRef) {
|
||||||
|
CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
|
||||||
|
CGImageRelease(imageRef);
|
||||||
|
imageRef = scaledImageRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!imageRef) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SD_UIKIT || SD_WATCH
|
||||||
|
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
|
||||||
|
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
|
||||||
|
#else
|
||||||
|
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
|
||||||
|
#endif
|
||||||
|
CGImageRelease(imageRef);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Decode
|
#pragma mark - Decode
|
||||||
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
- (BOOL)canDecodeFromData:(nullable NSData *)data {
|
||||||
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
|
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
|
||||||
|
@ -160,14 +220,34 @@
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CGSize thumbnailSize = CGSizeZero;
|
||||||
|
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
|
||||||
|
if (thumbnailSizeValue != nil) {
|
||||||
#if SD_MAC
|
#if SD_MAC
|
||||||
|
thumbnailSize = thumbnailSizeValue.sizeValue;
|
||||||
|
#else
|
||||||
|
thumbnailSize = thumbnailSizeValue.CGSizeValue;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL preserveAspectRatio = YES;
|
||||||
|
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
|
||||||
|
if (preserveAspectRatioValue != nil) {
|
||||||
|
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if SD_MAC
|
||||||
|
// If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
|
||||||
|
// Which decode frames in time and reduce memory usage
|
||||||
|
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
|
||||||
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
|
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
|
||||||
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
|
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
|
||||||
imageRep.size = size;
|
imageRep.size = size;
|
||||||
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
|
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
|
||||||
[animatedImage addRepresentation:imageRep];
|
[animatedImage addRepresentation:imageRep];
|
||||||
return animatedImage;
|
return animatedImage;
|
||||||
#else
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
@ -178,19 +258,17 @@
|
||||||
|
|
||||||
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
|
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
|
||||||
if (decodeFirstFrame || count <= 1) {
|
if (decodeFirstFrame || count <= 1) {
|
||||||
animatedImage = [[UIImage alloc] initWithData:data scale:scale];
|
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
|
||||||
} else {
|
} else {
|
||||||
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
|
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
|
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize];
|
||||||
if (!imageRef) {
|
if (!image) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
|
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
|
||||||
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
|
|
||||||
CGImageRelease(imageRef);
|
|
||||||
|
|
||||||
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
|
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
|
||||||
[frames addObject:frame];
|
[frames addObject:frame];
|
||||||
|
@ -205,7 +283,6 @@
|
||||||
CFRelease(source);
|
CFRelease(source);
|
||||||
|
|
||||||
return animatedImage;
|
return animatedImage;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Progressive Decode
|
#pragma mark - Progressive Decode
|
||||||
|
@ -225,6 +302,22 @@
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
_scale = scale;
|
_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
|
#if SD_UIKIT
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||||
#endif
|
#endif
|
||||||
|
@ -265,20 +358,13 @@
|
||||||
|
|
||||||
if (_width + _height > 0) {
|
if (_width + _height > 0) {
|
||||||
// Create the image
|
// Create the image
|
||||||
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
|
|
||||||
|
|
||||||
if (partialImageRef) {
|
|
||||||
CGFloat scale = _scale;
|
CGFloat scale = _scale;
|
||||||
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
|
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
|
||||||
if (scaleFactor != nil) {
|
if (scaleFactor != nil) {
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
#if SD_UIKIT || SD_WATCH
|
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
|
||||||
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:UIImageOrientationUp];
|
if (image) {
|
||||||
#else
|
|
||||||
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:kCGImagePropertyOrientationUp];
|
|
||||||
#endif
|
|
||||||
CGImageRelease(partialImageRef);
|
|
||||||
image.sd_imageFormat = self.class.imageFormat;
|
image.sd_imageFormat = self.class.imageFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,6 +456,22 @@
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
_scale = scale;
|
_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;
|
||||||
_imageSource = imageSource;
|
_imageSource = imageSource;
|
||||||
_imageData = data;
|
_imageData = data;
|
||||||
#if SD_UIKIT
|
#if SD_UIKIT
|
||||||
|
@ -421,23 +523,24 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
|
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
|
||||||
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
|
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
|
||||||
if (!imageRef) {
|
if (!image) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
image.sd_imageFormat = self.class.imageFormat;
|
||||||
// Image/IO create CGImage does not decode, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
|
// Image/IO create CGImage does not decode, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
|
||||||
CGImageRef newImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
|
CGImageRef imageRef = [SDImageCoderHelper CGImageCreateDecoded:image.CGImage];
|
||||||
if (!newImageRef) {
|
if (!imageRef) {
|
||||||
newImageRef = imageRef;
|
return image;
|
||||||
} else {
|
|
||||||
CGImageRelease(imageRef);
|
|
||||||
}
|
}
|
||||||
#if SD_MAC
|
#if SD_MAC
|
||||||
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:kCGImagePropertyOrientationUp];
|
image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:kCGImagePropertyOrientationUp];
|
||||||
#else
|
#else
|
||||||
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp];
|
image = [[UIImage alloc] initWithCGImage:imageRef scale:_scale orientation:image.imageOrientation];
|
||||||
#endif
|
#endif
|
||||||
CGImageRelease(newImageRef);
|
CGImageRelease(imageRef);
|
||||||
|
image.sd_isDecoded = YES;
|
||||||
|
image.sd_imageFormat = self.class.imageFormat;
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#import <ImageIO/ImageIO.h>
|
#import <ImageIO/ImageIO.h>
|
||||||
#import "UIImage+Metadata.h"
|
#import "UIImage+Metadata.h"
|
||||||
#import "SDImageHEICCoderInternal.h"
|
#import "SDImageHEICCoderInternal.h"
|
||||||
|
#import "SDImageIOAnimatedCoderInternal.h"
|
||||||
|
|
||||||
@implementation SDImageIOCoder {
|
@implementation SDImageIOCoder {
|
||||||
size_t _width, _height;
|
size_t _width, _height;
|
||||||
|
@ -19,6 +20,8 @@
|
||||||
CGImageSourceRef _imageSource;
|
CGImageSourceRef _imageSource;
|
||||||
CGFloat _scale;
|
CGFloat _scale;
|
||||||
BOOL _finished;
|
BOOL _finished;
|
||||||
|
BOOL _preserveAspectRatio;
|
||||||
|
CGSize _thumbnailSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
|
@ -74,7 +77,33 @@
|
||||||
scale = MAX([scaleFactor doubleValue], 1) ;
|
scale = MAX([scaleFactor doubleValue], 1) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIImage *image = [[UIImage alloc] initWithData:data scale:scale];
|
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];
|
||||||
|
CFRelease(source);
|
||||||
|
if (!image) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
|
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -95,6 +124,22 @@
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
_scale = scale;
|
_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
|
#if SD_UIKIT
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
||||||
#endif
|
#endif
|
||||||
|
@ -140,21 +185,13 @@
|
||||||
|
|
||||||
if (_width + _height > 0) {
|
if (_width + _height > 0) {
|
||||||
// Create the image
|
// Create the image
|
||||||
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
|
|
||||||
|
|
||||||
if (partialImageRef) {
|
|
||||||
CGFloat scale = _scale;
|
CGFloat scale = _scale;
|
||||||
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
|
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
|
||||||
if (scaleFactor != nil) {
|
if (scaleFactor != nil) {
|
||||||
scale = MAX([scaleFactor doubleValue], 1);
|
scale = MAX([scaleFactor doubleValue], 1);
|
||||||
}
|
}
|
||||||
#if SD_UIKIT || SD_WATCH
|
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize];
|
||||||
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:_orientation];
|
if (image) {
|
||||||
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:imageOrientation];
|
|
||||||
#else
|
|
||||||
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:_orientation];
|
|
||||||
#endif
|
|
||||||
CGImageRelease(partialImageRef);
|
|
||||||
CFStringRef uttype = CGImageSourceGetType(_imageSource);
|
CFStringRef uttype = CGImageSourceGetType(_imageSource);
|
||||||
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
|
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,25 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
|
||||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||||
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
||||||
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
|
||||||
if (context) {
|
NSValue *thumbnailSizeValue;
|
||||||
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
||||||
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
if (shouldScaleDown) {
|
||||||
coderOptions = [mutableCoderOptions copy];
|
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
|
||||||
|
CGFloat dimension = ceil(sqrt(thumbnailPixels));
|
||||||
|
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
|
||||||
}
|
}
|
||||||
|
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||||
|
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
|
||||||
|
mutableCoderOptions[SDImageCoderWebImageContext] = context;
|
||||||
|
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
|
||||||
|
|
||||||
if (!decodeFirstFrame) {
|
if (!decodeFirstFrame) {
|
||||||
// check whether we should use `SDAnimatedImage`
|
// check whether we should use `SDAnimatedImage`
|
||||||
|
@ -71,14 +84,9 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldDecode) {
|
if (shouldDecode) {
|
||||||
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
|
||||||
if (shouldScaleDown) {
|
|
||||||
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
|
|
||||||
} else {
|
|
||||||
image = [SDImageCoderHelper decodedImageWithImage:image];
|
image = [SDImageCoderHelper decodedImageWithImage:image];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -99,12 +107,25 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
|
||||||
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
|
||||||
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
|
||||||
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
|
||||||
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
|
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
|
||||||
if (context) {
|
NSValue *thumbnailSizeValue;
|
||||||
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
|
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
|
||||||
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
|
if (shouldScaleDown) {
|
||||||
coderOptions = [mutableCoderOptions copy];
|
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
|
||||||
|
CGFloat dimension = ceil(sqrt(thumbnailPixels));
|
||||||
|
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
|
||||||
}
|
}
|
||||||
|
if (context[SDWebImageContextImageThumbnailPixelSize]) {
|
||||||
|
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
|
||||||
|
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
|
||||||
|
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
|
||||||
|
mutableCoderOptions[SDImageCoderWebImageContext] = context;
|
||||||
|
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
|
||||||
|
|
||||||
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
|
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
|
||||||
if (!progressiveCoder) {
|
if (!progressiveCoder) {
|
||||||
|
|
|
@ -122,9 +122,12 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
|
||||||
SDWebImageAvoidAutoSetImage = 1 << 10,
|
SDWebImageAvoidAutoSetImage = 1 << 10,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
|
* By default, images are decoded respecting their original size.
|
||||||
* images to a size compatible with the constrained memory of devices.
|
* This flag will scale down the images to a size compatible with the constrained memory of devices.
|
||||||
* This flag take no effect if `SDWebImageAvoidDecodeImage` is set. And it will be ignored if `SDWebImageProgressiveLoad` is set.
|
* To control the limit memory bytes, check `SDImageCoderHelper.defaultScaleDownLimitBytes` (Defaults to 60MB on iOS)
|
||||||
|
* This will actually translate to use context option `.imageThumbnailPixelSize` from v5.5.0 (Defaults to (3966, 3966) on iOS). Previously does not.
|
||||||
|
* This flags effect the progressive and animated images as well from v5.5.0. Previously does not.
|
||||||
|
* @note If you need detail controls, it's better to use context option `imageThumbnailPixelSize` and `imagePreserveAspectRatio` instead.
|
||||||
*/
|
*/
|
||||||
SDWebImageScaleDownLargeImages = 1 << 11,
|
SDWebImageScaleDownLargeImages = 1 << 11,
|
||||||
|
|
||||||
|
@ -217,6 +220,19 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageT
|
||||||
*/
|
*/
|
||||||
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor;
|
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
|
||||||
|
Defaults to YES. (NSNumber)
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImagePreserveAspectRatio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A CGSize raw value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.imagePreserveAspectRatio`) the value size.
|
||||||
|
@note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
|
||||||
|
Defaults to CGSizeZero, which means no thumbnail generation at all. (NSValue)
|
||||||
|
*/
|
||||||
|
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache.
|
A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache.
|
||||||
If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time.
|
If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time.
|
||||||
|
|
|
@ -122,6 +122,8 @@ SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImage
|
||||||
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
|
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
|
||||||
SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer";
|
SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer";
|
||||||
SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor";
|
SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor";
|
||||||
|
SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio";
|
||||||
|
SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize";
|
||||||
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
|
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
|
||||||
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
|
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
|
||||||
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
|
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
|
||||||
|
|
|
@ -95,19 +95,45 @@ static id<SDImageLoader> _defaultImageLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
|
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
|
||||||
return [self cacheKeyForURL:url cacheKeyFilter:self.cacheKeyFilter];
|
return [self cacheKeyForURL:url context:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id<SDWebImageCacheKeyFilter>)cacheKeyFilter {
|
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return @"";
|
return @"";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cacheKeyFilter) {
|
NSString *key;
|
||||||
return [cacheKeyFilter cacheKeyForURL:url];
|
// Cache Key Filter
|
||||||
} else {
|
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
|
||||||
return url.absoluteString;
|
if (context[SDWebImageContextCacheKeyFilter]) {
|
||||||
|
cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
||||||
}
|
}
|
||||||
|
if (cacheKeyFilter) {
|
||||||
|
key = [cacheKeyFilter cacheKeyForURL:url];
|
||||||
|
} else {
|
||||||
|
key = url.absoluteString;
|
||||||
|
}
|
||||||
|
// Thumbnail Key Appending
|
||||||
|
NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
|
||||||
|
if (thumbnailSizeValue != nil) {
|
||||||
|
CGSize thumbnailSize = CGSizeZero;
|
||||||
|
#if SD_MAC
|
||||||
|
thumbnailSize = thumbnailSizeValue.sizeValue;
|
||||||
|
#else
|
||||||
|
thumbnailSize = thumbnailSizeValue.CGSizeValue;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BOOL preserveAspectRatio = YES;
|
||||||
|
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
|
||||||
|
if (preserveAspectRatioValue != nil) {
|
||||||
|
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
||||||
|
}
|
||||||
|
NSString *transformerKey = [NSString stringWithFormat:@"Thumbnail({%f,%f},%d)", thumbnailSize.width, thumbnailSize.height, preserveAspectRatio];
|
||||||
|
key = SDTransformedKeyForKey(key, transformerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
|
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
|
||||||
|
@ -188,8 +214,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
||||||
// Check whether we should query cache
|
// Check whether we should query cache
|
||||||
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
|
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
|
||||||
if (shouldQueryCache) {
|
if (shouldQueryCache) {
|
||||||
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
NSString *key = [self cacheKeyForURL:url context:context];
|
||||||
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
|
|
||||||
@weakify(operation);
|
@weakify(operation);
|
||||||
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
|
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
|
||||||
@strongify(operation);
|
@strongify(operation);
|
||||||
|
@ -303,8 +328,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
||||||
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
if (context[SDWebImageContextOriginalStoreCacheType]) {
|
||||||
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
|
||||||
}
|
}
|
||||||
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
NSString *key = [self cacheKeyForURL:url context:context];
|
||||||
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
|
|
||||||
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
||||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||||
|
|
||||||
|
@ -353,8 +377,7 @@ static id<SDImageLoader> _defaultImageLoader;
|
||||||
if (context[SDWebImageContextStoreCacheType]) {
|
if (context[SDWebImageContextStoreCacheType]) {
|
||||||
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
|
||||||
}
|
}
|
||||||
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
|
NSString *key = [self cacheKeyForURL:url context:context];
|
||||||
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
|
|
||||||
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
|
||||||
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
|
||||||
BOOL shouldTransformImage = originalImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer;
|
BOOL shouldTransformImage = originalImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer;
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
|
|
||||||
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
|
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
|
||||||
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
|
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
|
||||||
|
+ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
expect([UIImage sd_decodedAndScaledDownImageWithImage:nil]).to.beNil();
|
expect([UIImage sd_decodedAndScaledDownImageWithImage:nil]).to.beNil();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SD_UIKIT
|
|
||||||
- (void)test02ThatDecodedImageWithImageWorksWithARegularJPGImage {
|
- (void)test02ThatDecodedImageWithImageWorksWithARegularJPGImage {
|
||||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
|
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
|
||||||
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
||||||
|
@ -34,7 +33,11 @@
|
||||||
- (void)test03ThatDecodedImageWithImageDoesNotDecodeAnimatedImages {
|
- (void)test03ThatDecodedImageWithImageDoesNotDecodeAnimatedImages {
|
||||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
|
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
|
||||||
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
||||||
|
#if SD_MAC
|
||||||
|
UIImage *animatedImage = image;
|
||||||
|
#else
|
||||||
UIImage *animatedImage = [UIImage animatedImageWithImages:@[image] duration:0];
|
UIImage *animatedImage = [UIImage animatedImageWithImages:@[image] duration:0];
|
||||||
|
#endif
|
||||||
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:animatedImage];
|
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:animatedImage];
|
||||||
expect(decodedImage).toNot.beNil();
|
expect(decodedImage).toNot.beNil();
|
||||||
expect(decodedImage).to.equal(animatedImage);
|
expect(decodedImage).to.equal(animatedImage);
|
||||||
|
@ -61,7 +64,7 @@
|
||||||
- (void)test06ThatDecodeAndScaleDownImageWorks {
|
- (void)test06ThatDecodeAndScaleDownImageWorks {
|
||||||
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
|
NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
|
||||||
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
|
||||||
UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image];
|
UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:(60 * 1024 * 1024)];
|
||||||
expect(decodedImage).toNot.beNil();
|
expect(decodedImage).toNot.beNil();
|
||||||
expect(decodedImage).toNot.equal(image);
|
expect(decodedImage).toNot.equal(image);
|
||||||
expect(decodedImage.size.width).toNot.equal(image.size.width);
|
expect(decodedImage.size.width).toNot.equal(image.size.width);
|
||||||
|
@ -78,7 +81,6 @@
|
||||||
expect(decodedImage.size.width).to.equal(image.size.width);
|
expect(decodedImage.size.width).to.equal(image.size.width);
|
||||||
expect(decodedImage.size.height).to.equal(image.size.height);
|
expect(decodedImage.size.height).to.equal(image.size.height);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
- (void)test11ThatAPNGPCoderWorks {
|
- (void)test11ThatAPNGPCoderWorks {
|
||||||
NSURL *APNGURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"apng"];
|
NSURL *APNGURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"apng"];
|
||||||
|
@ -197,14 +199,47 @@ withLocalImageURL:(NSURL *)imageUrl
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3 - check thumbnail decoding
|
||||||
|
CGFloat pixelWidth = inputImage.size.width;
|
||||||
|
CGFloat pixelHeight = inputImage.size.height;
|
||||||
|
expect(pixelWidth).beGreaterThan(0);
|
||||||
|
expect(pixelHeight).beGreaterThan(0);
|
||||||
|
// check thumnail with scratch
|
||||||
|
CGFloat thumbnailWidth = 50;
|
||||||
|
CGFloat thumbnailHeight = 50;
|
||||||
|
UIImage *thumbImage = [coder decodedImageWithData:inputImageData options:@{
|
||||||
|
SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
|
||||||
|
SDImageCoderDecodePreserveAspectRatio : @(NO)
|
||||||
|
}];
|
||||||
|
expect(thumbImage).toNot.beNil();
|
||||||
|
expect(thumbImage.size).equal(CGSizeMake(thumbnailWidth, thumbnailHeight));
|
||||||
|
// check thumnail with aspect ratio limit
|
||||||
|
thumbImage = [coder decodedImageWithData:inputImageData options:@{
|
||||||
|
SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
|
||||||
|
SDImageCoderDecodePreserveAspectRatio : @(YES)
|
||||||
|
}];
|
||||||
|
expect(thumbImage).toNot.beNil();
|
||||||
|
CGFloat ratio = pixelWidth / pixelHeight;
|
||||||
|
CGFloat thumbnailRatio = thumbnailWidth / thumbnailHeight;
|
||||||
|
CGSize thumbnailPixelSize;
|
||||||
|
if (ratio > thumbnailRatio) {
|
||||||
|
thumbnailPixelSize = CGSizeMake(thumbnailWidth, round(thumbnailWidth / ratio));
|
||||||
|
} else {
|
||||||
|
thumbnailPixelSize = CGSizeMake(round(thumbnailHeight * ratio), thumbnailHeight);
|
||||||
|
}
|
||||||
|
// Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
|
||||||
|
expect(ABS(thumbImage.size.width - thumbnailPixelSize.width) <= 1);
|
||||||
|
expect(ABS(thumbImage.size.height - thumbnailPixelSize.height) <= 1);
|
||||||
|
|
||||||
|
|
||||||
if (supportsEncoding) {
|
if (supportsEncoding) {
|
||||||
// 3 - check if we can encode to the original format
|
// 4 - check if we can encode to the original format
|
||||||
if (encodingFormat == SDImageFormatUndefined) {
|
if (encodingFormat == SDImageFormatUndefined) {
|
||||||
encodingFormat = inputImageFormat;
|
encodingFormat = inputImageFormat;
|
||||||
}
|
}
|
||||||
expect([coder canEncodeToFormat:encodingFormat]).to.beTruthy();
|
expect([coder canEncodeToFormat:encodingFormat]).to.beTruthy();
|
||||||
|
|
||||||
// 4 - encode from UIImage to NSData using the inputImageFormat and check it
|
// 5 - encode from UIImage to NSData using the inputImageFormat and check it
|
||||||
NSData *outputImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:nil];
|
NSData *outputImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:nil];
|
||||||
expect(outputImageData).toNot.beNil();
|
expect(outputImageData).toNot.beNil();
|
||||||
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
|
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
|
||||||
|
|
|
@ -247,6 +247,27 @@
|
||||||
[self waitForExpectationsWithCommonTimeout];
|
[self waitForExpectationsWithCommonTimeout];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)test13ThatScaleDownLargeImageUseThumbnailDecoding {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"SDWebImageScaleDownLargeImages should translate to thumbnail decoding"];
|
||||||
|
NSURL *originalImageURL = [NSURL URLWithString:@"http://via.placeholder.com/3999x3999.png"]; // Max size for this API
|
||||||
|
NSUInteger defaultLimitBytes = SDImageCoderHelper.defaultScaleDownLimitBytes;
|
||||||
|
SDImageCoderHelper.defaultScaleDownLimitBytes = 1000 * 1000 * 4; // Limit 1000x1000 pixel
|
||||||
|
// From v5.5.0, the `SDWebImageScaleDownLargeImages` translate to `SDWebImageContextImageThumbnailPixelSize`, and works for progressive loading
|
||||||
|
[SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages | SDWebImageProgressiveLoad progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
|
||||||
|
expect(image).notTo.beNil();
|
||||||
|
expect(image.size).equal(CGSizeMake(1000, 1000));
|
||||||
|
if (finished) {
|
||||||
|
[expectation fulfill];
|
||||||
|
} else {
|
||||||
|
expect(image.sd_isIncremental).beTruthy();
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
[self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
|
||||||
|
SDImageCoderHelper.defaultScaleDownLimitBytes = defaultLimitBytes;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)testJPEGPath {
|
- (NSString *)testJPEGPath {
|
||||||
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
|
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
|
||||||
return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
|
return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
|
||||||
|
|
Loading…
Reference in New Issue