Authored by Olivier Poitrey

Refactor GIF support

Remove GIF specific code from main source, isolate it in a UIImage category to ease future addition of format support
@@ -67,6 +67,10 @@ @@ -67,6 +67,10 @@
67 5376131E155AD0D5005750A4 /* SDWebImagePrefetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D91148C56230056699D /* SDWebImagePrefetcher.h */; settings = {ATTRIBUTES = (Public, ); }; }; 67 5376131E155AD0D5005750A4 /* SDWebImagePrefetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D91148C56230056699D /* SDWebImagePrefetcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
68 5376131F155AD0D5005750A4 /* UIButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D93148C56230056699D /* UIButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68 5376131F155AD0D5005750A4 /* UIButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D93148C56230056699D /* UIButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
69 53761320155AD0D5005750A4 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D95148C56230056699D /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69 53761320155AD0D5005750A4 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D95148C56230056699D /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
  70 + 53EDFB8A17623F7C00698166 /* UIImage+MultiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 53EDFB8817623F7C00698166 /* UIImage+MultiFormat.h */; };
  71 + 53EDFB8B17623F7C00698166 /* UIImage+MultiFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 53EDFB8817623F7C00698166 /* UIImage+MultiFormat.h */; };
  72 + 53EDFB8C17623F7C00698166 /* UIImage+MultiFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 53EDFB8917623F7C00698166 /* UIImage+MultiFormat.m */; };
  73 + 53EDFB8D17623F7C00698166 /* UIImage+MultiFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = 53EDFB8917623F7C00698166 /* UIImage+MultiFormat.m */; };
70 A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; }; 74 A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; };
71 A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; }; 75 A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CC5172DC28500419892 /* UIImage+GIF.h */; };
72 A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; }; 76 A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; };
@@ -112,6 +116,8 @@ @@ -112,6 +116,8 @@
112 53922D94148C56230056699D /* UIButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIButton+WebCache.m"; path = "SDWebImage/UIButton+WebCache.m"; sourceTree = SOURCE_ROOT; }; 116 53922D94148C56230056699D /* UIButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIButton+WebCache.m"; path = "SDWebImage/UIButton+WebCache.m"; sourceTree = SOURCE_ROOT; };
113 53922D95148C56230056699D /* UIImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImageView+WebCache.h"; path = "SDWebImage/UIImageView+WebCache.h"; sourceTree = SOURCE_ROOT; }; 117 53922D95148C56230056699D /* UIImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImageView+WebCache.h"; path = "SDWebImage/UIImageView+WebCache.h"; sourceTree = SOURCE_ROOT; };
114 53922D96148C56230056699D /* UIImageView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+WebCache.m"; path = "SDWebImage/UIImageView+WebCache.m"; sourceTree = SOURCE_ROOT; }; 118 53922D96148C56230056699D /* UIImageView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+WebCache.m"; path = "SDWebImage/UIImageView+WebCache.m"; sourceTree = SOURCE_ROOT; };
  119 + 53EDFB8817623F7C00698166 /* UIImage+MultiFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+MultiFormat.h"; sourceTree = "<group>"; };
  120 + 53EDFB8917623F7C00698166 /* UIImage+MultiFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+MultiFormat.m"; sourceTree = "<group>"; };
115 53FB893F14D35D1A0020B787 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 121 53FB893F14D35D1A0020B787 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
116 53FB894814D35E9E0020B787 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 122 53FB894814D35E9E0020B787 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
117 A18A6CC5172DC28500419892 /* UIImage+GIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+GIF.h"; sourceTree = "<group>"; }; 123 A18A6CC5172DC28500419892 /* UIImage+GIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+GIF.h"; sourceTree = "<group>"; };
@@ -190,14 +196,16 @@ @@ -190,14 +196,16 @@
190 53922DA9148C562D0056699D /* Categories */ = { 196 53922DA9148C562D0056699D /* Categories */ = {
191 isa = PBXGroup; 197 isa = PBXGroup;
192 children = ( 198 children = (
193 - 535699B415113E7300A4C397 /* MKAnnotationView+WebCache.h */,  
194 - 535699B515113E7300A4C397 /* MKAnnotationView+WebCache.m */, 199 + 53EDFB8817623F7C00698166 /* UIImage+MultiFormat.h */,
  200 + 53EDFB8917623F7C00698166 /* UIImage+MultiFormat.m */,
195 A18A6CCB172DC33A00419892 /* NSData+GIF.h */, 201 A18A6CCB172DC33A00419892 /* NSData+GIF.h */,
196 A18A6CCC172DC33A00419892 /* NSData+GIF.m */, 202 A18A6CCC172DC33A00419892 /* NSData+GIF.m */,
197 - 53922D93148C56230056699D /* UIButton+WebCache.h */,  
198 - 53922D94148C56230056699D /* UIButton+WebCache.m */,  
199 A18A6CC5172DC28500419892 /* UIImage+GIF.h */, 203 A18A6CC5172DC28500419892 /* UIImage+GIF.h */,
200 A18A6CC6172DC28500419892 /* UIImage+GIF.m */, 204 A18A6CC6172DC28500419892 /* UIImage+GIF.m */,
  205 + 535699B415113E7300A4C397 /* MKAnnotationView+WebCache.h */,
  206 + 535699B515113E7300A4C397 /* MKAnnotationView+WebCache.m */,
  207 + 53922D93148C56230056699D /* UIButton+WebCache.h */,
  208 + 53922D94148C56230056699D /* UIButton+WebCache.m */,
201 53922D95148C56230056699D /* UIImageView+WebCache.h */, 209 53922D95148C56230056699D /* UIImageView+WebCache.h */,
202 53922D96148C56230056699D /* UIImageView+WebCache.m */, 210 53922D96148C56230056699D /* UIImageView+WebCache.m */,
203 ); 211 );
@@ -257,6 +265,7 @@ @@ -257,6 +265,7 @@
257 530E49EB16464C7F002868E7 /* SDWebImageDownloaderOperation.h in Headers */, 265 530E49EB16464C7F002868E7 /* SDWebImageDownloaderOperation.h in Headers */,
258 A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */, 266 A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */,
259 A18A6CCE172DC33A00419892 /* NSData+GIF.h in Headers */, 267 A18A6CCE172DC33A00419892 /* NSData+GIF.h in Headers */,
  268 + 53EDFB8B17623F7C00698166 /* UIImage+MultiFormat.h in Headers */,
260 ); 269 );
261 runOnlyForDeploymentPostprocessing = 0; 270 runOnlyForDeploymentPostprocessing = 0;
262 }; 271 };
@@ -276,6 +285,7 @@ @@ -276,6 +285,7 @@
276 530E49EA16464C7C002868E7 /* SDWebImageDownloaderOperation.h in Headers */, 285 530E49EA16464C7C002868E7 /* SDWebImageDownloaderOperation.h in Headers */,
277 A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */, 286 A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */,
278 A18A6CCD172DC33A00419892 /* NSData+GIF.h in Headers */, 287 A18A6CCD172DC33A00419892 /* NSData+GIF.h in Headers */,
  288 + 53EDFB8A17623F7C00698166 /* UIImage+MultiFormat.h in Headers */,
279 ); 289 );
280 runOnlyForDeploymentPostprocessing = 0; 290 runOnlyForDeploymentPostprocessing = 0;
281 }; 291 };
@@ -392,6 +402,7 @@ @@ -392,6 +402,7 @@
392 530E49ED16464C84002868E7 /* SDWebImageDownloaderOperation.m in Sources */, 402 530E49ED16464C84002868E7 /* SDWebImageDownloaderOperation.m in Sources */,
393 A18A6CCA172DC28500419892 /* UIImage+GIF.m in Sources */, 403 A18A6CCA172DC28500419892 /* UIImage+GIF.m in Sources */,
394 A18A6CD0172DC33A00419892 /* NSData+GIF.m in Sources */, 404 A18A6CD0172DC33A00419892 /* NSData+GIF.m in Sources */,
  405 + 53EDFB8D17623F7C00698166 /* UIImage+MultiFormat.m in Sources */,
395 ); 406 );
396 runOnlyForDeploymentPostprocessing = 0; 407 runOnlyForDeploymentPostprocessing = 0;
397 }; 408 };
@@ -410,6 +421,7 @@ @@ -410,6 +421,7 @@
410 53406750167780C40042B59E /* SDWebImageCompat.m in Sources */, 421 53406750167780C40042B59E /* SDWebImageCompat.m in Sources */,
411 A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */, 422 A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */,
412 A18A6CCF172DC33A00419892 /* NSData+GIF.m in Sources */, 423 A18A6CCF172DC33A00419892 /* NSData+GIF.m in Sources */,
  424 + 53EDFB8C17623F7C00698166 /* UIImage+MultiFormat.m in Sources */,
