在Android中裁剪图像时避免OutOfMemory错误

8
我想裁剪图像而不会出现 OutOfMemory 异常。
这意味着我拥有被裁剪图像的 x、y、宽度和高度,并希望在不将其带入内存的情况下裁剪原始图像。
是的,我知道使用 BitmapRegionDecoder 是一个好主意,但也许被裁剪的图像对于将其带入内存来说太大了。

事实上,我不想要裁剪后的位图,只是想将源文件中的裁剪后图像写入目标文件。


编辑:我想保存裁剪后的图像,而不仅仅是在 ImageView 中显示它。 我想将其保存在新文件中,而不失去尺寸

这是一个示例
enter image description here

在这种情况下,被裁剪的图像分辨率为 20000x20000,下面的代码无法使用,因为会出现 OOM:

BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);

使用inSampleSize来减小原始图片的尺寸是不错的,但我保存的结果不再是20000x20000。
我应该如何裁剪25000x25000的图片并将20000x20000部分保存到文件中?

为什么要点踩?分享你的知识。 - Sepehr Behroozi
1
Stack Overflow 是用于编程问题的。你有什么问题?如果你的问题是“我该如何做这个?”,请解释一下你已经尝试过什么,以及你遇到了哪些具体问题。 - CommonsWare
你能贴一些代码吗?你使用后台任务来完成这个吗?你在使用什么设备? - Hugo Gresse
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - ant
1
我认为调整所需的大型图像大小真的很棘手。请注意,Android是适用于内存有限的小型设备的系统。即使您使用RGB_565格式,25000x25000的图像大小也将超过1.1GB内存。因此,我唯一能建议的是尝试查看一些Java流库,可以在“飞行”中调整图像大小而无需将其加载到内存中。或者尝试查看NDK、RenderScript或OpenGL。 - ant
显示剩余2条评论
4个回答

1
简单来说,它需要大量的低级编程和优化。
正如你所看到的,这个区域中的许多答案都指向位图压缩等通用概念,虽然在大多数问题上确实适用,但并不特别适用于你的问题。
此外,像回答中建议的BitmapRegionDecoder也行不通。它确实可以防止将整个位图加载到RAM中,但是裁剪后的图像呢?裁剪图像后会得到一个巨大的位图,在任何情况下都会导致OOM。
因为正如你所描述的,你的问题需要从磁盘中读取或写入位图,就像从内存中读取或写入一样;这被称为BufferedBitmap(或类似的东西),它通过将位图的小块保存到磁盘上并在以后使用它们来高效地处理它所需的内存,从而避免了OOM。
任何其他想要仅通过缩放来解决问题的解决方案只完成了一半的工作。为什么?因为裁剪图像本身可能对内存来说太大了(正如你所说)。
然而,通过缩放来解决问题并不是那么糟糕,如果你不关心裁剪图像与用户在裁剪时看到的图像质量相比的质量。这就是Google Photos所做的,它只是简单地降低了裁剪图像的质量!
我还没有看到过 BufferedBitmap 类(如果有的话,那太棒了)。它们对于解决类似的问题非常方便。你可以查看 Telegram 的消息应用程序,它带有一个开源的图像裁剪功能实现;你猜对了,它使用了好老的 C 处理所有类似的恶心工作... 因此,我们可以得出结论,一个好的全局解决方案(或者更确切地说,适用的几个解决方案之一)似乎是低级别编程自己处理磁盘和内存。我知道我的回答没有给出任何复制粘贴式的解决方案,但至少我希望它给你一些思路,我的朋友。

是的,你说得对。我测试了几个图片编辑工具,猜猜怎么着?没有一个能给我想要的结果。 - Sepehr Behroozi

1
你有没有查看过BitmapRegionDecoder?它可以从原始图像中提取一个矩形区域。
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);

http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html


问题是矩形可能太大,这行代码可能会抛出OOM异常: Bitmap bitmap = bitmapRegionDecoder.decodeRegion(... - Sepehr Behroozi

1
您可以使用BitmapFactory来解决这个问题。要确定原始位图的大小而不将其放入内存中,请执行以下操作:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(..., options);

int originalImageWith = options.outWidth;
int originalImageHeight = options.outHeight;

现在您可以使用options.inSampleSize

如果设置为大于1的值,则请求解码器对原始图像进行子采样,返回较小的图像以节省内存。样本大小是解码位图中对应单个像素的任一维度中的像素数。例如,inSampleSize == 4返回一个宽/高为原始图像的1/4,像素数量为原始图像的1/16的图像。任何<=1的值都与1相同。注意:解码器使用基于2的幂的最终值,任何其他值都将向下舍入到最近的2的幂。

现在这不是一个完美的解决方案,但是您可以进行数学计算,找出可以在options.inSampleSize上使用的最接近2的因子来节省内存。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(..., options);

不好意思,我的意思完全不是那个。我会更新我的问题。 - Sepehr Behroozi
1
@SepehrBehroozi 不要更新问题,而是提出一个新的问题。我已经按照当前的问题回答了它。 - Ilya Gazman
尊敬的您,您并没有 :) 我想将裁剪后的图像保存到文件中而不仅仅是显示它。使用“inSampleSize”对于查看图像很好,但对于保存它来说会导致尺寸丢失。 - Sepehr Behroozi

0

BitmapRegionDecoder是裁剪大型或大尺寸图像的好方法,但它只适用于API 10及以上版本。

有一个叫BitmapRegionDecoder的类可能会有所帮助,但它只适用于API 10及以上版本。

如果您无法使用它:

许多图像格式都是压缩的,因此需要将其加载到内存中。

您需要了解最适合您需求的最佳图像格式,并仅使用您需要的内存自行读取它。

更容易的任务是在JNI中完成所有操作,这样即使您使用了大量内存,由于不受正常应用程序所强加的最大堆大小限制,至少您的应用程序不会很快陷入OOM。

当然,由于Android是开源的,您可以尝试使用BitmapRegionDecoder并将其用于任何设备。

参考: 在不将图像加载到内存中的情况下裁剪图像

或者您可以在以下找到其他有用的方法:

位图/画布的使用和NDK


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接