albert musagitov

Drawing and Images composing in CGContext

There was a task to compose and display images in one of projects. Composing process can be realized by overlaying of different objects (curves, symbols, text, images) in exact sequence on basic image. Also we need to compose images without slowing down the main thread. So I learned some information as following: interaction with CGContext, ways of drawing of different image fragments and background work. This is we'll talk about further.
Albert Musagitov
iOS Developer
You can create an image by different ways:

Draw UIKit objects: UIView, UIImageView, etc., or Core Animation objects: CALayer, CATextLayer, etc., and Core Animation and UIKit objects are drawing in CGContext context. Main differences that UIKit can't be drawn in background that required for this task so I chose CALayer objects and its subclasses.
It needs to start with a basic layer for original image. The basic layer size has to be equal to original image size in order not to lose image quality. Then we create for each overlay object its new own layer (CALayer for symbols, CAShapeLayer for curves, CATextLayer for text). Each layer has its configuration (colour, width, curve, transparency, location in basic layer - origin). All these layers have to be inserted in basic layer.
After all the layers' configuration it needs to create context CGContext. We have different approaches:

  1. Write required configuration manually

  2. Use UIGraphicsBeginImageContext(size) or UIGraphicsBeginImageContextWithOptions(size,opaque,scale) methods

  3. Create a UIGraphicsImageRenderer renderer with required context using UIGraphicsImageRenderer(size: rect.size) method

My set looks like this:
let width = layer.frame.size.width.int
let height = layer.frame.size.height.int
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel = 4
let bytesPerRow = bytesPerPixel * width
let rawData = malloc(height * bytesPerRow)
let bitsPerComponent = 8
guard let context = CGContext(data: rawData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGImageByteOrderInfo.order32Big.rawValue) 
Before you render the layer check if the layer turned over.
if layer.contentsAreFlipped() {
            	let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: layer.frame.size.height)
            	context.concatenate(flipVertical)
        	}
Call the layer render in context: layer.render(in: context) and create an image guard let cgImage = context.makeImage()
While working I faced the leak of memory in context working process. Firstly, I tried to free up the memory, that I allocated function for context, by free(rawData). It helped a little bit, despite the lowing of leak it still happens to be. The next step was to wrap all the work with context into autorelease {} block. It seems like the leak has gone but while simultaneous rendering of several images the main thread was blocked in background and leaks appeared again. So I decided each image render wrap into operation and use operationQueue, autorelease block was removed, main thread stopped to be blocked and leaks went away.
Thanks for reading!