美文网首页程序员iOS Developer
酷炫粒子效果合成图片

酷炫粒子效果合成图片

作者: SSBun | 来源:发表于2017-06-01 10:59 被阅读293次

    参考代码 酷炫粒子效果合成图片 code4app

    为了实现项目中的一些特效,最近就扒了一些代码,无意中发现了这个特效,感觉效果很酷,本来感觉实现起来应该是非常有难度的,但是看了源码过后,发现实现的方式十分的简单。主要的步骤就是:先把图片分解为像素点,然后再利用 CADisplayLink 与屏幕同步刷新动画。源代码是OC 的,而我需要使用 Swfit 版的,所以就将整个代码又用 Swift3.0 写了一遍。由于 Swift 的一些原因,中途也遇见到了一些问题,所以就在此再记录一遍,温故而知新吗!

    这是最终的效果图,粒子的发射速度,时间,起点等都是可以控制的。

    问题

    遇到的一个问题就是,需要如何解析 UnsafeMutableRawPointer 的数据,解决的方法如下

    let rawData: UnsafeMutableRawPointer = calloc(imageH*imageW*bytesPerPixel, MemoryLayout.size(ofValue: CChar()))
    .
    .
    .
    let bufferData = UnsafeRawBufferPointer(start: rawData, count: imageH*imageW*bytesPerPixel)
    
    

    通过 UnsafeRawBufferPointer 来将数据解析出来,count 后是数据所占的字节大小

    代码示例

    //
    //  BZEmitterLayer.swift
    //  BZEmitter
    //
    //  Copyright © 2017 SSBun. All rights reserved.
    //
    
    import Foundation
    import QuartzCore
    import UIKit
    
    struct BZParticle {
        var color: UIColor
        var point: CGPoint
        var  customColor: UIColor? {
            set {
                if let value = newValue {
                    color = value
                }
            }
            get {
                return color
            }
        }
        var randomPointRange: CGFloat? {
            set {
                let value = newValue ?? 0
                if value != 0 {
                    point.x = point.x - value + CGFloat(arc4random_uniform(UInt32(value) * 2))
                    point.y = point.y - value + CGFloat(arc4random_uniform(UInt32(value) * 2))
                }
            }
            get {
                return 0
            }
        }
        let delayTime: UInt32     = arc4random_uniform(30)
        let delayDuration: UInt32 = arc4random_uniform(10)
    }
    
    
    protocol BZEmitterLayerDelegate {
        func emitterLayerEndAnimation()
    }
    
    class BZEmitterLayer: CALayer {
        
        public var beginPoint: CGPoint       = .zero// 粒子发射起点
        public var ignoredBlack: Bool        = false// 忽略黑色粒子
        public var ignoredWhite: Bool        = false// 忽略白色粒子
        public var customColor: UIColor?      // 覆盖原粒子颜色,最后会是一个图片的纯色图片
        public var randomPointRange: CGFloat = 0// 不能等于0
        
        public var maxParticleCount: UInt32 = 0 // 每行最大的粒子数量
        public var image: UIImage? {            // 待渲染的图片
            didSet {
                if let image = image {
                    particleArray = self.getRGBAs(from: image)
                }
            }
        }
        public var emitterDelegate: BZEmitterLayerDelegate?
        
        private var animationTime: CGFloat     = 0
        private var animationDuration: CGFloat = 2
        private var displayLink:CADisplayLink?
        private var particleArray:[BZParticle] = []
        
        
        override init() {
            super.init()
            self.masksToBounds = false
            displayLink = CADisplayLink(target: self, selector: #selector(BZEmitterLayer.emitterAnimation))
            displayLink?.add(to: .current, forMode: .commonModes)
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func emitterAnimation() {
            self.setNeedsDisplay()
            animationTime += 0.6
        }
        
        override func draw(in ctx: CGContext) {
            var count = 0
            for particle in particleArray {
                if CGFloat(particle.delayTime) > animationTime {
                    continue
                }
                var curTime = animationTime - CGFloat(particle.delayTime)
                if curTime > animationDuration + CGFloat(particle.delayDuration) {
                    curTime = animationDuration + CGFloat(particle.delayDuration)
                    count += 1
                }
                let curX = self.easeInOutQuad(curTime, beginPoint.x, particle.point.x + self.bounds.size.width/2 - CGFloat(image!.cgImage!.width/2), animationDuration + CGFloat(particle.delayDuration))
                let curY = self.easeInOutQuad(curTime, beginPoint.y, particle.point.y + self.bounds.size.height/2 - CGFloat(image!.cgImage!.height/2), animationDuration + CGFloat(particle.delayDuration))
                
                ctx.addRect(CGRect(x:curX, y:curY, width:1, height:1))
                let components = particle.color.cgColor.components!
                ctx.setFillColor(red: components[0], green: components[1], blue: components[2], alpha: components[3])
                ctx.fillPath()
            }
            if (count == particleArray.count) {
                self.reset()
                self.emitterDelegate?.emitterLayerEndAnimation()
            }
        }
        
        func easeInOutQuad(_ time: CGFloat, _ begin: CGFloat, _ end: CGFloat, _ duration: CGFloat) -> CGFloat {
            let coverDistance = end - begin
            var newTime = time / (duration/2)
            if newTime < 1 {
                return coverDistance/2.0 * pow(newTime, 2) + begin
            }
            newTime -= 1
            return -coverDistance/2.0 * (newTime * (newTime - 2) - 1) + begin
        }
        
        
        func getRGBAs(from image: UIImage) -> [BZParticle] {
            let imageRef = image.cgImage!
            let imageW = imageRef.width
            let imageH = imageRef.height
            let colorSpace = CGColorSpaceCreateDeviceRGB()
            let bytesPerPixel = 4 // 一个像素4个字节
            let bytesPerRow = bytesPerPixel * imageW
            let rawData: UnsafeMutableRawPointer = calloc(imageH*imageW*bytesPerPixel, MemoryLayout.size(ofValue: CChar()))
            let bitsPerComponent = 8
            let context = CGContext(data: rawData, width: imageW, height: imageH, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageByteOrderInfo.order32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
            context?.draw(imageRef, in: CGRect(x: 0, y: 0, width: imageW, height: imageH))
            
            
            let addY = maxParticleCount == 0 ? 1 : imageH / Int(maxParticleCount)
            let addX = maxParticleCount == 0 ? 1 : imageW / Int(maxParticleCount)
            var result = [BZParticle]()
            let bufferData = UnsafeRawBufferPointer(start: rawData, count: imageH*imageW*bytesPerPixel)
            
            for y in stride(from: 0, to: imageH, by: addY) {
                for x in stride(from: 0, to: imageW, by: addX) {
                    let byteIndex = bytesPerRow*y + bytesPerPixel*x
                    let red   = CGFloat(bufferData[byteIndex]) / 255.0
                    let green = CGFloat(bufferData[byteIndex + 1]) / 255.0
                    let blue  = CGFloat(bufferData[byteIndex + 2]) / 255.0
                    let alpha = CGFloat(bufferData[byteIndex + 3]) / 255.0
                    
                    if alpha == 0 || (ignoredWhite && (red+green+blue == 3)) || (ignoredBlack && (red+green+blue == 0)) {
                        continue
                    }
                    let color = UIColor(red: red, green: green, blue: blue, alpha: alpha)
                    let point = CGPoint(x: x, y: y)
                    var particle = BZParticle(color: color, point: point)
                    if let custom = customColor {
                        particle.customColor = custom
                    }
                    if randomPointRange > 0 {
                        particle.randomPointRange = randomPointRange
                    }
                    result.append(particle)
                }
            }
            free(rawData)
            return result
        }
        
        func pause() {
            displayLink?.isPaused = true
        }
        
        func resume() {
            displayLink?.isPaused = false
        }
        
        func reset() {
            displayLink?.invalidate()
            displayLink = nil
            animationTime = 0
        }
        
        func restart() {
            self.reset()
            displayLink = CADisplayLink(target: self, selector: #selector(BZEmitterLayer.emitterAnimation))
            displayLink?.add(to: .current, forMode: .commonModes)
        }
    }
    

    Demo 地址

    相关文章

      网友评论

        本文标题:酷炫粒子效果合成图片

        本文链接:https://www.haomeiwen.com/subject/vhwsfxtx.html