简体   繁体   English

大多数内存有效的方法来重新调整android上的位图?

[英]Most memory efficient way to resize bitmaps on android?

I'm building an image-intensive social app where images are sent from the server to the device. 我正在构建一个图像密集型社交应用程序,其中图像从服务器发送到设备。 When the device has smaller screen resolutions, I need to resize the bitmaps, on device, to match their intended display sizes. 当设备具有较小的屏幕分辨率时,我需要调整设备上的位图大小以匹配其预期的显示大小。

The problem is that using createScaledBitmap causes me to run into a lot of out-of-memory errors after resizing a horde of thumbnail images. 问题是使用createScaledBitmap会导致我在调整大量缩略图图像后遇到大量的内存不足错误。

What's the most memory efficient way to resize bitmaps on Android? 什么是在Android上调整位图大小最有效的内存方式?

This answer is summarised from Loading large bitmaps Efficiently which explains how to use inSampleSize to load a down-scaled bitmap version. 这个答案总结从加载大位图有效地解释了如何使用inSampleSize来加载缩小的位图版本。

In particular Pre-scaling bitmaps explains the details of various methods, how to combine them, and which are the most memory efficient. 特别是预缩放位图解释了各种方法的细节,如何组合它们,以及哪些是最节省内存的。

There are three dominant ways to resize a bitmap on Android which have different memory properties: 在Android上调整具有不同内存属性的位图有三种主要方法:

createScaledBitmap API createScaledBitmap API

This API will take in an existing bitmap, and create a NEW bitmap with the exact dimensions you've selected. 此API将接收现有位图,并使用您选择的确切尺寸创建新位图。

On the plus side, you can get exactly the image size you're looking for (regardless of how it looks). 从好的方面来说,您可以准确地获得您正在寻找的图像尺寸(无论它看起来如何)。 But the downside, is that this API requires an existing bitmap in order to work . 但缺点是,此API需要现有的位图才能工作 Meaning the image would have to be loaded, decoded, and a bitmap created, before being able to create a new, smaller version. 这意味着在创建新的较小版本之前,必须加载,解码图像并创建位图。 This is ideal in terms of getting your exact dimensions, but horrible in terms of additional memory overhead. 这在获得精确尺寸方面是理想的,但在额外的内存开销方面却很糟糕。 As such, this is kind-of a deal breaker for most app developers who tend to be memory conscious 因此,对于大多数倾向于记忆的应用程序开发人员来说,这是一种交易破坏者

inSampleSize flag inSampleSize标志

BitmapFactory.Options has a property noted as inSampleSize that will resize your image while decoding it, to avoid the need to decode to a temporary bitmap. BitmapFactory.Options有一个标记为inSampleSize的属性,它会在解码时调整图像大小,以避免解码到临时位图。 This integer value used here will load an image at a 1/x reduced size. 此处使用的此整数值将以1 / x缩小的尺寸加载图像。 For example, setting inSampleSize to 2 returns an image that's half the size, and Setting it to 4 returns an image that's 1/ 4th the size. 例如,将inSampleSize设置为2将返回一半大小的图像,将其设置为4将返回大小为1/4的图像。 Basically image sizes will always be some power-of-two smaller than your source size. 基本上,图像尺寸总是比源尺寸小一些。

From a memory perspective, using inSampleSize is a really fast operation. 从内存的角度来看,使用inSampleSize是一种非常快速的操作。 Effectively, it will only decode every Xth pixel of your image into your resulting bitmap. 实际上,它只会将图像的每个第X个像素解码为生成的位图。 There's two main issues with inSampleSize though: 但是, inSampleSize存在两个主要问题:

  • It doesn't give you exact resolutions . 它没有给你准确的分辨率 It only decreases the size of your bitmap by some power of 2. 它只会将位图的大小减小2倍。

  • It doesn't produce the best quality resize . 它不会产生最佳质量的调整大小 Most resizing filters produce good looking images by reading blocks of pixels, and then weighting them to produce the resized pixel in question. 大多数调整大小的滤镜通过读取像素块产生漂亮的图像,然后对它们进行加权以产生所讨论的调整大小的像素。 inSampleSize avoids all this by just reading every few pixels. inSampleSize通过读取每几个像素inSampleSize避免所有这些。 The result is quite performant, and low memory, but quality suffers. 结果非常高效,内存不足,但质量却受到影响。

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 . 如果你只是处理缩小图像的一些pow2大小,并且过滤不是问题,那么你找不到比inSampleSize更高效的内存(或性能效率)方法。

