Set contents of CALayer to animated GIF?

前端 未结 3 886
我在风中等你
我在风中等你 2021-01-16 10:02

Is it possible to set the contents of a CALayer to an animated GIF and have it display that animation? I know that I can set the contents to an image like so:

相关标签:
3条回答
  • 2021-01-16 10:37
    - (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;
    }
    

    according to mahal its should be done

    0 讨论(0)
  • 2021-01-16 10:48

    Swift 3 version, but changed to receive URL (for my own purpose).

    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;
    }
    

    Important thing is written in the 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.

    So added this line:

     timesArray.append(NSNumber(value: 1.0))
    

    And also checked in exporting as video with AVAssetExportSession.

    Please call like this:

    if let url = Bundle.main.url(forResource: "animation", withExtension: "gif"){
        let animation = createGIFAnimation(url: url)
    }
    
    0 讨论(0)
  • 2021-01-16 10:48

    Well, okay, I guess this isn't that hard to do. Basically I check to see if the image is a GIF:

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

    And as you can see, I'm calling my custom createGIFAnimation method, which looks like this:

    - (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;
    }
    

    Pretty straightforward, although I would love some tips on how to improve it.

    0 讨论(0)
提交回复
热议问题