Skip to content

Commit 5db0d00

Browse files
author
Olivier Poitrey
committed
Merge pull request SDWebImage#375 from lavoy/master
Animated GIF Support
2 parents 17d04b7 + d789fd9 commit 5db0d00

File tree

9 files changed

+246
-9
lines changed

9 files changed

+246
-9
lines changed

Examples/SDWebImage Demo/MasterViewController.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
3030
target:self
3131
action:@selector(flushCache)];
3232
_objects = [NSArray arrayWithObjects:
33-
@"https://graph.facebook.com/olivier.poitrey/picture?height=200&width=200",
33+
@"http://assets.sbnation.com/assets/2512203/dogflops.gif",
3434
@"http://static2.dmcdn.net/static/video/656/177/44771656:jpeg_preview_small.jpg?20120509154705",
3535
@"http://static2.dmcdn.net/static/video/629/228/44822926:jpeg_preview_small.jpg?20120509181018",
3636
@"http://static2.dmcdn.net/static/video/116/367/44763611:jpeg_preview_small.jpg?20120509101749",

SDWebImage.xcodeproj/project.pbxproj

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@
6767
5376131E155AD0D5005750A4 /* SDWebImagePrefetcher.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D91148C56230056699D /* SDWebImagePrefetcher.h */; settings = {ATTRIBUTES = (Public, ); }; };
6868
5376131F155AD0D5005750A4 /* UIButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D93148C56230056699D /* UIButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
6969
53761320155AD0D5005750A4 /* UIImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53922D95148C56230056699D /* UIImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
70-
A99C65A116F9CA2600A73D75 /* SDWebImageCompat.m in Sources */ = {isa = PBXBuildFile; fileRef = 5340674F167780C40042B59E /* SDWebImageCompat.m */; };
70+
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 */; };
72+
A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; };
73+
A18A6CCA172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; };
74+
A18A6CCD172DC33A00419892 /* NSData+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CCB172DC33A00419892 /* NSData+GIF.h */; };
75+
A18A6CCE172DC33A00419892 /* NSData+GIF.h in Headers */ = {isa = PBXBuildFile; fileRef = A18A6CCB172DC33A00419892 /* NSData+GIF.h */; };
76+
A18A6CCF172DC33A00419892 /* NSData+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CCC172DC33A00419892 /* NSData+GIF.m */; };
77+
A18A6CD0172DC33A00419892 /* NSData+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CCC172DC33A00419892 /* NSData+GIF.m */; };
7178
/* End PBXBuildFile section */
7279

7380
/* Begin PBXContainerItemProxy section */
@@ -107,6 +114,10 @@
107114
53922D96148C56230056699D /* UIImageView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+WebCache.m"; path = "SDWebImage/UIImageView+WebCache.m"; sourceTree = SOURCE_ROOT; };
108115
53FB893F14D35D1A0020B787 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
109116
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>"; };
118+
A18A6CC6172DC28500419892 /* UIImage+GIF.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+GIF.m"; sourceTree = "<group>"; };
119+
A18A6CCB172DC33A00419892 /* NSData+GIF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+GIF.h"; sourceTree = "<group>"; };
120+
A18A6CCC172DC33A00419892 /* NSData+GIF.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+GIF.m"; sourceTree = "<group>"; };
110121
/* End PBXFileReference section */
111122

