Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use a lot of memory when drawing image with UIGraphicsImageRenderer?

I want to add a white border to the photo, while preserving the quality of the photo, so I use UIGraphicsImageRenderer to draw a white background, and then draw the photo up, the result is a dramatic increase in memory usage, is there any better way?

The resolution of the original picture is 4032 * 3024.

let renderer = UIGraphicsImageRenderer(size: CGSize(width: canvasSideLength, height: canvasSideLength))
let newImage = renderer.image { context in
    UIColor.white.setFill()
    context.fill(CGRect(x: 0, y: 0, width: canvasSideLength, height: canvasSideLength))
    image.draw(in: CGRect(x: photoCanvasX, y: photoCanvasY, width: photoCanvasWidth, height: photoCanvasHeight))
}

enter image description here enter image description here enter image description here

like image 360
Kinnouka Bokudo Avatar asked Apr 17 '20 02:04

Kinnouka Bokudo


1 Answers

When considering the memory used, don’t be misled by the size of the JPG or PNG file, because that is generally compressed. You will require four bytes per pixel when performing image operations in memory (e.g. width × height × 4, in pixels).

Worse, by default, UIGraphicsImageRenderer will generate images at screen resolution (e.g. potentially 2× or 3× depending upon your device). E.g. on a 3× device, consider:

let rect = CGRect(origin: .zero, size: CGSize(width: 8_519, height: 8_519))
let image = UIGraphicsImageRenderer(bounds: rect).image { _ in
    UIColor.white.setFill()
    UIBezierPath(rect: rect).fill()
}
print(image.cgImage!.width, "×", image.cgImage!.height)

That will print:

25557 × 25557

When you consider that it then takes 4 bytes per pixel, that adds up to 2.6gb. Even if the image is only 4,032 × 3,024, as suggested by your revised question, that’s still 439mb per image.

You may want to make sure to specify an explicit scale of 1:

let rect = CGRect(origin: .zero, size: CGSize(width: 8_519, height: 8_519))
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let image = UIGraphicsImageRenderer(bounds: rect, format: format).image { _ in
    UIColor.white.setFill()
    UIBezierPath(rect: rect).fill()
}
print(image.cgImage!.width, "×", image.cgImage!.height)

That will print, as you expected:

8519 × 8519

Then you are only going to require 290mb for the image. That’s still a lot of memory, but a lot less than if you use the default scale (on retina devices). Or, considering your revised 4,032 × 3,024 image, this 1× image could take only 49mb, 1/9th the size of the corresponding 3× image where you didn’t set the scale.

like image 119
Rob Avatar answered Nov 02 '22 10:11

Rob