Support progressive decoding for animated image. A little refactory to make coder protocol more readable

This commit is contained in:
DreamPiggy 2018-02-02 14:15:29 +08:00
parent f94dd00c52
commit 4563e714d7
19 changed files with 541 additions and 249 deletions

View File

@ -17,8 +17,7 @@
@property (nonatomic, readonly, nullable) CGImageRef CGImage;
@property (nonatomic, readonly, nullable) NSArray<NSImage *> *images;
@property (nonatomic, readonly) CGFloat scale;
- (nonnull instancetype)initWithCGImage:(nonnull CGImageRef)cgImage scale:(CGFloat)scale;
@property (nonatomic, readonly, nullable) NSBitmapImageRep *bitmapImageRep;
@end

View File

@ -14,7 +14,7 @@
- (CGImageRef)CGImage {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
CGImageRef cgImage = [self CGImageForProposedRect:&imageRect context:NULL hints:nil];
CGImageRef cgImage = [self CGImageForProposedRect:&imageRect context:nil hints:nil];
return cgImage;
}
@ -24,28 +24,22 @@
- (CGFloat)scale {
CGFloat scale = 1;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *rep = [self bestRepresentationForRect:imageRect context:NULL hints:nil];
NSInteger pixelsWide = rep.pixelsWide;
CGFloat width = rep.size.width;
CGFloat width = self.size.width;
if (width > 0) {
scale = pixelsWide / width;
// Use CGImage to get pixel width, NSImageRep.pixelsWide always double on Retina screen
NSUInteger pixelWidth = CGImageGetWidth(self.CGImage);
scale = pixelWidth / width;
}
return scale;
}
- (instancetype)initWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale {
NSSize size;
if (cgImage && scale > 0) {
NSInteger pixelsWide = CGImageGetWidth(cgImage);
NSInteger pixelsHigh = CGImageGetHeight(cgImage);
CGFloat width = pixelsWide / scale;
CGFloat height = pixelsHigh / scale;
size = NSMakeSize(width, height);
} else {
size = NSZeroSize;
- (NSBitmapImageRep *)bitmapImageRep {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
return (NSBitmapImageRep *)imageRep;
}
return [self initWithCGImage:cgImage size:size];
return nil;
}
@end

View File

@ -9,9 +9,18 @@
#import "SDWebImageCompat.h"
#import "NSData+ImageContentType.h"
@protocol SDWebImageAnimatedCoder;
@protocol SDAnimatedImage <NSObject>
@required
/**
The original animated image data for current image. If current image is not an animated format, return nil.
We may use this method to grab back the original image data if need, such as NSCoding or compare.
@return The animated image data
*/
- (nullable NSData *)animatedImageData;
/**
Total animated frame count.
It the frame count is less than 1, then the methods below will be ignored.
@ -42,6 +51,7 @@
*/
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index;
// These methods are for SDAnimatedImage class only but not for SDWebImageAnimatedCoder.
@optional
/**
Preload all frame image to memory. Then directly return the frame for index without decoding.
@ -51,6 +61,16 @@
*/
- (void)preloadAllFrames;
/**
Initializes the image with an animated coder. You can use the coder to decode the image frame later.
@note Normally we use `initWithData:scale:` to create custom animated image class. So you can implement your custom class without our built-in coder.
@param animatedCoder An animated coder which conform `SDWebImageAnimatedCoder` protocol
@param scale The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property. (For `NSImage`, `scale` property can be calculated from `size`)
@return An initialized object
*/
- (nullable instancetype)initWithAnimatedCoder:(nonnull id<SDWebImageAnimatedCoder>)animatedCoder scale:(CGFloat)scale;
@end
@interface SDAnimatedImage : UIImage <SDAnimatedImage>
@ -63,9 +83,10 @@
- (nullable instancetype)initWithContentsOfFile:(nonnull NSString *)path;
- (nullable instancetype)initWithData:(nonnull NSData *)data;
- (nullable instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale;
- (nullable instancetype)initWithAnimatedCoder:(nonnull id<SDWebImageAnimatedCoder>)animatedCoder scale:(CGFloat)scale;
/**
Current animated image format
Current animated image format.
*/
@property (nonatomic, assign, readonly) SDImageFormat animatedImageFormat;
/**
@ -73,6 +94,13 @@
*/
@property (nonatomic, copy, readonly, nullable) NSData *animatedImageData;
#if SD_MAC
/**
For AppKit, `NSImage` can contains multiple image representations with different scales. However, this class does not do that from the design. We processs the scale like UIKit and store it as a extra information for correctlly rendering in `SDAnimatedImageView`.
*/
@property (nonatomic, readonly) CGFloat scale;
#endif
/**
Preload all frame image to memory. Then directly return the frame for index without decoding.
The preloaded animated image frames will be removed when receiving memory warning.

View File

@ -31,18 +31,9 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
@interface SDAnimatedImage ()
@property (nonatomic, strong) id<SDWebImageAnimatedCoder> coder;
@property (nonatomic, assign, readwrite) NSUInteger animatedImageLoopCount;
@property (nonatomic, assign, readwrite) NSUInteger animatedImageFrameCount;
@property (nonatomic, copy, readwrite) NSData *animatedImageData;
@property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
@property (atomic, copy) NSArray<SDWebImageFrame *> *preloadAnimatedImageFrames;
@property (nonatomic, assign) BOOL animatedImageFramesPreloaded;
@property (nonatomic, assign) BOOL animatedImageLoopCountChecked;
@property (nonatomic, assign) BOOL animatedImageFrameCountChecked;
#if SD_MAC
@property (nonatomic, assign) CGFloat scale;
#endif
@end
@ -89,41 +80,64 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
if (!data || data.length == 0) {
return nil;
}
if (scale <= 0) {
scale = 1;
}
data = [data copy]; // avoid mutable data
id<SDWebImageAnimatedCoder> animatedCoder = nil;
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedManager].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageAnimatedCoder)]) {
if ([coder canDecodeFromData:data]) {
id<SDWebImageAnimatedCoder> animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data];
if (!animatedCoder) {
// check next coder
continue;
} else {
self.coder = animatedCoder;
animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data];
break;
}
}
}
}
if (!self.coder) {
if (!animatedCoder) {
return nil;
}
UIImage *image = [self.coder animatedImageFrameAtIndex:0];
UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
if (!image) {
return nil;
}
#if SD_MAC
self = [super initWithCGImage:image.CGImage size:NSZeroSize];
#else
self = [super initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
#endif
if (self) {
_coder = animatedCoder;
#if SD_MAC
_scale = scale;
#endif
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
_animatedImageFormat = format;
}
return self;
}
- (instancetype)initWithAnimatedCoder:(id<SDWebImageAnimatedCoder>)animatedCoder scale:(CGFloat)scale {
if (!animatedCoder) {
return nil;
}
if (scale <= 0) {
scale = 1;
}
UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
if (!image) {
return nil;
}
#if SD_MAC
self = [super initWithCGImage:image.CGImage scale:scale];
self = [super initWithCGImage:image.CGImage size:NSZeroSize];
#else
self = [super initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
#endif
if (self) {
_coder = animatedCoder;
#if SD_MAC
_scale = scale;
#endif
_animatedImageData = data;
NSData *data = [animatedCoder animatedImageData];
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
_animatedImageFormat = format;
}
@ -174,20 +188,17 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
#pragma mark - SDAnimatedImage
- (NSData *)animatedImageData {
return [self.coder animatedImageData];
}
- (NSUInteger)animatedImageLoopCount {
if (!self.animatedImageLoopCountChecked) {
self.animatedImageLoopCountChecked = YES;
_animatedImageLoopCount = [self.coder animatedImageLoopCount];
}
return _animatedImageLoopCount;
return [self.coder animatedImageLoopCount];
}
- (NSUInteger)animatedImageFrameCount {
if (!self.animatedImageFrameCountChecked) {
self.animatedImageFrameCountChecked = YES;
_animatedImageFrameCount = [self.coder animatedImageFrameCount];
}
return _animatedImageFrameCount;
return [self.coder animatedImageFrameCount];
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {

View File

@ -131,9 +131,11 @@ dispatch_semaphore_signal(self->_lock);
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL shouldAnimate;
@property (nonatomic, assign) BOOL isProgressive;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, assign) CGFloat animatedImageScale;
#if SD_MAC
@property (nonatomic, assign) CVDisplayLinkRef displayLink;
#else
@ -202,9 +204,11 @@ dispatch_semaphore_signal(self->_lock);
{
#if SD_MAC
self.wantsLayer = YES;
// Default value from `NSImageView`
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
self.imageScaling = NSImageScaleProportionallyDown;
self.imageAlignment = NSImageAlignCenter;
#endif
self.maxBufferCount = 0;
self.runLoopMode = [[self class] defaultRunLoopMode];
self.lock = dispatch_semaphore_create(1);
#if SD_UIKIT
@ -213,27 +217,41 @@ dispatch_semaphore_signal(self->_lock);
}
- (void)resetAnimated
- (void)resetAnimatedImage
{
LOCK({
self.animatedImage = nil;
self.totalFrameCount = 0;
self.totalLoopCount = 0;
self.currentFrame = 0;
self.currentFrame = nil;
self.currentFrameIndex = 0;
self.currentLoopCount = 0;
self.currentTime = 0;
self.bufferMiss = NO;
self.shouldAnimate = NO;
self.isProgressive = NO;
self.maxBufferCount = 0;
self.layer.contentsScale = 1;
[_frameBuffer removeAllObjects];
_frameBuffer = nil;
self.animatedImageScale = 1;
[_fetchQueue cancelAllOperations];
_fetchQueue = nil;
LOCK({
[_frameBuffer removeAllObjects];
_frameBuffer = nil;
});
}
- (void)resetProgressiveImage
{
self.animatedImage = nil;
self.totalFrameCount = 0;
self.totalLoopCount = 0;
// preserve current state
self.shouldAnimate = NO;
self.isProgressive = YES;
self.maxBufferCount = 0;
self.animatedImageScale = 1;
// preserve buffer cache
}
#pragma mark - Accessors
#pragma mark Public
@ -242,33 +260,63 @@ dispatch_semaphore_signal(self->_lock);
if (self.image == image) {
return;
}
// Check Progressive coding
self.isProgressive = NO;
if ([image conformsToProtocol:@protocol(SDAnimatedImage)] && image.sd_isIncremental) {
NSData *currentData = [((UIImage<SDAnimatedImage> *)image) animatedImageData];
if (currentData) {
NSData *previousData;
if ([self.image conformsToProtocol:@protocol(SDAnimatedImage)]) {
previousData = [((UIImage<SDAnimatedImage> *)self.image) animatedImageData];
}
// Check whether to use progressive coding
if (!previousData) {
// If previous data is nil
self.isProgressive = YES;
} else if ([currentData isEqualToData:previousData]) {
// If current data is equal to previous data
self.isProgressive = YES;
}
}
}
if (self.isProgressive) {
// Reset all value, but keep current state
[self resetProgressiveImage];
} else {
// Stop animating
[self stopAnimating];
// Reset all value
[self resetAnimated];
[self resetAnimatedImage];
}
// We need call super method to keep function. This will impliedly call `setNeedsDisplay`. But we have no way to avoid this when using animated image. So we call `setNeedsDisplay` again at the end.
super.image = image;
if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) {
NSUInteger animatedImageFrameCount = ((UIImage<SDAnimatedImage> *)image).animatedImageFrameCount;
// Check the frame count
if (animatedImageFrameCount <= 1) {
return;
}
self.animatedImage = (UIImage<SDAnimatedImage> *)image;
self.totalFrameCount = animatedImageFrameCount;
// Get the current frame and loop count.
self.totalLoopCount = self.animatedImage.animatedImageLoopCount;
// Get the scale
self.animatedImageScale = image.scale;
if (!self.isProgressive) {
self.currentFrame = image;
LOCK({
self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
});
}
// Ensure disabled highlighting; it's not supported (see `-setHighlighted:`).
super.highlighted = NO;
// UIImageView seems to bypass some accessors when calculating its intrinsic content size, so this ensures its intrinsic content size comes from the animated image.
[self invalidateIntrinsicContentSize];
// Get the first frame
self.currentFrame = [self.animatedImage animatedImageFrameAtIndex:0];
LOCK({
if (self.currentFrame) {
self.frameBuffer[@(0)] = self.currentFrame;
self.bufferMiss = NO;
} else {
self.bufferMiss = YES;
}
});
// Calculate max buffer size
[self calculateMaxBufferCount];
// Update should animate
@ -277,7 +325,6 @@ dispatch_semaphore_signal(self->_lock);
[self startAnimating];
}
self.layer.contentsScale = image.scale;
[self.layer setNeedsDisplay];
}
}
@ -360,6 +407,7 @@ dispatch_semaphore_signal(self->_lock);
}
#else
[_displayLink invalidate];
_displayLink = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
@ -546,9 +594,9 @@ dispatch_semaphore_signal(self->_lock);
{
if (self.animatedImage) {
#if SD_MAC
CVDisplayLinkStop(self.displayLink);
CVDisplayLinkStop(_displayLink);
#else
self.displayLink.paused = YES;
_displayLink.paused = YES;
#endif
} else {
#if SD_UIKIT
@ -627,8 +675,9 @@ dispatch_semaphore_signal(self->_lock);
#if SD_UIKIT
NSTimeInterval duration = displayLink.duration * displayLink.frameInterval;
#endif
NSUInteger totalFrameCount = self.totalFrameCount;
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % self.totalFrameCount;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
// Check if we have the frame buffer firstly to improve performance
if (!self.bufferMiss) {
@ -652,12 +701,17 @@ dispatch_semaphore_signal(self->_lock);
LOCK({
currentFrame = self.frameBuffer[@(currentFrameIndex)];
});
BOOL bufferFull = NO;
if (currentFrame) {
LOCK({
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
}
// Check whether we can stop fetch
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
});
self.currentFrame = currentFrame;
self.currentFrameIndex = nextFrameIndex;
@ -667,8 +721,19 @@ dispatch_semaphore_signal(self->_lock);
self.bufferMiss = YES;
}
// Update the loop count when last frame rendered
if (nextFrameIndex == 0 && !self.bufferMiss) {
// Progressive image reach the current last frame index. Keep the state and stop animating. Wait for later restart
if (self.isProgressive) {
// Recovery the current frame index and removed frame buffer (See above)
self.currentFrameIndex = currentFrameIndex;
LOCK({
self.frameBuffer[@(currentFrameIndex)] = self.currentFrame;
});
[self stopAnimating];
return;
}
// Update the loop count
if (nextFrameIndex == 0) {
self.currentLoopCount++;
// if reached the max loop count, stop animating, 0 means loop indefinitely
NSUInteger maxLoopCount = self.shouldCustomLoopCount ? self.animationRepeatCount : self.totalLoopCount;
@ -678,13 +743,22 @@ dispatch_semaphore_signal(self->_lock);
}
}
// Check if we should prefetch next frame
if (self.fetchQueue.operationCount == 0 && self.frameBuffer.count < self.totalFrameCount) {
// Check if we should prefetch next frame or current frame
NSUInteger fetchFrameIndex;
if (self.bufferMiss) {
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
fetchFrameIndex = currentFrameIndex;
} else {
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
fetchFrameIndex = nextFrameIndex;
}
if (!bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
UIImage<SDAnimatedImage> *animatedImage = self.animatedImage;
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
UIImage *nextFrame = [self.animatedImage animatedImageFrameAtIndex:nextFrameIndex];
UIImage *fetchFrame = [animatedImage animatedImageFrameAtIndex:fetchFrameIndex];
LOCK({
self.frameBuffer[@(nextFrameIndex)] = nextFrame;
self.frameBuffer[@(fetchFrameIndex)] = fetchFrame;
});
}];
[self.fetchQueue addOperation:operation];
@ -701,12 +775,15 @@ dispatch_semaphore_signal(self->_lock);
#pragma mark - CALayerDelegate (Informal)
#pragma mark Providing the Layer's Content
#if SD_UIKIT
- (void)displayLayer:(CALayer *)layer
{
if (_currentFrame) {
layer.contentsScale = self.animatedImageScale;
layer.contents = (__bridge id)_currentFrame.CGImage;
}
}
#endif
#if SD_MAC
- (BOOL)wantsUpdateLayer
@ -717,7 +794,10 @@ dispatch_semaphore_signal(self->_lock);
- (void)updateLayer
{
if (_currentFrame) {
self.layer.contentsScale = self.animatedImageScale;
self.layer.contents = (__bridge id)_currentFrame.CGImage;
} else {
[super updateLayer];
}
}
#endif
@ -732,13 +812,18 @@ dispatch_semaphore_signal(self->_lock);
if (self.maxBufferSize > 0) {
max = self.maxBufferSize;
} else {
// calculate based on current memory, these factors are by experience
// Calculate based on current memory, these factors are by experience
NSUInteger total = SDDeviceTotalMemory();
NSUInteger free = SDDeviceFreeMemory();
max = MIN(total * 0.2, free * 0.6);
}
NSUInteger maxBufferCount = (double)max / (double)bytes;
if (!maxBufferCount) {
// At least 1 frame
maxBufferCount = 1;
}
self.maxBufferCount = maxBufferCount;
}

View File

@ -26,6 +26,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp
/**
This is the image coder protocol to provide custom image decoding/encoding.
These methods are all required to implement.
You do not need to specify image scale during decoding because we may scale image later.
@note Pay attention that these methods are not called from main queue.
*/
@protocol SDWebImageCoder <NSObject>
@ -89,7 +90,7 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp
@param data The image data so we can look at it
@return YES if this coder can decode the data, NO otherwise
*/
- (BOOL)canIncrementallyDecodeFromData:(nullable NSData *)data;
- (BOOL)canIncrementalDecodeFromData:(nullable NSData *)data;
/**
Because incremental decoding need to keep the decoded context, we will alloc a new instance with the same class for each download operation to avoid conflicts
@ -97,25 +98,37 @@ FOUNDATION_EXPORT SDWebImageCoderOption _Nonnull const SDWebImageCoderEncodeComp
@return A new instance to do incremental decoding for the specify image format
*/
- (nonnull instancetype)initIncrementally;
- (nonnull instancetype)initIncremental;
/**
Incremental decode the image data to image.
Update the incremental decoding when new image data available
@param data The image data has been downloaded so far
@param finished Whether the download has finished
@return The decoded image from data
*/
- (nullable UIImage *)incrementallyDecodedImageWithData:(nullable NSData *)data finished:(BOOL)finished;
- (void)updateIncrementalData:(nullable NSData *)data finished:(BOOL)finished;
/**
Incremental decode the current image data to image.
@param options A dictionary containing any decoding options.
@return The decoded image from current data
*/
- (nullable UIImage *)incrementalDecodedImageWithOptions:(nullable SDWebImageCoderOptions *)options;
@end
/**
This is the animated image coder protocol for custom animated image class like `SDAnimatedImage`. Through it inherit from `SDWebImageCoder`. We currentlly only use the method `canDecodeFromData:` to detect the proper coder for specify animated image format.
*/
@protocol SDWebImageAnimatedCoder <SDWebImageCoder, SDAnimatedImage>
@required
/**
Because animated image coder should keep the original data, we will alloc a new instance with the same class for the specify animated image data
The init method should return nil if it can't decode the specify animated image data
The init method should return nil if it can't decode the specify animated image data to produce any frame.
After the instance created, we may call methods in `SDAnimatedImage` to produce animated image frame.
@param data The animated image data to be decode
@return A new instance to do animated decoding for specify image data

View File

@ -9,4 +9,4 @@
#import "SDWebImageCoder.h"
SDWebImageCoderOption const SDWebImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
SDWebImageCoderOption const SDWebImageCoderEncodeCompressionQuality = @"compressionQuality";
SDWebImageCoderOption const SDWebImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";

View File

@ -197,17 +197,20 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
static CGColorSpaceRef colorSpace;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if SD_MAC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
BOOL shouldUseSRGB = NO;
#else
BOOL shouldUseSRGB = NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_8_x_Max;
#if SD_UIKIT
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
shouldUseSRGB = processInfo.operatingSystemVersion.majorVersion >= 9;
#endif
if (shouldUseSRGB) {
// This is what iOS device used colorspace, combined with right bitmapInfo, even without decode, can still avoid extra CA::Render::copy_image(which marked `Color Copied Images` from Instruments)
// This is what iOS/tvOS device used colorspace, combined with right bitmapInfo, even without decode, can still avoid extra CA::Render::copy_image(which marked `Color Copied Images` from Instruments)
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
#pragma clang diagnostic pop
});
return colorSpace;
}

View File

@ -83,9 +83,6 @@
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
FOUNDATION_EXPORT CGFloat SDImageScaleForKey(NSString * _Nullable key);
FOUNDATION_EXPORT UIImage * _Nullable SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image);
FOUNDATION_EXPORT NSString *const _Nonnull SDWebImageErrorDomain;
#ifndef dispatch_queue_async_safe

View File

@ -7,8 +7,6 @@
*/
#import "SDWebImageCompat.h"
#import "UIImage+WebCache.h"
#import "NSImage+Additions.h"
#if !__has_feature(objc_arc)
#error SDWebImage is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
@ -18,64 +16,4 @@
#error SDWebImage need ARC for dispatch object
#endif
inline CGFloat SDImageScaleForKey(NSString * _Nullable key) {
CGFloat scale = 1;
if (!key) {
return scale;
}
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)])
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
#elif SD_MAC
if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)])
#endif
{
if (key.length >= 8) {
NSRange range = [key rangeOfString:@"@2x."];
if (range.location != NSNotFound) {
scale = 2.0;
}
range = [key rangeOfString:@"@3x."];
if (range.location != NSNotFound) {
scale = 3.0;
}
}
}
return scale;
}
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) {
return nil;
}
#if SD_UIKIT || SD_WATCH
if ((image.images).count > 0) {
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
[scaledImages addObject:SDScaledImageForKey(key, tempImage)];
}
UIImage *animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
if (animatedImage) {
animatedImage.sd_imageLoopCount = image.sd_imageLoopCount;
}
return animatedImage;
}
#endif
CGFloat scale = SDImageScaleForKey(key);
if (scale > 1) {
#if SD_UIKIT || SD_WATCH
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
#else
UIImage *scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale];
#endif
image = scaledImage;
}
return image;
}
NSString *const SDWebImageErrorDomain = @"SDWebImageErrorDomain";

