在 Android 中以正确的大小解码位图

Posted

技术标签:

【中文标题】在 Android 中以正确的大小解码位图【英文标题】:Decoding bitmaps in Android with the right size 【发布时间】:2011-02-08 03:53:30 【问题描述】:

我使用BitmapFactory.decodeFile 从 SD 卡解码位图。有时位图大于应用程序需要或堆允许的大小,因此我使用BitmapFactory.Options.inSampleSize 请求二次采样(较小)位图。

问题在于平台没有强制执行 inSampleSize 的确切值,有时我会得到一个位图,要么太小,要么对于可用内存来说仍然太大。

来自http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize:

注意:解码器将尝试实现 此请求,但生成的位图 可能有不同的维度 正是所要求的。 此外,2 的幂通常是 解码器更快/更容易 荣誉。

我应该如何解码 SD 卡中的位图以获得我需要的确切大小的位图,同时消耗尽可能少的内存来解码它?

编辑:

当前源代码:

BitmapFactory.Options bounds = new BitmapFactory.Options();
this.bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, bounds);
if (bounds.outWidth == -1)  // TODO: Error 
int width = bounds.outWidth;
int height = bounds.outHeight;
boolean withinBounds = width <= maxWidth && height <= maxHeight;
if (!withinBounds) 
    int newWidth = calculateNewWidth(int width, int height);
    float sampleSizeF = (float) width / (float) newWidth;
    int sampleSize = Math.round(sampleSizeF);
    BitmapFactory.Options resample = new BitmapFactory.Options();
    resample.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeFile(filePath, resample);

【问题讨论】:

你只使用 2 的幂吗?我完全可以理解为什么这样的东西会更喜欢那样,但我不确定我是否见过这样的 API 参考——“我们会尽力给你你所要求的,但它可能只是完全不同的东西"。 您可能还想明确设置位图的 outWidth 和 outHeight 属性,以控制大小。 我没有使用 2 的幂。2 的幂限制太大,生成的位图要么太小,要么太大。也许我应该使用另一个 API? 见鬼,inSampleSize 是一个整数已经受到限制。 outWidth 和 outHeight 是“只读的”。设置它们不会影响解码,设置它们的是解码器。我试过了。 【参考方案1】:

您在正确的轨道上,但是您正在尝试同时做两件事:读入文件并将其缩放到适当的大小。

第一步是将文件读取到比您需要的稍大的位图,使用 BitmapFactory.Options.inSampleSize 确保在您只需要较小的缩略图或屏幕分辨率时不会消耗过多的内存来读取大位图图片。

第二步是调用 Bitmap.createScaledBitmap() 来创建一个新的位图到你需要的精确分辨率。

确保在临时位图之后清理以回收其内存。 (要么让变量超出范围并让 GC 处理它,要么在加载大量图像并且内存紧张时调用 .recycle() 。)

【讨论】:

小心,你首先需要找到高宽比,否则生成的图像会被拉伸。【参考方案2】:

您可能想使用inJustDecodeBounds。将其设置为 TRUE 并按原样加载文件。

图像不会加载到内存中。但是 BitmapFactory.Optionsoutheightoutwidth 属性将包含指定图像的实际尺寸参数。计算你想对它进行多少次采样。即 1/2 或 1/4 或 1/8 等,并将 2/4/8 等分配给 inSampleSize

现在将 inJustDecodeBounds 设置为 FALSE 并调用 BitmapFactory.decodeFile() 以加载如上计算的精确尺寸的图像。

【讨论】:

这就是我已经在做的事情了。问题是没有为 inSampleSize 找到正确的值。问题是平台并不总是支持 inSampleSize,所以无论如何我可能会得到比需要的更小或更大的位图。 inSampleSize 应该返回 U 一个子采样图像。即确切的尺寸将取决于 U 正在加载的原始图像。相对于原始图像,它将是一半/四分之一/八分之一等。你可以加载最接近你需要的图像子样本,并在显示时适当地拉伸它。你接下来在做什么来显示图像?也许我可以在那里帮忙。嗯??... 这正是我现在正在做的事情,因为 inSampleSize 并不总是得到尊重,所以结果并不总是最优的。如果可能,我正在寻找替代方案。 非最优是什么意思。是图像被拉伸得太尴尬还是什么?... 不,位图有时根本不符合要求的大小。它要么太大,要么太小。如果它太大,设备可能会耗尽内存。如果太小,则分辨率低于预期。【参考方案3】:

首先,您需要将图像采样到最接近的采样值,以便位图变得内存高效,您已经完美地描述了这一点。完成后,您可以将其放大以适合您的屏幕。

// This function taking care of sampling
backImage =decodeSampledBitmapFromResource(getResources(),R.drawable.back, width, height);

// This will scale it to your screen width and height. you need to pass screen width and height.
backImage = Bitmap.createScaledBitmap(backImage, width, height, false); 

【讨论】:

【参考方案4】:

由于 API 已经声明样本量可能会或可能不会被接受。 所以我猜你在使用 BitmapFactory 时运气不好。

