将UIImage转换为原始NSData / 避免压缩

3
我有自己的图像下载器类,它持有一个队列并逐个(或一次性地)下载图像,将它们写入缓存文件夹,并在需要时从缓存文件夹中检索它们。我还有一个UIImageView子类,可以通过它传递URL,通过图像下载器类来查看设备上是否已存在该图像,如果存在则显示它,否则在下载完成后下载并显示它。
当图像下载完成后,我执行以下操作。我从下载的NSData创建一个UIImage,将下载的NSData保存到磁盘并返回UIImage。
// This is executed in a background thread
downloadedImage = [UIImage imageWithData:downloadedData];
BOOL saved = [fileManager createFileAtPath:filePath contents:downloadedData attributes:attributes];
// Send downloadedImage to the main thread and do something with it

为了检索现有的图片,我这样做。
// This is executed in a background thread
if ([fileManager fileExistsAtPath:filePath])
{
    NSData* imageData = [fileManager contentsAtPath:filePath];
    retrievedImage = [UIImage imageWithData:imageData];
    // Send retrievedImage to the main thread and do something with it
}

如您所见,我总是直接从下载的NSData创建UIImage,而不是使用UIImagePNGRepresentation创建NSData,因此图像永远不会被压缩。当您从压缩的NSData创建UIImage时,UIImage将在在主线程上渲染之前对其进行解压缩,从而阻止UI。由于我现在有一个包含许多小图像的UITableView,这些图像必须从磁盘下载或检索,如果出现这种情况,将会使滚动速度变慢,这是不可接受的。
现在是我的问题。用户还可以从相机胶卷中选择照片,保存它,然后它也必须出现在我的UITableView中。但是我似乎找不到一种方法将相机胶卷中的UIImage转换为NSData,而不使用UIImagePNGRepresentation。所以这是我的问题。
如何将UIImage转换为未压缩的NSData,以便稍后使用imageWithData将其转换回UIImage,使其在渲染之前不必解压缩?
或者
是否有任何方法可以在将UIImage发送到主线程之前进行解压缩并将其缓存,从而只需解压缩一次?
感谢您的帮助。
3个回答

4
您实际上想问的是如何以未压缩的NSData格式将UIImage转换为数据,以便稍后使用imageWithData将其转换回UIImage,从而在呈现之前无需解压缩。我建议您使用ImageIO框架。通过图像目标进行保存,通过图像源进行获取。这样可以快速读取UIImage并存储在磁盘上。

http://developer.apple.com/library/ios/#documentation/GraphicsImaging/Conceptual/ImageIOGuide/ikpg_dest/ikpg_dest.html

有没有办法在将UIImage发送到主线程之前进行解压缩并将其缓存,以便只需解压缩一次?

是的,好问题。这将是我的第二个建议:使用线程。这是人们经常处理表格时必须做的事情。当表格请求图像时,您要么已经拥有图像,要么没有。如果没有,您提供一个占位图像,并在后台获取真实图像。当真实图像准备就绪时,您已经安排好了通知。回到主线程,您告诉表视图再次请求该行的数据;这次您已经拥有了图像并提供它。用户因此会在图像出现之前看到轻微的延迟。我相信你已经看过很多这样的应用程序(《纽约时报》是一个很好的例子)。

我有一个进一步的建议,可能是最好的建议。您谈到从磁盘解压图像需要时间。但是,如果图像很小,这应该不需要任何时间。但是图像应该是小的,因为它将放入一个小的地方-一个表格单元格。换句话说,您应该在收到它们时提前缩小图像,以便在需要时准备好每个图像的小版本。向小空间提供大图像是浪费时间和内存的巨大浪费。
稍后添加:当然,如果您没有保存图像到磁盘上,很多担心都是不必要的。我不清楚为什么您需要这样做。希望您有一个充分的理由;但是,如果您只是将图像保留在内存中,则速度明显更快。

你好,感谢您的回复。 我已经在使用后台线程来加载图像。这些图像的大小与它们在tableView中显示的大小相同,因此它们尽可能小。并且一旦从磁盘加载它们,我会尽可能长时间地将它们保存在内存中。但是所有这些仍然不够,在浏览tableView时解压缩大约需要1/3的时间。 我使用了您的建议来使用ImageIO,在CGImageSourceCreateImageAtIndex中,我将kCGImageSourceShouldCache设置为YES。现在它将未压缩版本保存在内存中,并且呈现速度更快。谢谢。 - David
非常好!不过我还有一点担心,希望你在用户在实际设备上滚动表视图时能使用Instruments检查并确保一切正常。 - matt
我确实使用了时间分析器,这就是我发现解压缩导致如此巨大延迟的原因。copyImageBlockSetPNG和png_read_now是占用1/3时间的函数。我正在iPhone 3GS和iPod Touch 4G上进行测试,这是我们应用程序支持的两个最慢的设备。即使我关闭缓存并每次从磁盘加载图像时它们变得可见,我只能轻微地看到空的imageView,然后图像就会显示出来,但UI根本不会挂起。而且开启缓存后,完全无法察觉。这非常好,所以再次感谢。 - David
非常好,感谢您提供那份报告。这是一个有趣的问题,我很高兴ImageIO框架能够帮助解决它;它真的是iOS中不错的补充,但却鲜为人知。 - matt

2
我发现了解决方案:

CGImageRef downloadedImageRef = downloadedImage.CGImage;
CGDataProviderRef provider = CGImageGetDataProvider(downloadedImageRef);
NSData *data = CFBridgingRelease(CGDataProviderCopyData(provider));
// Then you can save the data

0
如果您下载数据并将其保存到磁盘上,则数据会以PNG、JPEG或GIF格式进行压缩。您不会下载未经压缩的图像数据。因此,在将文件保存到磁盘之前,您需要先解压缩。在保存之前解压缩会使文件变得更大,但这意味着在将数据读回CGImageRef或UIImage之前不需要解压缩。加载和解压缩大量图像是导致CPU速度变慢和滚动缓慢的原因。但是,简单地将所有内容保持在内存中已经解压缩并不是一个解决方案,因为这将使用完所有应用程序内存并在不久之后崩溃您的手机。您可能可以通过一些小数量的图像来解决这个问题,但这是您在编写代码时需要解决的基本设计缺陷。如果您愿意,您可以查看我关于此主题的博客文章video-and-memory-usage-on-ios-devices,该文章涉及视频,但处理大量不同图像时也存在相同的问题。我建议您将小图像以未压缩的格式(如TIFF或BMP)写入磁盘,这样只要ImageIO支持该特定格式,就可以轻松地将它们读回来。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接