diff --git a/Libraries/Image/RCTGIFImageDecoder.m b/Libraries/Image/RCTGIFImageDecoder.m index a40b42529d1619..e7270978d759d3 100644 --- a/Libraries/Image/RCTGIFImageDecoder.m +++ b/Libraries/Image/RCTGIFImageDecoder.m @@ -31,13 +31,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 *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 *delays = [NSMutableArray arrayWithCapacity:imageCount]; + NSMutableArray *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 *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL); + NSDictionary *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 *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 ^{}; } diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index f52f66a1acde71..756aed5eab81e1 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -82,7 +82,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 @@ -98,7 +98,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]; }