Skip to content

Commit

Permalink
Revert "Remove old GIF code (facebook#25636)"
Browse files Browse the repository at this point in the history
This reverts commit cf77067.
  • Loading branch information
janicduplessis committed Aug 30, 2019
1 parent 57dfb6d commit 1d2eefe
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 21 deletions.
20 changes: 10 additions & 10 deletions Libraries/Image/RCTAnimatedImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ - (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale
if (!imageSource) {
return nil;
}

BOOL framesValid = [self scanAndCheckFramesValidWithSource:imageSource];
if (!framesValid) {
CFRelease(imageSource);
return nil;
}

_imageSource = imageSource;

// grab image at the first index
UIImage *image = [self animatedImageFrameAtIndex:0];
if (!image) {
return nil;
}
self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

return self;
}

Expand All @@ -63,18 +63,18 @@ - (BOOL)scanAndCheckFramesValidWithSource:(CGImageSourceRef)imageSource
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
NSUInteger loopCount = [self imageLoopCountWithSource:imageSource];
NSMutableArray<RCTGIFCoderFrame *> *frames = [NSMutableArray array];

for (size_t i = 0; i < frameCount; i++) {
RCTGIFCoderFrame *frame = [[RCTGIFCoderFrame alloc] init];
frame.index = i;
frame.duration = [self frameDurationAtIndex:i source:imageSource];
[frames addObject:frame];
}

_frameCount = frameCount;
_loopCount = loopCount;
_frames = [frames copy];

return YES;
}

Expand Down Expand Up @@ -105,7 +105,7 @@ - (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source
}
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];

NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
if (delayTimeUnclampedProp != nil) {
frameDuration = [delayTimeUnclampedProp floatValue];
Expand All @@ -115,7 +115,7 @@ - (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source
frameDuration = [delayTimeProp floatValue];
}
}

CFRelease(cfFrameProperties);
return frameDuration;
}
Expand Down
92 changes: 88 additions & 4 deletions Libraries/Image/RCTGIFImageDecoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#import <React/RCTUtils.h>
#import <React/RCTAnimatedImage.h>
#import <React/RCTImageLoader.h>

@implementation RCTGIFImageDecoder

Expand All @@ -31,13 +32,96 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
resizeMode:(RCTResizeMode)resizeMode
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
RCTAnimatedImage *image = [[RCTAnimatedImage alloc] initWithData:imageData scale:scale];

if (!image) {
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
if (!imageSource) {
completionHandler(nil, nil);
return ^{};
}

NSDictionary<NSString *, id> *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
CGFloat loopCount = 0;
if ([[properties[(id)kCGImagePropertyGIFDictionary] allKeys] containsObject:(id)kCGImagePropertyGIFLoopCount]) {
loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
if (loopCount == 0) {
// A loop count of 0 means infinite
loopCount = HUGE_VALF;
} else {
// A loop count of 1 means it should repeat twice, 2 means, thrice, etc.
loopCount += 1;
}
}

UIImage *image = nil;
size_t imageCount = CGImageSourceGetCount(imageSource);
if (imageCount > 1) {

NSTimeInterval duration = 0;
NSMutableArray<NSNumber *> *delays = [NSMutableArray arrayWithCapacity:imageCount];
NSMutableArray<id /* CGIMageRef */> *images = [NSMutableArray arrayWithCapacity:imageCount];
for (size_t i = 0; i < imageCount; i++) {

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, NULL);
if (!imageRef) {
continue;
}
if (!image) {
image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
}

NSDictionary<NSString *, id> *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL);
NSDictionary<NSString *, id> *frameGIFProperties = frameProperties[(id)kCGImagePropertyGIFDictionary];

const NSTimeInterval kDelayTimeIntervalDefault = 0.1;
NSNumber *delayTime = frameGIFProperties[(id)kCGImagePropertyGIFUnclampedDelayTime] ?: frameGIFProperties[(id)kCGImagePropertyGIFDelayTime];
if (delayTime == nil) {
if (delays.count == 0) {
delayTime = @(kDelayTimeIntervalDefault);
} else {
delayTime = delays.lastObject;
}
}

const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
delayTime = @(kDelayTimeIntervalDefault);
}

duration += delayTime.doubleValue;
[delays addObject:delayTime];
[images addObject:(__bridge_transfer id)imageRef];
}
CFRelease(imageSource);

NSMutableArray<NSNumber *> *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
for (NSNumber *delayNumber in delays) {
[keyTimes addObject:@(runningDuration / duration)];
runningDuration += delayNumber.doubleValue;
}

[keyTimes addObject:@1.0];

// Create animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
animation.calculationMode = kCAAnimationDiscrete;
animation.repeatCount = loopCount;
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
image.reactKeyframeAnimation = animation;

} else {

// Don't bother creating an animation
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
if (imageRef) {
image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
CFRelease(imageRef);
}
CFRelease(imageSource);
}

completionHandler(nil, image);
return ^{};
}
Expand Down
19 changes: 16 additions & 3 deletions Libraries/Image/RCTImageView.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import <React/RCTUIImageViewAnimated.h>
#import <React/RCTImageBlurUtils.h>
#import <React/RCTImageUtils.h>
#import <React/RCTImageLoader.h>
#import <React/RCTImageLoaderProtocol.h>

/**
Expand Down Expand Up @@ -82,7 +83,7 @@ @implementation RCTImageView
// Whether the latest change of props requires the image to be reloaded
BOOL _needsReload;

RCTUIImageViewAnimated *_imageView;
UIImageView *_imageView;
}

- (instancetype)initWithBridge:(RCTBridge *)bridge
Expand All @@ -98,7 +99,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
selector:@selector(clearImageIfDetached)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
_imageView = [[RCTUIImageViewAnimated alloc] init];
_imageView = [[UIImageView alloc] init];
_imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_imageView];
}
Expand Down Expand Up @@ -226,6 +227,7 @@ - (void)cancelImageLoad
- (void)clearImage
{
[self cancelImageLoad];
[_imageView.layer removeAnimationForKey:@"contents"];
self.image = nil;
_imageSource = nil;
}
Expand Down Expand Up @@ -404,7 +406,18 @@ - (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error for
self->_pendingImageSource = nil;
}

self.image = image;
[self->_imageView.layer removeAnimationForKey:@"contents"];
if (image.reactKeyframeAnimation) {
CGImageRef posterImageRef = (__bridge CGImageRef)[image.reactKeyframeAnimation.values firstObject];
if (!posterImageRef) {
return;
}
// Apply renderingMode to animated image.
self->_imageView.image = [[UIImage imageWithCGImage:posterImageRef] imageWithRenderingMode:self->_renderingMode];
[self->_imageView.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
self.image = image;
}

if (isPartialLoad) {
if (self->_onPartialLoad) {
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Image/RCTUIImageViewAnimated.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ - (void)setImage:(UIImage *)image
if (self.image == image) {
return;
}

[self stop];
[self resetAnimatedImage];

if ([image respondsToSelector:@selector(animatedImageFrameAtIndex:)]) {
[self stop];
[self resetAnimatedImage];

NSUInteger animatedImageFrameCount = ((UIImage<RCTAnimatedImage> *)image).animatedImageFrameCount;

// In case frame count is 0, there is no reason to continue.
Expand Down
6 changes: 6 additions & 0 deletions React/CoreModules/RCTImageLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
#import <React/RCTImageCache.h>
#import <React/RCTImageLoaderProtocol.h>

@interface UIImage (ReactImageLoader)

@property (nonatomic, copy) CAKeyframeAnimation *reactKeyframeAnimation;

@end

@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTImageLoaderProtocol>
- (instancetype)init;
- (instancetype)initWithRedirectDelegate:(id<RCTImageRedirectProtocol>)redirectDelegate NS_DESIGNATED_INITIALIZER;
Expand Down
19 changes: 18 additions & 1 deletion React/CoreModules/RCTImageLoader.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@

static NSInteger RCTImageBytesForImage(UIImage *image)
{
CAKeyframeAnimation *keyFrameAnimation = [image reactKeyframeAnimation];
NSInteger singleImageBytes = image.size.width * image.size.height * image.scale * image.scale * 4;
return image.images ? image.images.count * singleImageBytes : singleImageBytes;
if (keyFrameAnimation) {
return keyFrameAnimation.values.count * singleImageBytes;
} else {
return image.images ? image.images.count * singleImageBytes : singleImageBytes;
}
}

@interface RCTImageLoader() <NativeImageLoaderSpec>
Expand All @@ -34,6 +39,16 @@ @interface RCTImageLoader() <NativeImageLoaderSpec>

@implementation UIImage (React)

- (CAKeyframeAnimation *)reactKeyframeAnimation
{
return objc_getAssociatedObject(self, _cmd);
}

- (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation
{
objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSInteger)reactDecodedImageBytes
{
NSNumber *imageBytes = objc_getAssociatedObject(self, _cmd);
Expand Down Expand Up @@ -274,9 +289,11 @@ - (void)setImageCache:(id<RCTImageCache>)cache
CGSizeEqualToSize(image.size, size)) {
return image;
}
CAKeyframeAnimation *animation = image.reactKeyframeAnimation;
CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
image = RCTTransformImage(image, size, scale, transform);
image.reactKeyframeAnimation = animation;
return image;
}

Expand Down

0 comments on commit 1d2eefe

Please sign in to comment.