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

我正在对图像进行一些操作,完成后我想将图像保存为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]; } 

这个代码工作,但问题是与视网膜的Mac,如果我打印NSBitmapImageRep对象,我得到不同的大小和像素矩形,当我的图像保存在磁盘上,它的大小的两倍:

 $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> 

我绑到的像素大小强制不关心视网膜的规模,因为我想保留原来的大小:

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

这次我打印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> 

任何想法如何解决这个问题,并保持原来的像素大小?

如果你有一个NSImage并且想把它作为一个映像文件保存到文件系统中,你不应该使用lockFocuslockFocus创build一个新的图像,这是确定为获得显示屏幕,没有别的。 因此, lockFocus使用屏幕的属性: 正常屏幕为72 dpi, 视网膜屏幕为144 dpi。 对于你想要的,我build议下面的代码:

 + (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]; } 

NSImage是分辨率感知的,并且当你在具有视网膜屏幕的系统上locking焦点时使用HiDPIgraphics上下文。
传递给NSBitmapImageRep初始化器的图像尺寸是以点(而不是像素)为单位的。 因此,在一个@ 2x上下文中,150.0点宽的图像使用300个水平像素。

您可以使用convertRectToBacking:或backingScaleFactor:来补偿@ 2x上下文。 (我没有尝试),或者你可以使用下面的NSImage类,它创build一个具有显式像素尺寸的绘图上下文:

 @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 

我在网上find这个代码,它在视网膜上工作。 粘贴在这里,希望可以帮助别人。

 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]; 

只要有人在这个线程绊倒。 这当然是有缺陷的解决scheme,无论设备在快速保存1x大小的图像(image.size)的工作

 public func writeToFile(path: String, atomically: Bool = true) -> Bool{ let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(self.size.width), pixelsHigh: Int(self.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bytesPerRow: 0, bitsPerPixel: 0)! bitmap.size = self.size NSGraphicsContext.saveGraphicsState() NSGraphicsContext.setCurrentContext(NSGraphicsContext(bitmapImageRep: bitmap)) self.drawAtPoint(CGPoint.zero, fromRect: NSRect.zero, operation: NSCompositingOperation.CompositeSourceOver, fraction: 1.0) NSGraphicsContext.restoreGraphicsState() if let imagePGNData = bitmap.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [NSImageCompressionFactor: 1.0]) { return imagePGNData.writeToFile((path as NSString).stringByStandardizingPath, atomically: atomically) } else { return false } } 

我的2美分的OS X包括写处理扩展名+离屏图像绘制(方法2); 可以用NSGraphicsContext.currentContextDrawingToScreen()来validation

 func createCGImage() -> CGImage? { //method 1 let image = NSImage(size: NSSize(width: bounds.width, height: bounds.height), flipped: true, drawingHandler: { rect in self.drawRect(self.bounds) return true }) var rect = CGRectMake(0, 0, bounds.size.width, bounds.size.height) return image.CGImageForProposedRect(&rect, context: bitmapContext(), hints: nil) //method 2 if let pdfRep = NSPDFImageRep(data: dataWithPDFInsideRect(bounds)) { return pdfRep.CGImageForProposedRect(&rect, context: bitmapContext(), hints: nil) } return nil } func PDFImageData(filter: QuartzFilter?) -> NSData? { return dataWithPDFInsideRect(bounds) } func bitmapContext() -> NSGraphicsContext? { var context : NSGraphicsContext? = nil if let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(bounds.size.width), pixelsHigh: Int(bounds.size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSCalibratedRGBColorSpace, bytesPerRow: Int(bounds.size.width) * 4, bitsPerPixel: 32) { imageRep.size = NSSize(width: bounds.size.width, height: bounds.size.height) context = NSGraphicsContext(bitmapImageRep: imageRep) } return context } func writeImageData(view: MyView, destination: NSURL) { if let dest = CGImageDestinationCreateWithURL(destination, imageUTType, 1, nil) { let properties = imageProperties let image = view.createCGImage()! let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) dispatch_async(queue) { CGImageDestinationAddImage(dest, image, properties) CGImageDestinationFinalize(dest) } } }