如何将 NSImage 保存为新文件

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

56722 次浏览

做这样的事情:

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

您可以像下面这样向 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

调用“ TIFF 表示”是必不可少的,否则您可能得不到一个有效的图像。

不知道你们怎么想,不过我更喜欢吃我的玉米卷饼。上面描述的方法可行,没有什么问题,但是我发现了一些遗漏的地方。在这里,我要强调我的观察结果:

  • 首先提供的最佳表示似乎是72DPI 即使图像分辨率大于这个值,你也会输 决心
  • 其次,多页图像,如动画 GIF 或 继续,尝试一个动画 GIF,你会发现动画丢失
  • 最后,任何元数据,如 EXIF,GPS 等数据将丢失。

所以如果你想转换图像,你真的想失去这一切吗? 如果你想吃饱,那么让我们继续读下去..。

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

让我们开始吧:

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

单幅图像列表: JPG,PNG,BMP,JPEG-2000

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

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

NSMutableData * imageData    = [NSMutableData data];

第二,获得一个 CGImageSourceRef。听起来已经很丑了,不是吗。这没有那么糟糕,让我们继续... ... 您真正想要的是源图像,而不是一个表示或 NSImage 数据块。不过,我们确实有个小问题。源代码可能不兼容,因此请确保根据 CGImageSourceCopyTypeIdentifier ()中列出的代码检查 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,确保它不是 null,然后让我们现在得到一个 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 分类方法如下:

+ (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. 从 index: CGImageSourceCreateImageAtIndex ()获取图像引用
  3. 获取元数据的副本: CGImageSourceCopyPropertiesAtIndex ()
  4. 将图像绘制到上下文中: CGContextDrawImage ()
  5. 从上下文获取调整大小的图像: CGBitmapContextCreateImage ()
  6. 现在将图像和元数据添加到 Dest Ref: CGImageDestinationAddImage ()

对源代码中嵌入的多个图像进行清洗和重复。

ICO 和 ICNS 之间的唯一区别是一个是一个图像,而另一个是一个文件中的多个图像。我打赌你能猜到哪个是哪个!;-) 对于这些格式,您必须调整到一个特定的大小,否则错误将随之而来。 不过在使用适当的 UTI 时,这个过程是完全相同的,只是调整大小要严格一些。

好吧,希望这对其他人有所帮助,你现在和我一样饱了!

哎呀,忘了说了。当您获得 NSData 对象时,可以根据需要使用它,如 writeToFile、 writeToURL 或者如果需要,可以创建另一个 NSImage。

编程愉快!

迅捷风格:

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 的可靠方法:

我有一个“图像井”,用户可以下降任何图像。这个“ 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行中,我们将 BitmapExpression 转换为 JPG 类型,并将压缩因子设置为“1”(无压缩)。

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

注意: 你可以在第三行使用另一个 NSBitmapImageFileType 来保存你的图像到另一种格式,它有很多:

NSBitmapImageFileType.NSBMPFileType
NSBitmapImageFileType.NSGIFFileType
NSBitmapImageFileType.NSPNGFileType

等等。

保存为 PNG 使用 Swift 3

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

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

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

有和没有压缩的目标解决方案

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