处理大位图

发布于 2024-08-20 10:52:37 字数 344 浏览 4 评论 0原文

我有一堆图像网址。我必须下载这些图像并将它们一一显示在我的应用程序中。我使用 SoftReferences 将图像保存在集合中,并将图像保存在 Sdcard 上,以避免重新获取并改善用户体验。

问题是我对位图的大小一无所知。事实证明,当我使用 BitmapFactory.decodeStream(InputStream) 方法时,我偶尔会遇到 OutOfMemoryExceptions 。因此,我选择使用 BitmapFactory Options(样本大小=2)对图像进行下采样。这提供了更好的输出:没有 OOM,但这会影响较小图像的质量。

此类情况我该如何处理?有没有办法选择性地仅对高分辨率图像进行下采样?

I have a bunch of image URLs. I have to download these images and display them in my application one-by-one. I am saving the images in a Collection using SoftReferences and also on Sdcard to avoid refetches and improve user experience.

The problem is I dont know anything about the size of the bitmaps. And as it turns out, I am getting OutOfMemoryExceptions sporadically, when I am using BitmapFactory.decodeStream(InputStream) method. So, I chose to downsample the images using BitmapFactory Options(sample size=2). This gave a better output: no OOMs, but this affects the quality of smaller images.

How should I handle such cases? Is there a way to selectively downsample only high resolution images?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

迷乱花海 2024-08-27 10:52:37

BitmapFactory.Options 类(我忽略了一个)中有一个名为 inJustDecodeBounds 的选项,其 javadoc 如下:

如果设置为 true,解码器将
返回 null(无位图),但是
out...字段仍将被设置,
允许调用者查询
位图,无需分配
其像素的内存。

我用它来找出位图的实际大小,然后选择使用 inSampleSize 选项对其进行缩减采样。这至少可以避免解码文件时出现任何 OOM 错误。

参考:
1. 处理更大的位图
2. 如何在解码之前获取位图信息

There is an option in BitmapFactory.Options class (one I overlooked) named inJustDecodeBounds, javadoc of which reads:

If set to true, the decoder will
return null (no bitmap), but the
out... fields will still be set,
allowing the caller to query the
bitmap without having to allocate the
memory for its pixels.

I used it to find out the actual size of the Bitmap and then chose to down sample it using inSampleSize option. This at least avoids any OOM errors while decoding the file.

Reference:
1. Handling larger Bitmaps
2. How do I get Bitmap info before I decode

囚你心 2024-08-27 10:52:37

经过几天的努力避免我在不同设备上遇到的所有 OutOfMemory 错误,我创建了这个:

private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
    Bitmap bitmap = null;
    try {
        BitmapFactory.Options outDimens = getBitmapDimensions(uri);

        int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);

        bitmap = downsampleBitmap(uri, sampleSize);

    } catch (Exception e) {
        //handle the exception(s)
    }

    return bitmap;
}

private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
    BitmapFactory.Options outDimens = new BitmapFactory.Options();
    outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)

    InputStream is= getContentResolver().openInputStream(uri);
    // if Options requested only the size will be returned
    BitmapFactory.decodeStream(is, null, outDimens);
    is.close();

    return outDimens;
}

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
    int inSampleSize = 1;

    if (height > targetHeight || width > targetWidth) {

        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) targetHeight);
        final int widthRatio = Math.round((float) width / (float) targetWidth);

        // Choose the smallest ratio as inSampleSize value, this will
        // guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}

private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
    Bitmap resizedBitmap;
    BitmapFactory.Options outBitmap = new BitmapFactory.Options();
    outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
    outBitmap.inSampleSize = sampleSize;

    InputStream is = getContentResolver().openInputStream(uri);
    resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
    is.close();

    return resizedBitmap;
}

此方法适用于我测试的所有设备,但我认为使用我不知道的其他过程可以更好的质量。

我希望我的代码可以帮助处于相同情况的其他开发人员。如果高级开发人员可以提供帮助,提供有关其他流程的建议,以避免流程中质量下降(降低),我也很感激。

After a few days struggling to avoid all OutOfMemory errors that I was getting with different devices, I create this:

private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
    Bitmap bitmap = null;
    try {
        BitmapFactory.Options outDimens = getBitmapDimensions(uri);

        int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);

        bitmap = downsampleBitmap(uri, sampleSize);

    } catch (Exception e) {
        //handle the exception(s)
    }

    return bitmap;
}

private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
    BitmapFactory.Options outDimens = new BitmapFactory.Options();
    outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)

    InputStream is= getContentResolver().openInputStream(uri);
    // if Options requested only the size will be returned
    BitmapFactory.decodeStream(is, null, outDimens);
    is.close();

    return outDimens;
}

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
    int inSampleSize = 1;

    if (height > targetHeight || width > targetWidth) {

        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) targetHeight);
        final int widthRatio = Math.round((float) width / (float) targetWidth);

        // Choose the smallest ratio as inSampleSize value, this will
        // guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    return inSampleSize;
}

private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
    Bitmap resizedBitmap;
    BitmapFactory.Options outBitmap = new BitmapFactory.Options();
    outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
    outBitmap.inSampleSize = sampleSize;

    InputStream is = getContentResolver().openInputStream(uri);
    resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
    is.close();

    return resizedBitmap;
}

This method works with all devices I tested, but I think the quality can be better using other process that I'm not aware.

I hope my code can help other developers in the same situation. I also appreciate if a senior developer can help, giving a suggestion about other process to avoid lose (less) quality in the process.

︶葆Ⅱㄣ 2024-08-27 10:52:37

我自己所做的是:

  • 使用 inJustDecodeBounds 获取图像的原始大小,
  • 具有用于加载位图的固定最大表面(例如 1Mpixels)
  • 检查图像表面是否低于限制,如果是的话,然后如果不计算应该加载位图以保持在最大表面以下的理想宽度和高度,则直接加载它
  • (这里需要做一些简单的数学计算)。这为您提供了加载位图时要应用的浮动比率,
  • 现在您希望将此比率转换为合适的 inSampleSize (2 的幂,不会降低图像质量)。我使用这个函数:
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
  • 然后因为位图将以比最大表面稍高的分辨率加载(因为您必须使用较小的2的幂),所以您必须调整位图的大小,但它会快得多。

What I've done myself is :

  • use inJustDecodeBounds to get the original size of the image
  • have a fixed maximum Surface for loading the Bitmap (say 1Mpixels)
  • check if the image surface is below the limit, if yes, then load it directly
  • if not compute the ideal width and height at which you should load the Bitmap to stay below the max surface (there is some simple maths to do here). This give you a float ratio that you want to apply when loading the bitmap
  • now you want to translate this ratio to a suitable inSampleSize (a power of 2 that doesn't degrade the image quality). I use this function :
int k = Integer.highestOneBit((int)Math.floor(ratio));
if(k==0) return 1;
else return k;
  • then because the Bitmap will have been loaded with a slightly higher resolution than the max surface (because you had to use a smaller power of 2), you'll have to resize the Bitmap, but it will be much faster.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文