酷炫粒子效果合成图片

参考代码 酷炫粒子效果合成图片 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 地址

  • BZEmitterLayer 项目地址
  • CADispalyLink的参考博客
  • Swift 中的指针操作

你可能感兴趣的:(酷炫粒子效果合成图片)