How to save a NSImage as a new file

CocoaImageMacosFileNsimage

Cocoa Problem Overview


How can I save a NSImage as a new file (png, jpg, ...) in a certain directory?

Cocoa Solutions


Solution 1 - Cocoa

You could add a category to NSImage like this

@interface NSImage(saveAsJpegWithName)
- (void) saveAsJpegWithName:(NSString*) fileName;
@end

@implementation NSImage(saveAsJpegWithName)

- (void) saveAsJpegWithName:(NSString*) fileName
{
    // Cache the reduced image
    NSData *imageData = [self TIFFRepresentation];
    NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
    NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
    imageData = [imageRep representationUsingType:NSJPEGFileType properties:imageProps];
    [imageData writeToFile:fileName atomically:NO];        
}

@end

The call to "TIFFRepresentation" is essential otherwise you may not get a valid image.

Solution 2 - Cocoa

Do something like this:

NSBitmapImageRep *imgRep = [[image representations] objectAtIndex: 0];
NSData *data = [imgRep representationUsingType: NSPNGFileType properties: nil];
[data writeToFile: @"/path/to/file.png" atomically: NO];

Solution 3 - Cocoa

Not sure about the rest of you, but I prefer eating my full enchilada. What is described above will work and nothing is wrong with it, but I found a few things left out. Here I will highlight my observations:

  • First the best representation is provided which seems to be 72 DPI even if the image resolution is greater than that. So you are losing resolution
  • Second what about multi-page images such as those in animated GIFs or PDFs. Go ahead, try an animated GIF, you will find the animation lost
  • Lastly any metadata such as EXIF, GPS, etc...data will be lost.

So if you want to convert that image, do you really want to lose out on all of this? If you wish to eat your full meal, then lets read on...

Sometimes and I do mean sometimes there is nothing better than good ole old school development. Yeah that means we have to do a bit of work!

Let's get started:

I create a category in NSData. These are class methods because you want these things to be thread safe and there is nothing safer than putting your stuff on the stack. There are two types of methods, one for the output of non-multi-page images and one for the output of multi-page images.

List of single images: JPG, PNG, BMP, JPEG-2000

List of multiple images: PDF, GIF, TIFF

First create a mutable data space in memory.

NSMutableData * imageData    = [NSMutableData data];

Second get a CGImageSourceRef. Yeah sounding ugly already isn't it. It's not that bad, let's keep going... You really want the source image not a representation or NSImage chunk of data. However, we do have a small issue. The source might not be compatible, so make sure you check your UTI against those listed from CGImageSourceCopyTypeIdentifiers()

Some code:

CGImageSourceRef imageSource = nil;
if ( /* CHECK YOUR UTI HERE */ )
    return CGImageSourceCreateWithURL( (CFURLRef)aURL, nil );

NSImage * anImage = [[NSImage alloc] initWithContentsOfURL:aURL];

if ( anImage )
    return CGImageSourceCreateWithData( (CFDataRef)[anImage TIFFRepresentation], nil );

Wait a sec, why is NSImage there? Well there are some formats that don't have metadata and CGImageSource does not support, but these are valid images. An example is old style PICT images.

Now we have a CGImageSourceRef, make sure it's not nil and then let's now get a CGImageDestinationRef. Wow all these ref's to keep track of. So far we are at 2!

We will use this function: CGImageDestinationCreateWithData()

  • 1st Param is your imageData (cast CFMutableDataRef)

  • 2nd Param is your output UTI, remember the list above. (e.g. kUTTypePNG)

  • 3rd Param is the count of images to save. For single image files this is 1 otherwise you can simply use the following:

    CGImageSourceGetCount( imageSource );

  • 4th Param is nil.

Check to see if you have this CGImageDestinationRef and now let's add our images from the source into it...this will also include any/all metadata and retain the resolution.

For multiple images we loop:

for ( NSUInteger i = 0; i < count; ++i )
                CGImageDestinationAddImageFromSource( imageDest, imageSource, i, nil );

For single image it's one line of code at index 0:

CGImageDestinationAddImageFromSource( imageDest, imageSource, 0, nil);

Ok, finalize it which writes it to disk or data container:

CGImageDestinationFinalize( imageDest );

So that Mutable Data from the beginning has all of our image data and metadata in it now.

Are we done yet? Almost, even with garbage collection you have to clean-up! Remember two Ref's one for the source and one for the destination so do a CFRelease()

Now we are done and what you end up with is a converted image retaining all of its metadata, resolution, etc...

My NSData category methods look like this:

+ (NSData *) JPGDataFromURL:(NSURL *)aURL;
+ (NSData *) PNGDataFromURL:(NSURL *)aURL;
+ (NSData *) BMPDataFromURL:(NSURL *)aURL;
+ (NSData *) JPG2DataFromURL:(NSURL *)aURL;

+ (NSData *) PDFDataFromURL:(NSURL *)aURL;
+ (NSData *) GIFDataFromURL:(NSURL *)aURL;
+ (NSData *) TIFFDataFromURL:(NSURL *)aURL;

What about resizing or ICO / ICNS? This is for another day, but in summary you first tackle resizing...

  1. Create a context with new size: CGBitmapContextCreate()
  2. Get the an image ref from index: CGImageSourceCreateImageAtIndex()
  3. Get a copy of the metadata: CGImageSourceCopyPropertiesAtIndex()
  4. Draw the image into the context: CGContextDrawImage()
  5. Get the resized image from the context: CGBitmapContextCreateImage()
  6. Now add the image and metadata to the Dest Ref: CGImageDestinationAddImage()

