19

我正在對圖像進行一些操作,完成後我想將圖像保存爲PNG磁盤。我做了以下內容:如何從NSImage保存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> 

任何想法如何解決這一問題,並保留原始像素大小?

回答

35

,並希望將其保存爲圖像文件文件系統,你應該從來沒有使用lockFocuslockFocus創建一個新的圖像,這是確定爲顯示屏幕,沒有別的。因此lockFocus使用屏幕的屬性:對於正常屏幕和對於視網膜屏幕144dpi的72dpi。對於你想要的,我建議下面的代碼:

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

' - [NSBitmapImageRep setSize:]'似乎從10.10開始纔可用。也許這就是爲什麼當我在Mavericks上試用你的代碼時,圖像不會被調整大小?雖然沒有任何例外情況發生......無論我傳遞的尺寸是多少,我都會得到與原始尺寸相同的圖像。 –

+0

@NicolasMiari我看到'newRep'的大小改變爲應該的值(以10.9爲目標,但在10.10上運行),但寫入磁盤的文件仍包含一個2x圖像。你有沒有想出一個解決方案? – Dov

+0

是的,Weichsel的答案在下面爲我工作。 –

14

NSImage是分辨率感知,並且當您在具有視網膜屏幕的系統上使用lockFocus時使用HiDPI圖形上下文。
您傳遞給NSBitmapImageRep初始值設定項的圖像尺寸以點(而非像素)爲單位。因此,一個150.0點寬的圖像在@ 2x上下文中使用300個水平像素。

您可以使用convertRectToBacking:backingScaleFactor:來補償@ 2x上下文。 (我沒有嘗試),或者您可以使用以下NSImage類別,創建具有明確的像素尺寸繪製上下文:如果你有一個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 
+0

我嘗試了很多不同的解決方案(包括這裏接受的答案)。這是我嘗試過的唯一解決方案。謝謝! – EsbenB

+0

我正在實施您的解決方案。我收到警告「isFlipped已棄用:先在OS X 10.6中棄用」。我是否應該忽略警告,或者更好地取消該呼叫? –

+0

另外,''將枚舉類型的隱式轉換'枚舉CGImageAlphaInfo'爲不同的枚舉類型'CGBitmapInfo'(又名'enum CGBitmapInfo')「'。這似乎是一個更嚴重的警告。我檢查了這兩個枚舉的定義,它們完全不同。但是'CGBitmapInfo'沒有任何'預乘alpha'的常量。 –

4

我在網上發現這個代碼,它在視網膜上工作。粘貼在這裏,希望可以幫助別人。

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

https://gist.github.com/mattstevens/4400775調整視網膜Mac上的NSImage輸出爲1x圖像 – flowers

+0

這似乎沒有直接回答這個問題 – abarisone

4

只要有人在這個線程上絆倒。這是肯定,做保存圖像的工作在1倍大小(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 
    } 
} 
0

我的2美分OS X包括處理擴展+屏幕外的圖像繪製(方法2寫有缺陷的解決方案);一個可以與NSGraphicsContext.currentContextDrawingToScreen驗證()

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) 
     } 
    } 
}