mirror of
https://github.com/status-im/react-native.git
synced 2025-01-09 17:15:54 +00:00
e4110456ab
Summary: GIF images are currently loaded as a CAKeyframeAnimation, however returning this animation directly from RCTImageLoader was dangerous, as any code that expected a UIImage would crash. This diff changes RCTGIFImageLoader to return a UIImage of the first frame, with the keyframe animation attached as an associated object. This way, code that is not expecting an animation will still work correctly.
112 lines
3.8 KiB
Objective-C
112 lines
3.8 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTGIFImageDecoder.h"
|
|
|
|
#import <ImageIO/ImageIO.h>
|
|
#import <MobileCoreServices/MobileCoreServices.h>
|
|
#import <QuartzCore/QuartzCore.h>
|
|
|
|
#import "RCTUtils.h"
|
|
|
|
@implementation RCTGIFImageDecoder
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (BOOL)canDecodeImageData:(NSData *)imageData
|
|
{
|
|
char header[7] = {};
|
|
[imageData getBytes:header length:6];
|
|
|
|
return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a");
|
|
}
|
|
|
|
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
|
|
size:(CGSize)size
|
|
scale:(CGFloat)scale
|
|
resizeMode:(UIViewContentMode)resizeMode
|
|
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
|
{
|
|
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
|
|
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
|
|
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
|
|
|
|
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 (!image) {
|
|
image = [UIImage imageWithCGImage:imageRef];
|
|
}
|
|
|
|
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 (i == 0) {
|
|
delayTime = @(kDelayTimeIntervalDefault);
|
|
} else {
|
|
delayTime = delays[i - 1];
|
|
}
|
|
}
|
|
|
|
const NSTimeInterval kDelayTimeIntervalMinimum = 0.02;
|
|
if (delayTime.floatValue < (float)kDelayTimeIntervalMinimum - FLT_EPSILON) {
|
|
delayTime = @(kDelayTimeIntervalDefault);
|
|
}
|
|
|
|
duration += delayTime.doubleValue;
|
|
delays[i] = delayTime;
|
|
images[i] = (__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 == 0 ? HUGE_VALF : loopCount;
|
|
animation.keyTimes = keyTimes;
|
|
animation.values = images;
|
|
animation.duration = duration;
|
|
image.reactKeyframeAnimation = animation;
|
|
|
|
} else {
|
|
|
|
// Don't bother creating an animation
|
|
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
|
|
if (imageRef) {
|
|
image = [UIImage imageWithCGImage:imageRef];
|
|
CFRelease(imageRef);
|
|
}
|
|
CFRelease(imageSource);
|
|
}
|
|
|
|
completionHandler(nil, image);
|
|
return ^{};
|
|
}
|
|
|
|
@end
|