I have an NSBitmapImageRep
which is W
xH
size.
I create NSImage
and call addRepresentation:
. Then I n
Thomas Johannesmeyer's answer using lockFocus
doesn't work as you may intend on Retina/HiDPI screens: it resizes to the desired points in the screen's native scale, not pixels.
This method, cobbled together from various answers including some in this related question, resizes to the specified pixel dimensions regardless of current screen DPI:
+ (NSImage *)resizedImage:(NSImage *)sourceImage toPixelDimensions:(NSSize)newSize
{
if (! sourceImage.isValid) return nil;
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:newSize.width
pixelsHigh:newSize.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
rep.size = newSize;
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];
[sourceImage drawInRect:NSMakeRect(0, 0, newSize.width, newSize.height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
NSImage *newImage = [[NSImage alloc] initWithSize:newSize];
[newImage addRepresentation:rep];
return newImage;
}
Complete Swift 3 answer (modified from @Erik Aigner above):
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
}
}
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.
Edit: Since this answer is still the accepted answer, but was written without Retina screens in mind, I will straight up link to a better solution further down the thread: Objective-C Swift 4
Because the method of Paresh is totally correct but deprecated since 10.8 I'll post the working 10.8 code below. All credit to Paresh's answer though.
- (NSImage *)imageResize:(NSImage*)anImage newSize:(NSSize)newSize {
NSImage *sourceImage = anImage;
[sourceImage setScalesWhenResized:YES];
// Report an error if the source isn't a valid image
if (![sourceImage isValid]){
NSLog(@"Invalid Image");
} else {
NSImage *smallImage = [[NSImage alloc] initWithSize: newSize];
[smallImage lockFocus];
[sourceImage setSize: newSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[sourceImage drawAtPoint:NSZeroPoint fromRect:CGRectMake(0, 0, newSize.width, newSize.height) operation:NSCompositeCopy fraction:1.0];
[smallImage unlockFocus];
return smallImage;
}
return nil;
}
@Marco's answer written in Swift 4:
extension NSImage {
func resized(to newSize: NSSize) -> NSImage? {
if let bitmapRep = NSBitmapImageRep(
bitmapDataPlanes: nil, pixelsWide: Int(newSize.width), pixelsHigh: Int(newSize.height),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: .calibratedRGB, bytesPerRow: 0, bitsPerPixel: 0
) {
bitmapRep.size = newSize
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmapRep)
draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: .zero, operation: .copy, fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()
let resizedImage = NSImage(size: newSize)
resizedImage.addRepresentation(bitmapRep)
return resizedImage
}
return nil
}
}
let targetSize = NSSize(width: 256.0, height: 256.0)
let newImageResized = myimage.resized(to: targetSize)
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
}