Fix OutOfMemoryError in BitmapCallback

v2
Mattia Iavarone 6 years ago
parent 44e600b283
commit 7704d0f6c9
  1. 2
      MIGRATION.md
  2. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/FullPictureRecorder.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java
  4. 6
      cameraview/src/main/utils/com/otaliastudios/cameraview/BitmapCallback.java
  5. 42
      cameraview/src/main/utils/com/otaliastudios/cameraview/CameraUtils.java
  6. 2
      demo/src/main/res/values/strings.xml

@ -58,3 +58,5 @@
TODO: document this TODO: document this
- UI Changes in the demo app, changed controls appearance, added some missing controls. - 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 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.

@ -1,8 +1,6 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.hardware.Camera; import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.support.media.ExifInterface; import android.support.media.ExifInterface;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -49,7 +47,7 @@ class FullPictureRecorder extends PictureRecorder {
try { try {
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data)); ExifInterface exif = new ExifInterface(new ByteArrayInputStream(data));
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
exifRotation = CameraUtils.decodeExifOrientation(exifOrientation); exifRotation = CameraUtils.readExifOrientation(exifOrientation);
} catch (IOException e) { } catch (IOException e) {
exifRotation = 0; exifRotation = 0;
} }

@ -80,7 +80,7 @@ public class PictureResult {
* @param callback a callback to be notified of image decoding * @param callback a callback to be notified of image decoding
*/ */
public void asBitmap(int maxWidth, int maxHeight, BitmapCallback callback) { 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 * @param callback a callback to be notified of image decoding
*/ */
public void asBitmap(BitmapCallback callback) { public void asBitmap(BitmapCallback callback) {
CameraUtils.decodeBitmap(getJpeg(), callback); asBitmap(-1, -1, callback);
} }
} }

@ -1,6 +1,7 @@
package com.otaliastudios.cameraview; package com.otaliastudios.cameraview;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
/** /**
@ -11,9 +12,10 @@ public interface BitmapCallback {
/** /**
* Notifies that the bitmap was succesfully decoded. * Notifies that the bitmap was succesfully decoded.
* This is run on the UI thread. * 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 @UiThread
void onBitmapReady(Bitmap bitmap); void onBitmapReady(@Nullable Bitmap bitmap);
} }

@ -7,7 +7,8 @@ import android.graphics.BitmapFactory;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.hardware.Camera; import android.hardware.Camera;
import android.os.Handler; 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.annotation.WorkerThread;
import android.support.media.ExifInterface; import android.support.media.ExifInterface;
@ -116,12 +117,17 @@ public class CameraUtils {
* @param callback a callback to be notified * @param callback a callback to be notified
*/ */
@SuppressWarnings("WeakerAccess") @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(); final Handler ui = new Handler();
WorkerHandler.run(new Runnable() { WorkerHandler.run(new Runnable() {
@Override @Override
public void run() { public void run() {
final Bitmap bitmap = decodeBitmap(source, maxWidth, maxHeight, options); final Bitmap bitmap = decodeBitmap(source, maxWidth, maxHeight, options, rotation);
ui.post(new Runnable() { ui.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -159,21 +165,29 @@ public class CameraUtils {
* @param maxHeight the max allowed height * @param maxHeight the max allowed height
* @param options the options to be passed to decodeByteArray * @param options the options to be passed to decodeByteArray
*/ */
// TODO ignores flipping
@SuppressWarnings({"SuspiciousNameCombination", "WeakerAccess"}) @SuppressWarnings({"SuspiciousNameCombination", "WeakerAccess"})
@Nullable
@WorkerThread @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 (maxWidth <= 0) maxWidth = Integer.MAX_VALUE;
if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE; if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE;
int orientation; int orientation;
boolean flip; boolean flip;
if (rotation == -1) {
InputStream stream = null; InputStream stream = null;
try { try {
// http://sylvana.net/jpegcrop/exif_orientation.html // http://sylvana.net/jpegcrop/exif_orientation.html
stream = new ByteArrayInputStream(source); stream = new ByteArrayInputStream(source);
ExifInterface exif = new ExifInterface(stream); ExifInterface exif = new ExifInterface(stream);
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
orientation = decodeExifOrientation(exifOrientation); orientation = readExifOrientation(exifOrientation);
flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL || flip = exifOrientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL ||
exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL || exifOrientation == ExifInterface.ORIENTATION_FLIP_VERTICAL ||
exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE || exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE ||
@ -185,11 +199,18 @@ public class CameraUtils {
flip = false; flip = false;
} finally { } finally {
if (stream != null) { if (stream != null) {
try { stream.close(); } catch (Exception ignored) {} try {
stream.close();
} catch (Exception ignored) { }
} }
} }
} else {
orientation = rotation;
flip = false;
}
Bitmap bitmap; Bitmap bitmap;
try {
if (maxWidth < Integer.MAX_VALUE || maxHeight < Integer.MAX_VALUE) { if (maxWidth < Integer.MAX_VALUE || maxHeight < Integer.MAX_VALUE) {
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(source, 0, source.length, options); BitmapFactory.decodeByteArray(source, 0, source.length, options);
@ -197,6 +218,7 @@ public class CameraUtils {
int outHeight = options.outHeight; int outHeight = options.outHeight;
int outWidth = options.outWidth; int outWidth = options.outWidth;
if (orientation % 180 != 0) { if (orientation % 180 != 0) {
//noinspection SuspiciousNameCombination
outHeight = options.outWidth; outHeight = options.outWidth;
outWidth = options.outHeight; outWidth = options.outHeight;
} }
@ -211,15 +233,17 @@ public class CameraUtils {
if (orientation != 0 || flip) { if (orientation != 0 || flip) {
Matrix matrix = new Matrix(); Matrix matrix = new Matrix();
matrix.setRotate(orientation); matrix.setRotate(orientation);
// matrix.postScale(1, -1) Flip... needs testing.
Bitmap temp = bitmap; Bitmap temp = bitmap;
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
temp.recycle(); temp.recycle();
} }
} catch (OutOfMemoryError e) {
bitmap = null;
}
return bitmap; return bitmap;
} }
static int decodeExifOrientation(int exifOrientation) { static int readExifOrientation(int exifOrientation) {
int orientation; int orientation;
switch (exifOrientation) { switch (exifOrientation) {
case ExifInterface.ORIENTATION_NORMAL: case ExifInterface.ORIENTATION_NORMAL:

@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">CameraView Engine Preview</string> <string name="app_name">CameraView</string>
</resources> </resources>

Loading…
Cancel
Save