Skip to content

Commit

Permalink
Merge pull request #3561 from dreampiggy/bugfix/macOS_limitBytes_anim…
Browse files Browse the repository at this point in the history
…atedImageRep

Fix macOS bug that SDImageCoderDecodeScaleDownLimitBytes still use the AnimatedImageRep and beyond the byte limit
  • Loading branch information
dreampiggy committed Jul 13, 2023
2 parents c51ba84 + 47fd1c9 commit f5f27a9
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 22 deletions.
1 change: 1 addition & 0 deletions SDWebImage/Core/SDImageCoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions SDWebImage/Core/SDImageCoderHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions SDWebImage/Core/SDImageCoderHelper.m
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,19 @@ + (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize
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;
Expand Down
32 changes: 10 additions & 22 deletions SDWebImage/Core/SDImageIOAnimatedCoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -384,10 +371,16 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
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);
Expand Down Expand Up @@ -432,11 +425,6 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
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];
Expand All @@ -445,7 +433,7 @@ - (UIImage *)decodedImageWithData:(NSData *)data options:(nullable SDImageCoderO
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;
Expand Down Expand Up @@ -568,7 +556,7 @@ - (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
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;
Expand Down Expand Up @@ -773,7 +761,7 @@ - (nullable instancetype)initWithAnimatedImageData:(nullable NSData *)data optio
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;
Expand Down

0 comments on commit f5f27a9

Please sign in to comment.