最近在研究如何在SCNNode上加載Gif圖片璧帝,總結出兩種解決方案叔锐。
首先介紹下SCNMaterial,它是SCNNode的材質(zhì)屬性紧帕,可以通過它給Node添加各種皮膚材質(zhì)盔然,根據(jù)官方文檔,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.
}
}
}
二 通過關鍵幀動畫來實現(xiàn)
//獲取Gif圖片數(shù)據(jù)
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很大炎码,幀數(shù)很多盟迟,此處我做了一些內(nèi)存優(yōu)化,極端情況內(nèi)存占用優(yōu)化至之前的1/10辅肾,這種方法相比于上一種加載速度會更快一點队萤,但內(nèi)存占用稍大轮锥。
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
}
//實際幀數(shù)跟優(yōu)化幀數(shù)比值
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