在 iOS 上缩小图像最节省内存的方法是啥?
Posted
技术标签:
【中文标题】在 iOS 上缩小图像最节省内存的方法是啥?【英文标题】:What is the most memory-efficient way of downscaling images on iOS?在 iOS 上缩小图像最节省内存的方法是什么? 【发布时间】:2012-10-26 10:04:04 【问题描述】:在后台线程中,我的应用程序需要从磁盘读取图像,将它们缩小到屏幕大小(1024x768 或 2048x1536)并将它们保存回磁盘。原始图像大部分来自相机胶卷,但其中一些可能具有更大的尺寸(例如 3000x3000)。
稍后,在不同的线程中,这些图像会经常缩小到 500x500 左右的不同尺寸并再次保存到磁盘。
这让我想知道:在 ios 中,性能和内存方面最有效的方法是什么?我使用了两种不同的 API:
使用 ImageIO 中的CGImageSource
and CGImageSourceCreateThumbnailAtIndex
;
drawing to CGBitmapContext
并使用 CGImageDestination
将结果保存到磁盘。
两者都对我有用,但我想知道它们在性能和内存使用方面是否有任何差异。当然,如果有更好的选择。
【问题讨论】:
我已经按照this question 中的描述以“老式方式”完成了它——通过一点点玩弄。那里的技术可能用于在“块”中进行缩放,以避免过度使用内存,但这需要一点工作。 (不过,并不是那么神秘。) 您在 Instruments 中查看过这两种方法的结果吗? @foggzilla:不,我还没有时间。我开始了赏金,希望有人能做这项研究。 【参考方案1】:如果两者中的任何一个更有效,那么它将是前者。
当您创建CGImageSource
时,您创建的正是其名称所说的——某种可以从中获取图像的不透明事物。在您的情况下,它将是对磁盘上事物的引用。当您要求 ImageIO 创建缩略图时,您明确告诉它“尽可能多地输出这么多像素”。
相反,如果您绘制到 CGBitmapContext
,那么在某些时候您会明确地将整个图像带入内存。
因此,第二种方法肯定会在某个时间点将整个图像一次存储在内存中。相反,前者不一定需要(在实践中,ImageIO 中毫无疑问会进行某种猜测,以确定最佳方式)。因此,在操作系统的所有可能实现中,要么前者是有利的,要么两者之间没有区别。
【讨论】:
【参考方案2】:我会尝试使用基于 c 的库,例如 leptonica。我不确定 ios 是否使用相对较新的 Accelerate Framework 优化了 Core Graphics,但 CoreGraphics 可能仅涉及重新调整图像大小的开销。最后...如果您想推出自己的实现,请尝试使用 vImageScale_??format??以一些内存映射文件为后盾,我看不到任何更快的东西。
http://developer.apple.com/library/ios/#documentation/Performance/Conceptual/vImage/Introduction/Introduction.html
附言。还要确保检查编译器优化标志。
【讨论】:
您好,感谢您在这里发帖。正如我在赏金笔记中提到的那样,“我正在寻找一个有实际研究/丰富的 API 知识支持的答案——只是猜测并不能解决问题”。 我很确定 vImageScale w/mem map 将是速度限制,不需要太多的工作来试验它。下一个最快的是从内存映射定向到新缓冲区的原始间隔读取,但是这会因不同的图像大小而变得复杂,并且看起来很糟糕。 可能有用...***.com/questions/10068095/… That 令人印象深刻。 (在您链接到的问题中找到这个。)【参考方案3】:使用 libjpeg-turbo,您可以使用 jpeg_decompress_struct
的 scale_num
和 scale_denom
字段,它只会解码图像中需要的块。它在 4S 的后台线程中给了我 250 毫秒的解码 + 缩放时间,原始图像为 3264x2448(来自相机,图像数据放置在内存中)到 iPhone 的显示分辨率。我想对于这么大的图像来说还可以,但仍然不是很好。
(是的,这很节省内存。您几乎可以逐行解码和存储图像)
【讨论】:
嗨,您知道任何使用 libjpeg-turbo 的示例 ObjC/iOS 源代码吗? 我猜我找到了an example。【参考方案4】:你在推特上说的和你的问题不符。
如果您遇到内存峰值,请查看 Instruments 以找出消耗内存的原因。仅高分辨率图像的数据就是 10 兆,如果它们不包含 alpha 通道,则生成的图像将约为 750k。
第一个问题是保持较低的内存使用率,为此,请确保您加载的所有图像在使用完毕后立即释放,这将确保底层 C/Objective-C API 释放立即获取内存,而不是等待 GC 运行,例如:
using (var img = UIImage.FromFile ("...")
using (var scaled = Scaler (img))
scaled.Save (...);
至于缩放,有多种缩放图像的方法。最简单的方法是创建一个上下文,然后在其上绘制,然后将图像从上下文中取出。 MonoTouch 的 UIImage.Scale 方法是这样实现的:
public UIImage Scale (SizeF newSize)
UIGraphics.BeginImageContext (newSize);
Draw (new RectangleF (0, 0, newSize.Width, newSize.Height));
var scaledImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return scaledImage;
性能将取决于您启用的上下文功能。例如,更高质量的缩放将需要更改插值质量:
context.InterpolationQuality = CGInterpolationQuality.High
另一种选择是不在 CPU 上运行缩放,而是在 GPU 上运行。为此,您将使用 CoreImage API 并使用 CIAffineTransform 过滤器。
至于哪个更快,留给别人去衡量
CGImage Scale (string file)
var ciimage = CIImage.FromCGImage (UIImage.FromFile (file));
// Create an AffineTransform that makes the image 1/5th of the size
var transform = CGAffineTransform.MakeScale (0.5f, 0.5f);
var affineTransform = new CIAffineTransform ()
Image = ciimage,
Transform = transform
;
var output = affineTransform.OutputImage;
var context = CIContext.FromOptions (null);
return context.CreateCGImage (output, output.Extent);
【讨论】:
嘿 Miguel,感谢您花时间写这个答案。如果我的推文具有误导性,我很抱歉。 Instruments 显示即使我立即处理对象,也可能有 4 或 5 个在后台线程中调整大小的图像导致内存峰值。我将编写一个自定义任务调度程序来一次禁止过多的内存占用线程,但我很好奇我是否可以调整图像的大小首先不将它们完全加载到内存中,磁盘到磁盘,可以这么说。 内存映射... [NSData initWithContentsOfFile :?path?:NSDataReadingUncached:nil];【参考方案5】:虽然我不能肯定地说它会有所帮助,但我认为值得尝试将工作推到 GPU 上。您可以通过以给定大小渲染带纹理的四边形,或使用GPUImage 及其调整大小功能自行完成。虽然它在旧设备上存在一些纹理大小限制,但它应该比基于 CPU 的解决方案具有更好的性能
【讨论】:
GPUImage 方法非常消耗内存【参考方案6】:我认为如果你想节省内存,你可以从一个瓦片到另一个瓦片读取源图像并压缩瓦片并保存到目标瓦片。
有一个来自苹果的例子。它是执行的方式。
https://developer.apple.com/library/ios/samplecode/LargeImageDownsizing/Introduction/Intro.html
您可以下载此项目并运行它。它是MRC,所以你可以非常流畅地使用它。
希望对您有所帮助。 :)
【讨论】:
以上是关于在 iOS 上缩小图像最节省内存的方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
根据文件名中的日期模式从目录中读取文件子集的更快和更节省内存的方法是啥?
在 Julia 中定义一个非常稀疏的网络矩阵的最有效方法是啥?
在 Win/Linux 上使用 C/C++ 为大页面分配内存的最简单方法是啥? [关闭]