Update the test case, fix the behavior of thumbnail pixel size when aspect ratio is YES.
This commit is contained in:
parent
fa124b4d11
commit
77283f6116
|
@ -37,6 +37,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAs
|
|||
/**
|
||||
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;
|
||||
|
|
|
@ -73,6 +73,16 @@
|
|||
*/
|
||||
+ (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
|
||||
@param image The image to be decoded
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
#import "UIImage+ForceDecode.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;
|
||||
}
|
||||
|
||||
static const size_t kBytesPerPixel = 4;
|
||||
static const size_t kBitsPerComponent = 8;
|
||||
|
@ -271,6 +277,53 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
|
|||
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 {
|
||||
if (![self shouldDecodeImage:image]) {
|
||||
return image;
|
||||
|
|
|
@ -164,12 +164,29 @@
|
|||
} else {
|
||||
NSMutableDictionary *thumbnailOptions = [NSMutableDictionary dictionary];
|
||||
thumbnailOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
|
||||
thumbnailOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = preserveAspectRatio ? @(MAX(thumbnailSize.width, thumbnailSize.height)) : @(MIN(thumbnailSize.width, thumbnailSize.height));
|
||||
CGFloat maxPixelSize;
|
||||
if (preserveAspectRatio) {
|
||||
if (pixelWidth > pixelHeight) {
|
||||
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) {
|
||||
|
|
|
@ -228,6 +228,7 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageP
|
|||
|
||||
/**
|
||||
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;
|
||||
|
|
|
@ -199,14 +199,42 @@ withLocalImageURL:(NSURL *)imageUrl
|
|||
#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
|
||||
UIImage *thumbImage = [coder decodedImageWithData:inputImageData options:@{
|
||||
SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(100, 100)),
|
||||
SDImageCoderDecodePreserveAspectRatio : @(NO)
|
||||
}];
|
||||
expect(thumbImage).toNot.beNil();
|
||||
expect(thumbImage.size).equal(CGSizeMake(100, 100));
|
||||
// check thumnail with aspect ratio limit
|
||||
thumbImage = [coder decodedImageWithData:inputImageData options:@{
|
||||
SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(100, 100)),
|
||||
SDImageCoderDecodePreserveAspectRatio : @(YES)
|
||||
}];
|
||||
expect(thumbImage).toNot.beNil();
|
||||
CGFloat ratio = pixelWidth / pixelHeight;
|
||||
CGSize thumbnailPixelSize;
|
||||
if (pixelWidth > pixelHeight) {
|
||||
thumbnailPixelSize = CGSizeMake(100, floor(100 / ratio));
|
||||
} else {
|
||||
thumbnailPixelSize = CGSizeMake(floor(100 * ratio), 100);
|
||||
}
|
||||
expect(thumbImage.size).equal(thumbnailPixelSize);
|
||||
|
||||
|
||||
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) {
|
||||
encodingFormat = inputImageFormat;
|
||||
}
|
||||
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];
|
||||
expect(outputImageData).toNot.beNil();
|
||||
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
|
||||
|
|
Loading…
Reference in New Issue