Rinse and repeat for multiple-images embedded in the source.

The only difference between an ICO and ICNS is that one is a single image while the other one is multiple-images in one file. Bet you can guess which is which?! ;-) For these formats you have to resize down to a particular size otherwise ERROR will ensue. The process though is exactly the same where you use the proper UTI, but the resizing is a bit more strict.

Ok hope this helps others out there and you are as full as I am now!

Opps, forgot to mention. When you get the NSData object do as you want with it such as writeToFile, writeToURL, or heck create another NSImage if you want.

Happy coding!

Solution 4 - Cocoa

save as PNG using swift3

import AppKit

extension NSImage {
	@discardableResult
	func saveAsPNG(url: URL) -> Bool {
		guard let tiffData = self.tiffRepresentation else {
			print("failed to get tiffRepresentation. url: \(url)")
			return false
		}
		let imageRep = NSBitmapImageRep(data: tiffData)
		guard let imageData = imageRep?.representation(using: .PNG, properties: [:]) else {
			print("failed to get PNG representation. url: \(url)")
			return false
		}
		do {
			try imageData.write(to: url)
			return true
		} catch {
			print("failed to write to disk. url: \(url)")
			return false
		}
	}
}

Solution 5 - Cocoa

Swift 4.2 Solution

public extension NSImage {
    public func writePNG(toURL url: URL) {
        
        guard let data = tiffRepresentation,
              let rep = NSBitmapImageRep(data: data),
              let imgData = rep.representation(using: .png, properties: [.compressionFactor : NSNumber(floatLiteral: 1.0)]) else {
                
            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) No tiff rep found for image writing to \(url)")
            return
        }
        
        do {
            try imgData.write(to: url)
        }catch let error {
            Swift.print("\(self) Error Function '\(#function)' Line: \(#line) \(error.localizedDescription)")
        }
    }
}

Solution 6 - Cocoa

Swift Style:

if let imgRep = image?.representations[0] as? NSBitmapImageRep
{
      if let data = imgRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [:])
      {
           data.writeToFile("/path/to/file.png", atomically: false)
      }
}

Solution 7 - Cocoa

To help with cross-platform code, I implemented a version ofUIImagePNGRepresentation() that runs on Mac (and uses NSImage).

#if os(macOS)

public func UIImagePNGRepresentation(_ image: NSImage) -> Data? {
    guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
        else { return nil }
    let imageRep = NSBitmapImageRep(cgImage: cgImage)
    imageRep.size = image.size // display size in points
    return imageRep.representation(using: .png, properties: [:])
}

#endif

Usage:

if let data = UIImagePNGRepresentation(myImage) {
    do { try data.write(to: url, options: [.atomic]) }
    catch let error { print("Error writing image (\(error))") }
}

Solution 8 - Cocoa

Just one more guaranteed to work approach using SWIFT:

I have an "Image Well" where to user can drop any image. And this "Image Well" has an image property (of type NSImage) accessed via outlet:

@IBOutlet weak var imageWell: NSImageView!

And the code that saves this image (you can put it inside the button action) is:

if imageWell.image != nil {
   let bMImg = NSBitmapImageRep(data: (imageWell?.image?.TIFFRepresentation)!)
   let dataToSave = bMImg?.representationUsingType(NSBitmapImageFileType.NSJPEGFileType, properties: [NSImageCompressionFactor : 1])
   dataToSave?.writeToFile("/users/user/desktop/image.jpg", atomically: true)
}

In the 1st line of the given code we check if our Image Well has an image.

In the 2nd line we make a bitmap representation of that image.

In the 3rd line we convert our BitmapRepresentation to a JPG type with a compression factor set to "1" (no compression).

In the 4th line we save the JPG data with a given path. "atomically: true" means that the file is saved as one piece and that assures us that the operation will be successfull.

N.B.: You can use another NSBitmapImageFileType in the 3rd line, to save your image to another format. It has plenty:

NSBitmapImageFileType.NSBMPFileType
NSBitmapImageFileType.NSGIFFileType
NSBitmapImageFileType.NSPNGFileType

etc.

Solution 9 - Cocoa

Objc solution with and without compression

NSImage* image;

// No compression
NSData* data = [image TIFFRepresentation];
// With compression
NSData* data = [image
                TIFFRepresentationUsingCompression:NSTIFFCompressionLZW
                factor:0.5f];

if (data != nil) {
    NSBitmapImageRep* bitmap = [[NSBitmapImageRep alloc] initWithData:data];
    if (bitmap != nil) {
        NSData* bitmapData = [bitmap
                              representationUsingType:NSBitmapImageFileTypePNG
                              properties:@{}];
        if (bitmapData != nil) {
            [bitmapData
             writeToFile:<file_path>
             atomically:true];
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionburkiView Question on Stackoverflow
Solution 1 - CocoaPegolonView Answer on Stackoverflow
Solution 2 - Cocoagarph0View Answer on Stackoverflow
Solution 3 - CocoaArvinView Answer on Stackoverflow
Solution 4 - CocoaneoneyeView Answer on Stackoverflow
Solution 5 - CocoaCharlton ProvatasView Answer on Stackoverflow
Solution 6 - CocoaPeter KreinzView Answer on Stackoverflow
Solution 7 - CocoaRobin StewartView Answer on Stackoverflow
Solution 8 - CocoaGerman G.View Answer on Stackoverflow
Solution 9 - CocoaAlexView Answer on Stackoverflow