diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/BitmapCallback.java b/cameraview/src/main/java/com/otaliastudios/cameraview/BitmapCallback.java index e6d244bc..a69e7a92 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/BitmapCallback.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/BitmapCallback.java @@ -12,7 +12,7 @@ public interface BitmapCallback { /** * Notifies that the bitmap was successfully decoded. * This is run on the UI thread. - * Returns a null object if a {@link OutOfMemoryError} was encountered. + * Returns a null object if an exception or an {@link OutOfMemoryError} was encountered. * * @param bitmap decoded bitmap, or null */ diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java index 763a81ae..b52e33f6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/PictureResult.java @@ -3,9 +3,11 @@ package com.otaliastudios.cameraview; import android.graphics.BitmapFactory; import android.location.Location; import android.os.Build; +import android.os.Handler; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.PictureFormat; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.size.Size; import java.io.File; @@ -107,11 +109,11 @@ public class PictureResult { /** * Returns the raw compressed, ready to be saved to file, - * in the given format. + * in the given format or null if there was some error. * - * @return the compressed data stream + * @return the compressed data stream or null */ - @NonNull + @Nullable public byte[] getData() { return data; } @@ -136,13 +138,19 @@ public class PictureResult { * @param callback a callback to be notified of image decoding */ public void toBitmap(int maxWidth, int maxHeight, @NonNull BitmapCallback callback) { + byte[] data = getData(); + if (data == null) { + final Handler ui = new Handler(); + WorkerHandler.execute(() -> ui.post(() -> callback.onBitmapReady(null))); + return; + } if (format == PictureFormat.JPEG) { - CameraUtils.decodeBitmap(getData(), maxWidth, maxHeight, new BitmapFactory.Options(), + CameraUtils.decodeBitmap(data, maxWidth, maxHeight, new BitmapFactory.Options(), rotation, callback); } else if (format == PictureFormat.DNG && Build.VERSION.SDK_INT >= 24) { // Apparently: BitmapFactory added DNG support in API 24. // https://github.com/aosp-mirror/platform_frameworks_base/blob/nougat-mr1-release/core/jni/android/graphics/BitmapFactory.cpp - CameraUtils.decodeBitmap(getData(), maxWidth, maxHeight, new BitmapFactory.Options(), + CameraUtils.decodeBitmap(data, maxWidth, maxHeight, new BitmapFactory.Options(), rotation, callback); } else { throw new UnsupportedOperationException("PictureResult.toBitmap() does not support " @@ -170,6 +178,12 @@ public class PictureResult { * @param callback a callback */ public void toFile(@NonNull File file, @NonNull FileCallback callback) { - CameraUtils.writeToFile(getData(), file, callback); + byte[] data = getData(); + if (data != null) { + CameraUtils.writeToFile(data, file, callback); + } else { + final Handler ui = new Handler(); + WorkerHandler.execute(() -> ui.post(() -> callback.onFileReady(null))); + } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index a8d2fd63..5ea13029 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -479,7 +479,7 @@ public class Camera2Engine extends CameraBaseEngine implements // Compute sizes. // TODO preview stream should never be bigger than 1920x1080 as per - // CameraDevice.createCaptureSession. This should be probably be applied + // CameraDevice.createCaptureSession. This should probably be applied // before all the other external selectors, to treat it as a hard limit. // OR: pass an int into these functions to be able to take smaller dims // when session configuration fails diff --git a/demo/src/main/kotlin/com/otaliastudios/cameraview/demo/PicturePreviewActivity.kt b/demo/src/main/kotlin/com/otaliastudios/cameraview/demo/PicturePreviewActivity.kt index 12cb37ef..74969234 100644 --- a/demo/src/main/kotlin/com/otaliastudios/cameraview/demo/PicturePreviewActivity.kt +++ b/demo/src/main/kotlin/com/otaliastudios/cameraview/demo/PicturePreviewActivity.kt @@ -27,10 +27,13 @@ class PicturePreviewActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_picture_preview) - val result = pictureResult ?: run { + + if (pictureResult == null || (pictureResult?.data?.size ?: 0) <= 0) { finish() return } + val result = requireNotNull(pictureResult) + val imageView = findViewById(R.id.image) val captureResolution = findViewById(R.id.nativeCaptureResolution) val captureLatency = findViewById(R.id.captureLatency) @@ -46,13 +49,13 @@ class PicturePreviewActivity : AppCompatActivity() { result.toBitmap(1000, 1000) { bitmap -> imageView.setImageBitmap(bitmap) } } catch (e: UnsupportedOperationException) { imageView.setImageDrawable(ColorDrawable(Color.GREEN)) - Toast.makeText(this, "Can't preview this format: " + result.getFormat(), Toast.LENGTH_LONG).show() + Toast.makeText(this, "Can't preview this format: " + result.format, Toast.LENGTH_LONG).show() } if (result.isSnapshot) { // Log the real size for debugging reason. val options = BitmapFactory.Options() options.inJustDecodeBounds = true - BitmapFactory.decodeByteArray(result.data, 0, result.data.size, options) + BitmapFactory.decodeByteArray(result.data, 0, requireNotNull(result.data).size, options) if (result.rotation % 180 != 0) { Log.e("PicturePreview", "The picture full size is ${result.size.height}x${result.size.width}") } else { @@ -76,13 +79,13 @@ class PicturePreviewActivity : AppCompatActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.share) { Toast.makeText(this, "Sharing...", Toast.LENGTH_SHORT).show() - val extension = when (pictureResult!!.format) { + val extension = when (requireNotNull(pictureResult).format) { PictureFormat.JPEG -> "jpg" PictureFormat.DNG -> "dng" else -> throw RuntimeException("Unknown format.") } - val file = File(filesDir, "picture.$extension") - CameraUtils.writeToFile(pictureResult!!.data, file) { file -> + val destFile = File(filesDir, "picture.$extension") + CameraUtils.writeToFile(requireNotNull(pictureResult?.data), destFile) { file -> if (file != null) { val context = this@PicturePreviewActivity val intent = Intent(Intent.ACTION_SEND)