如何将 NSImage 保存为新文件

发布于 2024-09-06 06:21:54 字数 45 浏览 8 评论 0原文

如何将 NSImage 保存为某个目录中的新文件(png、jpg、...)?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

無心 2024-09-13 06:21:54

您可以像这样向 NSImage 添加一个类别。

@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

对“TIFFRepresentation”的调用至关重要,否则您可能无法获得有效的图像。

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.

菊凝晚露 2024-09-13 06:21:54

做这样的事情:

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

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];
时光暖心i 2024-09-13 06:21:54

不知道你们其他人怎么样,但我更喜欢吃完整的玉米卷饼。上面描述的内容是可行的,没有任何问题,但我发现遗漏了一些东西。在这里我将强调我的观察:

  • 首先提供了最佳表示,似乎是 72 DPI
    即使图像分辨率大于该值。所以你输了
    分辨率
  • 其次,多页图像(例如动画 GIF 或动画中的图像)又如何呢?
    PDF。来吧,尝试一个动画 GIF,你会发现动画丢失了。
  • 最后,所有元数据,如 EXIF、GPS 等……数据都会丢失。

因此,如果您想转换该图像,您真的想失去所有这些吗?
如果你想吃饱饭,那么让我们继续读下去……

有时,我的意思是,有时没有什么比好的老式学校发展更好的了。是的,这意味着我们必须做一些工作!

让我们开始吧:

我在 NSData 中创建一个类别。这些是类方法,因为您希望这些东西是线程安全的,并且没有什么比将您的东西放在堆栈上更安全的了。有两种方法,一种用于非多页图像的输出,一种用于多页图像的输出。

单个图像列表: JPG、PNG、BMP、JPEG-2000

多个图像列表: PDF、GIF、TIFF

首先在内存中创建一个可变数据空间。

NSMutableData * imageData    = [NSMutableData data];

其次获取 CGImageSourceRef。是的,听起来已经很丑了不是吗?这并没有那么糟糕,让我们继续...您确实想要源图像而不是表示或 NSImage 数据块。但是,我们确实有一个小问题。源可能不兼容,因此请确保根据 CGImageSourceCopyTypeIdentifiers() 中列出的内容检查您的 UTI

一些代码:

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 );

等一下,为什么 NSImage 在那里?有些格式没有元数据,CGImageSource 也不支持,但这些是有效的图像。一个例子是旧式 PICT 图像。

现在我们有了一个 CGImageSourceRef,确保它不为零,然后让我们得到一个 CGImageDestinationRef。哇,所有这些参考文献都需要跟踪。到目前为止我们是 2!

我们将使用这个函数: CGImageDestinationCreateWithData()

  • 第一个参数是你的 imageData (cast CFMutableDataRef)
  • 第二个参数是你的输出 UTI,记住上面的列表。 (例如
    kUTTypePNG)
  • 第三个参数是要保存的图像数量。对于单个图像文件来说
    是 1 否则你可以简单地使用以下内容:

    CGImageSourceGetCount( imageSource );

  • 第四个参数为零。

检查您是否有此 CGImageDestinationRef,现在让我们将源中的图像添加到其中...这还将包括任何/所有元数据并保留分辨率。

对于多个图像,我们循环:

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

对于单个图像,它是索引 0 处的一行代码:

CGImageDestinationAddImageFromSource( imageDest, imageSource, 0, nil);

好的,完成它,将其写入磁盘或数据容器:

CGImageDestinationFinalize( imageDest );

这样,可变数据从一开始就包含了我们所有的图像数据和元数据。

我们完成了吗?几乎,即使有垃圾收集,你也必须清理!
记住两个 Ref,一个用于源,一个用于目标,因此执行 CFRelease()

现在我们完成了,您最终得到的是一个转换后的图像,保留其所有元数据、分辨率等...

我的 NSData 类别方法看起来像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;

调整大小或 ICO / ICNS 怎么样?
这是另一天的内容,但总而言之,您首先要解决调整大小问题...

  1. 创建具有新大小的上下文: CGBitmapContextCreate()
  2. 从索引获取图像引用: CGImageSourceCreateImageAtIndex()
  3. 获取元数据的副本: CGImageSourceCopyPropertiesAtIndex()
  4. 绘制图像进入上下文: CGContextDrawImage()
  5. 从上下文获取调整大小的图像: CGBitmapContextCreateImage()
  6. 现在将图像和元数据添加到目标引用: CGImageDestinationAddImage()

冲洗并重复嵌入源中的多个图像。

ICO 和 ICNS 之间的唯一区别是,一个是单个图像,而另一个是一个文件中的多个图像。我打赌你能猜出哪个是哪个?! ;-)
对于这些格式,您必须将大小调整到特定大小,否则会出现错误。
该过程与使用正确的 UTI 的过程完全相同,但调整大小更严格一些。

好吧,希望这可以帮助其他人,并且你和我现在一样饱!

哎呀,忘记说了。当你获得 NSData 对象时,可以按照你想要的方式使用它,例如 writeToFile、writeToURL,或者如果你愿意的话,创建另一个 NSImage。

快乐编码!

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!

失去的东西太少 2024-09-13 06:21:54

使用 swift3 另存为 PNG

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
        }
    }
}

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
        }
    }
}
删除→记忆 2024-09-13 06:21:54

Swift 4.2 解决方案

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)")
        }
    }
}

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)")
        }
    }
}
请叫√我孤独 2024-09-13 06:21:54

斯威夫特风格:

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

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)
      }
}
感情废物 2024-09-13 06:21:54

为了帮助跨平台代码,我实现了一个在 Mac 上运行的 UIImagePNGRepresentation() 版本(并使用 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

用法:

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

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))") }
}
溇涏 2024-09-13 06:21:54

带压缩和不带压缩的目标解决方案

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];
        }
    }
}

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];
        }
    }
}
咽泪装欢 2024-09-13 06:21:54

使用 SWIFT 的另一种保证有效的方法是:

我有一个“Image Well”,用户可以在其中放置任何图像。这个“Image Well”有一个通过插座访问的图像属性(NSImage 类型):

@IBOutlet weak var imageWell: NSImageView!

保存该图像的代码(您可以将其放在按钮操作中)是:

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)
}

在给定代码的第一行中,我们检查我们是否图像 井有一个图像。

在第二行中,我们制作该图像的位图表示。

在第三行中,我们将 BitmapRepresentation 转换为 JPG 类型,并将压缩因子设置为“1”(无压缩)。

在第 4 行中,我们使用给定路径保存 JPG 数据。 “atomically: true”意味着文件被保存为一个整体,这确保我们操作将会成功。

注意:您可以在第三行中使用另一个 NSBitmapImageFileType,将图像保存为其他格式。它有很多:

NSBitmapImageFileType.NSBMPFileType
NSBitmapImageFileType.NSGIFFileType
NSBitmapImageFileType.NSPNGFileType

等等。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文