-1

I have the following functions.

extension UIImage
{
    var width: CGFloat
    {
        return size.width
    }
    
    var height: CGFloat
    {
        return size.height
    }
    
    private static func circularImage(diameter: CGFloat, color: UIColor) -> UIImage
    {
        UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0)
        let context = UIGraphicsGetCurrentContext()!
        context.saveGState()

        let rect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
        context.setFillColor(color.cgColor)
        context.fillEllipse(in: rect)

        context.restoreGState()
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return image
    }
    
    private func addCentered(image: UIImage, tintColor: UIColor) -> UIImage
    {            
        let topImage = image.withTintColor(tintColor, renderingMode: .alwaysTemplate)
        let bottomImage = self
        
        UIGraphicsBeginImageContext(size)
        
        let bottomRect = CGRect(x: 0, y: 0, width: bottomImage.width, height: bottomImage.height)
        bottomImage.draw(in: bottomRect)
        
        let topRect = CGRect(x: (bottomImage.width - topImage.width) / 2.0,
                             y: (bottomImage.height - topImage.height) / 2.0,
                             width: topImage.width,
                             height: topImage.height)
        topImage.draw(in: topRect, blendMode: .normal, alpha: 1.0)
        
        let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
        
        UIGraphicsEndImageContext()
        
        return mergedImage
    }
}

They work fine, but how do I properly apply UIScreen.main.scale to support retina screens?

I've looked at what's been done here but can't figure it out yet.

Any ideas?

meaning-matters
  • 21,929
  • 10
  • 82
  • 142

1 Answers1

1

Accessing UIScreen.main.scale itself is a bit problematic, as you have to access it only from main thread (while you usually want to put a heavier image processing on a background thread). So I suggest one of these ways instead.

First of all, you can replace UIGraphicsBeginImageContext(size) with

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)

The last argument (0.0) is a scale, and based on docs "if you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen."

If instead you want to retain original image's scale on resulting UIImage, you can do this: after topImage.draw, instead of getting the UIImage with UIGraphicsGetImageFromCurrentImageContext, get CGImage with

let cgImage = context.makeImage()

and then construct UIImage with the scale and orientation of the original image (as opposed to defaults)

let mergedImage = UIImage(
    cgImage: cgImage, 
    scale: image.scale, 
    orientation: image.opientation)
timbre timbre
  • 12,648
  • 10
  • 46
  • 77
  • Thanks. Few issues: `UIImage` doesn't have `makeImage()`. What you suggest doesn't return the `topImage` drawn on top of the `bottomImage`; because you skip `UIGraphicsGetImageFromCurrentImageContext`, only the `topImage` is now returned. Replacing `UIGraphicsBeginImageContext(size)` was sufficient. I'd be happy to hear why. – meaning-matters Nov 09 '21 at 05:22
  • 1
    OK I missed a couple of things in second option: `makeImage` is not a function of UIImage, it's a function of `context`. Typically you are drawing in a context (which comes from UIGraphicsBeginImageContext), not in the UIImage, like you do in the `circularImage` function (i.e. usually it's `context.draw` in both places, not `topImage.draw`), and then you build the UIImage from it as the last step. And `makeImage` is the function of context, not the function of UIImage - that was my mistake, somehow thought you called your context `topImage` – timbre timbre Nov 09 '21 at 13:41
  • 1
    Why does it work: well, first of all you are skipping `UIGraphicsBeginImageContext` return value, which will be context (see your other function). And like I said typically you get context and draw inside it. However even though you are skipping it, the context is still created. When you call `UIImage.draw` it will draw in "current graphics context", which happens to be the context you just created. So manipulating the context you are creating affects the result, even when directly drawing inside the image. – timbre timbre Nov 09 '21 at 13:45
  • Thanks a lot Kiril for the very extensive answer and comments! – meaning-matters Nov 09 '21 at 15:34