413 ); 425 );
414 runOnlyForDeploymentPostprocessing = 0; 426 runOnlyForDeploymentPostprocessing = 0;
415 }; 427 };
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 8
9 #import "SDImageCache.h" 9 #import "SDImageCache.h"
10 #import "SDWebImageDecoder.h" 10 #import "SDWebImageDecoder.h"
11 -#import "UIImage+GIF.h" 11 +#import "UIImage+MultiFormat.h"
12 #import <CommonCrypto/CommonDigest.h> 12 #import <CommonCrypto/CommonDigest.h>
13 #import <mach/mach.h> 13 #import <mach/mach.h>
14 #import <mach/mach_host.h> 14 #import <mach/mach_host.h>
@@ -226,17 +226,10 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week @@ -226,17 +226,10 @@ static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week
226 NSData *data = [self diskImageDataBySearchingAllPathsForKey:key]; 226 NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
227 if (data) 227 if (data)
228 { 228 {
229 - if ([data sd_isGIF])  
230 - {  
231 - UIImage *image = [UIImage sd_animatedGIFWithData:data];  
232 - return [self scaledImageForKey:key image:image];  
233 - }  
234 - else  
235 - {  
236 - UIImage *image = [[UIImage alloc] initWithData:data];  
237 - UIImage *scaledImage = [self scaledImageForKey:key image:image];  
238 - return [UIImage decodedImageWithImage:scaledImage];  
239 - } 229 + UIImage *image = [UIImage sd_imageWithData:data];
  230 + image = [self scaledImageForKey:key image:image];
  231 + image = [UIImage decodedImageWithImage:image];
  232 + return image;
240 } 233 }
241 else 234 else
242 { 235 {
@@ -13,7 +13,13 @@ @@ -13,7 +13,13 @@
13 @implementation UIImage (ForceDecode) 13 @implementation UIImage (ForceDecode)
14 14
15 + (UIImage *)decodedImageWithImage:(UIImage *)image 15 + (UIImage *)decodedImageWithImage:(UIImage *)image
16 -{ 16 +{
  17 + if (image.images)
  18 + {
  19 + // Do not decode animated images
  20 + return image;
  21 + }
  22 +
17 CGImageRef imageRef = image.CGImage; 23 CGImageRef imageRef = image.CGImage;
18 CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)); 24 CGSize imageSize = CGSizeMake(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef));
19 CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize}; 25 CGRect imageRect = (CGRect){.origin = CGPointZero, .size = imageSize};
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 8
9 #import "SDWebImageDownloaderOperation.h" 9 #import "SDWebImageDownloaderOperation.h"
10 #import "SDWebImageDecoder.h" 10 #import "SDWebImageDecoder.h"
11 -#import "UIImage+GIF.h" 11 +#import "UIImage+MultiFormat.h"
12 #import <ImageIO/ImageIO.h> 12 #import <ImageIO/ImageIO.h>
13 13
14 @interface SDWebImageDownloaderOperation () 14 @interface SDWebImageDownloaderOperation ()
@@ -273,21 +273,13 @@ @@ -273,21 +273,13 @@
273 } 273 }
274 else 274 else
275 { 275 {
276 - BOOL isImageGIF = [self.imageData sd_isGIF];  
277 276
278 - UIImage *image;  
279 - if (isImageGIF)  
280 - {  
281 - image = [UIImage sd_animatedGIFWithData:self.imageData];  
282 - }  
283 - else  
284 - {  
285 - image = [[UIImage alloc] initWithData:self.imageData];  
286 - } 277 + UIImage *image = [UIImage sd_imageWithData:self.imageData];
287 278
288 image = [self scaledImageForKey:self.request.URL.absoluteString image:image]; 279 image = [self scaledImageForKey:self.request.URL.absoluteString image:image];
289 280
290 - if (!isImageGIF) { 281 + if (!image.images) // Do not force decod animated GIFs
  282 + {
291 image = [UIImage decodedImageWithImage:image]; 283 image = [UIImage decodedImageWithImage:image];
292 } 284 }
293 285
@@ -156,12 +156,12 @@ @@ -156,12 +156,12 @@
156 { 156 {
157 // Image refresh hit the NSURLCache cache, do not call the completion block 157 // Image refresh hit the NSURLCache cache, do not call the completion block
158 } 158 }
159 - else if (downloadedImage && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) 159 + // NOTE: We don't call transformDownloadedImage delegate method on animated images as most transformation code would mangle it
  160 + else if (downloadedImage && !downloadedImage.images && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)])
