From de759b949e4aa4904fd60a2fcbb874a3c857b50c Mon Sep 17 00:00:00 2001 From: Salil Apte Date: Fri, 30 Nov 2018 20:29:48 -0800 Subject: [PATCH] Fixes animated gifs incorrectly looping/not stopping on last frame (#21999) Summary: Currently, if you load an animated gif using the standard `Image` component, it will not correctly respect the loop count property found in the Netscape App Extension block of the file. The issues are as follows: 1) If the App Extension isn't present, the animated gif loops indefinitely when it should not loop at all. 2) If the App Extension is present, the animated gif loops one less time than it should. The other issue is that once the looping completes, the image doesn't pause at the last frame but instead, loops back to the beginning of the animation e.g. frame 1. The fix does a few things: 1) If there is _no_ App Extension present, the image doesn't loop at all 2) If there _is_ an App Extension present, it loops the correct amount of times. For instance, if the loop count is 1, it means the gif should loop _once_ after it finishes playing, for a total of _two_ total loops. 3) Once the number of loops completes (assuming loop count isn't set to 0 which means infinite), the animation pauses on the last frame. Pull Request resolved: https://github.com/facebook/react-native/pull/21999 Differential Revision: D13287005 Pulled By: hramos fbshipit-source-id: f7210ad40e0e76c9ec454953b8a067569d3feaaa --- Libraries/Image/RCTGIFImageDecoder.m | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Libraries/Image/RCTGIFImageDecoder.m b/Libraries/Image/RCTGIFImageDecoder.m index 322d137cda733c..0943493c9a283c 100644 --- a/Libraries/Image/RCTGIFImageDecoder.m +++ b/Libraries/Image/RCTGIFImageDecoder.m @@ -32,8 +32,18 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData { CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL); - NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue]; - + NSUInteger 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) { @@ -84,11 +94,12 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData // Create animation CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; animation.calculationMode = kCAAnimationDiscrete; - animation.repeatCount = loopCount == 0 ? HUGE_VALF : loopCount; + animation.repeatCount = loopCount; animation.keyTimes = keyTimes; animation.values = images; animation.duration = duration; animation.removedOnCompletion = NO; + animation.fillMode = kCAFillModeForwards; image.reactKeyframeAnimation = animation; } else {