ARKit 如何給SCNNode貼Gif圖片

miliPolo發表於2018-04-19

最近在研究如何在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

相關文章