inScaled, inDensity, inTargetDensity flags inScaled,inDensity,inTargetDensity标志

If you need to scale an image to a dimension that's not equal to a power of two, then you'll need the inScaled , inDensity and inTargetDensity flags of BitmapOptions . 如果您需要将图像缩放到的尺寸,这不是等于2的幂,那么你需要的inScaledinDensityinTargetDensity的标志BitmapOptions When inScaled flag has been set, the system will derive the scaling value to apply to your bitmap by dividing the inTargetDensity by the inDensity values. inScaled标志已设置时,系统会得出缩放值除以适用于您的位图inTargetDensityinDensity值。

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);

Using this method will re-size your image, and also apply a 'resizing filter' to it, that is, the end result will look better because some additional math has been taken into account during the resizing step. 使用此方法将重新调整图像大小,并对其应用“调整大小过滤器”,也就是说,最终结果看起来会更好,因为在调整大小步骤期间已考虑了一些额外的数学运算。 But be warned: that extra filter step, takes extra processing time , and can quickly add up for big images, resulting in slow resizes, and extra memory allocations for the filter itself. 但要注意: 额外的过滤步骤,需要额外的处理时间 ,并且可以快速添加大图像,导致调整缓慢,并为过滤器本身提供额外的内存分配。

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. 由于额外的过滤开销,将此技术应用于远大于所需大小的图像通常不是一个好主意。

Magic Combination 魔术组合

From a memory and performance perspective, you can combine these options for the best results. 从内存和性能角度来看,您可以组合这些选项以获得最佳结果。 (setting the inSampleSize , inScaled , inDensity and inTargetDensity flags) (设置inSampleSizeinScaledinDensityinTargetDensity标志)

inSampleSize will first be applied to the image, getting it to the next power-of-two LARGER than your target size. inSampleSize将首先应用于图像,使其达到下一个比目标尺寸更大的两倍。 Then, inDensity & inTargetDensity are used to scale the result to exact dimensions that you want, applying a filter operation to clean up the image. 然后,使用inDensityinTargetDensity将结果缩放到所需的精确尺寸,应用滤镜操作来清理图像。

Combining these two is a much faster operation, since the inSampleSize step will reduce the number of pixels that the resulting Density-based step will need to apply it's resizing filter on. 将这两者结合起来是一个快得多的操作,因为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);

If you're needing to fit an image to specific dimensions, and some nicer filtering, then this technique is the best bridge to getting the right size, but done in a fast, low-memory footprint operation. 如果您需要将图像适合特定尺寸更好的过滤,那么这种技术是获得正确尺寸的最佳桥梁,但是在快速,低内存占用操作中完成。

Getting image dimensions 获取图像尺寸

Getting the image size without decoding the whole image In order to resize your bitmap, you'll need to know the incoming dimensions. 在不解码整个图像的情况下获取图像大小为了调整位图大小,您需要知道传入的尺寸。 You can use the inJustDecodeBounds flag to help you get the dimensions of the image, w/o needing to actually decode the pixel data. 您可以使用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

You can use this flag to decode the size first, and then calculate the proper values for scaling to your target resolution. 您可以使用此标志首先解码大小,然后计算适当的值以缩放到目标分辨率。

As nice (and accurate) as this answer is, it's also very complicated. 尽管这个答案很好(也很准确),但它也很复杂。 Rather than re-invent the wheel, consider libraries like Glide , Picasso , UIL , Ion , or any number of others that implement this complex and error prone logic for you. 而不是重新发明轮子,考虑像GlidePicassoUILIon或其他任何为您实现这种复杂且容易出错的逻辑的库。

Colt himself even recommends taking a look at Glide and Picasso in the Pre-scaling Bitmaps Performance Patterns Video . Colt本人甚至建议在预缩放位图性能模式视频中看看Glide和Picasso。

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. 通过使用库,您可以获得Colt答案中提到的每一点效率,但是使用非常简单的API可以在每个Android版本中保持一致。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM