0

I need to allow the user to save an NSImage to a local file.

So I am using those extensions from this SO answer to can save the image in PNG Format

extension NSBitmapImageRep {
    var png: Data? {
        return representation(using: .png, properties: [:])
    }
}
extension Data {
    var bitmap: NSBitmapImageRep? {
        return NSBitmapImageRep(data: self)
    }
}
extension NSImage {
    var png: Data? {
        return tiffRepresentation?.bitmap?.png

    }
    func savePNG(to url: URL) -> Bool {
        do {
            try png?.write(to: url)
            return true
        } catch {
            print(error)
            return false
        }

    }
}

Is there an easier way to save NSImage in different formats like JPEG,TIFF,BMP etc

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
techno
  • 6,100
  • 16
  • 86
  • 192
  • `func save(to url: URL, as format: ImageFormat = .PNG)` where `ImageFormat` is an enum you define? Although, I'd argue for just extending `Data` to include the `save` function, so then your code is just `image.png?.save()` or `image.tiff?.save()`, etc. – nhgrif Sep 26 '17 at 19:49
  • @nhgrif Thanks.. but I'm new to SWIFT .... can you explain it better as an answer. – techno Sep 27 '17 at 09:56

2 Answers2

3

You can create a custom method to allow you to specify any image type and also the directory where you would like to save your NSImage. You can also set a default value to the destination directory as the current directory, so if you don't pass the directory url it will save to the current one:

extension NSImage {
    func save(as fileName: String, fileType: NSBitmapImageRep.FileType = .jpeg, at directory: URL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)) -> Bool {
        guard let tiffRepresentation = tiffRepresentation, directory.isDirectory, !fileName.isEmpty else { return false }
        do {
            try NSBitmapImageRep(data: tiffRepresentation)?
                .representation(using: fileType, properties: [:])?
                .write(to: directory.appendingPathComponent(fileName).appendingPathExtension(fileType.pathExtension))
            return true
        } catch {
            print(error)
            return false
        }
    }
}

You will need also to make sure the url passed to your method is a directory url. You can use URL resourceValues method to get the url isDirectoryKey value and check if it is true:

extension URL {
    var isDirectory: Bool {
       return (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
    }
}

You can also extend NSBitmapImageRep.FileType to provide the associated file path extension:

extension NSBitmapImageRep.FileType {
    var pathExtension: String {
        switch self {
        case .bmp:
            return "bmp"
        case .gif:
            return "gif"
        case .jpeg:
            return "jpg"
        case .jpeg2000:
            return "jp2"
        case .png:
            return "png"
        case .tiff:
            return "tif"
        }
    }
}

Playground Testing:

let desktopDirectory = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!
// lets change the current directory to the desktop directory
FileManager.default.changeCurrentDirectoryPath(desktopDirectory.path)

// get your nsimage
let picture  = NSImage(contentsOf: URL(string: "https://i.stack.imgur.com/Xs4RX.jpg")!)!

// this will save to the current directory
if picture.save(as: "profile") {
    print("file saved as profile.jpg which is the default type")
}
if picture.save(as: "profile", fileType: .png) {
    print("file saved as profile.png")
}
if picture.save(as: "profile", fileType: .tiff) {
    print("file saved as profile.tif")
}
if picture.save(as: "profile", fileType: .gif) {
    print("file saved as profile.gif")
}
if picture.save(as: "profile", fileType: .jpeg2000) {
    print("file saved as profile.jp2")
}

// you can also chose a choose another directory without the need to change the current directory
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
if picture.save(as: "profile", at: url) {
    print("file saved as profile.jpg at documentDirectory")
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

You can get a BMP, GIF, JPEG, JPEG2000, PNG, or a TIFF by converting your NSImage to a NSBitmapImageRep and then using its representation(using:) method, but it's a bit of a pain; you have to create an empty NSBitmapImageRep, set it as the current graphics context, and draw your NSImage into it (see this answer for details).

If you can require macOS 10.13, there are some handy methods on CIContext that are, in my opinion, easier to use, and which can convert an image to TIFF, JPEG, or PNG (no BMP, GIF, or JPEG2000 though; also, there's a method to convert to HEIF that appears to be iOS only as of the time of this writing):

func getPNGData(image: NSImage) -> NSData? {
    if let ci = image.cgImage(forProposedRect: nil, context: nil, hints: nil).map({ CIImage(cgImage: $0) }),
        let png = CIContext().pngRepresentation(of: ci, format: kCIFormatRGBAf, colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!) {
        // I'm colorblind, so it's very possible that the constants above
        // were poorly chosen. Choose whatever makes your colors look right
        return png
    } else {
        return nil
    }
}
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • CIContext? It doesn't sound right. "easier to use"? – El Tomato Sep 26 '17 at 22:00
  • @ElTomato Compare the code snippet above with the `NSBitmapImageRep` version I linked to. – Charles Srstka Sep 26 '17 at 22:46
  • @CharlesSrstka I'm using Xcode version 8.3.3 and running on OSX Sierra.I'm new to OSX Development.I don't know the exact version of swift I'm using and whether the applications I develop using this version of Xcode will be compatible with older versions of OSX. – techno Sep 27 '17 at 09:53
  • I would prefer a method that has better compatibility .. but it seems there is lot to implement in swift for simply saving an image.In C# we do `bitmap.save()` – techno Sep 27 '17 at 09:54
  • @techno Then you should probably use the `NSBitmapImageRep`-based solution that I linked to. It's compatible with every version of macOS / OS X that you're likely to encounter. – Charles Srstka Sep 27 '17 at 15:30
  • That code seems complex for a beginner like me.Is there an easy way to do this .. May be using external libraries like https://github.com/gavinbunney/Toucan.. Please suggest – techno Sep 27 '17 at 17:18
  • @techno Not that I know of. It's long been a well-known limitation in the API. Although, the fact that the code is in the linked answer for you to copy and paste into your project makes it considerably easier than doing it from scratch. ;-) – Charles Srstka Sep 27 '17 at 17:28
  • okay:) but after conversion to `NSBitmapImageRep`..How can I save the image in different formats..In the example you have linked there no mention of it. – techno Sep 27 '17 at 17:41
  • @techno `NSBitmapImageRep` has a method called `representation(using:)` which lets you export as data in a number of formats. – Charles Srstka Sep 27 '17 at 18:20
  • This helped me https://stackoverflow.com/a/38593987/848968 .. Hope its the right way to do it. – techno Sep 27 '17 at 18:34