View File

@ -6,12 +6,34 @@
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
typedef void(^SDWebImageNoParamsBlock)(void);
typedef NSString * SDWebImageContextOption NS_STRING_ENUM;
typedef NSDictionary<SDWebImageContextOption, id> SDWebImageContext;
#pragma mark - Image scale
/**
Return the image scale from the specify key, supports file name and url key
@param key The image cache key
@return The scale factor for image
*/
FOUNDATION_EXPORT CGFloat SDImageScaleForKey(NSString * _Nullable key);
/**
Scale the image with the scale factor from the specify key. If no need to scale, return the original image
This only works for `UIImage`(UIKit) or `NSImage`(AppKit).
@param key The image cache key
@param image The image
@return The scaled image
*/
FOUNDATION_EXPORT UIImage * _Nullable SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image);
#pragma mark - Context option
/**
A Dispatch group to maintain setImageBlock and completionBlock. This is used for custom setImageBlock advanced usage, such like perform background task but need to guarantee the completion block is called after setImageBlock. (dispatch_group_t)
*/

View File

@ -7,6 +7,97 @@
*/
#import "SDWebImageDefine.h"
#import "UIImage+WebCache.h"
#import "NSImage+Additions.h"
#pragma mark - Image scale
static inline NSArray<NSNumber *> * _Nonnull SDImageScaleFactors() {
return @[@2, @3];
}
inline CGFloat SDImageScaleForKey(NSString * _Nullable key) {
CGFloat scale = 1;
if (!key) {
return scale;
}
// Check if target OS support scale
#if SD_WATCH
if ([[WKInterfaceDevice currentDevice] respondsToSelector:@selector(screenScale)])
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
#elif SD_MAC
if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)])
#endif
{
// a@2x.png -> 8
if (key.length >= 8) {
// Fast check
BOOL isURL = [key hasPrefix:@"http://"] || [key hasPrefix:@"https://"];
for (NSNumber *scaleFactor in SDImageScaleFactors()) {
// @2x. for file name and normal url
NSString *fileScale = [NSString stringWithFormat:@"@%@x.", scaleFactor];
if ([key containsString:fileScale]) {
scale = scaleFactor.doubleValue;
return scale;
}
if (isURL) {
// %402x. for url encode
NSString *urlScale = [NSString stringWithFormat:@"%%40%@x.", scaleFactor];
if ([key containsString:urlScale]) {
scale = scaleFactor.doubleValue;
return scale;
}
}
}
}
}
return scale;
}
inline UIImage *SDScaledImageForKey(NSString * _Nullable key, UIImage * _Nullable image) {
if (!image) {
return nil;
}
CGFloat scale = SDImageScaleForKey(key);
if (scale > 1) {
UIImage *scaledImage;
if (image.sd_isAnimated) {
UIImage *animatedImage;
#if SD_UIKIT || SD_WATCH
// `UIAnimatedImage` images share the same size and scale.
NSMutableArray<UIImage *> *scaledImages = [NSMutableArray array];
for (UIImage *tempImage in image.images) {
UIImage *tempScaledImage = [[UIImage alloc] initWithCGImage:tempImage.CGImage scale:scale orientation:tempImage.imageOrientation];
[scaledImages addObject:tempScaledImage];
}
animatedImage = [UIImage animatedImageWithImages:scaledImages duration:image.duration];
animatedImage.sd_imageLoopCount = image.sd_imageLoopCount;
#else
// Animated GIF for `NSImage` need to grab `NSBitmapImageRep`
NSSize size = NSMakeSize(image.size.width / scale, image.size.height / scale);
animatedImage = [[NSImage alloc] initWithSize:size];
NSBitmapImageRep *bitmapImageRep = image.bitmapImageRep;
[animatedImage addRepresentation:bitmapImageRep];
#endif
scaledImage = animatedImage;
} else {
#if SD_UIKIT || SD_WATCH
scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:image.imageOrientation];
#else
scaledImage = [[NSImage alloc] initWithCGImage:image.CGImage size:NSZeroSize];
#endif
}
return scaledImage;
}
return image;
}
#pragma mark - Context option
SDWebImageContextOption const SDWebImageContextSetImageGroup = @"setImageGroup";
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";