160 { 161 {
161 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ 162 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
162 - {  
163 - BOOL isImageGIF = [data sd_isGIF];  
164 - UIImage *transformedImage = isImageGIF ? downloadedImage : [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url]; 163 + {
  164 + UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
165 165
166 dispatch_async(dispatch_get_main_queue(), ^ 166 dispatch_async(dispatch_get_main_queue(), ^
167 { 167 {
@@ -21,30 +21,42 @@ @@ -21,30 +21,42 @@
21 CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); 21 CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
22 22
23 size_t count = CGImageSourceGetCount(source); 23 size_t count = CGImageSourceGetCount(source);
24 - NSMutableArray *images = [NSMutableArray array];  
25 -  
26 - NSTimeInterval duration = 0.0f;  
27 -  
28 - for (size_t i = 0; i < count; i++) 24 +
  25 + UIImage *animatedImage;
  26 +
  27 + if (count <= 1)
29 { 28 {
30 - CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);  
31 -  
32 - NSDictionary *frameProperties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, i, NULL));  
33 - duration += [[[frameProperties objectForKey:(NSString*)kCGImagePropertyGIFDictionary] objectForKey:(NSString*)kCGImagePropertyGIFDelayTime] doubleValue];  
34 -  
35 - [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];  
36 -  
37 - CGImageRelease(image); 29 + animatedImage = [[UIImage alloc] initWithData:data];
38 } 30 }
39 -  
40 - CFRelease(source);  
41 -  
42 - if (!duration) 31 + else
43 { 32 {
44 - duration = (1.0f/10.0f)*count; 33 + NSMutableArray *images = [NSMutableArray array];
  34 +
  35 + NSTimeInterval duration = 0.0f;
  36 +
  37 + for (size_t i = 0; i < count; i++)
  38 + {
  39 + CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
  40 +
  41 + NSDictionary *frameProperties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, i, NULL));
  42 + duration += [[[frameProperties objectForKey:(NSString*)kCGImagePropertyGIFDictionary] objectForKey:(NSString*)kCGImagePropertyGIFDelayTime] doubleValue];
  43 +
  44 + [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
  45 +
  46 + CGImageRelease(image);
  47 + }
  48 +
  49 + if (!duration)
  50 + {
  51 + duration = (1.0f/10.0f)*count;
  52 + }
  53 +
  54 + animatedImage = [UIImage animatedImageWithImages:images duration:duration];
45 } 55 }
46 -  
47 - return [UIImage animatedImageWithImages:images duration:duration]; 56 +
  57 + CFRelease(source);
  58 +
  59 + return animatedImage;
48 } 60 }
49 61
50 + (UIImage *)sd_animatedGIFNamed:(NSString *)name 62 + (UIImage *)sd_animatedGIFNamed:(NSString *)name
  1 +//
  2 +// UIImage+MultiFormat.h
  3 +// SDWebImage
  4 +//
  5 +// Created by Olivier Poitrey on 07/06/13.
  6 +// Copyright (c) 2013 Dailymotion. All rights reserved.
  7 +//
  8 +
  9 +#import <UIKit/UIKit.h>
  10 +
  11 +@interface UIImage (MultiFormat)
  12 +
  13 ++ (UIImage *)sd_imageWithData:(NSData *)data;
  14 +
  15 +@end
  1 +//
  2 +// UIImage+MultiFormat.m
  3 +// SDWebImage
  4 +//
  5 +// Created by Olivier Poitrey on 07/06/13.
  6 +// Copyright (c) 2013 Dailymotion. All rights reserved.
  7 +//
  8 +
  9 +#import "UIImage+MultiFormat.h"
  10 +#import "UIImage+GIF.h"
  11 +
  12 +@implementation UIImage (MultiFormat)
  13 +
  14 ++ (UIImage *)sd_imageWithData:(NSData *)data
  15 +{
  16 + UIImage *image;
  17 +
  18 + if ([data sd_isGIF])
  19 + {
  20 + image = [UIImage sd_animatedGIFWithData:data];
  21 + }
  22 + else
  23 + {
  24 + image = [[UIImage alloc] initWithData:data];
  25 + }
  26 +
  27 + return image;
  28 +}
  29 +
  30 +@end