Resizing NSImage Shifts its Origin and Producing Wrong Results - swift

I'm using the following code to Resize an Image(Keeping Aspect Ratio).When I try to resize an Image whose size is 2432X3648 Pixels to 1800X2700 Pixels
The output image origin gets shifted.
extension NSImage {
func resizeTo(width: CGFloat, height: CGFloat) -> NSImage {
let ratioX = width / size.width
let ratioY = height / 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
}
}
Please see the resized sample image.I have marked the Image.The resizing code seems to work fine in other cases.Please advice..
UPDATE:
#LeoDabus I have used your code and it produces similar result for output size 1084X2086 Pixels.I had to slightly modify your code - only on a single line as it won't compile.The compiler auto suggested it.
NSGraphicsContext.current()?.imageInterpolation = .high
func resizeTo(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 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: CGPoint(x: (canvasSize.width - (size.width * ratio)) / 2, y: (canvasSize.height - (size.height * ratio)) / 2), size: NSSize(width: newWidth,height: newHeight)), from: NSRect(origin: .zero, size: size), operation: .copy, fraction: 1)
img.unlockFocus()
return img
}

What was actually wrong was only the canvas size which should be using the new width and height:
extension NSImage {
func resizedTo(width: CGFloat, height: CGFloat) -> NSImage {
let ratio = min(canvas.width/size.width, canvas.height/size.height)
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
}
}

Related

Add transparent padding around NSImage

I need to add some transparent padding around an NSImage.I referred this SO post Placing an NSImage on a transparent canvas in Swift
It was pointing to the the Github code https://gist.github.com/MaciejGad/11d8469b218817290ee77012edb46608 where it mentions of tweaking the resizeWhileMaintainingAspectRatioToSize method. I tried what the post has suggested but swift seems to miss the NSImage.copy method with the NSrect as parameter.So wrote the below extension method with my limited knowledge
func addPadding() -> NSImage? {
let newSize: NSSize
let widthRatio = size.width / self.size.width
let heightRatio = size.height / self.size.height
if widthRatio > heightRatio {
newSize = NSSize(width: floor(self.size.width * widthRatio), height: floor(self.size.height * widthRatio))
} else {
newSize = NSSize(width: floor(self.size.width * heightRatio), height: floor(self.size.height * heightRatio))
}
let img = NSImage(size: newSize)
let frame = NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Set the drawing context and make sure to remove the focus before returning.
img.lockFocus()
defer { img.unlockFocus() }
let representation = self.bestRepresentation(for: frame, context: nil, hints: nil)
// Draw the new image
if representation!.draw(in: frame) {
return img
}
// Return nil in case something went wrong.
return nil
}
But this does nothing to add padding to the image.
You can do it with this code:
extension NSImage {
var pixelSize: NSSize {
if let rep = self.representations.first {
let size = NSSize(
width: rep.pixelsWide,
height: rep.pixelsHigh
)
return size
}
return NSSize(width: 0, height: 0)
}
func withPadding(percent: Int) -> NSImage {
let originalSize = self.pixelSize
let newHeight = originalSize.height - originalSize.height * CGFloat(percent) / 100
let newWidth = originalSize.width - originalSize.width * CGFloat(percent) / 100
let sizeWithPadding = NSSize(
width: newWidth,
height: newHeight
)
guard let representation = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: Int(originalSize.width),
pixelsHigh: Int(originalSize.height),
bitsPerSample: 8,
samplesPerPixel: 4,
hasAlpha: true,
isPlanar: false,
colorSpaceName: .calibratedRGB,
bytesPerRow: 0,
bitsPerPixel: 0
)
else {
return self
}
representation.size = originalSize
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext.init(bitmapImageRep: representation)
let xPoint = (originalSize.width - newWidth) / 2
let yPoint = (originalSize.height - newHeight) / 2
self.draw(
in: NSRect(
x: xPoint,
y: yPoint,
width: sizeWithPadding.width,
height: sizeWithPadding.height
),
from: .zero,
operation: .copy,
fraction: 1.0
)
NSGraphicsContext.restoreGraphicsState()
let newImage = NSImage(size: originalSize)
newImage.addRepresentation(representation)
return newImage
}
}

