-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
209 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#import <UIKit/UIKit.h> | ||
|
||
/** | ||
UIImage (animatedGIF) | ||
This category adds class methods to `UIImage` to create an animated `UIImage` from an animated GIF. | ||
*/ | ||
@interface UIImage (animatedGIF) | ||
|
||
/* | ||
UIImage *animation = [UIImage animatedImageWithAnimatedGIFData:theData]; | ||
I interpret `theData` as a GIF. I create an animated `UIImage` using the source images in the GIF. | ||
The GIF stores a separate duration for each frame, in units of centiseconds (hundredths of a second). However, a `UIImage` only has a single, total `duration` property, which is a floating-point number. | ||
To handle this mismatch, I add each source image (from the GIF) to `animation` a varying number of times to match the ratios between the frame durations in the GIF. | ||
For example, suppose the GIF contains three frames. Frame 0 has duration 3. Frame 1 has duration 9. Frame 2 has duration 15. I divide each duration by the greatest common denominator of all the durations, which is 3, and add each frame the resulting number of times. Thus `animation` will contain frame 0 3/3 = 1 time, then frame 1 9/3 = 3 times, then frame 2 15/3 = 5 times. I set `animation.duration` to (3+9+15)/100 = 0.27 seconds. | ||
*/ | ||
+ (UIImage * _Nullable)animatedImageWithAnimatedGIFData:(NSData * _Nonnull)theData; | ||
|
||
/* | ||
UIImage *image = [UIImage animatedImageWithAnimatedGIFURL:theURL]; | ||
I interpret the contents of `theURL` as a GIF. I create an animated `UIImage` using the source images in the GIF. | ||
I operate exactly like `+[UIImage animatedImageWithAnimatedGIFData:]`, except that I read the data from `theURL`. If `theURL` is not a `file:` URL, you probably want to call me on a background thread or GCD queue to avoid blocking the main thread. | ||
*/ | ||
+ (UIImage * _Nullable)animatedImageWithAnimatedGIFURL:(NSURL * _Nonnull)theURL; | ||
|
||
@end |
119 changes: 119 additions & 0 deletions
119
WebEngageAppEx/Classes/ContentExtension/UIImage+animatedGIF.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
#import "UIImage+animatedGIF.h" | ||
#import <ImageIO/ImageIO.h> | ||
|
||
#if __has_feature(objc_arc) | ||
#define toCF (__bridge CFTypeRef) | ||
#define fromCF (__bridge id) | ||
#else | ||
#define toCF (CFTypeRef) | ||
#define fromCF (id) | ||
#endif | ||
|
||
@implementation UIImage (animatedGIF) | ||
|
||
static int delayCentisecondsForImageAtIndex(CGImageSourceRef const source, size_t const i) { | ||
int delayCentiseconds = 1; | ||
CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL); | ||
if (properties) { | ||
CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); | ||
if (gifProperties) { | ||
NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime); | ||
if (number == NULL || [number doubleValue] == 0) { | ||
number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime); | ||
} | ||
if ([number doubleValue] > 0) { | ||
// Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us. | ||
delayCentiseconds = (int)lrint([number doubleValue] * 100); | ||
} | ||
} | ||
CFRelease(properties); | ||
} | ||
return delayCentiseconds; | ||
} | ||
|
||
static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) { | ||
for (size_t i = 0; i < count; ++i) { | ||
imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL); | ||
delayCentisecondsOut[i] = delayCentisecondsForImageAtIndex(source, i); | ||
} | ||
} | ||
|
||
static int sum(size_t const count, int const *const values) { | ||
int theSum = 0; | ||
for (size_t i = 0; i < count; ++i) { | ||
theSum += values[i]; | ||
} | ||
return theSum; | ||
} | ||
|
||
static int pairGCD(int a, int b) { | ||
if (a < b) | ||
return pairGCD(b, a); | ||
while (true) { | ||
int const r = a % b; | ||
if (r == 0) | ||
return b; | ||
a = b; | ||
b = r; | ||
} | ||
} | ||
|
||
static int vectorGCD(size_t const count, int const *const values) { | ||
int gcd = values[0]; | ||
for (size_t i = 1; i < count; ++i) { | ||
// Note that after I process the first few elements of the vector, `gcd` will probably be smaller than any remaining element. By passing the smaller value as the second argument to `pairGCD`, I avoid making it swap the arguments. | ||
gcd = pairGCD(values[i], gcd); | ||
} | ||
return gcd; | ||
} | ||
|
||
static NSArray *frameArray(size_t const count, CGImageRef const images[count], int const delayCentiseconds[count], int const totalDurationCentiseconds) { | ||
int const gcd = vectorGCD(count, delayCentiseconds); | ||
size_t const frameCount = totalDurationCentiseconds / gcd; | ||
UIImage *frames[frameCount]; | ||
for (size_t i = 0, f = 0; i < count; ++i) { | ||
UIImage *const frame = [UIImage imageWithCGImage:images[i]]; | ||
for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) { | ||
frames[f++] = frame; | ||
} | ||
} | ||
return [NSArray arrayWithObjects:frames count:frameCount]; | ||
} | ||
|
||
static void releaseImages(size_t const count, CGImageRef const images[count]) { | ||
for (size_t i = 0; i < count; ++i) { | ||
CGImageRelease(images[i]); | ||
} | ||
} | ||
|
||
static UIImage *animatedImageWithAnimatedGIFImageSource(CGImageSourceRef const source) { | ||
size_t const count = CGImageSourceGetCount(source); | ||
CGImageRef images[count]; | ||
int delayCentiseconds[count]; // in centiseconds | ||
createImagesAndDelays(source, count, images, delayCentiseconds); | ||
int const totalDurationCentiseconds = sum(count, delayCentiseconds); | ||
NSArray *const frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds); | ||
UIImage *const animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0]; | ||
releaseImages(count, images); | ||
return animation; | ||
} | ||
|
||
static UIImage *animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceRef CF_RELEASES_ARGUMENT source) { | ||
if (source) { | ||
UIImage *const image = animatedImageWithAnimatedGIFImageSource(source); | ||
CFRelease(source); | ||
return image; | ||
} else { | ||
return nil; | ||
} | ||
} | ||
|
||
+ (UIImage *)animatedImageWithAnimatedGIFData:(NSData *)data { | ||
return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithData(toCF data, NULL)); | ||
} | ||
|
||
+ (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url { | ||
return animatedImageWithAnimatedGIFReleasingImageSource(CGImageSourceCreateWithURL(toCF url, NULL)); | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters