Merge pull request #3561 from dreampiggy/bugfix/macOS_limitBytes_animatedImageRep

Fix macOS bug that SDImageCoderDecodeScaleDownLimitBytes still use the AnimatedImageRep and beyond the byte limit
This commit is contained in:
DreamPiggy 2023-07-13 22:53:09 +08:00 committed by GitHub
commit f5f27a9e01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 30 additions and 22 deletions

View File

@ -83,6 +83,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeUseLazyDec
3. If the `framePixelSize < originalImagePixelSize`, then do thumbnail decoding (see `SDImageCoderDecodeThumbnailPixelSize`) use the `framePixelSize` and `preseveAspectRatio = YES`
4. Else, use the full pixel decoding (small than limit bytes)
5. Whatever result, this does not effect the animated/static behavior of image. So even if you set `limitBytes = 1 && frameCount = 100`, we will stll create animated image with each frame `1x1` pixel size.
@note You can use the logic from `+[SDImageCoder scaledSizeWithImageSize:limitBytes:bytesPerPixel:frameCount:]`
@note This option has higher priority than `.decodeThumbnailPixelSize`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleDownLimitBytes;

View File

@ -104,6 +104,12 @@ typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
*/
+ (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize preserveAspectRatio:(BOOL)preserveAspectRatio shouldScaleUp:(BOOL)shouldScaleUp;
/// Calculate the limited image size with the bytes, when using `SDImageCoderDecodeScaleDownLimitBytes`. This preserve aspect ratio and never scale up
/// @param imageSize The image size (in pixel or point defined by caller)
/// @param limitBytes The limit bytes
/// @param bytesPerPixel The bytes per pixel
/// @param frameCount The image frame count, 0 means 1 frame as well
+ (CGSize)scaledSizeWithImageSize:(CGSize)imageSize limitBytes:(NSUInteger)limitBytes bytesPerPixel:(NSUInteger)bytesPerPixel frameCount:(NSUInteger)frameCount;
/**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image. On iOS 15+, this may use `UIImage.preparingForDisplay()` to use CMPhoto for better performance than the old solution.
@param image The image to be decoded

View File

@ -428,6 +428,19 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return CGSizeMake(resultWidth, resultHeight);
}
+ (CGSize)scaledSizeWithImageSize:(CGSize)imageSize limitBytes:(NSUInteger)limitBytes bytesPerPixel:(NSUInteger)bytesPerPixel frameCount:(NSUInteger)frameCount {
if (CGSizeEqualToSize(imageSize, CGSizeZero)) return CGSizeMake(1, 1);
NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1);
CGFloat ratio = imageSize.height / imageSize.width;
CGFloat width = sqrt(totalFramePixelSize / ratio);
CGFloat height = width * ratio;
width = MAX(1, floor(width));
height = MAX(1, floor(height));
CGSize size = CGSizeMake(width, height);
return size;
}
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
if (![self shouldDecodeImage:image]) {
return image;

View File

@ -47,19 +47,6 @@ static CGImageRef __nullable SDCGImageCreateCopy(CGImageRef cg_nullable image) {
return newImage;
}
static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize originalSize, NSUInteger frameCount, NSUInteger bytesPerPixel) {
if (CGSizeEqualToSize(originalSize, CGSizeZero)) return CGSizeMake(1, 1);
NSUInteger totalFramePixelSize = limitBytes / bytesPerPixel / (frameCount ?: 1);
CGFloat ratio = originalSize.height / originalSize.width;
CGFloat width = sqrt(totalFramePixelSize / ratio);
CGFloat height = width * ratio;
width = MAX(1, floor(width));
height = MAX(1, floor(height));
CGSize size = CGSizeMake(width, height);
return size;
}
@interface SDImageIOCoderFrame : NSObject
@property (nonatomic, assign) NSUInteger index; // Frame index (zero based)
@ -384,10 +371,16 @@ static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize
lazyDecode = lazyDecodeValue.boolValue;
}
NSUInteger limitBytes = 0;
NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes];
if (limitBytesValue != nil) {
limitBytes = limitBytesValue.unsignedIntegerValue;
}
#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) {
if (limitBytes == 0 && (thumbnailSize.width == 0 || thumbnailSize.height == 0)) {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
if (imageRep) {
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
@ -432,11 +425,6 @@ static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize
size_t frameCount = CGImageSourceGetCount(source);
UIImage *animatedImage;
NSUInteger limitBytes = 0;
NSNumber *limitBytesValue = options[SDImageCoderDecodeScaleDownLimitBytes];
if (limitBytesValue != nil) {
limitBytes = limitBytesValue.unsignedIntegerValue;
}
// Parse the image properties
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
size_t width = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
@ -445,7 +433,7 @@ static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize
if (limitBytes > 0) {
// Hack since ImageIO public API (not CGImageDecompressor/CMPhoto) always return back RGBA8888 CGImage
CGSize imageSize = CGSizeMake(width, height);
CGSize framePixelSize = SDCalculateScaleDownPixelSize(limitBytes, imageSize, frameCount, 4);
CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:limitBytes bytesPerPixel:4 frameCount:frameCount];
// Override thumbnail size
thumbnailSize = framePixelSize;
preserveAspectRatio = YES;
@ -568,7 +556,7 @@ static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize
if (_limitBytes > 0) {
// Hack since ImageIO public API (not CGImageDecompressor/CMPhoto) always return back RGBA8888 CGImage
CGSize imageSize = CGSizeMake(_width, _height);
CGSize framePixelSize = SDCalculateScaleDownPixelSize(_limitBytes, imageSize, _frameCount, 4);
CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:_limitBytes bytesPerPixel:4 frameCount:_frameCount];
// Override thumbnail size
_thumbnailSize = framePixelSize;
_preserveAspectRatio = YES;
@ -773,7 +761,7 @@ static inline CGSize SDCalculateScaleDownPixelSize(NSUInteger limitBytes, CGSize
if (_limitBytes > 0) {
// Hack since ImageIO public API (not CGImageDecompressor/CMPhoto) always return back RGBA8888 CGImage
CGSize imageSize = CGSizeMake(_width, _height);
CGSize framePixelSize = SDCalculateScaleDownPixelSize(_limitBytes, imageSize, _frameCount, 4);
CGSize framePixelSize = [SDImageCoderHelper scaledSizeWithImageSize:imageSize limitBytes:_limitBytes bytesPerPixel:4 frameCount:_frameCount];
// Override thumbnail size
_thumbnailSize = framePixelSize;
_preserveAspectRatio = YES;