Keep same image quality after converting in swift

I am referring to ZImageCropper for my project to crop an image. However, it reduces the quality of the image during conversion.
I know one of the reasons the quality of image was reduced is due to UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, 1)
I have tried changing it to UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, 0) and UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, UIScreen.main.scale)
I reference my code from Resize and Crop 2 Images affected the original image quality It does help but when I check the quality of the image, it is still reduced. Is there any way to prevent a reduction in image quality?
in iOS 10 and above you can resize any UIImage without quality loss, hope this helps:
func resize(targetSize: CGSize) -> UIImage {
if #available(iOS 10.0, *) {
return UIGraphicsImageRenderer(size: targetSize).image { _ in
self.draw(in: CGRect(origin: .zero, size: targetSize))
}
} else {
return resizeImage(maxSize: targetSize.width)
}
}
func resizeImage(maxSize: CGFloat) -> UIImage {
var newWidth = size.width
var newHeight = size.height
if size.width >= maxSize {
newWidth = min(maxSize, size.width)
newHeight = maxSize * size.height / size.width
} else if size.height >= maxSize {
newHeight = min(maxSize, size.height)
newWidth = maxSize * size.width / size.height
}
UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}

White Space Around Resized Image

I'm trying to Resize Image by preserving aspect ratio.But there is this white space surrounding the resized image.
extension NSImage {
func resizeTo(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 canvasSize = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
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
}
}
What I'm I doing wrong?
First you are picking the smallest ratio from ratioX and ratioY, but later you create canvas using ratioX (of size width, height * ratioX). I'd say you need to create canvas using newWidth and newHeight.
let canvasSize = CGSize(width: newWidth, height: newHeight)
Additionally, be aware that this script will resize in both directions, i.e. will increase the size of small images.

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);
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
}
}

Rotate NSImage in Swift, Cocoa, Mac OSX

