Fix OutOfMemoryError in BitmapCallback

v2
Mattia Iavarone 6 years ago
parent 44e600b283
commit 7704d0f6c9
  1. 4
      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

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