旋转图像时如何避免 OutOfMemory ex?

Posted

技术标签:

【中文标题】旋转图像时如何避免 OutOfMemory ex?【英文标题】:How to avoid OutOfMemory ex while rotating the image? 【发布时间】:2013-07-12 13:03:16 【问题描述】:
public static boolean rotateBitmapByExifAndSave(File targetFile)

  if (targetFile==null || !targetFile.exists() || !targetFile.canRead() || !targetFile.canWrite())
      return false;

    boolean isSucceed = false;
    // detect if photo is need to be rotated
    try 
        final Matrix matrix = new Matrix();

        ExifInterface exifReader = new ExifInterface(targetFile.getAbsolutePath());

        int orientation = exifReader.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
        boolean isRotationNeeded = true;

        switch (orientation) 
        case ExifInterface.ORIENTATION_ROTATE_90:
            matrix.postRotate(90);
            break;

        case ExifInterface.ORIENTATION_ROTATE_180:
            matrix.postRotate(180);
            break;

        case ExifInterface.ORIENTATION_ROTATE_270:
            matrix.postRotate(270);
            break;

        default: // ExifInterface.ORIENTATION_NORMAL
            // Do nothing. The original image is fine.
            isRotationNeeded = false;
            isSucceed = true;
            break;
        

        if (isRotationNeeded)
            BitmapFactory.Options bmfOtions = new BitmapFactory.Options();
            Bitmap bitmap = null;
            FileInputStream fileInputStream = null;
            try 
                fileInputStream = new FileInputStream(targetFile);
                bitmap = BitmapFactory.decodeStream(fileInputStream,null,bmfOtions);
             catch (FileNotFoundException e)
                isSucceed = false;
            
            finally 
                if (fileInputStream != null)
                    try 
                        fileInputStream.close();
                     catch (IOException e) 
            
            if (bitmap!=null)
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                isSucceed = ImageUtils.saveBitmapToFile(bitmap, targetFile, 100);
                bitmap.recycle();
            
        

     catch (IOException e) 
        Log.e("ImageUtils", e);
     catch (Exception e) 
        // like there is no EXIF support?
        Log.e("ImageUtils", e);
     catch (Throwable e) 
        // stupid Out of VM's memory
        Log.e("ImageUtils", e.toString());
    

    return isSucceed; 

我使用这种方法来旋转设备相机拍摄的原始照片。现在的摄像头可能大于 8MPix(三星 Galaxy S4 有 13 兆像素摄像头)。即使使用更少的 MPix 摄像头(我的是 5 MP,2592 x 1944 像素,根据官方文档,与 ARGB_888 一起占用 19Mb 的 RAM)我已经得到了 OutOfMemory。所以问题是如何在不损失初始分辨率和质量的情况下旋转照片?

【问题讨论】:

您解决了这个问题吗? @斯坦 嘿斯坦我需要这个问题的答案。如果可以的话,帮帮我吧!谢谢 【参考方案1】:

由于没有答案,我假设没有答案,或者我只是问的问题有点不正确。看起来这里唯一的选择是to increase the app's heap size更新: 还有另一种选择 - 通过 NDK/JNI 处理位图,例如 here 或使用 android Image-Magic lib。 Image Magic 库非常酷,旋转图像只需:

ImageInfo imageInfo = new ImageInfo(imageFile.getAbsolutePath());
MagickImage magickImage = new MagickImage(imageInfo);
magickImage.setCompression(100); // to minimize loss
magickImage.rotateImage(90.0f).writeImage(imageInfo);

MagickImage 还有许多其他图像处理选项。模糊,哑光,比例,木炭等等。然而,它的库大小是值得注意的。作者做得很好,他们涵盖了所有可能的平台:arm64-v8a、armeabi、armeabi-v7a、mips、mips64、x86、x86_64,所有这些库的最终大小超过 36Mb。因此,在将所有库添加到一个 apk 之前,您应该考虑一下,也许使用清单打包 6 个不同版本以按芯片组/平台进行过滤是正确的方法。更新 另一种选择是convert Immutable Bitmap into Mutable (wrap bitmaps into MappedByteBuffer)

