19

Tôi đang thực hiện một số thao tác trên ảnh và sau khi hoàn tất, tôi muốn lưu ảnh thành PNG trên đĩa. Tôi đang làm như sau:Cách lưu tập tin PNG từ NSImage (các vấn đề về võng mạc)

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

Mã này đang làm việc, nhưng vấn đề là với mac võng mạc, nếu tôi in các đối tượng NSBitmapImageRep tôi nhận được một kích thước và điểm ảnh khác nhau rect và khi hình ảnh của tôi được lưu trên đĩa, đó là gấp đôi kích thước:

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

tôi trói buộc kích thước pixel để không chăm sóc về quy mô võng mạc, như tôi muốn giữ kích thước ban đầu:

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

lần này tôi nhận được kích thước phù hợp khi tôi in các đối tượng NSBitmapImageRep, nhưng khi tôi lưu tập tin của tôi, tôi vẫn nhận được cùng một vấn đề:

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

Bất kỳ ý tưởng làm thế nào để khắc phục điều này, và duy trì kích thước điểm ảnh gốc?

Trả lời

35

Nếu bạn có một NSImage và muốn lưu nó như một tập tin hình ảnh để hệ thống tệp, bạn nên không bao giờ sử dụng lockFocus! lockFocus tạo hình ảnh mới được xác định để hiển thị màn hình và không có gì khác. Do đó, lockFocus sử dụng các thuộc tính của màn hình: 72 dpi cho màn hình bình thường và 144 dpi cho màn hình võng mạc. Đối với những gì bạn muốn tôi đề xuất mã sau:

+ (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:]' dường như chỉ có sẵn từ 10.10. Có lẽ đó là lý do tại sao khi tôi thử mã của bạn trên Mavericks, hình ảnh không bị thu nhỏ lại? Mặc dù không có ngoại lệ được ném ... Tôi nhận được một hình ảnh có cùng kích thước với bản gốc, dù tôi có vượt qua cỡ nào. –

+0

@NicolasMiari Tôi thấy kích thước của 'newRep' thay đổi thành giá trị của nó (nhắm mục tiêu 10.9, nhưng chạy trên 10.10), nhưng tệp được ghi vào đĩa vẫn chứa một hình ảnh 2x. Bạn đã bao giờ nghĩ ra một giải pháp chưa? – Dov

+0

Vâng, câu trả lời của Weichsel bên dưới đã có tác dụng đối với tôi. –

14

NSImage là nhận thức về độ phân giải và sử dụng ngữ cảnh đồ họa HiDPI khi bạn lockFocus trên hệ thống có màn hình võng mạc.
Kích thước hình ảnh bạn chuyển đến bộ khởi tạo NSBitmapImageRep của bạn được tính theo điểm (không phải pixel). Do đó, hình ảnh rộng 150,0 điểm ảnh sử dụng 300 pixel ngang trong ngữ cảnh @ 2x.

Bạn có thể sử dụng convertRectToBacking: hoặc backingScaleFactor: để bù cho ngữ cảnh @ 2x. (Tôi đã không cố gắng đó), hoặc bạn có thể sử dụng NSImage loại sau, tạo ra một bối cảnh bản vẽ với kích thước pixel rõ ràng:

@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

Tôi đã thử một loạt các giải pháp khác nhau (bao gồm câu trả lời được chấp nhận ở đây). Đây là giải pháp duy nhất tôi đã thử làm việc. Cảm ơn! – EsbenB

+0

Tôi đang triển khai giải pháp của bạn. Tôi nhận được cảnh báo "isFlipped không được dùng nữa: không được dùng nữa trong OS X 10.6". Tôi có nên bỏ qua cảnh báo hay loại bỏ cuộc gọi tốt hơn không? –

+0

Ngoài ra, '" Chuyển đổi ngầm định từ kiểu liệt kê 'enum CGImageAlphaInfo' thành kiểu liệt kê khác nhau 'CGBitmapInfo' (aka 'enum CGBitmapInfo') "'. Điều này có vẻ là một cảnh báo nghiêm trọng hơn. Tôi đã kiểm tra định nghĩa của hai enums này, và chúng hoàn toàn khác. Tuy nhiên, 'CGBitmapInfo' không có bất kỳ hằng số nào đối với 'alpha ưu tiên'. –

4

Tôi tìm thấy mã này trên web và hoạt động trên võng mạc. Dán ở đây, hy vọng có thể giúp ai đó.

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 Thay đổi kích thước NSImage trên võng mạc Máy Mac cho đầu ra dưới dạng hình ảnh 1x – flowers

+0

Đây không phải là câu trả lời trực tiếp cho câu hỏi – abarisone

4

Chỉ cần bất kỳ ai gặp phải vấn đề này. Đây chắc chắn là giải pháp sai lầm mà không được công việc tiết kiệm hình ảnh ở kích thước 1x (image.size) bất kể thiết bị trong nhanh chóng

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 cent Mỹ cho OS X bao gồm ghi để xử lý phần mở rộng + hình ảnh offscreen vẽ (phương pháp 2); người ta có thể xác minh với 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) 
     } 
    } 
}