How to save PNG file from NSImage (retina issues)

后端 未结 6 860
情话喂你
情话喂你 2020-11-29 01:37

I\'m doing some operations on images and after I\'m done, I want to save the image as PNG on disk. I\'m doing the following:

+ (void)saveImage:(NSImage *)ima         


        
相关标签:
6条回答
  • 2020-11-29 02:00

    My 2 cents for OS X including write that handles extensions + offscreen image drawing (method 2); one can verify with 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)
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 02:01

    Here's a Swift 5 version based on Heinrich Giesen's answer:

    static func saveImage(_ image: NSImage, atUrl url: URL) {
        guard
            let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
            else { return } // TODO: handle error
        let newRep = NSBitmapImageRep(cgImage: cgImage)
        newRep.size = image.size // if you want the same size
        guard
            let pngData = newRep.representation(using: .png, properties: [:])
            else { return } // TODO: handle error
        do {
            try pngData.write(to: url)
        }
        catch {
            print("error saving: \(error)")
        }
    }
    
    0 讨论(0)
  • 2020-11-29 02:03

    I found this code on web , and it works on retina. Paste here, hope can help someone.

    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 讨论(0)
  • 2020-11-29 02:09

    If you have an NSImage and want to save it as an image file to the filesystem, you should never use lockFocus! lockFocus creates a new image which is determined for getting shown an the screen and nothing else. Therefore lockFocus uses the properties of the screen: 72 dpi for normal screens and 144 dpi for retina screens. For what you want I propose the following code:

    + (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];
    }
    
    0 讨论(0)
  • 2020-11-29 02:23

    NSImage is resolution aware and uses a HiDPI graphics context when you lockFocus on a system with retina screen.
    The image dimensions you pass to your NSBitmapImageRep initializer are in points (not pixels). An 150.0 point-wide image therefore uses 300 horizontal pixels in a @2x context.

    You could use convertRectToBacking: or backingScaleFactor: to compensate for the @2x context. (I didn't try that), or you can use the following NSImage category, that creates a drawing context with explicit pixel dimensions:

    @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 讨论(0)
  • 2020-11-29 02:24

    Just incase anyone stumbles up on this thread. Here is certainly flawed solution that does the job of saving image at 1x size (image.size) regardless of device in swift

    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 讨论(0)
提交回复
热议问题