【问题标题】:Set contents of CALayer to animated GIF?将 CALayer 的内容设置为动画 GIF?
【发布时间】:2014-06-09 17:38:59
【问题描述】:

是否可以将CALayer 的内容设置为动画 GIF 并让它显示该动画?我知道我可以将内容设置为这样的图像:

CALayer* subLayer = [CALayer layer];
NSImage *image = [[NSImage alloc] initWithData:data];
subLayer.contents = image;

并且图像会显示,但如果是动画,则不会显示动画。唯一的解决方案是获取GIF的单个帧,获取帧率,然后根据帧率更改子层的content?还是我忽略了一个更简单的方法?

【问题讨论】:

  • 您可以为图层的contents 设置动画,但您仍然需要获取所有单独的帧。 (通过“你可以动画”我的意思是:“你可以使用 CAKeyframeAnimation”)
  • 不清楚您为什么要尝试使用 CALayer 执行此操作。如果您的唯一目标是显示动画 GIF,还有更好的方法。
  • @BradAllred 我正在将大量图像加载到CALayers 中,然后一次单独显示一个,但有时图像是 GIF。我通常只会使用NSImageView,但与 Core Animation 相比,它的性能很糟糕。我想如果加载的图像是 GIF,我可以在 GIF 上使用 NSImageView 覆盖?
  • @DavidRönnqvist 这看起来是不是有点矫枉过正?

标签: objective-c cocoa core-animation


【解决方案1】:

Swift 3 版本,但改为接收 URL(出于我自己的目的)。

func createGIFAnimation(url:URL) -> CAKeyframeAnimation?{

    guard let src = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
    let frameCount = CGImageSourceGetCount(src)

    // Total loop time
    var time : Float = 0

    // Arrays
    var framesArray = [AnyObject]()
    var tempTimesArray = [NSNumber]()

    // Loop
    for i in 0..<frameCount {

        // Frame default duration
        var frameDuration : Float = 0.1;

        let cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(src, i, nil)
        guard let framePrpoerties = cfFrameProperties as? [String:AnyObject] else {return nil}
        guard let gifProperties = framePrpoerties[kCGImagePropertyGIFDictionary as String] as? [String:AnyObject]
            else { return nil }

        // Use kCGImagePropertyGIFUnclampedDelayTime or kCGImagePropertyGIFDelayTime
        if let delayTimeUnclampedProp = gifProperties[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber {
            frameDuration = delayTimeUnclampedProp.floatValue
        }
        else{
            if let delayTimeProp = gifProperties[kCGImagePropertyGIFDelayTime as String] as? NSNumber {
                frameDuration = delayTimeProp.floatValue
            }
        }

        // Make sure its not too small
        if frameDuration < 0.011 {
            frameDuration = 0.100;
        }

        // Add frame to array of frames
        if let frame = CGImageSourceCreateImageAtIndex(src, i, nil) {
            tempTimesArray.append(NSNumber(value: frameDuration))
            framesArray.append(frame)
        }

        // Compile total loop time
        time = time + frameDuration
    }

    var timesArray = [NSNumber]()
    var base : Float = 0
    for duration in tempTimesArray {
        timesArray.append(NSNumber(value: base))
        base.add( duration.floatValue / time )
    }

    // From documentation of 'CAKeyframeAnimation':
    // the first value in the array must be 0.0 and the last value must be 1.0.
    // The array should have one more entry than appears in the values array.
    // For example, if there are two values, there should be three key times.
    timesArray.append(NSNumber(value: 1.0))

    // Create animation
    let animation = CAKeyframeAnimation(keyPath: "contents")

    animation.beginTime = AVCoreAnimationBeginTimeAtZero
    animation.duration = CFTimeInterval(time)
    animation.repeatCount = Float.greatestFiniteMagnitude;
    animation.isRemovedOnCompletion = false
    animation.fillMode = kCAFillModeForwards
    animation.values = framesArray
    animation.keyTimes = timesArray
    //animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    animation.calculationMode = kCAAnimationDiscrete

    return animation;
}

重要的事情写在'CAKeyframeAnimation'的文档中:

数组中的第一个值必须是 0.0,最后一个值必须是 1.0。 该数组应该比 values 数组中出现的条目多一个。 例如,如果有两个值,则应该有三个关键时间。

所以添加了这一行:

 timesArray.append(NSNumber(value: 1.0))

并且还使用 AVAssetExportSession 签入导出为视频。

请这样调用:

if let url = Bundle.main.url(forResource: "animation", withExtension: "gif"){
    let animation = createGIFAnimation(url: url)
}

【讨论】:

  • 我的资源文件夹中有一个 gif 图像。如何使用上面的代码加载它?我需要将 gif 图像的 bundle url 发送到此方法吗?
  • 我知道这是旧的但是如果你想要一个自定义的重复计数,比如重复 gif 2 或 3 次怎么办?除无限以外的任何值都会导致 gif 不播放。
【解决方案2】:

好吧,我想这并不难做到。基本上我会检查图像是否为 GIF:

if ([format isEqualToString:@"gif"]){
    CAKeyframeAnimation *animation = [self createGIFAnimation:data];
    [subLayer addAnimation:animation forKey:@"contents"];
}

如您所见,我正在调用我的自定义 createGIFAnimation 方法,如下所示:

- (CAKeyframeAnimation *)createGIFAnimation:(NSData *)data{
    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithData:data];
    CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)(data), nil);
    NSNumber *frameCount = [rep valueForProperty:@"NSImageFrameCount"];

    // Total loop time
    float time = 0;

    // Arrays
    NSMutableArray *framesArray = [NSMutableArray array];
    NSMutableArray *tempTimesArray = [NSMutableArray array];

    // Loop
    for (int i = 0; i < frameCount.intValue; i++){

        // Frame default duration
        float frameDuration = 0.1f;

        // Frame duration
        CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(src,i,nil);
        NSDictionary *frameProperties = (__bridge NSDictionary*)cfFrameProperties;
        NSDictionary *gifProperties = frameProperties[(NSString*)kCGImagePropertyGIFDictionary];

        // Use kCGImagePropertyGIFUnclampedDelayTime or kCGImagePropertyGIFDelayTime
        NSNumber *delayTimeUnclampedProp = gifProperties[(NSString*)kCGImagePropertyGIFUnclampedDelayTime];
        if(delayTimeUnclampedProp) {
            frameDuration = [delayTimeUnclampedProp floatValue];
        } else {
            NSNumber *delayTimeProp = gifProperties[(NSString*)kCGImagePropertyGIFDelayTime];
            if(delayTimeProp) {
                frameDuration = [delayTimeProp floatValue];
            }
        }

        // Make sure its not too small
        if (frameDuration < 0.011f){
            frameDuration = 0.100f;
        }

        [tempTimesArray addObject:[NSNumber numberWithFloat:frameDuration]];

        // Release
        CFRelease(cfFrameProperties);

        // Add frame to array of frames
        CGImageRef frame = CGImageSourceCreateImageAtIndex(src, i, nil);
        [framesArray addObject:(__bridge id)(frame)];

        // Compile total loop time
        time = time + frameDuration;
    }

    NSMutableArray *timesArray = [NSMutableArray array];
    float base = 0;
    for (NSNumber* duration in tempTimesArray){
        //duration = [NSNumber numberWithFloat:(duration.floatValue/time) + base];
        base = base + (duration.floatValue/time);
        [timesArray addObject:[NSNumber numberWithFloat:base]];
    }

    // Create animation
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];

    animation.duration = time;
    animation.repeatCount = HUGE_VALF;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.values = framesArray;
    animation.keyTimes = timesArray;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.calculationMode = kCAAnimationDiscrete;

    return animation;
}

