From 7704d0f6c967c5850adda3ef03b833e7832903a7 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Fri, 21 Sep 2018 15:45:02 -0300 Subject: [PATCH] Fix OutOfMemoryError in BitmapCallback --- MIGRATION.md | 4 +- .../cameraview/FullPictureRecorder.java | 4 +- .../cameraview/PictureResult.java | 4 +- .../cameraview/BitmapCallback.java | 6 +- .../otaliastudios/cameraview/CameraUtils.java | 116 +++++++++++------- demo/src/main/res/values/strings.xml | 2 +- 6 files changed, 81 insertions(+), 55 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index dad1a77e..98f93694 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -57,4 +57,6 @@ - Added CameraView.setGridColor() and app:cameraGridColor. TODO: document this - UI Changes in the demo app, changed controls appearance, added some missing controls. - added all information from the VideoResult in the VideoPreviewActivity, same for pictures \ No newline at end of file + added all information from the VideoResult in the VideoPreviewActivity, same for pictures +- BitmapCallback result is now @Nullable ! This will happen if we encounter an OutOfMemoryError during decoding. + You should consider passing a maxWidth and maxHeight instead of loading the full image. \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/FullPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/FullPictureRecorder.java index 71bd71cf..9b7eeb57 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/FullPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/FullPictureRecorder.java @@ -1,8 +1,6 @@ package com.otaliastudios.cameraview; import android.hardware.Camera; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; import android.support.media.ExifInterface; import java.io.ByteArrayInputStream; @@ -49,7 +47,7 @@ class FullPictureRecorder extends PictureRecorder { try { ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data)); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - exifRotation = CameraUtils.decodeExifOrientation(exifOrientation); + exifRotation = CameraUtils.readExifOrientation(exifOrientation); } catch (IOException e) { exifRotation = 0; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java index 6bc1311c..8f9dfae2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java @@ -80,7 +80,7 @@ public class PictureResult { * @param callback a callback to be notified of image decoding */ public void asBitmap(int maxWidth, int maxHeight, BitmapCallback callback) { - CameraUtils.decodeBitmap(getJpeg(), maxWidth, maxHeight, callback); + CameraUtils.decodeBitmap(getJpeg(), maxWidth, maxHeight, rotation, callback); } /** @@ -91,6 +91,6 @@ public class PictureResult { * @param callback a callback to be notified of image decoding */ public void asBitmap(BitmapCallback callback) { - CameraUtils.decodeBitmap(getJpeg(), callback); + asBitmap(-1, -1, callback); } } diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/BitmapCallback.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/BitmapCallback.java index f7bc05a4..8c996bb5 100644 --- a/cameraview/src/main/utils/com/otaliastudios/cameraview/BitmapCallback.java +++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/BitmapCallback.java @@ -1,6 +1,7 @@ package com.otaliastudios.cameraview; import android.graphics.Bitmap; +import android.support.annotation.Nullable; import android.support.annotation.UiThread; /** @@ -11,9 +12,10 @@ public interface BitmapCallback { /** * Notifies that the bitmap was succesfully decoded. * This is run on the UI thread. + * Returns a null object if a {@link OutOfMemoryError} was encountered. * - * @param bitmap decoded bitmap + * @param bitmap decoded bitmap, or null */ @UiThread - void onBitmapReady(Bitmap bitmap); + void onBitmapReady(@Nullable Bitmap bitmap); } diff --git a/cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java b/cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java index 34bfa86f..40c4f49a 100644 --- a/cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java +++ b/cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java @@ -7,7 +7,8 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.hardware.Camera; import android.os.Handler; -import android.support.annotation.UiThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import android.support.media.ExifInterface; @@ -116,12 +117,17 @@ public class CameraUtils { * @param callback a callback to be notified */ @SuppressWarnings("WeakerAccess") - public static void decodeBitmap(final byte[] source, final int maxWidth, final int maxHeight, final BitmapFactory.Options options, final BitmapCallback callback) { + public static void decodeBitmap(final byte[] source, final int maxWidth, final int maxHeight, @NonNull final BitmapFactory.Options options, final BitmapCallback callback) { + decodeBitmap(source, maxWidth, maxHeight, options, -1, callback); + } + + @SuppressWarnings("WeakerAccess") + static void decodeBitmap(final byte[] source, final int maxWidth, final int maxHeight, @NonNull final BitmapFactory.Options options, final int rotation, final BitmapCallback callback) { final Handler ui = new Handler(); WorkerHandler.run(new Runnable() { @Override public void run() { - final Bitmap bitmap = decodeBitmap(source, maxWidth, maxHeight, options); + final Bitmap bitmap = decodeBitmap(source, maxWidth, maxHeight, options, rotation); ui.post(new Runnable() { @Override public void run() { @@ -159,67 +165,85 @@ public class CameraUtils { * @param maxHeight the max allowed height * @param options the options to be passed to decodeByteArray */ - // TODO ignores flipping @SuppressWarnings({"SuspiciousNameCombination", "WeakerAccess"}) + @Nullable @WorkerThread - public static Bitmap decodeBitmap(byte[] source, int maxWidth, int maxHeight, BitmapFactory.Options options) { + public static Bitmap decodeBitmap(byte[] source, int maxWidth, int maxHeight, @NonNull BitmapFactory.Options options) { + return decodeBitmap(source, maxWidth, maxHeight, options, -1); + } + + // Null: got OOM + // TODO ignores flipping. but it should be super rare. + @Nullable + static Bitmap decodeBitmap(byte[] source, int maxWidth, int maxHeight, @NonNull BitmapFactory.Options options, int rotation) { if (maxWidth <= 0) maxWidth = Integer.MAX_VALUE; if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE; int orientation; boolean flip; - InputStream stream = null; - try { - // http://sylvana.net/jpegcrop/exif_orientation.html - stream = new ByteArrayInputStream(source); - ExifInterface exif = new ExifInterface(stream); - int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - orientation = decodeExifOrientation(exifOrientation); - flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || - exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || - exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE || - exifOrientation == ExifInterface.ORIENTATION_TRANSVERSE; + if (rotation == -1) { + InputStream stream = null; + try { + // http://sylvana.net/jpegcrop/exif_orientation.html + stream = new ByteArrayInputStream(source); + ExifInterface exif = new ExifInterface(stream); + int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + orientation = readExifOrientation(exifOrientation); + flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || + exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || + exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE || + exifOrientation == ExifInterface.ORIENTATION_TRANSVERSE; - } catch (IOException e) { - e.printStackTrace(); - orientation = 0; - flip = false; - } finally { - if (stream != null) { - try { stream.close(); } catch (Exception ignored) {} + } catch (IOException e) { + e.printStackTrace(); + orientation = 0; + flip = false; + } finally { + if (stream != null) { + try { + stream.close(); + } catch (Exception ignored) { } + } } + } else { + orientation = rotation; + flip = false; } Bitmap bitmap; - if (maxWidth < Integer.MAX_VALUE || maxHeight < Integer.MAX_VALUE) { - options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(source, 0, source.length, options); + try { + if (maxWidth < Integer.MAX_VALUE || maxHeight < Integer.MAX_VALUE) { + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(source, 0, source.length, options); - int outHeight = options.outHeight; - int outWidth = options.outWidth; - if (orientation % 180 != 0) { - outHeight = options.outWidth; - outWidth = options.outHeight; - } + int outHeight = options.outHeight; + int outWidth = options.outWidth; + if (orientation % 180 != 0) { + //noinspection SuspiciousNameCombination + outHeight = options.outWidth; + outWidth = options.outHeight; + } - options.inSampleSize = computeSampleSize(outWidth, outHeight, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - bitmap = BitmapFactory.decodeByteArray(source, 0, source.length, options); - } else { - bitmap = BitmapFactory.decodeByteArray(source, 0, source.length); - } + options.inSampleSize = computeSampleSize(outWidth, outHeight, maxWidth, maxHeight); + options.inJustDecodeBounds = false; + bitmap = BitmapFactory.decodeByteArray(source, 0, source.length, options); + } else { + bitmap = BitmapFactory.decodeByteArray(source, 0, source.length); + } - if (orientation != 0 || flip) { - Matrix matrix = new Matrix(); - matrix.setRotate(orientation); - // matrix.postScale(1, -1) Flip... needs testing. - Bitmap temp = bitmap; - bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - temp.recycle(); + if (orientation != 0 || flip) { + Matrix matrix = new Matrix(); + matrix.setRotate(orientation); + Bitmap temp = bitmap; + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + temp.recycle(); + } + } catch (OutOfMemoryError e) { + bitmap = null; } return bitmap; } - static int decodeExifOrientation(int exifOrientation) { + static int readExifOrientation(int exifOrientation) { int orientation; switch (exifOrientation) { case ExifInterface.ORIENTATION_NORMAL: diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index 1c692d4f..87534621 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - CameraView Engine Preview + CameraView