问题
How can I save a NSImage as a new file (png, jpg, ...) in a certain directory?
回答1:
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];
回答2:
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.
回答3:
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...
- Create a context with new size: CGBitmapContextCreate()
- Get the an image ref from index: CGImageSourceCreateImageAtIndex()
- Get a copy of the metadata: CGImageSourceCopyPropertiesAtIndex()
- Draw the image into the context: CGContextDrawImage()
- Get the resized image from the context: CGBitmapContextCreateImage()
- 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!
回答4:
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
}
}
}
回答5:
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)")
}
}
}
回答6:
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)
}
}
回答7:
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.
回答8:
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))") }
}
来源:https://stackoverflow.com/questions/3038820/how-to-save-a-nsimage-as-a-new-file