What does an image cost in pixels, what does opaque change in a UIGraphicsImageRenderer, and what do resizing and rendering modes do?
Predict first
How many pixels, what alpha info, and what resizing mode?
import UIKit
let f = UIGraphicsImageRendererFormat(); f.scale = 2; f.opaque = false
let size = CGSize(width: 10, height: 20)
let a = UIGraphicsImageRenderer(size: size, format: f).image { c in UIColor.red.setFill(); c.fill(CGRect(origin: .zero, size: size)) }
print(a.size, a.scale, a.cgImage!.width, a.cgImage!.height, a.cgImage!.alphaInfo.rawValue)
f.opaque = true
let b = UIGraphicsImageRenderer(size: size, format: f).image { c in UIColor.red.setFill(); c.fill(CGRect(origin: .zero, size: size)) }
print(b.cgImage!.alphaInfo.rawValue, b.cgImage!.bitsPerPixel, a.cgImage!.bitsPerPixel)
let r = a.resizableImage(withCapInsets: UIEdgeInsets(top: 2, left: 3, bottom: 4, right: 5))
print(r.resizingMode == .tile, a.capInsets.left, r.capInsets.left)
print(a.withRenderingMode(.alwaysTemplate).renderingMode.rawValue, a.withTintColor(.blue, renderingMode: .alwaysOriginal).renderingMode.rawValue)
- (10.0, 20.0) 2.0 20 40 1, then 5 32 32, then true 0.0 3.0, then 2 1
- (10.0, 20.0) 2.0 10 20 1, then 5 24 32, then false 0.0 3.0, then 2 1
- (20.0, 40.0) 1.0 20 40 5, then 1 32 32, then true 0.0 3.0, then 1 2
Mental model. Points are the size on the page, pixels are the grains of ink; a high-density print has more grains in the same square.
A UIImage has a size in points and a scale; the bitmap behind it has size × scale pixels, 4 bytes each in the usual 32-bit format, and the rows may be padded. A 10×20-point image at scale 2 is 20×40 pixels (the check prints that), which is also the number to use when budgeting memory. UIGraphicsImageRendererFormat controls the scale (default: the screen scale, 3.0 in the simulator), opaque (default false) and the colour range. Setting opaque = true does not change memory here (both formats are 32 bits per pixel) but switches the alpha channel to “none, skip”: the check shows alpha info raw value 1 (premultiplied last) becoming 5 (none skip last). That matters downstream: an opaque image lets Core Animation skip blending when you set the layer opaque too.
Two image-modifier traps. resizableImage(withCapInsets:) returns an image whose resizingMode is .tile by default (raw value 0), not stretch, so a bubble background tiles its middle unless you pass .stretch. And renderingMode controls tinting: .alwaysTemplate uses the image as a mask for the tint colour, .alwaysOriginal keeps the colours, and .automatic lets the container decide (withTintColor(_:) defaults to automatic, with an explicit mode as its second argument). Decode and downsample large images off the main thread to the size you need before display, and cache the result.
The bitmap is size times scale; opaque changes the alpha channel, not the bit depth; resizable images tile by default.
Common traps
- Reading
sizeas the pixel size. Multiply byscale. - Building a stretchable bubble with the default
resizableImage. It tiles; pass.stretch.
They might follow up with
- When is an opaque renderer worthwhile? For images with no transparency, set together with an opaque layer.
- Where should big image decoding happen? Off the main thread, to display size.
Related questions
- 9.5.1 How do you draw with Core Graphics and UIBezierPath, and how do scale and caching affect it?
- 9.9.3 How does a layer place its bitmap: contentsGravity, contentsRect, contentsCenter, and how does…
- 12.7.1 How do you design an image loading pipeline: decoding, downsampling, caching and de-duplication?
← How do exclusion paths, line limits and truncation work in an…