Cómo guardar un archivo PNG desde NSImage (problemas de retina)

Estoy haciendo algunas operaciones en las imágenes y cuando termine, quiero guardar la imagen como PNG en el disco. Estoy haciendo lo siguiente:

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

Este código está funcionando, pero el problema es con retina mac, si imprimo el objeto NSBitmapImageRep obtengo un tamaño y píxeles diferentes rect y cuando mi imagen se guarda en el disco, es dos veces el tamaño:

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

Ataron para forzar el tamaño de píxel para que no se preocupen por la escala de la retina, ya que quiero preservar el tamaño original:

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

Esta vez obtengo el tamaño correcto cuando imprimo el objeto NSBitmapImageRep, pero cuando guardo mi archivo sigo teniendo el mismo problema:

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

¿Alguna idea de cómo solucionar esto y preservar el tamaño de píxel original?

Si tiene un NSImage y desea guardarlo como un archivo de imagen en el sistema de archivos, ¡ nunca debe usar lockFocus ! lockFocus crea una nueva imagen que se determina para mostrarse en la pantalla y nada más. Por lockFocus tanto, lockFocus usa las propiedades de la pantalla: 72 ppp para pantallas normales y 144 ppp para pantallas retina . Para lo que quiere, propongo el siguiente código:

 + (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 es consciente de la resolución y utiliza un contexto de gráficos HiDPI cuando lockFocus el lockFocus en un sistema con pantalla Retina.
Las dimensiones de la imagen que pasa a su inicializador NSBitmapImageRep están en puntos (no en píxeles). Por lo tanto, una imagen de 150.0 puntos de ancho usa 300 píxeles horizontales en un contexto @ 2x.

Puede usar convertRectToBacking: o backingScaleFactor: para compensar el contexto @ 2x. (No lo intenté), o puede usar la siguiente categoría NSImage , que crea un contexto de dibujo con dimensiones de píxeles explícitos:

 @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 

Encontré este código en la web, y funciona en retina. Pegar aquí, la esperanza puede ayudar a alguien.

 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 restreGraphicsState]; NSData *data = [rep representationUsingType:NSPNGFileType properties:nil]; 

Solo en caso de que alguien tropiece con este hilo. Aquí hay una solución defectuosa que hace el trabajo de guardar imágenes a un tamaño de 1x (tamaño de la imagen), independientemente del dispositivo en rápida

 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.restreGraphicsState() if let imagePGNData = bitmap.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [NSImageCompressionFactor: 1.0]) { return imagePGNData.writeToFile((path as NSString).stringByStandardizingPath, atomically: atomically) } else { return false } } 

Mis 2 centavos para OS X, incluida la escritura que maneja extensiones + dibujo de imagen fuera de pantalla (método 2); uno puede verificar con 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) } } } 
Intereting Posts