멋진 입자 효과 합성 사진

7887 단어
참고 코드 쿨 입자 효과 합성 이미지 코드 4 app
프로젝트 중의 일부 특수효과를 실현하기 위해 최근에 코드를 벗겼는데 무심결에 이 특수효과를 발견했는데 효과가 매우 멋있다고 느꼈다. 원래 실현하기가 매우 어려울 것 같았지만 원본 코드를 본 후에 실현하는 방식이 매우 간단하다는 것을 발견했다.주요 절차는 먼저 그림을 픽셀로 분해한 다음에 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)
    }
}

데모 주소

  • BZEmitterLayer 프로젝트 주소
  • CADispalyLink의 참조 블로그
  • Swift의 포인터 조작
  • 좋은 웹페이지 즐겨찾기