captureSnapshot() API

pull/1/head
Mattia Iavarone 7 years ago
parent fe33c1fa7a
commit 1e5563e833
  1. 11
      README.md
  2. 109
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  3. 5
      camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java
  4. 1
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  5. 11
      camerakit/src/main/java/com/flurgle/camerakit/CameraUtils.java
  6. 44
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java

@ -7,6 +7,7 @@
- *simpler APIs* - *simpler APIs*
- *docs and comments in code* - *docs and comments in code*
- *introduced sessionType (picture or video), replacing Method and Permissions stuff* - *introduced sessionType (picture or video), replacing Method and Permissions stuff*
- *new `captureSnapshot` API*
- *new `setLocation` and `setWhiteBalance` APIs* - *new `setLocation` and `setWhiteBalance` APIs*
- *option to pass a `File` when recording a video* - *option to pass a `File` when recording a video*
- *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`* - *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 - System permission handling
- Dynamic sizing behavior - Dynamic sizing behavior
- Create a `CameraView` of any size (not just presets!) - 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 - Automatic output cropping to match your `CameraView` bounds
- Multiple capture methods - Multiple capture methods
- While taking pictures, image is captured normally using the camera APIs. - Take high-resolution pictures with `capturePicture`
- While shooting videos, image is captured as a freeze frame of the `CameraView` preview (similar to SnapChat and Instagram) - 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 - Built-in tap to focus
- `CameraUtils` to help with Bitmaps and orientations - `CameraUtils` to help with Bitmaps and orientations
- EXIF support - EXIF support
@ -109,7 +110,7 @@ camera.setCameraListener(new CameraListener() {
@Override @Override
public void onPictureTaken(byte[] picture) { public void onPictureTaken(byte[] picture) {
// Create a bitmap or a file... // 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, ...); CameraUtils.decodeBitmap(picture, ...);
} }
}); });
@ -117,6 +118,8 @@ camera.setCameraListener(new CameraListener() {
camera.captureImage(); 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 ### Capturing Video
TODO: test size and orientation stuff. TODO: test size and orientation stuff.

@ -63,6 +63,7 @@ class Camera1 extends CameraImpl {
private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1(); private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1();
private boolean mIsSetup = false; private boolean mIsSetup = false;
private boolean mIsCapturingImage = false; private boolean mIsCapturingImage = false;
private boolean mIsCapturingVideo = false;
private final Object mLock = new Object(); private final Object mLock = new Object();
@ -360,57 +361,62 @@ class Camera1 extends CameraImpl {
void captureImage() { void captureImage() {
if (mIsCapturingImage) return; if (mIsCapturingImage) return;
if (!isCameraOpened()) return; if (!isCameraOpened()) return;
switch (mSessionType) { if (mIsCapturingVideo || mSessionType == SESSION_TYPE_VIDEO) {
case SESSION_TYPE_PICTURE: captureSnapshot();
// Set boolean to wait for image callback return;
mIsCapturingImage = true; }
synchronized (mLock) {
Camera.Parameters parameters = mCamera.getParameters(); // Set boolean to wait for image callback
parameters.setRotation(computeExifOrientation()); mIsCapturingImage = true;
mCamera.setParameters(parameters); synchronized (mLock) {
} Camera.Parameters parameters = mCamera.getParameters();
mCamera.takePicture(null, null, null, parameters.setRotation(computeExifRotation());
new Camera.PictureCallback() { // TODO: add flipping
@Override mCamera.setParameters(parameters);
public void onPictureTaken(byte[] data, Camera camera) { }
mCameraListener.onPictureTaken(data); mCamera.takePicture(null, null, null,
mIsCapturingImage = false; new Camera.PictureCallback() {
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() {
@Override @Override
public void onPreviewFrame(final byte[] data, Camera camera) { public void onPictureTaken(byte[] data, Camera camera) {
// Got to rotate the preview frame, since byte[] data here does not include mCameraListener.onPictureTaken(data);
// EXIF tags automatically set by camera. So either we add EXIF, or we rotate. mIsCapturingImage = false;
Camera.Parameters params = mCamera.getParameters(); camera.startPreview(); // This is needed, read somewhere in the docs.
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();
} }
}); });
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 @Override
@ -458,8 +464,9 @@ class Camera1 extends CameraImpl {
/** /**
* Returns the orientation to be set as a exif tag. This is already managed by * 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)}. * 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; return (mDeviceOrientation + mSensorOffset) % 360;
} }
@ -507,7 +514,10 @@ class Camera1 extends CameraImpl {
@Override @Override
void startVideo(@NonNull File videoFile) { void startVideo(@NonNull File videoFile) {
mVideoFile = videoFile; mVideoFile = videoFile;
if (mIsCapturingVideo) return;
if (!isCameraOpened()) return;
if (mSessionType == SESSION_TYPE_VIDEO) { if (mSessionType == SESSION_TYPE_VIDEO) {
mIsCapturingVideo = true;
initMediaRecorder(); initMediaRecorder();
try { try {
mMediaRecorder.prepare(); mMediaRecorder.prepare();
@ -525,6 +535,7 @@ class Camera1 extends CameraImpl {
@Override @Override
void endVideo() { void endVideo() {
mIsCapturingVideo = false;
mMediaRecorder.stop(); mMediaRecorder.stop();
mMediaRecorder.release(); mMediaRecorder.release();
mMediaRecorder = null; mMediaRecorder = null;

@ -182,6 +182,11 @@ class Camera2 extends CameraImpl {
} }
@Override
void captureSnapshot() {
}
@Override @Override
void startVideo(@NonNull File videoFile) { void startVideo(@NonNull File videoFile) {

@ -33,6 +33,7 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback {
abstract void setLocation(double latitude, double longitude); abstract void setLocation(double latitude, double longitude);
abstract void captureImage(); abstract void captureImage();
abstract void captureSnapshot();
abstract void startVideo(@NonNull File file); abstract void startVideo(@NonNull File file);
abstract void endVideo(); abstract void endVideo();

@ -52,6 +52,7 @@ public class CameraUtils {
public void run() { public void run() {
int orientation = 0; int orientation = 0;
boolean flip = false;
try { try {
// http://sylvana.net/jpegcrop/exif_orientation.html // http://sylvana.net/jpegcrop/exif_orientation.html
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(source)); ExifInterface exif = new ExifInterface(new ByteArrayInputStream(source));
@ -75,16 +76,24 @@ public class CameraUtils {
default: orientation = 0; 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) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
orientation = 0; orientation = 0;
flip = false;
} }
Bitmap bitmap = BitmapFactory.decodeByteArray(source, 0, source.length); Bitmap bitmap = BitmapFactory.decodeByteArray(source, 0, source.length);
if (orientation != 0) { 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();

@ -660,6 +660,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/** /**
* Set the current session type to either picture or video. * 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_PICTURE
* @see CameraKit.Constants#SESSION_TYPE_VIDEO * @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() { public void captureImage() {
mCameraImpl.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 * Starts recording a video with selected options, in a file called
* "video.mp4" in the default folder. * "video.mp4" in the default folder.
@ -824,6 +854,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
return mCameraImpl != null ? mCameraImpl.getPreviewSize() : null; return mCameraImpl != null ? mCameraImpl.getPreviewSize() : null;
} }
/** /**
* Returns the size used for the capture, * Returns the size used for the capture,
* or null if it hasn't been computed yet (for example if the surface is not ready). * 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; 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) { private void requestPermissions(boolean requestCamera, boolean requestAudio) {
Activity activity = null; Activity activity = null;
Context context = getContext(); Context context = getContext();

Loading…
Cancel
Save