Expose the data and format when SDAnimatedImageRep created with APNG/GIF/WebP/HEICS

This can avoid extra decoding (slow) for some cases if you need compressed image data
This commit is contained in:
DreamPiggy 2023-07-26 22:55:02 +08:00
parent 8dbf8ed97a
commit 1f4292ec82
5 changed files with 41 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 assigned frames firstly
if ([imageRep isKindOfClass:[SDAnimatedImageRep class]]) {
SDAnimatedImageRep *animatedImageRep = (SDAnimatedImageRep *)imageRep;
NSData *imageData = [animatedImageRep animatedImageData];
if (imageData) {
return imageData;
}
}
#endif
return [self sd_imageDataAsFormat:SDImageFormatUndefined];
}