Home > Software design >  How to create an empty, coloured CGImage canvas in Swift
How to create an empty, coloured CGImage canvas in Swift

Time:10-23

I want to draw some lines over an empty CGImage black canvas. Should be something trivial creating a blank CGImage of an specific size but I'm not able. I've tried to extend CGImage init? calling self.init(width:height:...bitmapInfo:provider:...) with the required information but I don't know enough about CGBitmapInfo and CGDataProvider.

Any help or guidance on how to create this empty canvas? Any other approach not using UIKit?

Thanks!

CodePudding user response:

You don't draw on a CGImage, you draw on a CGContext. What you want to do is create a bitmap context, draw into the bitmap context, then create an image from the context bitmap buffer. Here is a sample that I typed up in a Playground.

import UIKit

// CGContexts like rowbytes that are multiples of 16.
func goodBytesPerRow(_ width: Int) -> Int {
    return (((width * 4)   15) / 16) * 16
}

func drawMyImage() -> CGImage? {
    let bounds = CGRect(x: 0, y:0, width: 200, height: 200)
    let intWidth = Int(ceil(bounds.width))
    let intHeight = Int(ceil(bounds.height))
    let bitmapContext = CGContext(data: nil,
                                  width: intWidth, height: intHeight,
                                  bitsPerComponent: 8,
                                  bytesPerRow: goodBytesPerRow(intWidth),
                                  space: CGColorSpace(name: CGColorSpace.sRGB)!,
                                  bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)

    if let cgContext = bitmapContext {
        cgContext.saveGState()
        cgContext.setFillColor(gray: 0, alpha: 1.0)
        cgContext.fill(bounds)

        /* ... do other drawing here ... */

        cgContext.restoreGState()

        return cgContext.makeImage()
    }

    return nil
}

let image = drawMyImage()

This is drawing using 32 bit ARGB values. Core Graphics likes it best when its row bytes is a mutiple of 16 (or at least it did in 2006 when I wrote a Quartz 2D book). So goodBytesPerRow calculates, for 32-bit ARGB pixels, a rowBytes that is a multiple of 16 for a given width.

The "bitmapInfo" is a combination of constants and determines more specifics of the pixel format CGImageAlphaInfo and CGBitmapInfo. In this case we're saying we want the alpha channel to be first (so ARGB instead of RGBA) and we want to use pixels where the color channels are premultiplied by the alpha values.

Once you have the bitmap context you can draw what you like. You said you wanted to draw some lines on a black background - so this just gives you the black background and leaves the lines as an exercise for the reader.

At the very end you get a CGImage from the context using makeImage. If you wanted a UIImage, you could construct one with UIImage(cgImage:)

P.S. You would use CGImageProvider if you wanted to construct an image from a raw chunk of memory, or by streaming data out of a file, or some other source. It basically tells the system how to get the image data.

In this case, when we create the bitmap context, we pass in "nil" as the data which asks the OS to allocate the frame buffer for the image for us.

  • Related