How To Save PNG file From NSImage (retina issues) The Right Way?
I had trouble with the answer provided in original thread too. Further reading landed me on a post by Erica Sadun related to debugging code for retina displays without a retina display. She creates a bitmap of the desired size, then replaces the current drawing context (display based/retina influenced) with the generic one associated with the new bitmap. She then renders the original image into the bitmap (using the generic graphics context).
I took her code and made a quick category on NSImage which seems to do the job for me. After calling
NSBitmapImageRep *myRep = [myImage unscaledBitmapImageRep];
you should have a bitmap of the proper (original) dimensions, regardless of the type of physical display you started with. From this point, you can call representationUsingType:properties
on the unscaled bitmap to get whatever format you are looking to write out.
Here is my category (header omitted). Note - you may need to expose the colorspace portion of the bitmap initializer. This is the value that works for my particular case.
-(NSBitmapImageRep *)unscaledBitmapImageRep {
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:self.size.width
pixelsHigh:self.size.height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:0
bitsPerPixel:0];
rep.size = self.size;
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:
[NSGraphicsContext graphicsContextWithBitmapImageRep:rep]];
[self drawAtPoint:NSMakePoint(0, 0)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0];
[NSGraphicsContext restoreGraphicsState];
return rep;
}
Thank tad & SnowPaddler.
For anyone who is not familiar with Cocoa and using Swift 4, you can view Swift 2 & Swift 3 version from edit history:
import Cocoa
func unscaledBitmapImageRep(forImage image: NSImage) -> NSBitmapImageRep {
guard let rep = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: Int(image.size.width),
pixelsHigh: Int(image.size.height),
bitsPerSample: 8,
samplesPerPixel: 4,
hasAlpha: true,
isPlanar: false,
colorSpaceName: .deviceRGB,
bytesPerRow: 0,
bitsPerPixel: 0
) else {
preconditionFailure()
}
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
image.draw(at: .zero, from: .zero, operation: .sourceOver, fraction: 1.0)
NSGraphicsContext.restoreGraphicsState()
return rep
}
func writeImage(
image: NSImage,
usingType type: NSBitmapImageRep.FileType,
withSizeInPixels size: NSSize?,
to url: URL) throws {
if let size = size {
image.size = size
}
let rep = unscaledBitmapImageRep(forImage: image)
guard let data = rep.representation(using: type, properties: [.compressionFactor: 1.0]) else {
preconditionFailure()
}
try data.write(to: url)
}