|
|
@@ -13,6 +13,7 @@
|
|
|
#import "SDImageCoderHelper.h"
|
|
|
#import "SDAnimatedImageRep.h"
|
|
|
#import "UIImage+ForceDecode.h"
|
|
|
+#import "SDInternalMacros.h"
|
|
|
|
|
|
// Specify DPI for vector format in CGImageSource, like PDF
|
|
|
static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
|
|
|
@@ -32,6 +33,8 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
@implementation SDImageIOAnimatedCoder {
|
|
|
size_t _width, _height;
|
|
|
CGImageSourceRef _imageSource;
|
|
|
+ BOOL _incremental;
|
|
|
+ SD_LOCK_DECLARE(_lock); // Lock only apply for incremental animation decoding
|
|
|
NSData *_imageData;
|
|
|
CGFloat _scale;
|
|
|
NSUInteger _loopCount;
|
|
|
@@ -328,7 +331,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
if (decodeFirstFrame || count <= 1) {
|
|
|
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize forceDecode:NO options:nil];
|
|
|
} else {
|
|
|
- NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
|
|
|
+ NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:count];
|
|
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
|
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize forceDecode:NO options:nil];
|
|
|
@@ -364,6 +367,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
if (self) {
|
|
|
NSString *imageUTType = self.class.imageUTType;
|
|
|
_imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge NSString *)kCGImageSourceTypeIdentifierHint : imageUTType});
|
|
|
+ _incremental = YES;
|
|
|
CGFloat scale = 1;
|
|
|
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
|
|
|
if (scaleFactor != nil) {
|
|
|
@@ -386,6 +390,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
preserveAspectRatio = preserveAspectRatioValue.boolValue;
|
|
|
}
|
|
|
_preserveAspectRatio = preserveAspectRatio;
|
|
|
+ SD_LOCK_INIT(_lock);
|
|
|
#if SD_UIKIT
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
|
|
|
#endif
|
|
|
@@ -394,6 +399,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
}
|
|
|
|
|
|
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
|
|
|
+ NSCParameterAssert(_incremental);
|
|
|
if (_finished) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -421,11 +427,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ SD_LOCK(_lock);
|
|
|
// For animated image progressive decoding because the frame count and duration may be changed.
|
|
|
[self scanAndCheckFramesValidWithImageSource:_imageSource];
|
|
|
+ SD_UNLOCK(_lock);
|
|
|
}
|
|
|
|
|
|
- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
|
|
|
+ NSCParameterAssert(_incremental);
|
|
|
UIImage *image;
|
|
|
|
|
|
if (_width + _height > 0) {
|
|
|
@@ -606,17 +615,21 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
}
|
|
|
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
|
|
|
NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
|
|
|
- NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray array];
|
|
|
+ _loopCount = loopCount;
|
|
|
|
|
|
+ NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
|
|
|
for (size_t i = 0; i < frameCount; i++) {
|
|
|
SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
|
|
|
frame.index = i;
|
|
|
frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
|
|
|
[frames addObject:frame];
|
|
|
}
|
|
|
+ if (frames.count != frameCount) {
|
|
|
+ // frames not match, do not override current value
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
|
|
|
_frameCount = frameCount;
|
|
|
- _loopCount = loopCount;
|
|
|
_frames = [frames copy];
|
|
|
|
|
|
return YES;
|
|
|
@@ -635,16 +648,48 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
|
|
|
}
|
|
|
|
|
|
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
|
|
|
- if (index >= _frameCount) {
|
|
|
- return 0;
|
|
|
+ NSTimeInterval duration;
|
|
|
+ // Incremental Animation decoding may update frames when new bytes available
|
|
|
+ // Which should use lock to ensure frame count and frames match, ensure atomic logic
|
|
|
+ if (_incremental) {
|
|
|
+ SD_LOCK(_lock);
|
|
|
+ if (index >= _frames.count) {
|
|
|
+ SD_UNLOCK(_lock);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ duration = _frames[index].duration;
|
|
|
+ SD_UNLOCK(_lock);
|
|
|
+ } else {
|
|
|
+ if (index >= _frames.count) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ duration = _frames[index].duration;
|
|
|
}
|
|
|
- return _frames[index].duration;
|
|
|
+ return duration;
|
|
|
}
|
|
|
|
|
|
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
|
|
|
- if (index >= _frameCount) {
|
|
|
- return nil;
|
|
|
+ UIImage *image;
|
|
|
+ // Incremental Animation decoding may update frames when new bytes available
|
|
|
+ // Which should use lock to ensure frame count and frames match, ensure atomic logic
|
|
|
+ if (_incremental) {
|
|
|
+ SD_LOCK(_lock);
|
|
|
+ if (index >= _frames.count) {
|
|
|
+ SD_UNLOCK(_lock);
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+ image = [self safeAnimatedImageFrameAtIndex:index];
|
|
|
+ SD_UNLOCK(_lock);
|
|
|
+ } else {
|
|
|
+ if (index >= _frames.count) {
|
|
|
+ return nil;
|
|
|
+ }
|
|
|
+ image = [self safeAnimatedImageFrameAtIndex:index];
|
|
|
}
|
|
|
+ return image;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIImage *)safeAnimatedImageFrameAtIndex:(NSUInteger)index {
|
|
|
NSDictionary *options;
|
|
|
BOOL forceDecode = NO;
|
|
|
if (@available(iOS 15, tvOS 15, *)) {
|