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. 116
      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
- 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
- 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;
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;
}

@ -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);
}
}

@ -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);
}

@ -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:

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

Loading…
Cancel
Save