Merge pull request #3573 from dreampiggy/feature/keep_animated_data_macOS_imageRep

Expose the data and format when SDAnimatedImageRep created with APNG/GIF/WebP/HEICS
This commit is contained in:
DreamPiggy 2023-07-27 15:28:18 +08:00 committed by GitHub
commit bc4d0c7c62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 3 deletions

View File

@ -88,6 +88,7 @@
/**
Current animated image format.
@note This format is only valid when `animatedImageData` not nil
*/
@property (nonatomic, assign, readonly) SDImageFormat animatedImageFormat;

View File

@ -10,6 +10,8 @@
#if SD_MAC
#import "NSData+ImageContentType.h"
/**
A subclass of `NSBitmapImageRep` to fix that GIF duration issue because `NSBitmapImageRep` will reset `NSImageCurrentFrameDuration` by using `kCGImagePropertyGIFDelayTime` but not `kCGImagePropertyGIFUnclampedDelayTime`.
This also fix the GIF loop count issue, which will use the Netscape standard (See http://www6.uniovi.es/gifanim/gifabout.htm) to only place once when the `kCGImagePropertyGIFLoopCount` is nil. This is what modern browser's behavior.
@ -18,6 +20,14 @@
*/
@interface SDAnimatedImageRep : NSBitmapImageRep
/// Current animated image format.
/// @note This format is only valid when `animatedImageData` not nil
@property (nonatomic, assign, readonly) SDImageFormat animatedImageFormat;
/// This allows to retrive the compressed data like GIF using `sd_imageData` on parent `NSImage`, without re-encoding (waste CPU and RAM)
/// @note This is typically nonnull when you create with `initWithData:`, even it's marked as weak, because ImageIO retain it
@property (nonatomic, readonly, nullable, weak) NSData *animatedImageData;
@end
#endif

View File

@ -19,6 +19,7 @@
@interface SDAnimatedImageRep ()
/// This wrap the animated image frames for legacy animated image coder API (`encodedDataWithImage:`).
@property (nonatomic, readwrite, weak) NSArray<SDImageFrame *> *frames;
@property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
@end
@implementation SDAnimatedImageRep {
@ -33,9 +34,12 @@
}
- (instancetype)copyWithZone:(NSZone *)zone {
SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
CFRetain(imageRep->_imageSource);
return imageRep;
SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
// super will copy all ivars
if (imageRep->_imageSource) {
CFRetain(imageRep->_imageSource);
}
return imageRep;
}
// `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer
@ -63,15 +67,19 @@
if (!type) {
return self;
}
_animatedImageData = data; // CGImageSource will retain the data internally, no extra copy
SDImageFormat format = SDImageFormatUndefined;
if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
// Fix the `NSBitmapImageRep` GIF loop count calculation issue
// Which will use 0 when there are no loop count information metadata in GIF data
format = SDImageFormatGIF;
NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatPNG;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageAPNGCoder imageLoopCountWithSource:imageSource];
@ -79,6 +87,7 @@
} else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
// HEIC
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatHEIC;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageHEICCoder imageLoopCountWithSource:imageSource];
@ -86,11 +95,15 @@
} else if (CFStringCompare(type, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
// WebP
// Do initialize about frame count, current frame/duration and loop count
format = SDImageFormatWebP;
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
[self setProperty:NSImageCurrentFrame withValue:@(0)];
NSUInteger loopCount = [SDImageAWebPCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else {
format = [NSData sd_imageFormatForImageData:data];
}
_animatedImageFormat = format;
}
return self;
}

View File

@ -46,6 +46,7 @@
Encode the current image to the data, the image format is unspecified
@note If the receiver is `SDAnimatedImage`, this will return the animated image data if available. No more extra encoding process.
@note For macOS, if the receiver contains only `SDAnimatedImageRep`, this will return the animated image data if available. No more extra encoding process.
@return The encoded data. If can't encode, return nil
*/
- (nullable NSData *)sd_imageData;

View File

@ -8,6 +8,7 @@
#import "UIImage+MultiFormat.h"
#import "SDImageCodersManager.h"
#import "SDAnimatedImageRep.h"
@implementation UIImage (MultiFormat)
@ -28,6 +29,18 @@
}
- (nullable NSData *)sd_imageData {
#if SD_MAC
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
// Check weak animated data firstly
if ([imageRep isKindOfClass:[SDAnimatedImageRep class]]) {
SDAnimatedImageRep *animatedImageRep = (SDAnimatedImageRep *)imageRep;
NSData *imageData = [animatedImageRep animatedImageData];
if (imageData) {
return imageData;
}
}
#endif
return [self sd_imageDataAsFormat:SDImageFormatUndefined];
}

View File

@ -446,6 +446,14 @@
UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
NSData *data = [SDImageGIFCoder.sharedCoder encodedDataWithImage:animatedImage format:SDImageFormatGIF options:nil];
expect(data).notTo.beNil();
#if SD_MAC
// Test implementation use SDAnimatedImageRep
SDAnimatedImageRep *rep = (SDAnimatedImageRep *)animatedImage.representations.firstObject;
expect([rep isKindOfClass:SDAnimatedImageRep.class]);
expect(rep.animatedImageData).equal(data);
expect(rep.animatedImageFormat).equal(SDImageFormatGIF);
#endif
// Test new API
NSData *data2 = [SDImageGIFCoder.sharedCoder encodedDataWithFrames:frames loopCount:0 format:SDImageFormatGIF options:nil];