1
/*
2
 * This file is part of the SDWebImage package.
3
 * (c) Olivier Poitrey <rs@dailymotion.com>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8

9
#import "SDAnimatedImage.h"
10
#import "NSImage+Compatibility.h"
11
#import "SDImageCoder.h"
12
#import "SDImageCodersManager.h"
13
#import "SDImageFrame.h"
14
#import "UIImage+MemoryCacheCost.h"
15
#import "UIImage+Metadata.h"
16
#import "UIImage+MultiFormat.h"
17
#import "SDImageCoderHelper.h"
18
#import "SDImageAssetManager.h"
19
#import "objc/runtime.h"
20

21 2
static CGFloat SDImageScaleFromPath(NSString *string) {
22 2
    if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
23 2
    NSString *name = string.stringByDeletingPathExtension;
24 2
    __block CGFloat scale = 1;
25
    
26 2
    NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
27 2
    [pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
28 2
        scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
29 2
    }];
30
    
31 2
    return scale;
32
}
33

34
@interface SDAnimatedImage ()
35

36
@property (nonatomic, strong) id<SDAnimatedImageCoder> animatedCoder;
37
@property (nonatomic, assign, readwrite) SDImageFormat animatedImageFormat;
38
@property (atomic, copy) NSArray<SDImageFrame *> *loadedAnimatedImageFrames; // Mark as atomic to keep thread-safe
39
@property (nonatomic, assign, getter=isAllFramesLoaded) BOOL allFramesLoaded;
40

41
@end
42

43
@implementation SDAnimatedImage
44
@dynamic scale; // call super
45

46
#pragma mark - UIImage override method
47 2
+ (instancetype)imageNamed:(NSString *)name {
48 1
#if __has_include(<UIKit/UITraitCollection.h>)
49 1
    return [self imageNamed:name inBundle:nil compatibleWithTraitCollection:nil];
50
#else
51
    return [self imageNamed:name inBundle:nil];
52 1
#endif
53
}
54

55
#if __has_include(<UIKit/UITraitCollection.h>)
56 1
+ (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle compatibleWithTraitCollection:(UITraitCollection *)traitCollection {
57 1
    if (!traitCollection) {
58 1
        traitCollection = UIScreen.mainScreen.traitCollection;
59
    }
60 1
    CGFloat scale = traitCollection.displayScale;
61 1
    return [self imageNamed:name inBundle:bundle scale:scale];
62
}
63
#else
64 1
+ (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle {
65 1
    return [self imageNamed:name inBundle:bundle scale:0];
66
}
67
#endif
68

69
// 0 scale means automatically check
70 2
+ (instancetype)imageNamed:(NSString *)name inBundle:(NSBundle *)bundle scale:(CGFloat)scale {
71 2
    if (!name) {
72 0
        return nil;
73
    }
74 2
    if (!bundle) {
75 2
        bundle = [NSBundle mainBundle];
76
    }
77 2
    SDImageAssetManager *assetManager = [SDImageAssetManager sharedAssetManager];
78 2
    SDAnimatedImage *image = (SDAnimatedImage *)[assetManager imageForName:name];
79 2
    if ([image isKindOfClass:[SDAnimatedImage class]]) {
80 0
        return image;
81
    }
82 2
    NSString *path = [assetManager getPathForName:name bundle:bundle preferredScale:&scale];
83 2
    if (!path) {
84 2
        return image;
85
    }
86 2
    NSData *data = [NSData dataWithContentsOfFile:path];
87 2
    if (!data) {
88 0
        return image;
89
    }
90 2
    image = [[self alloc] initWithData:data scale:scale];
91 2
    if (image) {
92 2
        [assetManager storeImage:image forName:name];
93
    }
94
    
95 2
    return image;
96
}
97

98 2
+ (instancetype)imageWithContentsOfFile:(NSString *)path {
99 2
    return [[self alloc] initWithContentsOfFile:path];
100
}
101

102 2
+ (instancetype)imageWithData:(NSData *)data {
103 2
    return [[self alloc] initWithData:data];
104
}
105

106 0
+ (instancetype)imageWithData:(NSData *)data scale:(CGFloat)scale {
107 0
    return [[self alloc] initWithData:data scale:scale];
108
}
109

110 2
- (instancetype)initWithContentsOfFile:(NSString *)path {
111 2
    NSData *data = [NSData dataWithContentsOfFile:path];
112 2
    return [self initWithData:data scale:SDImageScaleFromPath(path)];
113
}
114

115 2
- (instancetype)initWithData:(NSData *)data {
116 2
    return [self initWithData:data scale:1];
117
}
118

119 2
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
120 2
    return [self initWithData:data scale:scale options:nil];
121
}
122

123 2
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale options:(SDImageCoderOptions *)options {
124 2
    if (!data || data.length == 0) {
125 0
        return nil;
126
    }
127 2
    data = [data copy]; // avoid mutable data
128 2
    id<SDAnimatedImageCoder> animatedCoder = nil;
129 2
    for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
130 2
        if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
131 2
            if ([coder canDecodeFromData:data]) {
132 2
                if (!options) {
133 2
                    options = @{SDImageCoderDecodeScaleFactor : @(scale)};
134
                }
135 2
                animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:data options:options];
136 2
                break;
137
            }
138
        }
139
    }
140 2
    if (!animatedCoder) {
141 2
        return nil;
142
    }
143 2
    return [self initWithAnimatedCoder:animatedCoder scale:scale];
144
}
145

146 2
- (instancetype)initWithAnimatedCoder:(id<SDAnimatedImageCoder>)animatedCoder scale:(CGFloat)scale {
147 2
    if (!animatedCoder) {
148 0
        return nil;
149
    }
150 2
    UIImage *image = [animatedCoder animatedImageFrameAtIndex:0];
151 2
    if (!image) {
152 0
        return nil;
153
    }
154 1
#if SD_MAC
155 1
    self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:kCGImagePropertyOrientationUp];
156
#else
157 1
    self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
158 1
#endif
159 2
    if (self) {
160 2
        // Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP)
161 2
        if (animatedCoder.animatedImageFrameCount > 1) {
162 2
            _animatedCoder = animatedCoder;
163
        }
164 2
        NSData *data = [animatedCoder animatedImageData];
165 2
        SDImageFormat format = [NSData sd_imageFormatForImageData:data];
166 2
        _animatedImageFormat = format;
167
    }
168 2
    return self;
169
}
170

171
#pragma mark - Preload
172 2
- (void)preloadAllFrames {
173 2
    if (!_animatedCoder) {
174 0
        return;
175
    }
176 2
    if (!self.isAllFramesLoaded) {
177 2
        NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
178 2
        for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
179 2
            UIImage *image = [self animatedImageFrameAtIndex:i];
180 2
            NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
181 2
            SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration]; // through the image should be nonnull, used as nullable for `animatedImageFrameAtIndex:`
182 2
            [frames addObject:frame];
183
        }
184 2
        self.loadedAnimatedImageFrames = frames;
185 2
        self.allFramesLoaded = YES;
186
    }
187
}
188

189 2
- (void)unloadAllFrames {
190 2
    if (!_animatedCoder) {
191 0
        return;
192
    }
193 2
    if (self.isAllFramesLoaded) {
194 2
        self.loadedAnimatedImageFrames = nil;
195 2
        self.allFramesLoaded = NO;
196
    }
197
}
198

199
#pragma mark - NSSecureCoding
200 2
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
201 2
    self = [super initWithCoder:aDecoder];
202 2
    if (self) {
203 2
        _animatedImageFormat = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(animatedImageFormat))];
204 2
        NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
205 2
        if (!animatedImageData) {
206 0
            return self;
207
        }
208 2
        CGFloat scale = self.scale;
209 2
        id<SDAnimatedImageCoder> animatedCoder = nil;
210 2
        for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
211 2
            if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
212 2
                if ([coder canDecodeFromData:animatedImageData]) {
213 2
                    animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
214 2
                    break;
215
                }
216
            }
217
        }
218 2
        if (!animatedCoder) {
219 0
            return self;
220
        }
221 2
        if (animatedCoder.animatedImageFrameCount > 1) {
222 2
            _animatedCoder = animatedCoder;
223
        }
224
    }
225 2
    return self;
226
}
227

228 2
- (void)encodeWithCoder:(NSCoder *)aCoder {
229 2
    [super encodeWithCoder:aCoder];
230 2
    [aCoder encodeInteger:self.animatedImageFormat forKey:NSStringFromSelector(@selector(animatedImageFormat))];
231 2
    NSData *animatedImageData = self.animatedImageData;
232 2
    if (animatedImageData) {
233 2
        [aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
234
    }
235
}
236

237 2
+ (BOOL)supportsSecureCoding {
238 2
    return YES;
239
}
240

241
#pragma mark - SDAnimatedImageProvider
242

243 2
- (NSData *)animatedImageData {
244 2
    return [self.animatedCoder animatedImageData];
245
}
246

247 2
- (NSUInteger)animatedImageLoopCount {
248 2
    return [self.animatedCoder animatedImageLoopCount];
249
}
250

251 2
- (NSUInteger)animatedImageFrameCount {
252 2
    return [self.animatedCoder animatedImageFrameCount];
253
}
254

255 2
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
256 2
    if (index >= self.animatedImageFrameCount) {
257 0
        return nil;
258
    }
259 2
    if (self.isAllFramesLoaded) {
260 2
        SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
261 2
        return frame.image;
262
    }
263 2
    return [self.animatedCoder animatedImageFrameAtIndex:index];
264
}
265

266 2
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
267 2
    if (index >= self.animatedImageFrameCount) {
268 0
        return 0;
269
    }
270 2
    if (self.isAllFramesLoaded) {
271 0
        SDImageFrame *frame = [self.loadedAnimatedImageFrames objectAtIndex:index];
272 0
        return frame.duration;
273
    }
274 2
    return [self.animatedCoder animatedImageDurationAtIndex:index];
275
}
276

277
@end
278

279
@implementation SDAnimatedImage (MemoryCacheCost)
280

281 2
- (NSUInteger)sd_memoryCost {
282 2
    NSNumber *value = objc_getAssociatedObject(self, @selector(sd_memoryCost));
283 2
    if (value != nil) {
284 0
        return value.unsignedIntegerValue;
285
    }
286
    
287 2
    CGImageRef imageRef = self.CGImage;
288 2
    if (!imageRef) {
289 0
        return 0;
290
    }
291 2
    NSUInteger bytesPerFrame = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
292 2
    NSUInteger frameCount = 1;
293 2
    if (self.isAllFramesLoaded) {
294 0
        frameCount = self.animatedImageFrameCount;
295
    }
296 2
    frameCount = frameCount > 0 ? frameCount : 1;
297 2
    NSUInteger cost = bytesPerFrame * frameCount;
298 2
    return cost;
299
}
300

301
@end
302

303
@implementation SDAnimatedImage (Metadata)
304

305 0
- (BOOL)sd_isAnimated {
306 0
    return YES;
307
}
308

309 0
- (NSUInteger)sd_imageLoopCount {
310 0
    return self.animatedImageLoopCount;
311
}
312

313 0
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
314 0
    return;
315
}
316

317 0
- (SDImageFormat)sd_imageFormat {
318 0
    return self.animatedImageFormat;
319
}
320

321 0
- (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat {
322 0
    return;
323
}
324

325 0
- (BOOL)sd_isVector {
326 0
    return NO;
327
}
328

329
@end
330

331
@implementation SDAnimatedImage (MultiFormat)
332

333 0
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data {
334 0
    return [self sd_imageWithData:data scale:1];
335
}
336

337 0
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale {
338 0
    return [self sd_imageWithData:data scale:scale firstFrameOnly:NO];
339
}
340

341 0
+ (nullable UIImage *)sd_imageWithData:(nullable NSData *)data scale:(CGFloat)scale firstFrameOnly:(BOOL)firstFrameOnly {
342 0
    if (!data) {
343 0
        return nil;
344
    }
345 0
    return [[self alloc] initWithData:data scale:scale options:@{SDImageCoderDecodeFirstFrameOnly : @(firstFrameOnly)}];
346
}
347

348 0
- (nullable NSData *)sd_imageData {
349 0
    NSData *imageData = self.animatedImageData;
350 0
    if (imageData) {
351 0
        return imageData;
352 0
    } else {
353 0
        return [self sd_imageDataAsFormat:self.animatedImageFormat];
354
    }
355
}
356

357 0
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
358 0
    return [self sd_imageDataAsFormat:imageFormat compressionQuality:1];
359
}
360

361 0
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality {
362 0
    return [self sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:NO];
363
}
364

365 0
- (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat compressionQuality:(double)compressionQuality firstFrameOnly:(BOOL)firstFrameOnly {
366 0
    if (firstFrameOnly) {
367 0
        // First frame, use super implementation
368 0
        return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
369
    }
370 0
    NSUInteger frameCount = self.animatedImageFrameCount;
371 0
    if (frameCount <= 1) {
372 0
        // Static image, use super implementation
373 0
        return [super sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
374
    }
375 0
    // Keep animated image encoding, loop each frame.
376 0
    NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:frameCount];
377 0
    for (size_t i = 0; i < frameCount; i++) {
378 0
        UIImage *image = [self animatedImageFrameAtIndex:i];
379 0
        NSTimeInterval duration = [self animatedImageDurationAtIndex:i];
380 0
        SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
381 0
        [frames addObject:frame];
382
    }
383 0
    UIImage *animatedImage = [SDImageCoderHelper animatedImageWithFrames:frames];
384 0
    NSData *imageData = [animatedImage sd_imageDataAsFormat:imageFormat compressionQuality:compressionQuality firstFrameOnly:firstFrameOnly];
385 0
    return imageData;
386
}
387

388
@end

Read our documentation on viewing source code .

Loading