I have an NSBitmapImageRep
which is W
xH
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 W
xH
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?
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:NSCompositingOperationCopy fraction:1.0];
[smallImage unlockFocus];
return smallImage;
}
return nil;
}
[NSGraphicsContext currentContext]
returns nil for me. any idea? –
Bedeck sourceImage
? developer.apple.com/documentation/appkit/nsimage/… This may not be what the caller wants, –
Yttrium 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;
}
lockFocus()
method is dated; that method is now soft-deprecated. Marco's approach will produce non-retina images on-screen but you can solve that by adding * 2
to the values for the pixelsWide
and pixelsHigh
parameters of the NSBitmapImageRep
initializer. That makes this approach work well on-screen for retina. (I have no idea what it does for printed images...who prints stuff in 2022?) –
Kayleigh @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)
NSGraphicsContext.current.imageInterpolation = .none
before the draw statement. –
Ijssel * 2
to the values for pixelsWide
and pixelsHigh
in the NSBitmapRepresentation
initializer. The scaled images will then match those from the older lockFocus()
scaling approach discussed in other answers. –
Kayleigh EDIT You can resize image using below function:
- (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] autorelease];
[smallImage lockFocus];
[sourceImage setSize: newSize];
[[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
[sourceImage compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
[smallImage unlockFocus];
return smallImage;
}
return nil;
}
Secondly like this:
NSData *imageData = [yourImg TIFFRepresentation]; // converting img into data
NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData]; // converting into BitmapImageRep
NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor]; // any number betwwen 0 to 1
imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps]; // use NSPNGFileType if needed
NSImage *resizedImage = [[NSImage alloc] initWithData:imageData]; // image created from data
Actually it is not necessary to modify any source image parameters like size
. The following snippet is already in Swift, but I think you can infer the Objective-C version from it:
func resized(to: CGSize) -> NSImage {
let img = NSImage(size: to)
img.lockFocus()
defer {
img.unlockFocus()
}
if let ctx = NSGraphicsContext.current {
ctx.imageInterpolation = .high
draw(in: NSRect(origin: .zero, size: to),
from: NSRect(origin: .zero, size: size),
operation: .copy,
fraction: 1)
}
return img
}
to:
is set to 16x16 the image becomes 32x32. Possibly retina calculation? How to correct this? –
Ijssel 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
}
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
}
}
Here is a Swift 3 version keeping ratio of image, just set minimumSize as the minimum height or width you want:
func imageResized(image: NSImage) -> NSImage {
let ratio = image.size.height / image.size.width
let width: CGFloat
let height: CGFloat
// We keep ratio of image
if ratio > 1 {
width = minimumSize
height = minimumSize * ratio
} else {
width = minimumSize
height = minimumSize * (1 / ratio)
}
let destSize = NSSize(width: width, height: height)
let newImage = NSImage(size: destSize)
newImage.lockFocus()
image.draw(in: NSRect(x: 0, y: 0, width: destSize.width, height: destSize.height), from: NSRect(x: 0, y: 0, width: image.size.width, height: image.size.height), operation: .sourceOver, fraction: 1.0)
newImage.unlockFocus()
newImage.size = destSize
return NSImage(data: newImage.tiffRepresentation!)!
}
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.
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;
2024 - All solutions here do not preserve colorspace such as wide color gamut or cmyk or gray colorspace. 8, 10, 16 bits per components are widely used. On top lockFocus/unlockFocus should be avoided as it has been deprecated and it forces display colorspace/display scale. NSBitmapRep doesn't support creation with new colorspaces so it's necessary to draw using Quartz context. NOTE: Cocoa doesn't support all graphic context combinations. Pixel formats supported for bitmap graphics contexts. This method will also rasterize (convert to bitmap) vector images (symbol, svg, pdf) at a higher resolution.
import AppKit
extension NSImage {
// bitmap images must be resized by drawing, cannot use cgImage(forProposedRect
// vector images can be resized using cgImage forproposedRect
// only cgImage can provide correct colorspace (no nscolorspacename exists for wide color, edr)
// expect small performance penalty ~20% compared to drawing to NSBitmapRep context
func resized(to newSize: NSSize) -> NSImage {
if !isValid { return self }
let hints : [NSImageRep.HintKey : Any] = [.ctm : NSAffineTransform(), .interpolation : NSImageInterpolation.high] //disable retina scaling
var proposedRect = CGRect(origin: .zero, size: newSize)
guard let imageRep = bestRepresentation(for: proposedRect, context: nil, hints: [:]),
let cgImage = imageRep.cgImage(forProposedRect: &proposedRect, context: nil, hints: hints) else { return self }
var bitmapInfo : UInt32 = 0
let colorSpaceModel = cgImage.colorSpace?.model ?? .rgb
// Best combination for colorspace model with alpha
// [https://developer.apple.com/library/archive/samplecode/ImageApp/Listings/ImageFilter_m.html#//apple_ref/doc/uid/DTS10003685-ImageFilter_m-DontLinkElementID_11][2]
if colorSpaceModel == .rgb {
bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
}
if colorSpaceModel == .monochrome || colorSpaceModel == .cmyk {
bitmapInfo = CGImageAlphaInfo.none.rawValue
}
//10-bit or 12-bit need to be drawn using 16 bpc context e.g. converted
var bitsPerComponent = cgImage.bitsPerComponent
if cgImage.bitsPerComponent > 8 && cgImage.bitsPerComponent <= 16 {
bitsPerComponent = 16
}
if cgImage.bitsPerComponent == 32 {
bitmapInfo |= CGBitmapInfo.floatComponents.rawValue
}
var cgContext = CGContext(data: nil,
width: Int(newSize.width),
height: Int(newSize.height),
bitsPerComponent: bitsPerComponent,
bytesPerRow: 0,
space: cgImage.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: bitmapInfo)
if cgContext == nil {
// context cannot be created with provided values. Continue with 8bps 32bpp RGBA context
cgContext = CGContext(data: nil,
width: Int(newSize.width),
height: Int(newSize.height),
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpace(name: CGColorSpace.sRGB)!,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
}
guard cgContext != nil else { return self }
cgContext!.interpolationQuality = .high
cgContext!.draw(cgImage, in: CGRect(origin: .zero, size: newSize), byTiling: false)
guard let resizedCGImage = cgContext!.makeImage() else { return self }
let resizedImage = NSImage(cgImage: resizedCGImage,
size: NSMakeSize(CGFloat(resizedCGImage.width), CGFloat(resizedCGImage.height)))
return resizedImage
}
}
On a wide gamut display you will see safari icon (samples):
It's not possible to use + (instancetype)imageWithSize:(NSSize)size flipped:(BOOL)drawingHandlerShouldBeCalledWithFlippedContext drawingHandler:(BOOL (^)(NSRect dstRect))drawingHandler
because it copies the original imagerep into drawing handler (memory waste) and the image colorspace/context depends on the destination (monitor) where it's being drawn. Please note XCode quicklook has a bug for this API call (image is drawn with wrong colorspace). Use NSImageView to test this API.
Don't forget to test CMYK and Greyscale colorspace
Converting existing colored NSBitmapImageRep with a new colorspace will lead to a much bigger performance penalty.
if let colorSpace = (representations.first as? NSBitmapImageRep)?.colorSpace {
bitmapRep = bitmapRep?.converting(to: colorSpace, renderingIntent: .default)
}
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)
}
}
© 2022 - 2025 — McMap. All rights reserved.