但出路是使用您自己的位图阅读器。但我建议您坚持使用 BitmapFactory,因为它经过了良好的测试和标准化。

我会尽量不要太担心额外的内存消耗,但尝试找出它不尊重这些值的原因。可悲的是我不知道这一点:(

【讨论】:

也许有使用 BitmapFactory 的替代方案,但方式不同。是否可以分块加载位图,在内存中重新缩放它们然后加入它们?当然,它会更慢,但它会提供准确的结果。 你想优化什么,空间还是分辨率?? 分辨率和堆内存是主要关注点。获取所需的位图应尽可能少占用内存(当然,位图除外)。 我建议你不要太担心,因为这些库通常需要使用额外的空间来优化速度【参考方案5】:
ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filesPath), THUMBSIZE, THUMBSIZE))

您可以根据您的要求直接设置 THUMBSIZE,但是我不确定较低级别的实现细节、输出图像质量及其性能,但当我需要显示缩略图时我通常更喜欢它。

【讨论】:

这可以为你释放位图ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filesPath), THUMBSIZE, THUMBSIZE), ThumbnailUtils.OPTIONS_RECYCLE_INPUT)【参考方案6】:

对于那些正在从资源中寻找精确尺寸图像的人。

对于精确宽度传递 reqHight=0 和精确高度传递 reqWidth=0

public static Bitmap decodeBitmapResource(Context context, int resId, int reqWidth, int reqHeight)

    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(context.getResources(), resId, options);
    float scale = 1;
    if (reqWidth != 0 && reqHeight == 0)
    
        options.inSampleSize = (int) Math.ceil(options.outWidth / (float) reqWidth);
        scale = (float) options.outWidth / (float) reqWidth;
    
    else if (reqHeight != 0 && reqWidth == 0)
    
        options.inSampleSize = (int) Math.ceil(options.outHeight / (float) reqHeight);
        scale = (float) options.outHeight / (float) reqHeight;
    
    else if (reqHeight != 0 && reqWidth != 0)
    
        float x = (float) options.outWidth / (float) reqWidth;
        float y = (float) options.outHeight / (float) reqHeight;
        scale = x > y ? x : y;
    
    reqWidth = (int) ((float) options.outWidth / scale);
    reqHeight = (int) ((float) options.outHeight / scale);
    options.inJustDecodeBounds = false;
    Bitmap tmp = BitmapFactory.decodeResource(context.getResources(), resId, options);
    Bitmap out = Bitmap.createScaledBitmap(tmp, reqWidth, reqHeight, false);
    tmp.recycle();
    tmp = null;
    return out;

【讨论】:

【参考方案7】:

自从我使用 inSampleSize 以来,我再也没有遇到内存不足的问题。所以 inSampleSize 对我来说非常稳定。我建议你仔细检查这个问题。尺寸可能与您要求的不完全相同。但无论如何都应该减少它,并且不应该有更多的“内存不足”。我还建议在这里发布您的代码 sn-p,也许它有问题。

【讨论】:

【参考方案8】:

正如 the100rabh 所说,如果您使用 BitmapFactory,您实际上并没有太多选择。但是,位图解码非常简单,根据您要使用的缩放算法,动态缩放它通常相当简单,而无需将原始位图的大部分读入内存(通常,您将只需将原始位图的 1-2 行所需的内存增加一倍)。

【讨论】:

【参考方案9】:

当设置inScaled标志时(默认为TRUE),如果inDensity和inTargetDensity不为0,位图在加载时会被缩放以匹配inTargetDensity,而不是依赖于图形系统每次绘制时都缩放它画布。所以只需将 inScaled 设置为 false 即可。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;

【讨论】:

【参考方案10】:

通过将解码后的图像文件重新缩放到其位图大小的 70%,我能够大致获得“正确的大小”。

Bitmap myBitmap = BitmapFactory.decodeFile(path);
if(myBitmap != null)

  //reduce to 70% size; bitmaps produce larger than actual image size
  Bitmap rescaledMyBitmap = Bitmap.createScaledBitmap(
                                                  myBitmap, 
                                                  myBitmap.getWidth()/10*7, 
                                                  myBitmap.getHeight()/10*7, 
                                                  false);

  image.setImageBitmap(rescaledMyBitmap);
                

我希望这对您有所帮助!和平!

【讨论】:

【参考方案11】:

Documentation 非常不言自明。首先,我想从那里制作一个小片段,但发现完整阅读更有助于理解,并且不会因记忆而失败。最后,它非常小。

【讨论】:

以上是关于在 Android 中以正确的大小解码位图的主要内容,如果未能解决你的问题,请参考以下文章

解码后的位图字节大小?

如何在.NET中以与浏览器相同的大小呈现文本给文本的CSS

HarmonyOS之深入解析图像的位图操作和属性解码

状态栏图标在 Android 中以白色状态栏隐藏

有时位图解码返回 SkImageDecoder::Factory 返回 null 并显示损坏的图像

如何在 Android 中调整位图的大小?