I have an NSBitmapImageRep which is WxH size.
I create NSImage and call addRepresentation:. Then I need to resize the NSImage.
I tried setSize method but it doesn't work. What should I do?
I have an NSBitmapImageRep which is WxH size.
I create NSImage and call addRepresentation:. Then I need to resize the NSImage.
I tried setSize method but it doesn't work. What should I do?
Here's a Swift 4 version of Thomas Johannesmeyer's answer:
func resize(image: NSImage, w: Int, h: Int) -> NSImage {
var destSize = NSMakeSize(CGFloat(w), CGFloat(h))
var newImage = NSImage(size: destSize)
newImage.lockFocus()
image.draw(in: NSMakeRect(0, 0, destSize.width, destSize.height), from: NSMakeRect(0, 0, image.size.width, image.size.height), operation: NSCompositingOperation.sourceOver, fraction: CGFloat(1))
newImage.unlockFocus()
newImage.size = destSize
return NSImage(data: newImage.tiffRepresentation!)!
}
And Swift 4 version of Marco's answer:
func resize(image: NSImage, w: Int, h: Int) -> NSImage {
let destSize = NSMakeSize(CGFloat(w), CGFloat(h))
let rep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(destSize.width), pixelsHigh: Int(destSize.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0)
rep?.size = destSize
NSGraphicsContext.saveGraphicsState()
if let aRep = rep {
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: aRep)
}
image.draw(in: NSMakeRect(0, 0, destSize.width, destSize.height), from: NSZeroRect, operation: NSCompositingOperation.copy, fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()
let newImage = NSImage(size: destSize)
if let aRep = rep {
newImage.addRepresentation(aRep)
}
return newImage
}
2020 | SWIFT 4 and 5:
usage:
let resizedImg = someImage.resizedCopy(w: 500.0, h:500.0)
let scaledImg = someImage.scaledCopy( sizeOfLargerSide: 1000.0)
//and bonus:
scaledImg.writePNG(toURL: someUrl )
code:
extension NSImage {
func scaledCopy( sizeOfLargerSide: CGFloat) -> NSImage {
var newW: CGFloat
var newH: CGFloat
var scaleFactor: CGFloat
if ( self.size.width > self.size.height) {
scaleFactor = self.size.width / sizeOfLargerSide
newW = sizeOfLargerSide
newH = self.size.height / scaleFactor
}
else{
scaleFactor = self.size.height / sizeOfLargerSide
newH = sizeOfLargerSide
newW = self.size.width / scaleFactor
}
return resizedCopy(w: newW, h: newH)
}
func resizedCopy( w: CGFloat, h: CGFloat) -> NSImage {
let destSize = NSMakeSize(w, h)
let newImage = NSImage(size: destSize)
newImage.lockFocus()
self.draw(in: NSRect(origin: .zero, size: destSize),
from: NSRect(origin: .zero, size: self.size),
operation: .copy,
fraction: CGFloat(1)
)
newImage.unlockFocus()
guard let data = newImage.tiffRepresentation,
let result = NSImage(data: data)
else { return NSImage() }
return result
}
public func writePNG(toURL url: URL) {
guard let data = tiffRepresentation,
let rep = NSBitmapImageRep(data: data),
let imgData = rep.representation(using: .png, properties: [.compressionFactor : NSNumber(floatLiteral: 1.0)]) else {
Swift.print("\(self) Error Function '\(#function)' Line: \(#line) No tiff rep found for image writing to \(url)")
return
}
do {
try imgData.write(to: url)
}catch let error {
Swift.print("\(self) Error Function '\(#function)' Line: \(#line) \(error.localizedDescription)")
}
}
}
For just scaling NSBitmapImageRep
static NSBitmapImageRep *i_scale_bitmap(const NSBitmapImageRep *bitmap, const uint32_t width, const uint32_t height)
{
NSBitmapImageRep *new_bitmap = NULL;
CGImageRef dest_image = NULL;
CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGContextRef context = CGBitmapContextCreate(NULL, (size_t)width, (size_t)height, PARAM(bitsPerComponent, 8), PARAM(bytesPerRow, (size_t)(width * 4)), space, kCGImageAlphaPremultipliedLast);
CGImageRef src_image = [bitmap CGImage];
CGRect rect = CGRectMake((CGFloat)0.f, (CGFloat)0.f, (CGFloat)width, (CGFloat)height);
CGContextDrawImage(context, rect, src_image);
dest_image = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(space);
new_bitmap = [[NSBitmapImageRep alloc] initWithCGImage:dest_image];
CGImageRelease(dest_image);
return new_bitmap;
}
And for scaling a NSImage based on NSBitmapImageRep
ImageImp *imgimp_create_scaled(const ImageImp *image, const uint32_t new_width, const uint32_t new_height)
{
NSImage *src_image = (NSImage*)image;
NSBitmapImageRep *src_bitmap, *dest_bitmap;
NSImage *scaled_image = nil;
cassert_no_null(src_image);
cassert([[src_image representations] count] == 1);
cassert([[[src_image representations] objectAtIndex:0] isKindOfClass:[NSBitmapImageRep class]]);
src_bitmap = (NSBitmapImageRep*)[[(NSImage*)image representations] objectAtIndex:0];
cassert_no_null(src_bitmap);
dest_bitmap = i_scale_bitmap(src_bitmap, new_width, new_height);
scaled_image = [[NSImage alloc] initWithSize:NSMakeSize((CGFloat)new_width, (CGFloat)new_height)];
[scaled_image addRepresentation:dest_bitmap];
cassert([scaled_image retainCount] == 1);
[dest_bitmap release];
return (ImageImp*)scaled_image;
}
Draw directly over NSImage ([NSImage lockFocus], etc) will create a NSCGImageSnapshotRep not a NSBitmapImageRep.
You can resize the image to the desired size using the following functions:
import AppKit
extension NSImage {
/**
Resizes the image to the given size.
*/
func resize(withSize targetSize: NSSize) -> NSImage {
let newImage = NSImage(size: targetSize)
newImage.lockFocus()
draw(in: CGRect(origin: .zero, size: targetSize), from: CGRect(origin: .zero, size: size), operation: .sourceOver, fraction: 1.0)
newImage.unlockFocus()
return newImage
}
/**
Resizes the image to the given size maintaining its original aspect ratio.
*/
func resizeMaintainingAspectRatio(withSize targetSize: NSSize) -> NSImage {
let newSize: NSSize
let widthRatio = targetSize.width / size.width
let heightRatio = targetSize.height / size.height
if(widthRatio > heightRatio) {
newSize = NSSize(width: floor(size.width * widthRatio), height: floor(size.height * widthRatio))
} else {
newSize = NSSize(width: floor(size.width * heightRatio), height: floor(size.height * heightRatio))
}
return resize(withSize: newSize)
}
}
Based on Marco's answer:
In my case, resampling and preservation of bitsPerSample did not work with the proposed method (my greyscale image of 8 bps changed to 64 bps). With small modifications I managed to make it work.
If anybody is facing the same issue, here's my solution:
- (NSImage*) resizeImage:(NSImage*)smallImage newSize:(NSSize)newSize {
NSBitmapImageRep *resizeRep =
[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes: nil
pixelsWide: newSize.width
pixelsHigh: newSize.height
bitsPerSample: 8
samplesPerPixel: 1
hasAlpha: NO
isPlanar: NO
colorSpaceName: NSCalibratedWhiteColorSpace
bytesPerRow: 0
bitsPerPixel: 8];
[resizeRep setSize: newSize];
[smallImage setSize: newSize]; // ADDITION
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *newCurrentContext = [NSGraphicsContext graphicsContextWithBitmapImageRep: resizeRep];
[newCurrentContext setImageInterpolation:NSImageInterpolationHigh]; // ADDITION
[NSGraphicsContext setCurrentContext: newCurrentContext];
[smallImage drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, newSize.width, newSize.height) operation:NSCompositingOperationCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
NSImage *resizedImage = [[NSImage alloc] initWithSize:newSize];
[resizedImage addRepresentation: resizeRep];
return resizedImage;