techno
techno

Reputation: 6518

Resizing an Image by preserving aspect ratio in swift

I have the following extension to resize an image.

extension NSImage {
    func resizeImage(width: CGFloat, _ height: CGFloat) -> NSImage {
        let img = NSImage(size: CGSize(width:width, height:height))

        img.lockFocus()
        let ctx = NSGraphicsContext.current()
        ctx?.imageInterpolation = .high
        self.draw(in: NSMakeRect(0, 0, width, height), from: NSMakeRect(0, 0, size.width, size.height), operation: .copy, fraction: 1)
        img.unlockFocus()

        return img
    }
}

On resizing the aspect ratio is not preserved.

How can I modify the code to preserve aspect ratio?

Please advice.

Update:

This the logic used in C# .I don't know how to do this in swift.

double ratioX = (double) canvasWidth / (double) originalWidth;
    double ratioY = (double) canvasHeight / (double) originalHeight;
    // use whichever multiplier is smaller
    double ratio = ratioX < ratioY ? ratioX : ratioY;

    // now we can get the new height and width
    int newHeight = Convert.ToInt32(originalHeight * ratio);
    int newWidth = Convert.ToInt32(originalWidth * ratio);

    // Now calculate the X,Y position of the upper-left corner 
    // (one of these will always be zero)
    int posX = Convert.ToInt32((canvasWidth - (originalWidth * ratio)) / 2);
    int posY = Convert.ToInt32((canvasHeight - (originalHeight * ratio)) / 2);

Upvotes: 1

Views: 2059

Answers (2)

Ely
Ely

Reputation: 9141

Here is a working solution that doesn't use the deprecated lockFocus() and unlockFocus() methods:

public static func resizeImage(_ sourceImage: NSImage, size: NSSize) -> NSImage {
   
    let targetFrame = NSMakeRect(0, 0, size.width, size.height)
    let sourceSize = sourceImage.size
    let ratioHeight = size.height / sourceSize.height
    let ratioWidth = size.width / sourceSize.width
   
    var cropRect = NSZeroRect
    
    if ratioHeight >= ratioWidth {
        cropRect.size.width = floor(size.width / ratioHeight)
        cropRect.size.height = sourceSize.height
    } else {
        cropRect.size.width = sourceSize.width
        cropRect.size.height = floor(size.height / ratioWidth)
    }
    
    cropRect.origin.x = floor( (sourceSize.width - cropRect.size.width) / 2 )
    cropRect.origin.y = floor( (sourceSize.height - cropRect.size.height) / 2 )
    
    let targetImage = NSImage(size: targetFrame.size, flipped: false) { targetFrame in
        sourceImage.draw(in: targetFrame, from: cropRect, operation: .copy, fraction: 1.0, respectFlipped: true, hints: [NSImageRep.HintKey.interpolation: NSImageInterpolation.high.rawValue])
        return true
    }
    
    return targetImage
}

Upvotes: 0

Leo Dabus
Leo Dabus

Reputation: 236538

You can change your method signature to make it scale your image using a percentage instead of a size:

extension NSImage {
    func resizedTo(width: CGFloat, height: CGFloat) -> NSImage {
        let ratioX = width / size.width
        let ratioY = height / size.height
        let ratio = ratioX < ratioY ? ratioX : ratioY
        let canvasSize = NSSize(width: size.width * ratio, height: size.height * ratio)
        let img = NSImage(size: canvasSize)
        img.lockFocus()
        NSGraphicsContext.current?.imageInterpolation = .high
        draw(in: NSRect(origin: CGPoint(x: (canvasSize.width - (size.width * ratio)) / 2, y: (canvasSize.height - (size.height * ratio)) / 2), size: canvasSize), from: NSRect(origin: .zero, size: size), operation: .copy, fraction: 1)
        img.unlockFocus()
        return img
    }
    func resizedTo(percentage: CGFloat) -> NSImage {
        let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
        let img = NSImage(size: canvasSize)
        img.lockFocus()
        NSGraphicsContext.current?.imageInterpolation = .high
        draw(in: NSRect(origin: .zero, size: canvasSize), from: NSRect(origin: .zero, size: size), operation: .copy, fraction: 1)
        img.unlockFocus()
        return img
    }
    func resizedTo(width: CGFloat) -> NSImage {
        let canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
        let img = NSImage(size: canvasSize)
        img.lockFocus()
        NSGraphicsContext.current?.imageInterpolation = .high
        draw(in: NSRect(origin: .zero, size: canvasSize), from: NSRect(origin: .zero, size: size), operation: .copy, fraction: 1)
        img.unlockFocus()
        return img
    }
}

Upvotes: 2

Related Questions