非常简单,虽然我很喜欢一些关于如何改进它的提示。

【讨论】:

  • 刚刚添加了一个小的改进,将 NSBitmapImageRep 替换为 CGImageSourceGetCount 并添加了 arrayWithCapacity 提示。
  • @mahaltertin 感谢您的建议。与其更新帖子,不如发布带有改进代码的新答案,或者发表评论概述您的改进。
  • size_t frameCount = CGImageSourceGetCount(src); // 而不是第 2 行和第 4 行
  • [NSMutableArray arrayWithCapacity:frameCount] // 创建可变数组时的提示
  • 以上要求的改进。如果您愿意,可以将它们添加到您的帖子中。
【解决方案3】:
- (CAKeyframeAnimation *)createGIFAnimation:(NSData *)data{

    CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)(data), nil);
    int frameCount =(int) CGImageSourceGetCount(src);

    // Total loop time
    float time = 0;

    // Arrays
    NSMutableArray *framesArray = [NSMutableArray array];
    NSMutableArray *tempTimesArray = [NSMutableArray array];

    // Loop
    for (int i = 0; i < frameCount; i++){

        // Frame default duration
        float frameDuration = 0.1f;

        // Frame duration
        CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(src,i,nil);
        NSDictionary *frameProperties = (__bridge NSDictionary*)cfFrameProperties;
        NSDictionary *gifProperties = frameProperties[(NSString*)kCGImagePropertyGIFDictionary];

        // Use kCGImagePropertyGIFUnclampedDelayTime or kCGImagePropertyGIFDelayTime
        NSNumber *delayTimeUnclampedProp = gifProperties[(NSString*)kCGImagePropertyGIFUnclampedDelayTime];
        if(delayTimeUnclampedProp) {
            frameDuration = [delayTimeUnclampedProp floatValue];
        } else {
            NSNumber *delayTimeProp = gifProperties[(NSString*)kCGImagePropertyGIFDelayTime];
            if(delayTimeProp) {
                frameDuration = [delayTimeProp floatValue];
            }
        }

        // Make sure its not too small
        if (frameDuration < 0.011f){
            frameDuration = 0.100f;
        }

        [tempTimesArray addObject:[NSNumber numberWithFloat:frameDuration]];

        // Release
        CFRelease(cfFrameProperties);

        // Add frame to array of frames
        CGImageRef frame = CGImageSourceCreateImageAtIndex(src, i, nil);
        [framesArray addObject:(__bridge id)(frame)];

        // Compile total loop time
        time = time + frameDuration;
    }

    NSMutableArray *timesArray = [NSMutableArray array];
    float base = 0;
    for (NSNumber* duration in tempTimesArray){
        //duration = [NSNumber numberWithFloat:(duration.floatValue/time) + base];
        base = base + (duration.floatValue/time);
        [timesArray addObject:[NSNumber numberWithFloat:base]];
    }

    // Create animation
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];

    animation.duration = time;
    animation.repeatCount = HUGE_VALF;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.values = framesArray;
    animation.keyTimes = timesArray;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.calculationMode = kCAAnimationDiscrete;

    return animation;
}

按照 mahal 应该这样做

【讨论】:

  • 感谢您的示例代码。我在里面发现了两个漏洞。 srcframe 应该是 CFRelease()
猜你喜欢
  • 2013-10-06
  • 1970-01-01
  • 1970-01-01
  • 2013-03-28
  • 2016-07-11
  • 2019-04-18
  • 2011-04-25
  • 1970-01-01
  • 2020-12-13
相关资源
最近更新 更多