Reputation: 495
Body: I've developed a drawing app in Swift that offers various brush types with different textures, implemented using CALayers for brush strokes. However, l've encountered a temperature increase and performance changes when merging these CALayers. Here's what I've tried so far:
Despite these efforts, the issue persists. I'd appreciate any insights, suggestions, or best practices to optimize CALayer merging and improve performance while avoiding temperature increases. Has anyone else faced similar challenges or found effective solutions in a Swift drawing app context?
Code Snippet:
//
// Brush1.swift
// sketch-book
//
// Created by Rezwan on 3/27/24.
//
import UIKit
import AVKit
class Brush1: UIBezierPath, SketchTool {
var path: CGMutablePath
var lineColor: UIColor
var lineAlpha: CGFloat
var drawingPenType: PenType
// new Variavle start
var shape: UIImage!
var shape2: UIImage!
var pts: [CGPoint] = []
var pts2: [CGPoint] = []
var shapeSize: CGSize = CGSize(width: 40, height: 40)
var pixelsPerIteration: CGFloat = 1.0
var loopIndex = 0
var layerarray:[CALayer] = []
var previousPoint: CGPoint!
public var drawLayer = CALayer()
public var drawLayer2 = CALayer()
public var original = CALayer()
// new variable end
var setting:BrushSetting!
override init() {
path = CGMutablePath.init()
lineColor = .black
lineAlpha = 0
drawingPenType = .normal
super.init()
lineCapStyle = CGLineCap.round
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setInitialPoint(_ firstPoint: CGPoint) {
print("------Brush1")
drawLayer2.frame = drawLayer.bounds
original.frame = drawLayer.bounds
drawLayer.addSublayer(drawLayer2)
drawLayer.addSublayer(original)
// reset points array to only the starting point
//print("Start:", startPoint)
loopIndex = 0
pts = [firstPoint]
pts2 = [firstPoint]
layerarray = []
previousPoint = nil
draw()
}
func finishDrawing(){
let startDate = Date()
print("----finishDrawing")
self.screenshotOfLayer(layer: self.drawLayer2) { image in
let endDate = Calendar.current.date(byAdding: .hour, value: 2, to: startDate)!
let secondsDifference = endDate.timeIntervalSince(startDate)
print("Difference in seconds: \(secondsDifference)")
guard let image = image else{return}
let imageLayer = self.createLayerFromImage(image: image)
imageLayer.name = "Brush"
self.layerarray = [ imageLayer]
// imageLayer.backgroundColor = UIColor.red.cgColor
// imageLayer.bounds = drawLayer.bounds
// imageLayer.contents = image.cgImage
// imageLayer.compositingFilter = setting.filter
// imageLayer.minificationFilterBias = 10
self.drawLayer2.isHidden = true
// self.drawLayer.addSublayer(imageLayer)
self.drawLayer2.removeFromSuperlayer()
self.drawLayer2 = CALayer()
}
}
func moveFromPoint(_ startPoint: CGPoint, toPoint endPoint: CGPoint) {
let x = Int(endPoint.x)
let y = Int(endPoint.y)
pts.append(CGPoint(x: x, y: y))
pts2 = interpolatePoints(from: pts)
// setNeedsDisplay()
}
func createBezierRenderingBox(_ previousPoint2: CGPoint, widhPreviousPoint previousPoint1: CGPoint, withCurrentPoint cpoint: CGPoint) -> CGRect {
let mid1 = middlePoint(previousPoint1, previousPoint2: previousPoint2)
let mid2 = middlePoint(cpoint, previousPoint2: previousPoint1)
let subpath = CGMutablePath.init()
subpath.move(to: CGPoint(x: mid1.x, y: mid1.y))
subpath.addQuadCurve(to: CGPoint(x: mid2.x, y: mid2.y), control: CGPoint(x: previousPoint1.x, y: previousPoint1.y))
path.addPath(subpath)
var boundingBox: CGRect = subpath.boundingBox
boundingBox.origin.x -= lineWidth * 2.0
boundingBox.origin.y -= lineWidth * 2.0
boundingBox.size.width += lineWidth * 4.0
boundingBox.size.height += lineWidth * 4.0
return boundingBox
}
private func middlePoint(_ previousPoint1: CGPoint, previousPoint2: CGPoint) -> CGPoint {
return CGPoint(x: (previousPoint1.x + previousPoint2.x) * 0.5, y: (previousPoint1.y + previousPoint2.y) * 0.5)
}
}
extension Brush1{
func interpolatePoints(from array: [CGPoint]) -> [CGPoint] {
var result: [CGPoint] = []
var prevPoint: CGPoint?
for point in array {
if let prev = prevPoint {
let diffX = point.x - prev.x
let diffY = point.y - prev.y
if abs(diffX) != 0 || abs(diffY) != 0 {
let steps = max(abs(diffX), abs(diffY))
for i in 0..<Int(steps ){
let newX = prev.x + (diffX * CGFloat(i) / CGFloat(steps))
let newY = prev.y + (diffY * CGFloat(i) / CGFloat(steps))
if (Int(prev.x) == Int(newX) && Int(prev.y) == Int(newY)){
}else{
let p = CGPoint(x: Double(Int(newX)), y: Double(Int(newY)))
result.append(p)
}
}
}
}
if let p = prevPoint{
if (Int(p.x) == Int(point.x) && Int(p.y) == Int(point.y)){
}else{
result.append(CGPoint(x: Int(point.x), y: Int(point.y)))
}
}
prevPoint = point
}
if (result.count > 0){
result.remove(at: 0)
}
return result
}
func draw2() {
for i in layerarray{
self.drawLayer.addSublayer(i)
}
}
func draw() {
guard pts2.count > 0 else { return }
for idx in loopIndex..<(pts2.count) {
let startx: CGFloat = pts2[idx].x
let starty: CGFloat = pts2[idx].y
let dstRect: CGRect = .init(x: 0.0, y: 0.0, width: shapeSize.width, height: shapeSize.height)
let x: CGFloat = startx
let y: CGFloat = starty
if isAdd(p: pts2[idx], space: getSpace()){
previousPoint = pts2[idx]
let imageLayer = CAReplicatorLayer()
// imageLayer.backgroundColor = UIColor.red.cgColor
if setting.angle{
imageLayer.rotate(angle: self.setting.angles.randomElement()!)
}else{
imageLayer.rotate(angle: CGFloat(self.setting.degree))
}
let maxScale:CGFloat = 1.0
let s = 1.0 - setting.dynamicScale
let scale:CGFloat = CGFloat(s)+(CGFloat(idx)/400.0) <= maxScale ? CGFloat(s)+(CGFloat(idx)/400.0) : maxScale
if setting.isAnimation{
// DispatchQueue.main.asyncAfter(deadline: .now()+0.02) {
imageLayer.transform = CATransform3DScale(imageLayer.transform, scale, scale, 1)
// }
}else{
imageLayer.transform = CATransform3DScale(imageLayer.transform, scale, scale, 1)
}
let minopacity:CGFloat = CGFloat(1.0-setting.dynamicOpacity)
let dunamicOpacity:CGFloat = minopacity+(CGFloat(idx)/300.0) <= CGFloat(setting.opacity) ? minopacity+(CGFloat(idx)/300.0) : CGFloat(setting.opacity)
imageLayer.opacity = Float(dunamicOpacity)
imageLayer.bounds = dstRect
imageLayer.position = CGPoint(x:x ,y:y)
imageLayer.contents = shape?.cgImage
imageLayer.name = "Brush"
imageLayer.compositingFilter = setting.filter
imageLayer.minificationFilterBias = 10
// layerarray.append(imageLayer)
// self.draw.addSublayer(imageLayer)
// self.drawLayer.addSublayer(imageLayer)
self.drawLayer2.addSublayer(imageLayer)
}
loopIndex = idx
}
}
func isAdd(p:CGPoint, space:Int)->Bool{
if let previousPoint = previousPoint{
let a = max((abs(previousPoint.x - p.x)),(abs(previousPoint.y - p.y)))
if a >= (CGFloat(space)){
return true
}else{
//print(a)
return false
}
}else{
return true
}
}
func getSpace()->Int{
print(shapeSize)
return Int(setting.distance)
}
func setShape(brush:BrushShape){
shape = brush.image
updateSetting(setting:brush.setting)
}
func updateSetting(setting:BrushSetting){
let newSize = AVMakeRect(
aspectRatio: shape.size,
insideRect: CGRect(
origin: .zero,
size: CGSize(
width: Int(setting.size),
height: Int(setting.size)))).size
self.shapeSize = newSize
self.setting = setting
}
}
extension Brush1{
func createLayerFromImage(image: UIImage) -> CALayer {
original.contents = image.cgImage
original.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
return original
}
func screenshotOfLayer(layer: CALayer, completion: @escaping (UIImage?) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
// Create a bitmap context with the size of the layer
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, false, UIScreen.main.scale)
// Get the current context
guard let context = UIGraphicsGetCurrentContext() else {
UIGraphicsEndImageContext()
DispatchQueue.main.async {
completion(nil)
}
return
}
// Render the layer into the bitmap context
layer.render(in: context)
// Get the image from the context
guard let screenshotImage = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
DispatchQueue.main.async {
completion(nil)
}
return
}
// End the context
UIGraphicsEndImageContext()
// Call the completion handler with the screenshot image
DispatchQueue.main.async {
completion(screenshotImage)
}
}
}
}
typealias Dispatch = DispatchQueue
extension Dispatch {
static func background(_ task: @escaping () -> ()) {
Dispatch.global(qos: .userInteractive).async {
task()
}
}
static func main(_ task: @escaping () -> ()) {
Dispatch.main.async {
task()
}
}
}
Additional Information:
Seeking optimizations or alternative approaches to merging CALayers efficiently without impacting performance. Request for Assistance:
I'm seeking guidance on mitigating the temperature increase and performance change issue on iPhone when merging CALayers in my Swift drawing app. Any insights or suggestions would be greatly appreciated.
Thank you for your help!
Upvotes: 0
Views: 45