how to get png representation of an NSImage object in Swift
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
get PNG representation of NSImage in swift
The documentation says:
func representationUsingType(_ storageType: NSBitmapImageFileType,
properties properties: [NSObject : AnyObject]) -> NSData?
So it expects a dictionary, not a nil value. Supply an empty dict like this:
var pngCoverImage = bitmap!.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: [:])
Only if an Optional is specified (that is it where [NSObject : AnyObject]?)
you could pass a nil value.
How to save PNG file from NSImage (retina issues)
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];
}
NSImage to NSData as PNG Swift
You can use the NSImage
property TIFFRepresentation to convert your NSImage
to NSData
:
let imageData = yourImage.TIFFRepresentation
If you need to save your image data to a PNG file you can use NSBitmapImageRep(data:)
and representationUsingType
to create an extension to help you convert Data
to PNG format:
Update: Xcode 11 • Swift 5.1
extension NSBitmapImageRep {
var png: Data? { representation(using: .png, properties: [:]) }
}
extension Data {
var bitmap: NSBitmapImageRep? { NSBitmapImageRep(data: self) }
}
extension NSImage {
var png: Data? { tiffRepresentation?.bitmap?.png }
}
usage
let picture = NSImage(contentsOf: URL(string: "https://i.stack.imgur.com/Xs4RX.jpg")!)!
let imageURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first!.appendingPathComponent("image.png")
if let png = picture.png {
do {
try png.write(to: imageURL)
print("PNG image saved")
} catch {
print(error)
}
}
Saving NSImage in Different Formats Locally
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")
}
NSImage missing alpha channel
Set alpha to true in NSBitmapImageRep:
let imageRep = NSBitmapImageRep(data: your_data)
imageRep?.hasAlpha = true
Get Image from CALayer or NSView (swift 3)
Here are some NSView
options:
extension NSView {
/// Get `NSImage` representation of the view.
///
/// - Returns: `NSImage` of view
func image() -> NSImage {
let imageRepresentation = bitmapImageRepForCachingDisplay(in: bounds)!
cacheDisplay(in: bounds, to: imageRepresentation)
return NSImage(cgImage: imageRepresentation.cgImage!, size: bounds.size)
}
}
Or
extension NSView {
/// Get `Data` representation of the view.
///
/// - Parameters:
/// - fileType: The format of file. Defaults to PNG.
/// - properties: A dictionary that contains key-value pairs specifying image properties.
/// - Returns: `Data` for image.
func data(using fileType: NSBitmapImageRep.FileType = .png, properties: [NSBitmapImageRep.PropertyKey: Any] = [:]) -> Data {
let imageRepresentation = bitmapImageRepForCachingDisplay(in: bounds)!
cacheDisplay(in: bounds, to: imageRepresentation)
return imageRepresentation.representation(using: fileType, properties: properties)!
}
}
Some CALayer
options:
extension CALayer {
/// Get `NSImage` representation of the layer.
///
/// - Returns: `NSImage` of the layer.
func image() -> NSImage {
let width = Int(bounds.width * contentsScale)
let height = Int(bounds.height * contentsScale)
let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
imageRepresentation.size = bounds.size
let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!
render(in: context.cgContext)
return NSImage(cgImage: imageRepresentation.cgImage!, size: bounds.size)
}
}
Or
extension CALayer {
/// Get `Data` representation of the layer.
///
/// - Parameters:
/// - fileType: The format of file. Defaults to PNG.
/// - properties: A dictionary that contains key-value pairs specifying image properties.
///
/// - Returns: `Data` for image.
func data(using fileType: NSBitmapImageRep.FileType = .png, properties: [NSBitmapImageRep.PropertyKey: Any] = [:]) -> Data {
let width = Int(bounds.width * contentsScale)
let height = Int(bounds.height * contentsScale)
let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)!
imageRepresentation.size = bounds.size
let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!
render(in: context.cgContext)
return imageRepresentation.representation(using: fileType, properties: properties)!
}
}
MacOS - How to programmatically count bytes for an image in Swift?
As you probably already noticed there is no pngData
method for NSImage
. You need to first get the image tiffRepresentation
, initialize a new NSBitmapImageRep
object and get the png
storageType representation:
let data = NSBitmapImageRep(data: NSImage(imageLiteralResourceName: "toto").tiffRepresentation!)!.representation(using: .png, properties: [:])!
print(data.count)
Related Topics
"This Class Is Not Key Value Coding-Compliant" Using Coreimage
Cannot Assign to Value: 'self' Is Immutable
Can't Load Images on MAC Screensaver Release Build (It Works on Xcode Debug Build)
Swift Protocol Extension Implementing Another Protocol with Shared Associated Type
Sizing a UIpickerview Inside a UIalertview
Convert from Nsdictionary to [String:Any]
Swift: Parse, Query Date Field Based on Nsdate()
Navigationlink Doesn't Work with New .Searchable Modifier on Swiftui 3.0
How to Correctly Use Shouldcompactonlaunch in Realmswift
Swift Playground with Debugger Support
What Was The Reason for Swift Assignment Evaluation to Void
Uimarkuptextprintformatter and MAC Catalyst
Easiest Way to Truncate Float to 2 Decimal Places
Pfobject Unable to Be Cast to Custom Subclass
Detecting Swipes on All Four Directions on Watchkit Using The Storyboard
Answers by Crashlytics - Adding Custom Event