From 1e5563e833ea36a4416c1b2ed11655a551690b4a Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 3 Aug 2017 14:27:41 +0200 Subject: [PATCH] captureSnapshot() API --- README.md | 11 +- .../api16/com/flurgle/camerakit/Camera1.java | 109 ++++++++++-------- .../api21/com/flurgle/camerakit/Camera2.java | 5 + .../com/flurgle/camerakit/CameraImpl.java | 1 + .../com/flurgle/camerakit/CameraUtils.java | 11 +- .../com/flurgle/camerakit/CameraView.java | 44 +++++++ 6 files changed, 127 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index c850b200..0566b555 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ - *simpler APIs* - *docs and comments in code* - *introduced sessionType (picture or video), replacing Method and Permissions stuff* +- *new `captureSnapshot` API* - *new `setLocation` and `setWhiteBalance` APIs* - *option to pass a `File` when recording a video* - *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`* @@ -51,11 +52,11 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt - System permission handling - Dynamic sizing behavior - Create a `CameraView` of any size (not just presets!) - - Or let it adapt to the sensor preview size + - Center inside or center crop behaviors - Automatic output cropping to match your `CameraView` bounds - Multiple capture methods - - While taking pictures, image is captured normally using the camera APIs. - - While shooting videos, image is captured as a freeze frame of the `CameraView` preview (similar to SnapChat and Instagram) + - Take high-resolution pictures with `capturePicture` + - Take quick snapshots as a freeze frame of the preview with `captureSnapshot`, even while recording videos (similar to SnapChat and Instagram) - Built-in tap to focus - `CameraUtils` to help with Bitmaps and orientations - EXIF support @@ -109,7 +110,7 @@ camera.setCameraListener(new CameraListener() { @Override public void onPictureTaken(byte[] picture) { // Create a bitmap or a file... - // CameraUtils will read EXIF orientation for you. + // CameraUtils will read EXIF orientation for you, in a worker thread. CameraUtils.decodeBitmap(picture, ...); } }); @@ -117,6 +118,8 @@ camera.setCameraListener(new CameraListener() { camera.captureImage(); ``` +You can also use `camera.captureSnapshot()` to capture a preview frame. This is faster, though has lower quality, and can be used while recording videos. + ### Capturing Video TODO: test size and orientation stuff. diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index 2dbce590..4ae120b0 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -63,6 +63,7 @@ class Camera1 extends CameraImpl { private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1(); private boolean mIsSetup = false; private boolean mIsCapturingImage = false; + private boolean mIsCapturingVideo = false; private final Object mLock = new Object(); @@ -360,57 +361,62 @@ class Camera1 extends CameraImpl { void captureImage() { if (mIsCapturingImage) return; if (!isCameraOpened()) return; - switch (mSessionType) { - case SESSION_TYPE_PICTURE: - // Set boolean to wait for image callback - mIsCapturingImage = true; - synchronized (mLock) { - Camera.Parameters parameters = mCamera.getParameters(); - parameters.setRotation(computeExifOrientation()); - mCamera.setParameters(parameters); - } - mCamera.takePicture(null, null, null, - new Camera.PictureCallback() { - @Override - public void onPictureTaken(byte[] data, Camera camera) { - mCameraListener.onPictureTaken(data); - mIsCapturingImage = false; - camera.startPreview(); // This is needed, read somewhere in the docs. - } - }); - break; - - case SESSION_TYPE_VIDEO: - // If we are in a video session, camera captures are fast captures coming - // from the preview stream. - // TODO: will this work while recording a video? test... - mIsCapturingImage = true; - mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { + if (mIsCapturingVideo || mSessionType == SESSION_TYPE_VIDEO) { + captureSnapshot(); + return; + } + + // Set boolean to wait for image callback + mIsCapturingImage = true; + synchronized (mLock) { + Camera.Parameters parameters = mCamera.getParameters(); + parameters.setRotation(computeExifRotation()); + // TODO: add flipping + mCamera.setParameters(parameters); + } + mCamera.takePicture(null, null, null, + new Camera.PictureCallback() { @Override - public void onPreviewFrame(final byte[] data, Camera camera) { - // Got to rotate the preview frame, since byte[] data here does not include - // EXIF tags automatically set by camera. So either we add EXIF, or we rotate. - Camera.Parameters params = mCamera.getParameters(); - final int rotation = computeExifOrientation(); - final boolean flip = rotation % 180 != 0; - final int preWidth = mPreviewSize.getWidth(); - final int preHeight = mPreviewSize.getHeight(); - final int postWidth = flip ? preHeight : preWidth; - final int postHeight = flip ? preWidth : preHeight; - final int format = params.getPreviewFormat(); - new Thread(new Runnable() { - @Override - public void run() { - byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, rotation); - YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); - mCameraListener.processYuvImage(yuv); - mIsCapturingImage = false; - } - }).start(); + public void onPictureTaken(byte[] data, Camera camera) { + mCameraListener.onPictureTaken(data); + mIsCapturingImage = false; + camera.startPreview(); // This is needed, read somewhere in the docs. } }); - break; - } + } + + + @Override + void captureSnapshot() { + if (mIsCapturingImage) return; + if (!isCameraOpened()) return; + // TODO: will this work while recording a video? test... + mIsCapturingImage = true; + mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() { + @Override + public void onPreviewFrame(final byte[] data, Camera camera) { + // Got to rotate the preview frame, since byte[] data here does not include + // EXIF tags automatically set by camera. So either we add EXIF, or we rotate. + // TODO: add exif, and also care about flipping. + Camera.Parameters params = mCamera.getParameters(); + final int rotation = computeExifRotation(); + final boolean flip = rotation % 180 != 0; + final int preWidth = mPreviewSize.getWidth(); + final int preHeight = mPreviewSize.getHeight(); + final int postWidth = flip ? preHeight : preWidth; + final int postHeight = flip ? preWidth : preHeight; + final int format = params.getPreviewFormat(); + new Thread(new Runnable() { + @Override + public void run() { + byte[] rotatedData = RotationHelper.rotate(data, preWidth, preHeight, rotation); + YuvImage yuv = new YuvImage(rotatedData, format, postWidth, postHeight, null); + mCameraListener.processYuvImage(yuv); + mIsCapturingImage = false; + } + }).start(); + } + }); } @Override @@ -458,8 +464,9 @@ class Camera1 extends CameraImpl { /** * Returns the orientation to be set as a exif tag. This is already managed by * the camera APIs as long as you call {@link Camera.Parameters#setRotation(int)}. + * This ignores flipping for facing camera. */ - private int computeExifOrientation() { + private int computeExifRotation() { return (mDeviceOrientation + mSensorOffset) % 360; } @@ -507,7 +514,10 @@ class Camera1 extends CameraImpl { @Override void startVideo(@NonNull File videoFile) { mVideoFile = videoFile; + if (mIsCapturingVideo) return; + if (!isCameraOpened()) return; if (mSessionType == SESSION_TYPE_VIDEO) { + mIsCapturingVideo = true; initMediaRecorder(); try { mMediaRecorder.prepare(); @@ -525,6 +535,7 @@ class Camera1 extends CameraImpl { @Override void endVideo() { + mIsCapturingVideo = false; mMediaRecorder.stop(); mMediaRecorder.release(); mMediaRecorder = null; diff --git a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java index 791c8d8f..0f68f1f7 100644 --- a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java +++ b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java @@ -182,6 +182,11 @@ class Camera2 extends CameraImpl { } + @Override + void captureSnapshot() { + + } + @Override void startVideo(@NonNull File videoFile) { diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index 4595ee92..6cb131aa 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -33,6 +33,7 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback { abstract void setLocation(double latitude, double longitude); abstract void captureImage(); + abstract void captureSnapshot(); abstract void startVideo(@NonNull File file); abstract void endVideo(); diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java index b6d4431f..edeed560 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java @@ -52,6 +52,7 @@ public class CameraUtils { public void run() { int orientation = 0; + boolean flip = false; try { // http://sylvana.net/jpegcrop/exif_orientation.html ExifInterface exif = new ExifInterface(new ByteArrayInputStream(source)); @@ -75,16 +76,24 @@ public class CameraUtils { default: orientation = 0; } + + 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; } Bitmap bitmap = BitmapFactory.decodeByteArray(source, 0, source.length); - if (orientation != 0) { + 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(); diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 0fec32cb..b39bf3bd 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -660,6 +660,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { /** * Set the current session type to either picture or video. + * When sessionType is video, + * - {@link #startCapturingVideo(File)} will not throw any exception + * - {@link #captureImage()} will fallback to {@link #captureSnapshot()} * * @see CameraKit.Constants#SESSION_TYPE_PICTURE * @see CameraKit.Constants#SESSION_TYPE_VIDEO @@ -773,11 +776,38 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * Asks the camera to capture an image of the current scene. + * This will trigger {@link CameraListener#onPictureTaken(byte[])} if a listener + * was registered. + * + * Note that if sessionType is {@link CameraKit.Constants#SESSION_TYPE_VIDEO}, this + * falls back to {@link #captureSnapshot()} (that is, we will capture a preview frame). + * + * @see #captureSnapshot() + */ public void captureImage() { mCameraImpl.captureImage(); } + /** + * Asks the camera to capture a snapshot of the current preview. + * This eventually triggers {@link CameraListener#onPictureTaken(byte[])} if a listener + * was registered. + * + * The difference with {@link #captureImage()} is that: + * - this capture is faster, so it might be better on slower cameras, though the result can be + * generally blurry or low quality + * - this can be called even if sessionType is {@link CameraKit.Constants#SESSION_TYPE_VIDEO} + * + * @see #captureImage() + */ + public void captureSnapshot() { + mCameraImpl.captureSnapshot(); + } + + /** * Starts recording a video with selected options, in a file called * "video.mp4" in the default folder. @@ -824,6 +854,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { return mCameraImpl != null ? mCameraImpl.getPreviewSize() : null; } + /** * Returns the size used for the capture, * or null if it hasn't been computed yet (for example if the surface is not ready). @@ -834,6 +865,19 @@ public class CameraView extends FrameLayout implements LifecycleObserver { return mCameraImpl != null ? mCameraImpl.getCaptureSize() : null; } + + /** + * Returns the size used for capturing snapshots. + * This is equal to {@link #getPreviewSize()}. + * + * @return a Size + */ + @Nullable + public Size getSnapshotSize() { + return getPreviewSize(); + } + + private void requestPermissions(boolean requestCamera, boolean requestAudio) { Activity activity = null; Context context = getContext();