View File

@ -45,6 +45,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (strong, nonatomic, nullable) NSMutableData *imageData;
@property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse`
@property (copy, nonatomic, nullable) NSString *cacheKey;
@property (assign, nonatomic, readwrite) NSInteger expectedSize;
@property (strong, nonatomic, nullable, readwrite) NSURLResponse *response;
@ -350,22 +351,45 @@ didReceiveResponse:(NSURLResponse *)response
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedManager].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] init];
[((id<SDWebImageProgressiveCoder>)coder) canIncrementalDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] initIncremental];
break;
}
}
}
[self.progressiveCoder updateIncrementalData:imageData finished:finished];
// progressive decode the image in coder queue
dispatch_async(self.coderQueue, ^{
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
// check whether we should use `SDAnimatedImage`
UIImage *image;
if ([self.context valueForKey:SDWebImageContextAnimatedImageClass]) {
Class animatedImageClass = [self.context valueForKey:SDWebImageContextAnimatedImageClass];
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [animatedImageClass instancesRespondToSelector:@selector(initWithAnimatedCoder:scale:)] && [self.progressiveCoder conformsToProtocol:@protocol(SDWebImageAnimatedCoder)]) {
CGFloat scale = SDImageScaleForKey(self.cacheKey);
image = [[animatedImageClass alloc] initWithAnimatedCoder:(id<SDWebImageAnimatedCoder>)self.progressiveCoder scale:scale];
}
}
if (!image) {
BOOL decodeFirstFrame = self.options & SDWebImageDownloaderDecodeFirstFrameOnly;
image = [self.progressiveCoder incrementalDecodedImageWithOptions:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame)}];
image = [self scaledImageForKey:self.cacheKey image:image];
}
if (image) {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
BOOL shouldDecode = self.shouldDecompressImages;
if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}
if (shouldDecode) {
image = [SDWebImageCoderHelper decodedImageWithImage:image];
}
if (!finished) {
image.sd_isIncremental = YES;
}
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
@ -431,14 +455,13 @@ didReceiveResponse:(NSURLResponse *)response
// decode the image in coder queue
dispatch_async(self.coderQueue, ^{
BOOL decodeFirstFrame = self.options & SDWebImageDownloaderDecodeFirstFrameOnly;
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
UIImage *image;
if (!decodeFirstFrame) {
// check whether we should use `SDAnimatedImage`
if ([self.context valueForKey:SDWebImageContextAnimatedImageClass]) {
Class animatedImageClass = [self.context valueForKey:SDWebImageContextAnimatedImageClass];
if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) {
CGFloat scale = SDImageScaleForKey(key);
CGFloat scale = SDImageScaleForKey(self.cacheKey);
image = [[animatedImageClass alloc] initWithData:imageData scale:scale];
if (self.options & SDWebImageDownloaderPreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) {
[((id<SDAnimatedImage>)image) preloadAllFrames];
@ -448,10 +471,10 @@ didReceiveResponse:(NSURLResponse *)response
}
if (!image) {
image = [[SDWebImageCodersManager sharedManager] decodedImageWithData:imageData options:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame)}];
image = [self scaledImageForKey:key image:image];
image = [self scaledImageForKey:self.cacheKey image:image];
}
BOOL shouldDecode = YES;
BOOL shouldDecode = self.shouldDecompressImages;
if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
@ -459,8 +482,8 @@ didReceiveResponse:(NSURLResponse *)response
// animated image do not decode
shouldDecode = NO;
}
if (shouldDecode) {
if (self.shouldDecompressImages) {
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
if (shouldScaleDown) {
image = [SDWebImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
@ -468,7 +491,6 @@ didReceiveResponse:(NSURLResponse *)response
image = [SDWebImageCoderHelper decodedImageWithImage:image];
}
}
}
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
@ -519,6 +541,13 @@ didReceiveResponse:(NSURLResponse *)response
}
#pragma mark Helper methods
- (NSString *)cacheKey {
if (!_cacheKey) {
_cacheKey = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
}
return _cacheKey;
}
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}

View File

@ -15,7 +15,7 @@
@note Use `SDWebImageGIFCoder` for fully animated GIFs. For `UIImageView`, it will produce animated `UIImage`(`NSImage` on macOS) for rendering. For `SDAnimatedImageView`, it will use `SDAnimatedImage` for rendering.
@note The recommended approach for animated GIFs is using `SDAnimatedImage` with `SDAnimatedImageView`. It's more performant than `UIImageView` for GIF displaying(especially on memory usage)
*/
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder, SDWebImageAnimatedCoder>
@interface SDWebImageGIFCoder : NSObject <SDWebImageProgressiveCoder, SDWebImageAnimatedCoder>
@property (nonatomic, class, readonly, nonnull) SDWebImageGIFCoder *sharedCoder;

View File

@ -26,14 +26,12 @@
@implementation SDWebImageGIFCoder {
size_t _width, _height;
#if SD_UIKIT || SD_WATCH
UIImageOrientation _orientation;
#endif
CGImageSourceRef _imageSource;
NSData *_imageData;
NSUInteger _loopCount;
NSUInteger _frameCount;
NSArray<SDGIFCoderFrame *> *_frames;
BOOL _finished;
}
- (void)dealloc
@ -167,6 +165,71 @@
return frameDuration;
}
#pragma mark - Progressive Decode
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatGIF);
}
- (instancetype)initIncremental {
self = [super init];
if (self) {
_imageSource = CGImageSourceCreateIncremental((__bridge CFDictionaryRef)@{(__bridge_transfer NSString *)kCGImageSourceShouldCache : @(YES)});
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
_imageData = data;
_finished = finished;
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
// Update the data source, we must pass ALL the data, not just the new bytes
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
if (_width + _height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
if (properties) {
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
CFRelease(properties);
}
}
// For animated image progressive decoding because the frame count and duration may be changed.
[self scanAndCheckFramesValidWithImageSource:_imageSource];
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDWebImageCoderOptions *)options {
UIImage *image;
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
#if SD_UIKIT || SD_WATCH
image = [[UIImage alloc] initWithCGImage:partialImageRef];
#elif SD_MAC
image = [[UIImage alloc] initWithCGImage:partialImageRef size:NSZeroSize];
#endif
CGImageRelease(partialImageRef);
}
}
return image;
}
#pragma mark - Encode
- (BOOL)canEncodeToFormat:(SDImageFormat)format {
return (format == SDImageFormatGIF);
@ -252,8 +315,7 @@
return self;
}
- (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource
{
- (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
if (!imageSource) {
return NO;
}
@ -275,26 +337,26 @@
return YES;
}
- (NSUInteger)animatedImageLoopCount
{
- (NSData *)animatedImageData {
return _imageData;
}
- (NSUInteger)animatedImageLoopCount {
return _loopCount;
}
- (NSUInteger)animatedImageFrameCount
{
- (NSUInteger)animatedImageFrameCount {
return _frameCount;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index
{
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
if (index >= _frameCount) {
return 0;
}
return _frames[index].duration;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index
{
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
if (!imageRef) {
return nil;
@ -309,7 +371,7 @@
#if SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef size:NSZeroSize];
#else
UIImage *image = [UIImage imageWithCGImage:newImageRef];
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef];
#endif
CGImageRelease(newImageRef);
return image;

View File

@ -19,6 +19,7 @@
#endif
CGImageSourceRef _imageSource;
NSUInteger _frameCount;
BOOL _finished;
}
- (void)dealloc {
@ -63,7 +64,7 @@
}
}
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
switch ([NSData sd_imageFormatForImageData:data]) {
case SDImageFormatWebP:
// Do not support WebP progressive decoding
@ -100,7 +101,7 @@
}
#pragma mark - Progressive Decode
- (instancetype)initIncrementally {
- (instancetype)initIncremental {
self = [super init];
if (self) {
_imageSource = CGImageSourceCreateIncremental(NULL);
@ -111,8 +112,11 @@
return self;
}
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
UIImage *image;
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
_finished = finished;
// The following code is from http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/
// Thanks to the author @Nyx0uf
@ -142,6 +146,10 @@
#endif
}
}
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDWebImageCoderOptions *)options {
UIImage *image;
if (_width + _height > 0) {
// Create the image
@ -176,13 +184,6 @@
}
}
if (finished) {
if (_imageSource) {
CFRelease(_imageSource);
_imageSource = NULL;
}
}
return image;
}

View File

@ -57,6 +57,7 @@ dispatch_semaphore_signal(self->_lock);
CGContextRef _canvas;
BOOL _hasAnimation;
BOOL _hasAlpha;
BOOL _finished;
CGFloat _canvasWidth;
CGFloat _canvasHeight;
dispatch_semaphore_t _lock;
@ -92,7 +93,7 @@ dispatch_semaphore_signal(self->_lock);
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatWebP);
}
- (BOOL)canIncrementallyDecodeFromData:(NSData *)data {
- (BOOL)canIncrementalDecodeFromData:(NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == SDImageFormatWebP);
}
@ -176,7 +177,7 @@ dispatch_semaphore_signal(self->_lock);
}
#pragma mark - Progressive Decode
- (instancetype)initIncrementally {
- (instancetype)initIncremental {
self = [super init];
if (self) {
// Progressive images need transparent, so always use premultiplied RGBA
@ -185,16 +186,24 @@ dispatch_semaphore_signal(self->_lock);
return self;
}
- (UIImage *)incrementallyDecodedImageWithData:(NSData *)data finished:(BOOL)finished {
if (!_idec) {
return nil;
- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
if (_finished) {
return;
}
UIImage *image;
_imageData = data;
_finished = finished;
VP8StatusCode status = WebPIUpdate(_idec, data.bytes, data.length);
if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) {
return nil;
return;
}
}
- (BOOL)incrementalFinished {
return _finished;
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDWebImageCoderOptions *)options {
UIImage *image;
int width = 0;
int height = 0;
@ -250,13 +259,6 @@ dispatch_semaphore_signal(self->_lock);
CGContextRelease(canvas);
}
if (finished) {
if (_idec) {
WebPIDelete(_idec);
_idec = NULL;
}
}
return image;
}
@ -517,8 +519,7 @@ static void FreeImageData(void *info, const void *data, size_t size) {
return self;
}
- (BOOL)scanAndCheckFramesValidWithDemuxer:(WebPDemuxer *)demuxer
{
- (BOOL)scanAndCheckFramesValidWithDemuxer:(WebPDemuxer *)demuxer {
if (!demuxer) {
return NO;
}
@ -586,6 +587,10 @@ static void FreeImageData(void *info, const void *data, size_t size) {
return YES;
}
- (NSData *)animatedImageData {
return _imageData;
}
- (NSUInteger)animatedImageLoopCount {
return _loopCount;
}

View File

@ -29,4 +29,9 @@
*/
@property (nonatomic, assign, readonly) BOOL sd_isAnimated;
/**
Indicating whether the image is during incremental decoding and may not contains full pixels.
*/
@property (nonatomic, assign) BOOL sd_isIncremental;
@end

View File

@ -7,11 +7,11 @@
*/
#import "UIImage+WebCache.h"
#import "NSImage+Additions.h"
#import "objc/runtime.h"
#if SD_UIKIT
#import "objc/runtime.h"
@implementation UIImage (WebCache)
- (NSUInteger)sd_imageLoopCount {
@ -32,6 +32,15 @@
return (self.images != nil);
}
- (void)setSd_isIncremental:(BOOL)sd_isIncremental {
objc_setAssociatedObject(self, @selector(sd_isIncremental), @(sd_isIncremental), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)sd_isIncremental {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isIncremental));
return value.boolValue;
}
@end
#endif
@ -42,39 +51,39 @@
- (NSUInteger)sd_imageLoopCount {
NSUInteger imageLoopCount = 0;
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
imageLoopCount = [[bitmapRep valueForProperty:NSImageLoopCount] unsignedIntegerValue];
break;
}
NSBitmapImageRep *bitmapImageRep = self.bitmapImageRep;
if (bitmapImageRep) {
imageLoopCount = [[bitmapImageRep valueForProperty:NSImageLoopCount] unsignedIntegerValue];
}
return imageLoopCount;
}
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
[bitmapRep setProperty:NSImageLoopCount withValue:@(sd_imageLoopCount)];
break;
}
NSBitmapImageRep *bitmapImageRep = self.bitmapImageRep;
if (bitmapImageRep) {
[bitmapImageRep setProperty:NSImageLoopCount withValue:@(sd_imageLoopCount)];
}
}
- (BOOL)sd_isAnimated {
BOOL isGIF = NO;
for (NSImageRep *rep in self.representations) {
if ([rep isKindOfClass:[NSBitmapImageRep class]]) {
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep;
NSUInteger frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
NSBitmapImageRep *bitmapImageRep = self.bitmapImageRep;
if (bitmapImageRep) {
NSUInteger frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
isGIF = frameCount > 1 ? YES : NO;
break;
}
}
return isGIF;
}
- (void)setSd_isIncremental:(BOOL)sd_isIncremental {
objc_setAssociatedObject(self, @selector(sd_isIncremental), @(sd_isIncremental), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)sd_isIncremental {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isIncremental));
return value.boolValue;
}
@end
#endif