Search code examples
swiftmacosnsimage

Resizing NSImage keeping aspect ratio reducing the image size while trying to scale up


I'm using the following code to resize an NSImage by preserving the aspect ratio. But this extension method keeps reducing the image size when I try to increment the same

func resizeMaintainingAspectRatio(width: CGFloat, height: CGFloat) -> NSImage? {
        let ratioX = (width/(NSScreen.main?.backingScaleFactor)!) / size.width
        let ratioY = (height/(NSScreen.main?.backingScaleFactor)!) / size.height
        var ratio = ratioX < ratioY ? ratioX : ratioY
        let newHeight = size.height * ratio
        let newWidth = size.width * ratio
        let canvasSize = CGSize(width: newWidth, height: newHeight)
        let img = NSImage(size: canvasSize)
        img.lockFocus()
        let context = NSGraphicsContext.current
        context?.imageInterpolation = .high
        draw(in: NSRect(origin: .zero, size: NSSize(width: newWidth,height: newHeight)), from: NSRect(origin: .zero, size: size) , operation: .copy, fraction: 1)
        img.unlockFocus()
        return img
    }

Usage:

    let w = CGFloat(logoimage!.size.width) + 10
    let h=CGFloat(logoimage!.size.height) + 10
    logoimage=originallogoimage?.resizeMaintainingAspectRatio(width: w, height: h)

Solution

  • This is because NSImage's member size does not give you the actual physical dimension of the image, but rather its backing store resolution on the screen which is screen resolution dependent (downscaled by the -backingScaleFactor).

    In your code, while your logoimage's underlying image representation still holds the actual physical dimension, you downscaled its size by the -backingScaleFactor (which scales down the actual dimension of the image) to get the ratio, causing the resultant image being even smaller as the new image uses a downscaled size derived by an already-downscaled size. This is redundant, for the conversion between the image's physical pixel resolution and the backing store resolution happens automatically.

    By simply removing backingScaleFactor from the code, here's the code for resizedMaintainingAspectRatio(width:height:):

    extension NSImage {
        func resizedMaintainingAspectRatio(width: CGFloat, height: CGFloat) -> NSImage {
            let ratioX = width / size.width
            let ratioY = height / size.height
            let ratio = ratioX < ratioY ? ratioX : ratioY
            let newHeight = size.height * ratio
            let newWidth = size.width * ratio
            let newSize = NSSize(width: newWidth, height: newHeight)
            let image = NSImage(size: newSize)
            image.lockFocus()
            let context = NSGraphicsContext.current
            context!.imageInterpolation = .high
            draw(in: NSRect(origin: .zero, size: newSize), from: NSZeroRect, operation: .copy, fraction: 1)
            image.unlockFocus()
            return image
        }
    }
    

    Since lockFocus() is deprecated after macOS 13.0, you can use init(size:flipped:drawingHandler:) instead:

    extension NSImage {
        func resizedMaintainingAspectRatio(width: CGFloat, height: CGFloat) -> NSImage {
            let ratioX = width / size.width
            let ratioY = height / size.height
            let ratio = ratioX < ratioY ? ratioX : ratioY
            let newHeight = size.height * ratio
            let newWidth = size.width * ratio
            let newSize = NSSize(width: newWidth, height: newHeight)
            let image = NSImage(size: newSize, flipped: false) { destRect in
                NSGraphicsContext.current!.imageInterpolation = .high
                self.draw(in: destRect, from: NSZeroRect, operation: .copy, fraction: 1)
                return true
            }
            return image
        }
    }
    

    Now the image should be resized to the correct size by doing the following:

    logoimage = logoimage!.resizedMaintainingAspectRatio(width: logoimage!.size.width + 10,
                                                         height: logoimage!.size.height + 10)