在没有 OutOfMemory 的情况下裁剪图像 - Android

Crop image without OutOfMemory - Android

我想在不出现 OutOfMemory 异常的情况下裁剪图像。
这意味着我有裁剪图像的 x、y、宽度和高度,并且想裁剪原始图像而不将其存入内存。
是的,我知道 BitmapRegionDecoder 是个好主意,但可能裁剪后的图像太大而无法存储。

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


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

这是例子

在这种情况下,裁剪后的图像分辨率为 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 部分保存到文件中?

你检查过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

您可以使用 BitmapFactory 解决此问题。要确定原始位图大小而不将其放入内存,请执行以下操作:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(..., options);

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

现在您可以使用options.inSampleSize

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

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

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

BitmapRegionDecoder 是裁剪大或大图像的好方法,但它适用于 API 10 及更高版本。

有一个名为 BitmapRegionDecoder 的 class 可能会对您有所帮助,但它可以从 API 10 及更高版本获得。

如果你不能使用它:

许多图像格式都经过压缩,因此需要以某种方式加载到内存中。

您需要阅读最适合您需要的图像格式,然后自己阅读,只使用您需要的内存。

一个更简单的任务是在 JNI 中完成这一切,这样即使您将使用大量内存,至少您的应用程序不会很快进入 OOM,因为它不会受到限制对普通应用程序施加的最大堆大小。

当然,由于android是开源的,你可以尝试使用BitmapRegionDecoder,任何设备都可以使用。

参考: Crop image without loading into memory

或者您可以在下面找到可能对您有帮助的其他方法:

Bitmap/Canvas use and the NDK

简而言之,它需要大量的底层编程和优化。

如您所见,该区域的许多答案都指向位图压缩等的一般概念,这些概念确实适用于大多数问题,但不适用于您的问题。

答案中建议的 BitmapRegionDecoder 也无法正常工作。它肯定会阻止将整个位图加载到 RAM 中,但是裁剪后的图像呢?裁剪图像后,它会为您提供一个巨大的位图,无论如何都会给您带来 OOM。

因为您所描述的问题,需要像从内存中写入或读取位图一样从磁盘写入或读取位图;一种称为 BufferedBitmap(或类似)的东西,它通过将位图的小块保存到磁盘并在以后使用它们来有效地处理它所需的内存,从而避免 OOM。

任何其他想要解决缩放问题的解决方案都只能完成一半的工作。为什么?因为裁剪后的图像本身对于内存来说太大了(如您所说)。

但是,如果您不关心裁剪图像的质量与用户在裁剪图像时看到的质量相比,通过缩放解决问题并没有那么糟糕。 Google Photos就是这样做的,它只是降低了裁剪图像的质量,非常简单!

我还没有看到任何 BufferedBitmap 类(但如果有,那就太棒了)。他们肯定会在解决类似问题时变得得心应手。

您可以查看带有 open-source 图片裁剪功能的 Telegram 消息传递应用程序;你猜对了,它处理了所有与旧 C 类似的令人讨厌的工作......因此,我们可能会得出结论,一个好的全局解决方案(或者更好地说,几个适用的解决方案之一)似乎是 low-level 编程以自己处理磁盘和内存。

我知道我的回答未能为您的问题提供任何 copy-paste-ish 解决方案,但至少我希望它能给您一些想法,我的朋友。