112123
/* Begin PBXFrameworksBuildPhase section */
@@ -181,8 +192,12 @@
181192
children = (
182193
535699B415113E7300A4C397 /* MKAnnotationView+WebCache.h */,
183194
535699B515113E7300A4C397 /* MKAnnotationView+WebCache.m */,
195+
A18A6CCB172DC33A00419892 /* NSData+GIF.h */,
196+
A18A6CCC172DC33A00419892 /* NSData+GIF.m */,
184197
53922D93148C56230056699D /* UIButton+WebCache.h */,
185198
53922D94148C56230056699D /* UIButton+WebCache.m */,
199+
A18A6CC5172DC28500419892 /* UIImage+GIF.h */,
200+
A18A6CC6172DC28500419892 /* UIImage+GIF.m */,
186201
53922D95148C56230056699D /* UIImageView+WebCache.h */,
187202
53922D96148C56230056699D /* UIImageView+WebCache.m */,
188203
);
@@ -240,6 +255,8 @@
240255
531041DC157EAFA400BBABC3 /* MKAnnotationView+WebCache.h in Headers */,
241256
530E49E916464C26002868E7 /* SDWebImageOperation.h in Headers */,
242257
530E49EB16464C7F002868E7 /* SDWebImageDownloaderOperation.h in Headers */,
258+
A18A6CC8172DC28500419892 /* UIImage+GIF.h in Headers */,
259+
A18A6CCE172DC33A00419892 /* NSData+GIF.h in Headers */,
243260
);
244261
runOnlyForDeploymentPostprocessing = 0;
245262
};
@@ -257,6 +274,8 @@
257274
53761320155AD0D5005750A4 /* UIImageView+WebCache.h in Headers */,
258275
530E49E816464C25002868E7 /* SDWebImageOperation.h in Headers */,
259276
530E49EA16464C7C002868E7 /* SDWebImageDownloaderOperation.h in Headers */,
277+
A18A6CC7172DC28500419892 /* UIImage+GIF.h in Headers */,
278+
A18A6CCD172DC33A00419892 /* NSData+GIF.h in Headers */,
260279
);
261280
runOnlyForDeploymentPostprocessing = 0;
262281
};
@@ -362,7 +381,6 @@
362381
isa = PBXSourcesBuildPhase;
363382
buildActionMask = 2147483647;
364383
files = (
365-
A99C65A116F9CA2600A73D75 /* SDWebImageCompat.m in Sources */,
366384
531041C4157EAFA400BBABC3 /* SDImageCache.m in Sources */,
367385
531041C5157EAFA400BBABC3 /* SDWebImageDecoder.m in Sources */,
368386
531041C6157EAFA400BBABC3 /* SDWebImageDownloader.m in Sources */,
@@ -372,6 +390,8 @@
372390
531041CA157EAFA400BBABC3 /* UIImageView+WebCache.m in Sources */,
373391
531041CB157EAFA400BBABC3 /* MKAnnotationView+WebCache.m in Sources */,
374392
530E49ED16464C84002868E7 /* SDWebImageDownloaderOperation.m in Sources */,
393+
A18A6CCA172DC28500419892 /* UIImage+GIF.m in Sources */,
394+
A18A6CD0172DC33A00419892 /* NSData+GIF.m in Sources */,
375395
);
376396
runOnlyForDeploymentPostprocessing = 0;
377397
};
@@ -388,6 +408,8 @@
388408
5376130F155AD0D5005750A4 /* UIImageView+WebCache.m in Sources */,
389409
530E49EC16464C84002868E7 /* SDWebImageDownloaderOperation.m in Sources */,
390410
53406750167780C40042B59E /* SDWebImageCompat.m in Sources */,
411+
A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */,
412+
A18A6CCF172DC33A00419892 /* NSData+GIF.m in Sources */,
391413
);
392414
runOnlyForDeploymentPostprocessing = 0;
393415
};

SDWebImage/NSData+GIF.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// NSData+GIF.h
3+
// SDWebImage
4+
//
5+
// Created by Andy LaVoy on 4/28/13.
6+
// Copyright (c) 2013 Dailymotion. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface NSData (GIF)
12+
13+
- (BOOL)isGIF;
14+
15+
@end

SDWebImage/NSData+GIF.m

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// NSData+GIF.m
3+
// SDWebImage
4+
//
5+
// Created by Andy LaVoy on 4/28/13.
6+
// Copyright (c) 2013 Dailymotion. All rights reserved.
7+
//
8+
9+
#import "NSData+GIF.h"
10+
11+
@implementation NSData (GIF)
12+
13+
- (BOOL)isGIF
14+
{
15+
BOOL isGIF = NO;
16+
17+
uint8_t c;
18+
[self getBytes:&c length:1];
19+
20+
switch (c)
21+
{
22+
case 0x47: // probably a GIF
23+
isGIF = YES;
24+
break;
25+
default:
26+
break;
27+
}
28+
29+
return isGIF;
30+
}
31+
32+
@end

SDWebImage/SDImageCache.m

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "SDImageCache.h"
1010
#import "SDWebImageDecoder.h"
11+
#import "UIImage+GIF.h"
1112
#import <CommonCrypto/CommonDigest.h>
1213
#import <mach/mach.h>
1314
#import <mach/mach_host.h>
@@ -180,9 +181,16 @@ - (UIImage *)diskImageForKey:(NSString *)key
180181
NSData *data = [NSData dataWithContentsOfFile:path];
181182
if (data)
182183
{
183-
UIImage *image = [[UIImage alloc] initWithData:data];
184-
UIImage *scaledImage = [self scaledImageForKey:key image:image];
185-
return [UIImage decodedImageWithImage:scaledImage];
184+
if ([data isGIF])
185+
{
186+
return [UIImage animatedGIFWithData:data];
187+
}
188+
else
189+
{
190+
UIImage *image = [[UIImage alloc] initWithData:data];
191+
UIImage *scaledImage = [self scaledImageForKey:key image:image];
192+
return [UIImage decodedImageWithImage:scaledImage];
193+
}
186194
}
187195
else
188196
{

SDWebImage/SDWebImageCompat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@
3737
#define SDDispatchQueueSetterSementics assign
3838
#endif
3939

40-
extern inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image);
40+
extern inline UIImage *SDScaledImageForKey(NSString *key, UIImage *image);

SDWebImage/SDWebImageManager.m

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
#import "SDWebImageManager.h"
10+
#import "UIImage+GIF.h"
1011
#import <objc/message.h>
1112

