如何从NSImage保存PNG文件(视网膜问题)

Lud*_*dry 26 macos nsimage image-resizing nsbitmapimagerep retina-display

我正在对图像进行一些操作,在完成之后,我想将图像保存为磁盘上的PNG.我正在做以下事情:

+ (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];
}
Run Code Online (Sandbox Code Playgroud)

这段代码工作正常,但问题出在视网膜mac上,如果我打印NSBitmapImageRep对象,我得到一个不同的大小和像素rect,当我的图像保存在磁盘上时,它的大小是两倍:

$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>
Run Code Online (Sandbox Code Playgroud)

因为我想要保留原始大小,我强制像素大小不要关心视网膜比例:

imageRepresentation.pixelsWide = image.size.width;
imageRepresentation.pixelsHigh = image.size.height;
Run Code Online (Sandbox Code Playgroud)

这次我在打印NSBitmapImageRep对象时得到了正确的大小,但是当我保存文件时,我仍然遇到同样的问题:

$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>
Run Code Online (Sandbox Code Playgroud)

知道如何解决这个问题,并保留原始像素大小?

小智 44

如果您有NSImage并希望将其作为图像文件保存到文件系统,则永远不要使用lockFocus!lockFocus创建一个新图像,确定是否显示屏幕而不是其他内容.因此lockFocus使用屏幕属性:正常屏幕为72 dpi,视网膜屏幕为144 dpi .对于你想要的,我提出以下代码:

+ (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];
}
Run Code Online (Sandbox Code Playgroud)

  • ` - [NSBitmapImageRep setSize:]`似乎仅在10.10之后可用.也许这就是为什么当我在Mavericks上尝试你的代码时,图像没有调整大小?虽然没有抛出任何异常......无论我通过什么尺寸,我都会得到与原始尺寸相同的图像. (2认同)

Tho*_*ing 16

NSImage具有分辨率并且lockFocus在具有视网膜屏幕的系统上使用HiDPI图形上下文.
传递给NSBitmapImageRep初始值设定项的图像尺寸以点(不是像素)为单位.因此,150.0点宽的图像在@ 2x上下文中使用300个水平像素.

您可以使用convertRectToBacking:backingScaleFactor:来补偿@ 2x上下文.(我没有尝试过),或者您可以使用以下NSImage类别创建具有显式像素尺寸的绘图上下文:

@interface NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error;

@end

@implementation NSImage (SSWPNGAdditions)

- (BOOL)writePNGToURL:(NSURL*)URL outputSizeInPixels:(NSSize)outputSizePx error:(NSError*__autoreleasing*)error
{
    BOOL result = YES;
    NSImage* scalingImage = [NSImage imageWithSize:[self size] flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
        [self drawAtPoint:NSMakePoint(0.0, 0.0) fromRect:dstRect operation:NSCompositeSourceOver fraction:1.0];
        return YES;
    }];
    NSRect proposedRect = NSMakeRect(0.0, 0.0, outputSizePx.width, outputSizePx.height);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    CGContextRef cgContext = CGBitmapContextCreate(NULL, proposedRect.size.width, proposedRect.size.height, 8, 4*proposedRect.size.width, colorSpace, kCGBitmapByteOrderDefault|kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(colorSpace);
    NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:NO];
    CGContextRelease(cgContext);
    CGImageRef cgImage = [scalingImage CGImageForProposedRect:&proposedRect context:context hints:nil];
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)(URL), kUTTypePNG, 1, NULL);
    CGImageDestinationAddImage(destination, cgImage, nil);
    if(!CGImageDestinationFinalize(destination))
    {
        NSDictionary* details = @{NSLocalizedDescriptionKey:@"Error writing PNG image"};
        [details setValue:@"ran out of money" forKey:NSLocalizedDescriptionKey];
        *error = [NSError errorWithDomain:@"SSWPNGAdditionsErrorDomain" code:10 userInfo:details];
        result = NO;
    }
    CFRelease(destination);
    return result;
}

@end
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你指出警告.我修正了原来的答案. (2认同)

小智 5

我在网上找到了这个代码,它适用于视网膜.粘贴在这里,希望可以帮助别人.

NSImage *computerImage = [NSImage imageNamed:NSImageNameComputer];
NSInteger size = 256;

NSBitmapImageRep *rep = [[NSBitmapImageRep alloc]
                  initWithBitmapDataPlanes:NULL
                                pixelsWide:size
                                pixelsHigh:size
                             bitsPerSample:8
                           samplesPerPixel:4
                                  hasAlpha:YES
                                  isPlanar:NO
                            colorSpaceName:NSCalibratedRGBColorSpace
                               bytesPerRow:0
                              bitsPerPixel:0];
[rep setSize:NSMakeSize(size, size)];

[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext     graphicsContextWithBitmapImageRep:rep]];
[computerImage drawInRect:NSMakeRect(0, 0, size, size)  fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0];
[NSGraphicsContext restoreGraphicsState];

NSData *data = [rep representationUsingType:NSPNGFileType properties:nil]; 
Run Code Online (Sandbox Code Playgroud)


Zev*_*erg 5

这是基于Heinrich Giesen 答案的 Swift 5 版本:

static func saveImage(_ image: NSImage, atUrl url: URL) {
    guard
        let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
        else { return } // TODO: handle error
    let newRep = NSBitmapImageRep(cgImage: cgImage)
    newRep.size = image.size // if you want the same size
    guard
        let pngData = newRep.representation(using: .png, properties: [:])
        else { return } // TODO: handle error
    do {
        try pngData.write(to: url)
    }
    catch {
        print("error saving: \(error)")
    }
}
Run Code Online (Sandbox Code Playgroud)