【问题标题】:How to save a NSImage as a new file如何将 NSImage 保存为新文件
【发布时间】:2011-03-03 14:26:30
【问题描述】:

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

【问题讨论】:

  • 我添加了赏金,因为有人称第一个选项为丑陋的黑客,我似乎无法在 google 上轻松找到看似正确和明确的答案,请更多投票/答案。

标签: cocoa image macos file nsimage


【解决方案1】:

你可以像这样向 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”的调用是必不可少的,否则您可能无法获得有效的图像。

【讨论】:

  • 但是 TIFF 是 RGB 并且会丢弃任何透明度信息。
  • 不,TIFF 可以很好地处理 Alpha 和多页图像。是什么让你产生了它没有的想法?这是 Apple 推荐的视网膜图像(多表示 TIFF)的目标格式。
  • TIFF 应该支持 alpha 和 Transparency en.wikipedia.org/wiki/Transparency_(graphic)
  • 支持 Gif 吗?
  • 什么是 ImageProps ?传递的这个值是什么?
【解决方案2】:

做这样的事情:

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

【讨论】:

  • 您应该以比假设它是第一个表示更可靠的方式获得位图图像表示。你也不能假设有一个;例如,如果图像是从 PDF 加载的,那么将会有 NSPDFImageRep,而不是 NSBitmapImageRep。
  • 这是错误的黑客攻击。请参阅下面的常规答案。
  • 如果有人得到[* representationUsingType:properties:]: unrecognized selector sent to instance,解决方法在这里:NSCGImageSnapshotRep, how get bitmapData
【解决方案3】:

不确定你们其他人的情况,但我更喜欢吃完整的墨西哥卷饼。上面描述的内容会起作用并且没有任何问题,但是我发现了一些遗漏的东西。在这里,我将强调我的观察:

  • 首先提供了最好的表示,似乎是 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);

  • 第 4 个参数为零。

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

对于多张图片,我们循环:

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

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

CGImageDestinationAddImageFromSource( imageDest, imageSource, 0, nil);

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

CGImageDestinationFinalize( imageDest );

所以 Mutable Data 从一开始就包含了我们所有的图像数据和元数据。

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

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

我的 NSData 类别方法如下所示:

+ (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. 现在将图像和元数据添加到 Dest Ref:CGImageDestinationAddImage()

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

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

好的,希望这可以帮助其他人,你和我现在一样充实!

哎呀,忘了说。当你得到 NSData 对象时,你可以随心所欲地使用它,例如 writeToFile、writeToURL,或者如果你愿意,可以创建另一个 NSImage。

编码愉快!

【讨论】:

  • 这是一个好的开始,当然比其他答案更好(即使那些被评为更高),但它并不完整。首先,此处介绍的逻辑本身不会处理 PDF 文件(CGImageDestinationAddImageFromSource 将不起作用,因为图像源将为零)。 PDF 和其他不可复制的图像源需要特殊处理(通过缩略图获取图像或以其他方式复制数据)。其次,它本身不会复制任何元数据,您需要通过从图像源中检索属性字典并将其添加到图像目标来显式地执行此操作。
  • 请教两个问题。 1)你怎么能确定 [anImage TIFFRepresentation] 总是返回一些东西?没有没有 TIFFRepresentation 的 NSImage 吗? 2) 您能否完整地为这些功能中的至少一个(比如 JPEPDataFromURL)提供一个工作顺序代码-sn-p?我尝试按照您的指示进行操作,至少对我而言 - 它不起作用(我正在尝试将传入的视频数据帧以 JPEG 格式保存到磁盘,并且我的输入来自 AVSession 的 CMSampleBuffer 通过 NSImage - 我尝试使用您的代码将其保存到文件中。
【解决方案4】:

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

【讨论】:

  • 让 tiffData = image.tiffRepresentation! let imageRep = NSBitmapImageRep(data: tiffData) let data = imageRep?.representation(using: .PNG, properties: [:]) do { try data?.write(to: URL(fileURLWithPath: path3), options: NSData.WritingOptions () ) } 捕捉 { print("(error)") }
【解决方案5】:

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

【讨论】:

  • 你能解释一下为什么你使用self.self而不是self吗?有什么区别吗?
  • 原来没有区别。也许这是在早期版本的 swift 中,但我认为它使用了打印显式类名,但看起来要么只是返回 &lt;&lt;Class-Name&gt; &lt;Memory-Address&gt;&gt;
【解决方案6】:

Swift 风格:

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】:

    为了帮助编写跨平台代码,我实现了一个在 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))") }
    }
    

    【讨论】:

    • 这对我来说效果很好,但我不得不删除 imageRep.size = image.size 代码以保持与原始图像大小相同。
    【解决方案8】:

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

    我有一个“图像井”,用户可以在其中放置任何图像。而这个“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)
    }
    

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

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

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

    在第 4 行中,我们使用给定路径保存 JPG 数据。 "atomically: true" 意味着文件被保存为一个片段,这向我们保证操作会成功。

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

    NSBitmapImageFileType.NSBMPFileType
    NSBitmapImageFileType.NSGIFFileType
    NSBitmapImageFileType.NSPNGFileType
    

    等等

    【讨论】:

      【解决方案9】:

      带和不带压缩的 Objc 解决方案

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-04
        • 1970-01-01
        • 2017-02-16
        • 1970-01-01
        • 2016-11-06
        • 2012-09-22
        • 1970-01-01
        • 2013-11-17
        相关资源
        最近更新 更多