Je fais quelques opérations sur les images et une fois terminé, je veux enregistrer l'image au format PNG sur le disque. Je fais ce qui suit:
+ (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];
}
Ce code fonctionne, mais le problème vient de retina mac. Si j'imprime l'objet NSBitmapImageRep, je reçois une taille et des pixels différents et, lorsque mon image est enregistrée sur le disque, sa taille est deux fois plus grande:
$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>
J'ai lié pour forcer la taille des pixels à ne pas prendre en compte l'échelle de la rétine, car je veux conserver la taille d'origine:
imageRepresentation.pixelsWide = image.size.width;
imageRepresentation.pixelsHigh = image.size.height;
Cette fois, je reçois la bonne taille lorsque j'imprime l'objet NSBitmapImageRep, mais lorsque je sauvegarde mon fichier, le problème persiste:
$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>
Avez-vous une idée de comment résoudre ce problème et préserver la taille de pixel d'origine?
Si vous avez une NSImage
et que vous voulez l'enregistrer en tant que fichier image dans le système de fichiers, vous devriez jamais utiliser lockFocus
! lockFocus
crée une nouvelle image qui est déterminée pour être affichée à l'écran et rien d'autre. Par conséquent, lockFocus
utilise les propriétés de l'écran: 72 ppp pour les écrans normaux et 144 ppp pour les écrans retina. Pour ce que vous voulez, je propose le code suivant:
+ (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
est sensible à la résolution et utilise un contexte graphique HiDPI lorsque vous lockFocus
sur un système avec écran Retina.
Les dimensions de l'image que vous transmettez à votre initialiseur NSBitmapImageRep
sont en points (et non en pixels). Une image de 150,0 points utilise par conséquent 300 pixels horizontaux dans un contexte @ 2x.
Vous pouvez utiliser convertRectToBacking: ou backingScaleFactor: pour compenser le contexte @ 2x. (Je n'ai pas essayé cela), ou vous pouvez utiliser la catégorie NSImage
suivante, qui crée un contexte de dessin avec des dimensions de pixels explicites:
@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
J'ai trouvé ce code sur le Web, et cela fonctionne sur la rétine. Collez ici, l'espoir peut aider quelqu'un.
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];
Juste au cas où quelqu'un trébuche sur ce fil. Voici une solution imparfaite qui permet d’enregistrer une image au format 1x (taille d'image), quel que soit le périphérique utilisé
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
}
}
Mes 2 cents pour OS X, y compris l’écriture qui gère les extensions + le dessin d’image hors écran (méthode 2); on peut vérifier avec 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)
}
}
}