Most memory efficient way to resize bitmaps on android?

我正在构建一个图像密集型的社交应用程序,图像可以从服务器发送到设备上。当设备的屏幕分辨率较小时,我需要在设备上调整位图的大小,以匹配其预期的显示大小。

问题是,使用 CreateScaledBitmap (android.Graphics.Bitmap,% 20int,% 20int,% 20boolean)”> createScaledBitmap 会导致我在调整大量缩略图之后遇到许多内存不足的错误。

在 Android 上调整位图大小的最有效的内存方法是什么?

15271 次浏览

这个问题的答案总结如下: 高效地加载大型位图 which explains how to use inSampleSize to load a down-scaled bitmap 版本。

In particular Pre-scaling bitmaps explains the details of various 方法,如何组合它们,以及哪些方法的内存效率最高。

在 Android 上有三种主要的调整位图大小的方法,它们具有不同的内存属性:

createScaledBitmap API

这个 API 将采用一个现有的位图,并创建一个新的位图与精确的尺寸你已经选择。

从好的方面来说,你可以得到你想要的图片大小(不管它看起来如何)。但坏处是 这个 API 需要一个 < strong > 现有的 位图才能工作。这意味着在能够创建一个新的、更小的版本之前,必须先加载、解码和创建一个位图。这在获取精确的维度方面是理想的,但是在额外的内存开销方面是可怕的。因此,对于大多数倾向于内存意识的应用程序开发者来说,这是一种交易破坏者

InSampleSize 标志

BitmapFactory.Options有一个属性称为 inSampleSize,它可以在解码时调整图像的大小,以避免解码为临时位图。这里使用的整数值将以缩小后的1/x 大小加载图像。例如,将 inSampleSize设置为2将返回一半大小的图像,将其设置为4将返回1/4大小的图像。基本上,图像大小总是比源大小小两倍。

从内存的角度来看,使用 inSampleSize是一个非常快的操作。实际上,它只会将图像的每个 Xth 像素解码到生成的位图中。但 inSampleSize有两个主要问题:

  • 它没有给出精确的分辨率 ,只是将位图的大小减小了一些2的幂。

  • 它不能产生最好的质量调整大小 。大多数调整大小的过滤器通过读取像素块,然后对它们进行加权来生成有问题的调整大小的像素,从而生成好看的图像。inSampleSize通过每读取几个像素就避免了这一切。结果是性能非常好,内存很低,但是质量受到影响。

If you're only dealing with shrinking your image by some pow2 size, and filtering isn't an issue, then you can't find a more memory efficient (or performance efficient) method than inSampleSize.

标度,密度,目标密度标志

如果你需要缩放图像到一个不等于2的维度,那么你需要 BitmapOptionsinScaledinDensityinTargetDensity标志。当设置了 inScaled标志后,系统将通过将 inTargetDensity除以 inDensity值来获得应用于位图的缩放值。

mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth;


// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);

使用这种方法可以重新调整图像的大小,并对其应用一个“调整大小过滤器”,也就是说,最终的结果看起来会更好,因为在调整大小的步骤中已经考虑了一些额外的数学问题。但是要注意: 额外的过滤步骤,需要额外的处理时间,并且可以快速地为大图像累加,导致调整大小缓慢,并且为过滤器本身分配额外的内存。

It’s generally not a good idea to apply this technique to an image that’s significantly larger than your desired size, due to the extra filtering overhead.

魔法组合

从内存和性能的角度来看,您可以组合这些选项以获得最佳结果。(设置 inSampleSizeinScaledinDensityinTargetDensity标志)

inSampleSize将首先应用于图像,使其达到比目标大小大两倍的下一次幂。然后,使用 inDensityinTargetDensity将结果缩放到您想要的精确尺寸,应用过滤操作来清理图像。

结合这两个是一个更快的操作,因为 inSampleSize步骤将减少像素的数量,由此产生的密度为基础的步骤将需要应用它的调整大小过滤器上。

mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity =  dstWidth * mBitmapOptions.inSampleSize;


// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);

如果您需要适合特定尺寸的图像,还有一些更好的过滤,那么这种技术是获得正确大小的最佳桥梁,但在一个快速,低内存占用操作。

获取图像尺寸

在不解码整个图像的情况下获取图像大小 为了调整位图的大小,您需要知道传入的维度。您可以使用 inJustDecodeBounds标志来帮助您获得图像的尺寸,我们需要实际解码像素数据。

// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;




//now go resize the image to the size you want

您可以使用此标志首先解码大小,然后计算适当的值以缩放到目标分辨率。

尽管这个答案很好(也很准确) ,但它也非常复杂。与其重新发明轮子,不如考虑像 滑行毕加索UIL离子这样的库,或者其他为您实现这种复杂且容易出错的逻辑的库。

柯尔特自己甚至建议看看 预缩放位图性能模式视频中的格莱德和毕加索。

By using libraries, you can get every bit of efficiency mentioned in Colt's answer, but with vastly simpler APIs that work consistently across every version of Android.