Is there an easy way to rotate a NSImage in a Mac OSX app? Or just set the orientation from portrait to landscape using Swift?
I am playing around with CATransform3DMakeAffineTransform but I can't get it to work.
CATransform3DMakeAffineTransform(CGAffineTransformMakeRotation(CGFloat(M_PI) * 90/180))
It's the first time for me to work with transformations. So please be patient with me :) Maybe I'm working on a wrong approach...
Can anybody help me please?
Thanks!
public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {
var imageBounds = NSZeroRect ; imageBounds.size = self.size
let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform()
transform.rotateByDegrees(degrees)
pathBounds.transformUsingAffineTransform(transform)
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
let rotatedImage = NSImage(size: rotatedBounds.size)
//Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform
transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center)
transform.translateXBy(+(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotateByDegrees(degrees)
// Move the coordinate system bak to normal
transform.translateXBy(-(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image
rotatedImage.lockFocus()
transform.concat()
self.drawInRect(imageBounds, fromRect: NSZeroRect, operation: NSCompositingOperation.CompositeCopy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
var image = NSImage(named:"test.png")!.imageRotatedByDegreess(CGFloat(90)) //use only this values 90, 180, or 270
}
Updated for Swift 3:
public extension NSImage {
public func imageRotatedByDegreess(degrees:CGFloat) -> NSImage {
var imageBounds = NSZeroRect ; imageBounds.size = self.size
let pathBounds = NSBezierPath(rect: imageBounds)
var transform = NSAffineTransform()
transform.rotate(byDegrees: degrees)
pathBounds.transform(using: transform as AffineTransform)
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y, pathBounds.bounds.size.width, pathBounds.bounds.size.height )
let rotatedImage = NSImage(size: rotatedBounds.size)
//Center the image within the rotated bounds
imageBounds.origin.x = NSMidX(rotatedBounds) - (NSWidth(imageBounds) / 2)
imageBounds.origin.y = NSMidY(rotatedBounds) - (NSHeight(imageBounds) / 2)
// Start a new transform
transform = NSAffineTransform()
// Move coordinate system to the center (since we want to rotate around the center)
transform.translateX(by: +(NSWidth(rotatedBounds) / 2 ), yBy: +(NSHeight(rotatedBounds) / 2))
transform.rotate(byDegrees: degrees)
// Move the coordinate system bak to normal
transform.translateX(by: -(NSWidth(rotatedBounds) / 2 ), yBy: -(NSHeight(rotatedBounds) / 2))
// Draw the original image, rotated, into the new image
rotatedImage.lockFocus()
transform.concat()
self.draw(in: imageBounds, from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}
class SomeClass: NSViewController {
var image = NSImage(named:"test.png")!.imageRotatedByDegreess(degrees: CGFloat(90)) //use only this values 90, 180, or 270
}
Thank for this solution, however it did not worked perfectly for me.
As you may have noticed that pathBounds is not used anywhere. In my opinion is has to be used like so:
let rotatedBounds:NSRect = NSMakeRect(NSZeroPoint.x, NSZeroPoint.y , pathBounds.bounds.size.width, pathBounds.bounds.size.height )
Otherwise the image will be rotated but cropped to a square bounds.
Letting IKImageView do the heavy lifting:
import Quartz
extension NSImage {
func imageRotated(by degrees: CGFloat) -> NSImage {
let imageRotator = IKImageView()
var imageRect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height)
let cgImage = self.cgImage(forProposedRect: &imageRect, context: nil, hints: nil)
imageRotator.setImage(cgImage, imageProperties: [:])
imageRotator.rotationAngle = CGFloat(-(degrees / 180) * CGFloat(M_PI))
let rotatedCGImage = imageRotator.image().takeUnretainedValue()
return NSImage(cgImage: rotatedCGImage, size: NSSize.zero)
}
}
Here's a simple Swift (4+) solution to drawing an image that is rotated around the center:
extension NSImage {
/// Rotates the image by the specified degrees around the center.
/// Note that if the angle is not a multiple of 90°, parts of the rotated image may be drawn outside the image bounds.
func rotated(by angle: CGFloat) -> NSImage {
let img = NSImage(size: self.size, flipped: false, drawingHandler: { (rect) -> Bool in
let (width, height) = (rect.size.width, rect.size.height)
let transform = NSAffineTransform()
transform.translateX(by: width / 2, yBy: height / 2)
transform.rotate(byDegrees: angle)
transform.translateX(by: -width / 2, yBy: -height / 2)
transform.concat()
self.draw(in: rect)
return true
})
img.isTemplate = self.isTemplate // preserve the underlying image's template setting
return img
}
}
This one works also for non-square images, Swift 5.
extension NSImage {
func rotated(by degrees : CGFloat) -> NSImage {
var imageBounds = NSRect(x: 0, y: 0, width: size.width, height: size.height)
let rotatedSize = AffineTransform(rotationByDegrees: degrees).transform(size)
let newSize = CGSize(width: abs(rotatedSize.width), height: abs(rotatedSize.height))
let rotatedImage = NSImage(size: newSize)
imageBounds.origin = CGPoint(x: newSize.width / 2 - imageBounds.width / 2, y: newSize.height / 2 - imageBounds.height / 2)
let otherTransform = NSAffineTransform()
otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
otherTransform.rotate(byDegrees: degrees)
otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)
rotatedImage.lockFocus()
otherTransform.concat()
draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}
Building on #FrankByte.com's code, this version should extend correctly in both x and y on any image and any rotation.
extension NSImage {
func rotated(by degrees: CGFloat) -> NSImage {
let sinDegrees = abs(sin(degrees * CGFloat.pi / 180.0))
let cosDegrees = abs(cos(degrees * CGFloat.pi / 180.0))
let newSize = CGSize(width: size.height * sinDegrees + size.width * cosDegrees,
height: size.width * sinDegrees + size.height * cosDegrees)
let imageBounds = NSRect(x: (newSize.width - size.width) / 2,
y: (newSize.height - size.height) / 2,
width: size.width, height: size.height)
let otherTransform = NSAffineTransform()
otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2)
otherTransform.rotate(byDegrees: degrees)
otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2)
let rotatedImage = NSImage(size: newSize)
rotatedImage.lockFocus()
otherTransform.concat()
draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0)
rotatedImage.unlockFocus()
return rotatedImage
}
}