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

Time:07-24

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)

CodePudding user response:

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)
  • Related