ARKit 如何给SCNNode贴Gif图片

最近在研究如何在SCNNode上加载Gif图片,总结出两种解决方案。
首先介绍下SCNMaterial,它是SCNNode的材质属性,可以通过它给Node添加各种皮肤材质,根据官方文档,SCNMaterial的contents可以用UIColor、UIImage、CALayer、NSURL等等,真是无敌了,虽然UIView没有提及,但是我自己试验之后也是可以的,不过加载Gif会堵塞UI线程。

Specifies the receiver's contents. This can be a color (NSColor, UIColor, CGColorRef), 
an image (NSImage, UIImage, CGImageRef), a layer (CALayer), a path (NSString or NSURL), 
a SpriteKit scene (SKScene), a texture (SKTexture, id<MTLTexture> or GLKTextureInfo), 
or a floating value between 0 and 1 (NSNumber) for metalness and roughness properties.
png
一 通过将Gif图片转成MP4文件

需要先将Gif图片转成MP4文件,存在缓存中,通过AVPlayer即可加载,转mp4还是有些耗时,建议后台进行预处理。

//此处贴出关键代码
let fileData:NSData = model.getFileData()
let tempUrl = URL(fileURLWithPath:NSTemporaryDirectory()).appendingPathComponent("temp.mp4")
GIF2MP4(data: fileData as Data)?.convertAndExport(to: tempUrl, completion: {
    let playerItem = AVPlayerItem.init(url: tempUrl)
    let player = AVPlayer.init(playerItem: playerItem)
    player.play()
              
    material.diffuse.contents = player
    })
}

func convertAndExport(to url :URL , completion: @escaping () -> Void ) {
        outputURL = url
        prepare()
        
        var index = 0
        var delay = 0.0 - gif.frameDurations[0]
        let queue = DispatchQueue(label: "mediaInputQueue")
        videoWriterInput.requestMediaDataWhenReady(on: queue) {
            var isFinished = true
            
            while index < self.gif.frames.count {
                if self.videoWriterInput.isReadyForMoreMediaData == false {
                    isFinished = false
                    break
                }
                
                if let cgImage = self.gif.getFrame(at: index) {
                    let frameDuration = self.gif.frameDurations[index]
                    delay += Double(frameDuration)
                    let presentationTime = CMTime(seconds: delay, preferredTimescale: 600)
                    let result = self.addImage(image: UIImage(cgImage: cgImage), withPresentationTime: presentationTime)
                    if result == false {
                        fatalError("addImage() failed")
                    } else {
                        index += 1
                    }
                }
            }
            
            if isFinished {
                self.videoWriterInput.markAsFinished()
                self.videoWriter.finishWriting() {
                    DispatchQueue.main.async {
                        completion()
                    }
                }
            } else {
                // Fall through. The closure will be called again when the writer is ready.
            }
        }
    }

二 通过关键帧动画来实现
//获取Gif图片数据
let fileData = model.getFileData()
let animation : CAKeyframeAnimation = createGIFAnimationYY(data: fileData)

//通过CALayer给material赋值                
let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: imageW, height: imageH)
layer.add(animation, forKey: "contents")

//直接将CALayer赋值给material,Gif只会显示右下角1/4                
let tempView = UIView.init(frame: CGRect(x: 0, y: 0, width: imageW, height: imageH))
tempView.layer.bounds = CGRect(x: -imageW/2, y: -imageH/2, width: imageW, height: imageH)
tempView.layer.addSublayer(layer)
material.diffuse.contents = tempView.layer

转成关键帧动画,由于有些Gif很大,帧数很多,此处我做了一些内存优化,极端情况内存占用优化至之前的1/10,这种方法相比于上一种加载速度会更快一点,但内存占用稍大。

func createGIFAnimationYY(data:NSData) -> CAKeyframeAnimation {
    
    let image = YYImage.init(data: data as Data)
    let bytes = Int.init(bitPattern: (image?.animatedImageBytesPerFrame())!)
    let nFrame = Int.init(bitPattern: (image?.animatedImageFrameCount())!)
    let bufferSize = Float(bytes*nFrame)
    let total = getDeviceMemoryTotal();
    let free = getDeviceFreeMemory();
    print("start: bufferSize:",bufferSize," totalMem: ",total," free: ",free)
    var maxSize = min(total*0.2, free!*0.6)
    maxSize = max(maxSize, Float(BUFFER_SIZE))
    if maxSize > bufferSize {
        maxSize = bufferSize
    }
    var maxBufferCount = Int(maxSize / Float(bytes))
    if maxBufferCount < 1 {
        maxBufferCount = 1
    } else if maxBufferCount > 256 {
        maxBufferCount = 256
    }
    //实际帧数跟优化帧数比值
    let ratio = Float(nFrame)/Float(maxBufferCount)
    
    // Total loop time
    var time : Float = 0
    
    // Arrays
    var framesArray = [AnyObject]()
    var tempTimesArray = [NSNumber]()
    
    for i in 0..<maxBufferCount {
        let curFrame = image?.animatedImageFrame(at: UInt(Float(i)*ratio))
        framesArray.append(curFrame!.cgImage!)
        var frameDuration = image?.animatedImageDuration(at: UInt(Float(i)*ratio))
        if frameDuration! - 0.011 < 0 {
            frameDuration = 0.100;
        }
        tempTimesArray.append(NSNumber(value: frameDuration!))
        print(frameDuration)
        time = time + Float(frameDuration!)
    }
    print("End: ",time)
    
    var timesArray = [NSNumber]()
    var base : Float = 0
    for duration in tempTimesArray {
        timesArray.append(NSNumber(value: base))
        base = base.advanced(by: duration.floatValue / time)
    }
    
    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.calculationMode = kCAAnimationDiscrete
    
    return animation;
}
效果图
demo.gif

原文链接:https://www.jianshu.com/p/f5d13540c08d

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,551评论 4 58
  • 江湖自在人心,你心里有江湖,那就是江湖;如果没有,可能是天堂、可能是地狱、或是庸庸碌碌、匆匆忙忙、平平淡淡了...
    夜已空阅读 445评论 4 9
  • 过了几天,我们全家要回爸爸的单位了。 那一天是我叔叔要送我们,叔叔赶了一个马车,那是一批枣红色的漂亮的不停的走来走...
    姚君心理咨询师阅读 277评论 0 1
  • 原文:子曰:“禘,自既灌而往者,吾不欲观之矣。” 孔子说:“(他们的)天子祭祀仪式,从祭酒开始后所有的一切礼仪,我...
    哈皮波阅读 1,018评论 0 0