1213
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>
@@ -121,6 +122,12 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
121122
}
122123
__block id<SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished)
123124
{
125+
BOOL isImageGIF = [data isGIF];
126+
if (isImageGIF)
127+
{
128+
downloadedImage = [UIImage animatedGIFWithData:data];
129+
}
130+
124131
if (weakOperation.cancelled)
125132
{
126133
completedBlock(nil, nil, SDImageCacheTypeNone, finished);
@@ -149,7 +156,7 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
149156
{
150157
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^
151158
{
152-
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
159+
UIImage *transformedImage = isImageGIF ? downloadedImage : [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
153160

154161
dispatch_async(dispatch_get_main_queue(), ^
155162
{
@@ -158,7 +165,8 @@ - (NSString *)cacheKeyForURL:(NSURL *)url
158165

159166
if (transformedImage && finished)
160167
{
161-
[self.imageCache storeImage:transformedImage imageData:nil forKey:key toDisk:cacheOnDisk];
168+
NSData *dataToStore = isImageGIF ? data : nil;
169+
[self.imageCache storeImage:transformedImage imageData:dataToStore forKey:key toDisk:cacheOnDisk];
162170
}
163171
});
164172
}

SDWebImage/UIImage+GIF.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// UIImage+GIF.h
3+
// LBGIFImage
4+
//
5+
// Created by Laurin Brandner on 06.01.12.
6+
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
7+
//
8+
9+
#import "NSData+GIF.h"
10+
#import <UIKit/UIKit.h>
11+
12+
@interface UIImage (GIF)
13+
14+
+ (UIImage *)animatedGIFNamed:(NSString *)name;
15+
+ (UIImage *)animatedGIFWithData:(NSData *)data;
16+
17+
- (UIImage *)animatedImageByScalingAndCroppingToSize:(CGSize)size;
18+
19+
@end

SDWebImage/UIImage+GIF.m

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//
2+
// UIImage+GIF.m
3+
// LBGIFImage
4+
//
5+
// Created by Laurin Brandner on 06.01.12.
6+
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
7+
//
8+
9+
#import "UIImage+GIF.h"
10+
#import <ImageIO/ImageIO.h>
11+
12+
@implementation UIImage (GIF)
13+
14+
+ (UIImage *)animatedGIFWithData:(NSData *)data
15+
{
16+
if (!data)
17+
{
18+
return nil;
19+
}
20+
21+
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
22+
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++)
29+
{
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);
38+
}
39+
40+
CFRelease(source);
41+
42+
if (!duration)
43+
{
44+
duration = (1.0f/10.0f)*count;
45+
}
46+
47+
return [UIImage animatedImageWithImages:images duration:duration];
48+
}
49+
50+
+ (UIImage *)animatedGIFNamed:(NSString *)name
51+
{
52+
CGFloat scale = [UIScreen mainScreen].scale;
53+
54+
if (scale > 1.0f)
55+
{
56+
NSString *retinaPath = [[NSBundle mainBundle] pathForResource:[name stringByAppendingString:@"@2x"] ofType:@"gif"];
57+
58+
NSData *data = [NSData dataWithContentsOfFile:retinaPath];
59+
60+
if (data)
61+
{
62+
return [UIImage animatedGIFWithData:data];
63+
}
64+
65+
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
66+
67+
data = [NSData dataWithContentsOfFile:path];
68+
69+
if (data)
70+
{
71+
return [UIImage animatedGIFWithData:data];
72+
}
73+
74+
return [UIImage imageNamed:name];
75+
}
76+
else
77+
{
78+
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"gif"];
79+
80+
NSData *data = [NSData dataWithContentsOfFile:path];
81+
82+
if (data)
83+
{
84+
return [UIImage animatedGIFWithData:data];
85+
}
86+
87+
return [UIImage imageNamed:name];
88+
}
89+
}
90+
91+
- (UIImage *)animatedImageByScalingAndCroppingToSize:(CGSize)size
92+
{
93+
if (CGSizeEqualToSize(self.size, size) || CGSizeEqualToSize(size, CGSizeZero))
94+
{
95+
return self;
96+
}
97+
98+
CGSize scaledSize = size;
99+
CGPoint thumbnailPoint = CGPointZero;
100+
101+
CGFloat widthFactor = size.width / self.size.width;
102+
CGFloat heightFactor = size.height / self.size.height;
103+
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor :heightFactor;
104+
scaledSize.width = self.size.width * scaleFactor;
105+
scaledSize.height = self.size.height * scaleFactor;
106+
107+
if (widthFactor > heightFactor)
108+
{
109+
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
110+
}
111+
else if (widthFactor < heightFactor)
112+
{
113+
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
114+
}
115+
116+
NSMutableArray *scaledImages = [NSMutableArray array];
117+
118+
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
119+
120+
for (UIImage *image in self.images)
121+
{
122+
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
123+
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
124+
125+
[scaledImages addObject:newImage];
126+
}
127+
128+
UIGraphicsEndImageContext();
129+
130+
return [UIImage animatedImageWithImages:scaledImages duration:self.duration];
131+
}
132+
133+
@end

0 commit comments

Comments
 (0)