Search code examples
iosswiftcore-graphicscgcontext

Save image from CGContext on background


Context

The user is sketching with straight lines on a canvas in the current CGContext. The user can draw straight lines, scale(pinch), and translate(pan) the whole drawing. Once the user leaves the drawing scene, the app saves into a data structure all the drawn lines.

Problem

I want to save the drawing into an image (png, jpeg doesn't matter) when the user is on a completely different scene, hitting upload to send the image of the drawing to the server.

So far, I've found a way to export to an image only the current core graphics context. But I can't figure out how to create a context on the background, replicate the drawing from the stored data and then export it to an image, without rendering it on the screen.

I don't want to save the image while the user is on the canvas scene because I need to save the image without any transformations applied (i.e., scale, translate).


Solution

  • You could save it (to docs or temp directory) when the user finishes drawing, then load it to upload / display, etc.

    Or, use UIGraphicsImageRenderer to generate a UIImage using your saved data structure.

    Here's a quick, very simple example.

    We create an array of points, then generate an image looping through those points with .addLine to a bezier path:

    class RenderDrawingVC: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let points: [CGPoint] = [
                CGPoint(x: 60, y: 40),
                CGPoint(x: 140, y: 40),
                CGPoint(x: 140, y: 80),
                CGPoint(x: 160, y: 120),
                CGPoint(x: 100, y: 160),
                CGPoint(x: 80, y: 120),
                CGPoint(x: 20, y: 20),
            ]
        
            let img = renderImage(bkgColor: .systemYellow, lineColor: .systemRed, size: CGSize(width: 200, height: 200), points: points)
            
            // img is now a 200x200 UIImage
            // save it, upload it, display it, whatever
    
        }
    
        func renderImage(bkgColor: UIColor, lineColor: UIColor, size: CGSize, points: [CGPoint]) -> UIImage {
            let fmt = UIGraphicsImageRendererFormat()
            fmt.scale = 1
            fmt.opaque = true
            let rndr = UIGraphicsImageRenderer(size: size, format: fmt)
            let newImg = rndr.image { ctx in
                ctx.cgContext.setFillColor(bkgColor.cgColor)
                ctx.cgContext.addRect(CGRect(origin: .zero, size: size))
                ctx.cgContext.drawPath(using: .fill)
                let bez = UIBezierPath()
                bez.move(to: points[0])
                for i in 1..<points.count {
                    bez.addLine(to: points[i])
                }
                ctx.cgContext.setFillColor(UIColor.clear.cgColor)
                ctx.cgContext.setStrokeColor(lineColor.cgColor)
                ctx.cgContext.setLineWidth(4)
                ctx.cgContext.setLineJoin(.round)
                ctx.cgContext.setLineCap(.round)
                ctx.cgContext.addPath(bez.cgPath)
                ctx.cgContext.drawPath(using: .stroke)
            }
            return newImg
        }
    
    }
    

    The resulting image:

    enter image description here