【讨论】:

您的用例似乎并没有首先证明 JPEG 的解码/编码是合理的。这不仅是内存问题(正如您接受的答案指出的那样,有很多方法可以避免 OOM),它还会影响性能和图像质量。您可以根据 EXIF 标志对 JPEG 图像执行无损旋转,如 here 所示。 Android 库可以在 GitHub 上找到 我尝试了 LLJTran/AndroidMediaUtil 解决方案,但失败了!我试图旋转一个非常大的 JPEG 图像 (2400x4200)。它运行缓慢,并且在没有生成 JPEG 的情况下出现 OOM。 顺便说一句,AndroidMediaUtil 有烦人的“功能”——他们在资源中包含了一个 xml 文件(makernote.xml),因此他们在@ss 中造成了痛苦,因为 lib/jar 项目不能包含任何资源(不包括 aar 格式)。即使您将其作为模块导入 AndroidStudio - 这也无济于事。所以我不得不将这个 xml 中的信息硬编码到 String 中以使其工作。另外 2400x4200 也不是很大,因为我的手机摄像头可以拍摄 2400x3200。 Re: xml 文件,您可以在 Android“库”项目中添加 res 文件夹。然后,您通过 Properties/Android/Reference 将此 jar 添加到您的应用程序项目中(请参阅***.com/a/9393482/192373)。这并没有让我对他们的糟糕表现感到高兴。 无论如何,Android ImageMagick 是一个不错的选择。使用这个库旋转图像需要大约 5 行代码,而且运行速度很快......但这个库不是轻量级的,尤其是在制作通用 apk 时(大约 36 兆)。【参考方案2】:

制作方法名解码文件:

public static Bitmap decodeFile(File f,int WIDTH,int HIGHT)
         try 
             //Decode image size
             BitmapFactory.Options o = new BitmapFactory.Options();
             o.inJustDecodeBounds = true;
             BitmapFactory.decodeStream(new FileInputStream(f),null,o);

             //The new size we want to scale to
             final int REQUIRED_WIDTH=WIDTH;
             final int REQUIRED_HIGHT=HIGHT;
             //Find the correct scale value. It should be the power of 2.
             int scale=1;
             while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
                 scale*=2;

             //Decode with inSampleSize
             BitmapFactory.Options o2 = new BitmapFactory.Options();
             o2.inSampleSize=scale;
             return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
          catch (FileNotFoundException e) 
         return null;
     

然后像这样调用这个方法(你可以在按钮点击监听器中调用这个方法)

Bitmap bi = decodeFile(new File(path),1280,800);

其中 path 是您保存图像的图像路径.. 在我的情况下是

String path = Environment.getExternalStorageDirectory().toString() + "/nature.jpg";

如果有任何问题 - 询问 :) 希望这会有所帮助。

【讨论】:

这个错误是因为内存不足,所以你必须设置它的高度和宽度,在这个方法中传递它 你读过这个吗:所以问题是如何旋转照片而不损失它的初始尺寸和质量? 你也可以在这个中设置相同的图像大小。 我这样做是因为我有足够的内存然后也会发生这个异常,,, 当然我可以设置相同的图像大小,但你没看到它没用吗?仅按原样解码图像和使用您的代码解码图像并设置原始图像大小有什么区别?这个 sn-p 旨在通过在将原始图像加载到内存之前缩小原始图像的大小来减少内存。这就是它计算比例(缩小比例)的原因。我正在努力避免这种情况。所以在我的情况下不需要比例计算,因为它总是1。

以上是关于旋转图像时如何避免 OutOfMemory ex?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免使用 <canvas> 旋转的图像截断?

在 R 中保存旋转图像 - 避免背景并保持大小

Xamarin:Android - 带有大位图的OutOfMemory异常 - 如何解决?

UIImageView 手势(缩放、旋转)问题

如何避免内存泄漏溢出

带 inJustDecodeBounds=true 的 OutOfMemory 解码图像