Rezwan Khan chowdhury
Rezwan Khan chowdhury

Reputation: 495

Seeking Solutions for Temperature Rise and Performance Impact When Merging CALayers in Swift Drawing App

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:

  1. Profiling the app with Instruments to detect any memory or performance issues.
  2. Experimenting with different merging techniques and optimizations.

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

Answers (0)

Related Questions