macosretina-displayimage-resizingnsimagensbitmapimagerep

How to save PNG file from NSImage (retina issues)


I'm doing some operations on images, and after I'm done, I want to save the image as PNG on the disk. I'm doing the following:

+ (void)saveImage:(NSImage *)image atPath:(NSString *)path {
        
    [image lockFocus] ;
    NSBitmapImageRep *imageRepresentation = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect(0.0, 0.0, image.size.width, image.size.height)] ;
    [image unlockFocus] ;
        
    NSData *data = [imageRepresentation representationUsingType:NSPNGFileType properties:nil];
    [data writeToFile:path atomically:YES];
}

This code is working, but there's a problem on Macs with Retina screens; if I print the NSBitmapImageRep object, I get a different size and pixels rectangle, and when my image is saved on the disk, it's twice the size:

$0 = 0x0000000100413890 NSBitmapImageRep 0x100413890 Size={300, 300} ColorSpace=sRGB IEC61966-2.1 colorspace BPS=8 BPP=32 Pixels=600x600 Alpha=YES Planar=NO Format=0 CurrentBacking=<CGImageRef: 0x100414830>

I tried to force the pixel size to not care about the Retina scale, as I want to preserve the original size:

imageRepresentation.pixelsWide = image.size.width;
imageRepresentation.pixelsHigh = image.size.height;

This time I get the right size when I print the NSBitmapImageRep object, but when I save the file, I still get the same issue:

$0 = 0x0000000100413890 NSBitmapImageRep 0x100413890 Size={300, 300} ColorSpace=sRGB IEC61966-2.1 colorspace BPS=8 BPP=32 Pixels=300x300 Alpha=YES Planar=NO Format=0 CurrentBacking=<CGImageRef: 0x100414830>

Any idea how to fix this, and preserve the original pixel size?


Solution

  • If you have an NSImage and want to save it as an image file to the filesystem, you should never use lockFocus! lockFocus creates a new image which is determined for getting shown an the screen and nothing else. Therefore lockFocus uses the properties of the screen: 72 dpi for normal screens and 144 dpi for retina screens. For what you want I propose the following code:

    + (void)saveImage:(NSImage *)image atPath:(NSString *)path {
    
        CGImageRef cgRef = [image CGImageForProposedRect:NULL
                                                 context:nil
                                                   hints:nil];
    
        NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithCGImage:cgRef];
        [newRep setSize:[image size]]; // if you want the same resolution
        NSData *pngData = [newRep representationUsingType:NSPNGFileType properties:nil];
        [pngData writeToFile:path atomically:YES];
        [newRep autorelease];
    }