diff --git a/README.md b/README.md
index 3b0c037b..2c72a1f6 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Try out all the unique features using the CameraKit Demo from the Google Play st
- [`ckFlash`](#ckflash)
- [`ckPictureMode`](#ckpicturemode)
- [`ckTapToFocus`](#cktaptofocus)
+ - [`ckAutoFocus`](#ckautofocus)
- [Capturing Images](#capturing-images)
- [Capturing Video](#capturing-video)
- [Automatic Permissions Behavior](#automatic-permissions-behavior)
@@ -124,7 +125,8 @@ camera.setCameraListener(new CameraListener() {
camerakit:ckFacing="back"
camerakit:ckFlash="off"
camerakit:ckPictureMode="quality"
- camerakit:ckTapToFocus="on" />
+ camerakit:ckTapToFocus="on"
+ camerakit:ckAutoFocus="true" />
```
#### `ckCropOutput`
@@ -134,7 +136,6 @@ camera.setCameraListener(new CameraListener() {
| `true` | Crop the output image or video to only contain what can be seen on the `CameraView` preview. |
| `false` | Output the full image or video regardless of what is visible on the `CameraView` preview. |
-
#### `ckFacing`
| Value | Description |
@@ -167,6 +168,13 @@ camera.setCameraListener(new CameraListener() {
| `invisible` | Tap to focus is enabled, but no focus circle appears. |
| `off` | Tap to focus is off. |
+#### `ckAutoFocus`
+
+| Value | Description |
+| --------------| -------------|
+| `true` | Continuously allow the `CameraView` preview to adjust focus automatically. |
+| `false` | Never adjust focus during preview. |
+
### Capturing Images
To capture an image just call `CameraView.capturePicture()`. Make sure you setup a `CameraListener` to handle the image callback.
diff --git a/build.gradle b/build.gradle
index b1557280..a3330d48 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.0-beta1'
+ classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/camerakit/src/main/AndroidManifest.xml b/camerakit/src/main/AndroidManifest.xml
index 1bbbb482..f108e9a6 100644
--- a/camerakit/src/main/AndroidManifest.xml
+++ b/camerakit/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
package="com.flurgle.camerakit">
+
FLASH_MODES = new SparseArrayCompat<>();
+
+ static {
+ FLASH_MODES.put(CameraKit.Constants.FLASH_OFF, Camera.Parameters.FLASH_MODE_OFF);
+ FLASH_MODES.put(CameraKit.Constants.FLASH_ON, Camera.Parameters.FLASH_MODE_ON);
+ FLASH_MODES.put(CameraKit.Constants.FLASH_AUTO, Camera.Parameters.FLASH_MODE_AUTO);
+ }
+
+ private int mCameraId;
+
+ Camera mCamera;
+
+ private Camera.Parameters mCameraParameters;
+
+ private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
+
+ private boolean mShowingPreview;
+
+ private boolean mAutoFocus;
+
+ private int mFacing;
+
+ private int mFlash;
+
+ private int mDisplayOrientation;
+
+ private SortedSet mPreviewSizes;
+ private SortedSet mCaptureSizes;
+
+ private MediaRecorder mMediaRecorder;
+
+ Camera1(CameraListener callback, PreviewImpl preview) {
+ super(callback, preview);
+ preview.setCallback(new PreviewImpl.Callback() {
+ @Override
+ public void onSurfaceChanged() {
+ if (mCamera != null) {
+ setUpPreview();
+ adjustCameraParameters();
+ }
+ }
+ });
+
+
+ mPreviewSizes = new TreeSet<>();
+ mCaptureSizes = new TreeSet<>();
+ }
+
+ @Override
+ void start() {
+ chooseCamera();
+ openCamera();
+ if (mPreview.isReady()) {
+ setUpPreview();
+ }
+ mShowingPreview = true;
+ mCamera.startPreview();
+ }
+
+ @Override
+ void stop() {
+ if (mCamera != null) {
+ mCamera.stopPreview();
+ }
+ mShowingPreview = false;
+ releaseCamera();
+ }
+
+ @Override
+ boolean isCameraOpened() {
+ return mCamera != null;
+ }
+
+ @Override
+ void setFacing(int facing) {
+ if (mFacing == facing) {
+ return;
+ }
+ mFacing = facing;
+ if (isCameraOpened()) {
+ stop();
+ start();
+ }
+ }
+
+ @Override
+ int getFacing() {
+ return mFacing;
+ }
+
+ @Override
+ void setFlash(int flash) {
+ if (flash == mFlash) {
+ return;
+ }
+ if (setFlashInternal(flash)) {
+ mCamera.setParameters(mCameraParameters);
+ }
+ }
+
+ @Override
+ int getFlash() {
+ return mFlash;
+ }
+
+ @Override
+ boolean getAutoFocus() {
+ if (!isCameraOpened()) {
+ return mAutoFocus;
+ }
+ String focusMode = mCameraParameters.getFocusMode();
+ return focusMode != null && focusMode.contains("continuous");
+ }
+
+ @Override
+ void capturePicture() {
+ if (!isCameraOpened()) {
+ throw new IllegalStateException("Camera is not ready. Call start() before takePicture().");
+ }
+ if (getAutoFocus()) {
+ mCamera.cancelAutoFocus();
+ mCamera.autoFocus(new Camera.AutoFocusCallback() {
+ @Override
+ public void onAutoFocus(boolean success, Camera camera) {
+ takePictureInternal();
+ }
+ });
+ } else {
+ takePictureInternal();
+ }
+ }
+
+ void takePictureInternal() {
+ mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
+ @Override
+ public void onPictureTaken(byte[] data, Camera camera) {
+ getCameraListener().onPictureTaken(data);
+ camera.startPreview();
+ }
+ });
+ }
+
+ @Override
+ void captureStill() {
+
+ }
+
+ @Override
+ void startVideo() {
+ try {
+ prepareMediaRecorder();
+ } catch (IOException e) {
+ if (mMediaRecorder != null) {
+ mMediaRecorder.release();
+ }
+
+ return;
+ }
+ }
+
+ void prepareMediaRecorder() throws IOException {
+ mCamera.unlock();
+
+ mMediaRecorder = new MediaRecorder();
+ mMediaRecorder.setCamera(mCamera);
+
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+
+ mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
+
+ mMediaRecorder.setOutputFile(new File(getView().getContext().getExternalFilesDir(null), "video.mp4").getAbsolutePath());
+
+ mMediaRecorder.prepare();
+ mMediaRecorder.start();
+ }
+
+ @Override
+ void endVideo() {
+ if (mMediaRecorder != null) {
+ mMediaRecorder.stop();
+ }
+
+ getCameraListener().onVideoTaken(new File(getView().getContext().getExternalFilesDir(null), "video.mp4"));
+ }
+
+ @Override
+ void setDisplayOrientation(int displayOrientation) {
+ if (mDisplayOrientation == displayOrientation) {
+ return;
+ }
+ mDisplayOrientation = displayOrientation;
+ if (isCameraOpened()) {
+ int cameraRotation = calcCameraRotation(displayOrientation);
+ mCameraParameters.setRotation(cameraRotation);
+ mCamera.setParameters(mCameraParameters);
+ final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
+ if (needsToStopPreview) {
+ mCamera.stopPreview();
+ }
+ mCamera.setDisplayOrientation(cameraRotation);
+ if (needsToStopPreview) {
+ mCamera.startPreview();
+ }
+ }
+ }
+
+ private int calcCameraRotation(int rotation) {
+ if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ return (360 - (mCameraInfo.orientation + rotation) % 360) % 360;
+ } else { // back-facing
+ return (mCameraInfo.orientation - rotation + 360) % 360;
+ }
+ }
+
+ private void setUpPreview() {
+ try {
+ if (mPreview.getOutputClass() == SurfaceHolder.class) {
+ final boolean needsToStopPreview = mShowingPreview && Build.VERSION.SDK_INT < 14;
+ if (needsToStopPreview) {
+ mCamera.stopPreview();
+ }
+ mCamera.setPreviewDisplay(mPreview.getSurfaceHolder());
+ if (needsToStopPreview) {
+ mCamera.startPreview();
+ }
+ } else {
+ mCamera.setPreviewTexture(mPreview.getSurfaceTexture());
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void adjustCameraParameters() {
+ SortedSet sizes = mPreviewSizes;
+ Size previewSize = chooseOptimalSize(sizes);
+ final Camera.Size currentSize = mCameraParameters.getPictureSize();
+ if (currentSize.width != previewSize.getWidth() || currentSize.height != previewSize.getHeight()) {
+ final Size pictureSize = mCaptureSizes.last();
+ if (mShowingPreview) {
+ mCamera.stopPreview();
+ }
+
+ mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
+ mPreview.setTruePreviewSize(previewSize.getWidth(), previewSize.getHeight());
+
+ mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
+ mCameraParameters.setRotation(calcCameraRotation(mDisplayOrientation));
+ setAutoFocusInternal(mAutoFocus);
+ setFlashInternal(mFlash);
+ mCamera.setParameters(mCameraParameters);
+ if (mShowingPreview) {
+ mCamera.startPreview();
+ }
+ }
+ }
+
+ private Size chooseOptimalSize(SortedSet sizes) {
+ if (!mPreview.isReady()) { // Not yet laid out
+ return sizes.last(); // Return the smallest size
+ }
+
+ int desiredWidth;
+ int desiredHeight;
+ final int surfaceWidth = mPreview.getWidth();
+ final int surfaceHeight = mPreview.getHeight();
+ if (mDisplayOrientation == 90 || mDisplayOrientation == 270) {
+ desiredWidth = surfaceHeight;
+ desiredHeight = surfaceWidth;
+ } else {
+ desiredWidth = surfaceWidth;
+ desiredHeight = surfaceHeight;
+ }
+ Size result = null;
+ for (Size size : sizes) { // Iterate from small to large
+ if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
+ return size;
+
+ }
+ result = size;
+ }
+ return result;
+ }
+
+ private void chooseCamera() {
+ for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) {
+ Camera.getCameraInfo(i, mCameraInfo);
+ if (mCameraInfo.facing == mFacing) {
+ mCameraId = i;
+ return;
+ }
+ }
+ mCameraId = INVALID_CAMERA_ID;
+ }
+
+ private void openCamera() {
+ if (mCamera != null) {
+ releaseCamera();
+ }
+ mCamera = Camera.open(mCameraId);
+ mCameraParameters = mCamera.getParameters();
+ // Supported preview sizes
+ mPreviewSizes.clear();
+ for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
+ mPreviewSizes.add(new Size(size.width, size.height));
+ }
+ // Supported picture sizes;
+ mCaptureSizes.clear();
+ for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
+ mCaptureSizes.add(new Size(size.width, size.height));
+ }
+
+ adjustCameraParameters();
+ mCamera.setDisplayOrientation(calcCameraRotation(mDisplayOrientation));
+
+ getCameraListener().onCameraOpened();
+ }
+
+ private void releaseCamera() {
+ if (mCamera != null) {
+ mCamera.release();
+ mCamera = null;
+ getCameraListener().onCameraClosed();
+ }
+ }
+
+ private boolean setAutoFocusInternal(boolean autoFocus) {
+ mAutoFocus = autoFocus;
+ if (isCameraOpened()) {
+ final List modes = mCameraParameters.getSupportedFocusModes();
+ if (autoFocus && modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+ } else if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) {
+ mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED);
+ } else if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) {
+ mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY);
+ } else {
+ mCameraParameters.setFocusMode(modes.get(0));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean setFlashInternal(int flash) {
+ if (isCameraOpened()) {
+ List modes = mCameraParameters.getSupportedFlashModes();
+ String mode = FLASH_MODES.get(flash);
+ if (modes != null && modes.contains(mode)) {
+ mCameraParameters.setFlashMode(mode);
+ mFlash = flash;
+ return true;
+ }
+ String currentMode = FLASH_MODES.get(mFlash);
+ if (modes == null || !modes.contains(currentMode)) {
+ mCameraParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+ mFlash = CameraKit.Constants.FLASH_OFF;
+ return true;
+ }
+ return false;
+ } else {
+ mFlash = flash;
+ return false;
+ }
+ }
+
+}
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/java/com/flurgle/camerakit/Camera2.java
index 0f01b6c4..a428a0ca 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/Camera2.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/Camera2.java
@@ -3,6 +3,7 @@ package com.flurgle.camerakit;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.ImageFormat;
+import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
@@ -13,7 +14,6 @@ import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
-import android.media.MediaRecorder;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
@@ -22,6 +22,12 @@ import android.util.SparseIntArray;
import android.view.Surface;
import android.view.View;
+import com.flurgle.camerakit.encoding.VideoEncoder;
+import com.flurgle.camerakit.utils.AspectRatio;
+import com.flurgle.camerakit.utils.Size;
+import com.flurgle.camerakit.utils.YuvUtils;
+
+import java.io.IOException;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -45,7 +51,7 @@ public class Camera2 extends CameraViewImpl {
private CameraCaptureSession mCaptureSession;
private CaptureRequest.Builder mPreviewRequestBuilder;
private ImageReader mImageReader;
- private MediaRecorder mMediaRecorder;
+ private VideoEncoder mVideoEncoder;
private Semaphore mCameraOpenCloseLock;
@@ -61,6 +67,10 @@ public class Camera2 extends CameraViewImpl {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
+ private boolean mCropOutput;
+
+ private boolean mIsRecording;
+
Camera2(Context context, CameraListener cameraListener, PreviewImpl preview) {
super(cameraListener, preview);
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
@@ -163,6 +173,11 @@ public class Camera2 extends CameraViewImpl {
return mFlash;
}
+ @Override
+ boolean getAutoFocus() {
+ return true;
+ }
+
@Override
void capturePicture() {
if (mFacing == INTERNAL_FACINGS.get(CameraKit.Constants.FACING_BACK)) {
@@ -184,11 +199,17 @@ public class Camera2 extends CameraViewImpl {
if (mCamera == null) {
return;
}
+
+ mIsRecording = true;
}
@Override
void endVideo() {
-
+ mIsRecording = false;
+ if (mVideoEncoder != null) {
+ mVideoEncoder.stopEncoder();
+ mVideoEncoder = null;
+ }
}
@Override
@@ -214,6 +235,10 @@ public class Camera2 extends CameraViewImpl {
}
}
+ void setCropOutput(boolean cropOutput) {
+ this.mCropOutput = cropOutput;
+ }
+
private boolean chooseCameraIdByFacing() {
try {
int internalFacing = mFacing;
@@ -373,7 +398,7 @@ public class Camera2 extends CameraViewImpl {
Size previewSize = getOptimalPreviewSize();
- mPreview.setBufferSize(previewSize.getWidth(), previewSize.getHeight());
+ mPreview.setTruePreviewSize(previewSize.getWidth(), previewSize.getHeight());
Surface surface = mPreview.getSurface();
try {
mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -412,6 +437,8 @@ public class Camera2 extends CameraViewImpl {
360) % 360);
// Stop preview and capture a still picture.
mCaptureSession.stopRepeating();
+
+ mOnImageAvailableListener.allowCallback();
mCaptureSession.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
@@ -437,7 +464,6 @@ public class Camera2 extends CameraViewImpl {
}
}
-
private final CameraDevice.StateCallback mCameraDeviceCallback = new CameraDevice.StateCallback() {
@Override
@@ -545,15 +571,41 @@ public class Camera2 extends CameraViewImpl {
return;
}
- if (!mAllowOneCallback) {
+ if (!mAllowOneCallback && !mIsRecording) {
image.close();
return;
}
- mAllowOneCallback = false;
+ Rect crop = null;
+ if (mCropOutput) {
+
+ }
+
+ if (mAllowOneCallback) {
+ mAllowOneCallback = false;
+ byte[] out = YuvUtils.createRGB(image, crop);
+ getCameraListener().onPictureTaken(out);
+ }
+
+ if (mIsRecording) {
+ if (mVideoEncoder == null) {
+ try {
+ mVideoEncoder = new VideoEncoder(getView().getContext(), mFacing, image.getWidth(), image.getHeight());
+ } catch (IOException e) {
+
+ }
+ }
+
+ if (mVideoEncoder != null) {
+ byte[] out = YuvUtils.getYUVData(image);
+ try {
+ mVideoEncoder.encode(out);
+ } catch (Exception e) {
+
+ }
+ }
+ }
- byte[] out = YuvUtils.createRGB(image);
- getCameraListener().onPictureTaken(out);
image.close();
}
};
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
index 7acd6264..f970fe82 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
@@ -6,8 +6,6 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
@@ -23,11 +21,22 @@ import android.util.AttributeSet;
import android.view.Display;
import android.widget.FrameLayout;
+import com.flurgle.camerakit.utils.DisplayOrientationDetector;
+
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import static com.flurgle.camerakit.CameraKit.Constants.*;
+import static com.flurgle.camerakit.CameraKit.Constants.FACING_BACK;
+import static com.flurgle.camerakit.CameraKit.Constants.FACING_FRONT;
+import static com.flurgle.camerakit.CameraKit.Constants.FLASH_AUTO;
+import static com.flurgle.camerakit.CameraKit.Constants.FLASH_OFF;
+import static com.flurgle.camerakit.CameraKit.Constants.FLASH_ON;
+import static com.flurgle.camerakit.CameraKit.Constants.PICTURE_MODE_QUALITY;
+import static com.flurgle.camerakit.CameraKit.Constants.PICTURE_MODE_SPEED;
+import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_INVISIBLE;
+import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_OFF;
+import static com.flurgle.camerakit.CameraKit.Constants.TAP_TO_FOCUS_VISIBLE;
public class CameraView extends FrameLayout {
@@ -64,6 +73,7 @@ public class CameraView extends FrameLayout {
private boolean mCropOutput;
private int mTapToFocus;
+ private boolean mAutoFocus;
private boolean mAdjustViewBounds;
@@ -108,6 +118,10 @@ public class CameraView extends FrameLayout {
mTapToFocus = a.getInteger(R.styleable.CameraView_ckTapToFocus, TAP_TO_FOCUS_VISIBLE);
}
+ if (attr == R.styleable.CameraView_ckAutoFocus) {
+ mAutoFocus = a.getBoolean(R.styleable.CameraView_ckAutoFocus, true);
+ }
+
if (attr == R.styleable.CameraView_android_adjustViewBounds) {
mAdjustViewBounds = a.getBoolean(R.styleable.CameraView_android_adjustViewBounds, false);
}
@@ -116,7 +130,7 @@ public class CameraView extends FrameLayout {
}
final PreviewImpl preview = new TextureViewPreview(context, this);
- mCameraImpl = new Camera2(context, mCameraListener, preview);
+ mCameraImpl = new Camera1(mCameraListener, preview);
setFacing(mFacing);
setFlash(mFlash);
@@ -236,6 +250,10 @@ public class CameraView extends FrameLayout {
this.mTapToFocus = tapToFocus;
}
+ public void setAutoFocus(boolean autoFocus) {
+ this.mAutoFocus = autoFocus;
+ }
+
public void setCameraListener(CameraListener cameraListener) {
this.mCameraListener = new CameraListenerMiddleWare(cameraListener);
mCameraImpl.setCameraListener(mCameraListener);
@@ -253,11 +271,17 @@ public class CameraView extends FrameLayout {
}
public void startRecordingVideo() {
-
+ mCameraImpl.startVideo();
}
public void stopRecordingVideo() {
-
+ mCameraImpl.endVideo();
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mCameraListener.onVideoTaken(new File(getContext().getExternalFilesDir(null), "video.mp4"));
+ }
+ }, 1000);
}
private void requestCameraPermission() {
@@ -351,24 +375,13 @@ public class CameraView extends FrameLayout {
@Override
public void onPictureTaken(byte[] picture) {
super.onPictureTaken(picture);
- if (mCropOutput) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(picture, 0, picture.length);
- int previewWidth = mCameraImpl.mPreview.getWidth();
- int previewHeight = mCameraImpl.mPreview.getWidth();
-
- AspectRatio viewRatio = AspectRatio.of(getWidth(), getHeight());
- AspectRatio previewRatio = AspectRatio.of(previewWidth, previewHeight);
-
- mCameraListener.onPictureTaken(picture);
- } else {
- mCameraListener.onPictureTaken(picture);
- }
+ mCameraListener.onPictureTaken(picture);
}
@Override
public void onVideoTaken(File video) {
super.onVideoTaken(video);
- mCameraListener.onCameraOpened();
+ mCameraListener.onVideoTaken(video);
}
}
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraViewImpl.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraViewImpl.java
index 4855415e..c9c986be 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/CameraViewImpl.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraViewImpl.java
@@ -2,7 +2,7 @@ package com.flurgle.camerakit;
import android.view.View;
-abstract class CameraViewImpl {
+public abstract class CameraViewImpl {
protected CameraListener mCameraListener;
@@ -31,6 +31,8 @@ abstract class CameraViewImpl {
abstract int getFlash();
+ abstract boolean getAutoFocus();
+
abstract void capturePicture();
abstract void captureStill();
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/PictureCaptureCallback.java b/camerakit/src/main/java/com/flurgle/camerakit/PictureCaptureCallback.java
index 2880a3c1..bc6297b5 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/PictureCaptureCallback.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/PictureCaptureCallback.java
@@ -6,7 +6,7 @@ import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.support.annotation.NonNull;
-abstract class PictureCaptureCallback extends CameraCaptureSession.CaptureCallback {
+public abstract class PictureCaptureCallback extends CameraCaptureSession.CaptureCallback {
static final int STATE_PREVIEW = 0;
static final int STATE_LOCKING = 1;
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/PreviewImpl.java b/camerakit/src/main/java/com/flurgle/camerakit/PreviewImpl.java
index 8fa2947a..3341c78e 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/PreviewImpl.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/PreviewImpl.java
@@ -5,7 +5,9 @@ import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
-abstract class PreviewImpl {
+import com.flurgle.camerakit.utils.AspectRatio;
+
+public abstract class PreviewImpl {
interface Callback {
void onSurfaceChanged();
@@ -14,9 +16,11 @@ abstract class PreviewImpl {
private Callback mCallback;
private int mWidth;
-
private int mHeight;
+ private int mTrueWidth;
+ private int mTrueHeight;
+
void setCallback(Callback callback) {
mCallback = callback;
}
@@ -43,10 +47,19 @@ abstract class PreviewImpl {
return null;
}
- void setBufferSize(int width, int height) {
+ void setTruePreviewSize(int width, int height) {
+ this.mTrueWidth = width;
+ this.mTrueHeight = height;
+
AspectRatio aspectRatio = AspectRatio.of(width, height);
int targetHeight = (int) (getView().getWidth() * aspectRatio.toFloat());
- float scaleY = (float) targetHeight / (float) getView().getHeight();
+ float scaleY;
+ if (getView().getHeight() > 0) {
+ scaleY = (float) targetHeight / (float) getView().getHeight();
+ } else {
+ scaleY = 1;
+ }
+
if (scaleY > 1) {
getView().setScaleY(scaleY);
} else {
@@ -57,6 +70,9 @@ abstract class PreviewImpl {
void setSize(int width, int height) {
mWidth = width;
mHeight = height;
+
+ // Refresh true preview size to adjust scaling
+ setTruePreviewSize(mTrueWidth, mTrueHeight);
}
int getWidth() {
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/TextureViewPreview.java b/camerakit/src/main/java/com/flurgle/camerakit/TextureViewPreview.java
index a2f62800..bca38e64 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/TextureViewPreview.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/TextureViewPreview.java
@@ -90,9 +90,11 @@ public class TextureViewPreview extends PreviewImpl {
@TargetApi(15)
@Override
- void setBufferSize(int width, int height) {
- super.setBufferSize(width, height);
- mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
+ void setTruePreviewSize(int width, int height) {
+ super.setTruePreviewSize(width, height);
+ if (mTextureView.getSurfaceTexture() != null) {
+ mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
+ }
}
void configureTransform() {
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/encoding/Encoder.java b/camerakit/src/main/java/com/flurgle/camerakit/encoding/Encoder.java
new file mode 100644
index 00000000..8759c97a
--- /dev/null
+++ b/camerakit/src/main/java/com/flurgle/camerakit/encoding/Encoder.java
@@ -0,0 +1,82 @@
+package com.flurgle.camerakit.encoding;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+public abstract class Encoder {
+
+ protected BlockingQueue queue = new ArrayBlockingQueue(100);
+ int width;
+ int height;
+ int cameraFacing;
+ long startMS = 0;
+ byte[] rotatedFrameData = null;
+ byte[] planeManagedData = null;
+ boolean encoderStarted = false;
+
+ public Encoder(int cameraFacing, int width, int height) {
+ this.cameraFacing = cameraFacing;
+ this.height = height;
+ this.width = width;
+ }
+
+ abstract public void encode(byte[] rawData);
+
+ public abstract void stopEncoder();
+
+ public boolean hasEncoderStarted() {
+ return encoderStarted;
+ }
+
+ public void setStartMS(long ms) {
+ this.startMS = ms;
+ }
+
+ public static void YV12toYUV420PackedSemiPlanar(final byte[] input, byte[] out, final int width, final int height) {
+
+ final int frameSize = width * height;
+ final int qFrameSize = frameSize / 4;
+
+ for (int i = 0; i < input.length; i++) {
+ if (i < frameSize)
+ out[i] = input[i];
+ if (i < (qFrameSize)) {
+ out[frameSize + i * 2] = input[frameSize + i + qFrameSize]; // Cb (U)
+ out[frameSize + i * 2 + 1] = input[frameSize + i]; // Cr (V)
+ }
+ }
+ }
+
+ static byte[] NV21toYUV420p(byte[] data, int width, int height) {
+ int len_target = (width * height * 3) / 2;
+ byte[] buf_target = new byte[len_target];
+ System.arraycopy(data, 0, buf_target, 0, width * height);
+
+ for (int i = 0; i < (width * height / 4); i++) {
+ buf_target[(width * height) + i] = data[(width * height) + 2 * i + 1];
+ buf_target[(width * height) + (width * height / 4) + i] = data[(width * height) + 2 * i];
+ }
+ return buf_target;
+ }
+
+ public void rotateYUV420Degree90(byte[] data, byte[] output, int imageWidth, int imageHeight) {
+ int i = 0;
+ for (int x = 0; x < imageWidth; x++) {
+ for (int y = imageHeight - 1; y >= 0; y--) {
+ output[i] = data[y * imageWidth + x];
+ i++;
+ }
+ }
+
+ i = imageWidth * imageHeight * 3 / 2 - 1;
+ for (int x = imageWidth - 1; x > 0; x = x - 2) {
+ for (int y = 0; y < imageHeight / 2; y++) {
+ output[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
+ i--;
+ output[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
+ i--;
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/encoding/Frame.java b/camerakit/src/main/java/com/flurgle/camerakit/encoding/Frame.java
new file mode 100644
index 00000000..1fbc7a69
--- /dev/null
+++ b/camerakit/src/main/java/com/flurgle/camerakit/encoding/Frame.java
@@ -0,0 +1,12 @@
+package com.flurgle.camerakit.encoding;
+
+public class Frame {
+
+ public int id;
+ public byte[] frameData;
+
+ public Frame(int id) {
+ this.id = id;
+ }
+
+}
\ No newline at end of file
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/encoding/VideoEncoder.java b/camerakit/src/main/java/com/flurgle/camerakit/encoding/VideoEncoder.java
new file mode 100644
index 00000000..c3163cbd
--- /dev/null
+++ b/camerakit/src/main/java/com/flurgle/camerakit/encoding/VideoEncoder.java
@@ -0,0 +1,157 @@
+package com.flurgle.camerakit.encoding;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ArrayBlockingQueue;
+
+public class VideoEncoder extends Encoder {
+
+ File mVideoFile;
+ FileOutputStream mFOS;
+
+ MediaCodec mMediaCodec;
+ ByteBuffer[] inputBuffers;
+ ByteBuffer[] outputBuffers;
+ MediaFormat mediaFormat = null;
+ public static int frameID = 0;
+
+ public VideoEncoder(Context context, int cameraFacing, int width, int height) throws IOException {
+ super(cameraFacing, width, height);
+
+ try {
+ mVideoFile = new File(context.getExternalFilesDir(null), "video.mp4");
+ mFOS = new FileOutputStream(mVideoFile, false);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ queue = new ArrayBlockingQueue<>(100);
+
+ mMediaCodec = MediaCodec.createEncoderByType("video/avc");
+ mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
+ mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 125000);
+ mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
+ mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);
+ mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+ mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+
+ try {
+ mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
+
+ mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ frameID = 0;
+
+ rotatedFrameData = new byte[width * height * (ImageFormat.getBitsPerPixel(ImageFormat.YV12)) / 8];
+ planeManagedData = new byte[width * height * (ImageFormat.getBitsPerPixel(ImageFormat.YV12)) / 8];
+
+ encoderStarted = true;
+ mMediaCodec.start();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void stopEncoder() {
+ encoderStarted = false;
+ if (mMediaCodec != null) {
+ mMediaCodec.stop();
+ mMediaCodec.release();
+ mMediaCodec = null;
+ }
+
+ if (mFOS != null) {
+ try {
+ mFOS.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+
+
+ @Override
+ public void encode(byte[] rawData) {
+ inputBuffers = mMediaCodec.getInputBuffers();
+ outputBuffers = mMediaCodec.getOutputBuffers();
+
+ int inputBufferIndex = mMediaCodec.dequeueInputBuffer(0);
+ if (inputBufferIndex >= 0) {
+ ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
+ inputBuffer.clear();
+
+ int size = inputBuffer.limit();
+ inputBuffer.put(rawData);
+
+ mMediaCodec.queueInputBuffer(inputBufferIndex, 0, size, (System.currentTimeMillis() - startMS) * 1000, 0);
+ } else {
+ return;
+ }
+
+ MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+ int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
+ do {
+ if (outputBufferIndex >= 0) {
+ Frame frame = new Frame(frameID);
+ ByteBuffer outBuffer = outputBuffers[outputBufferIndex];
+ byte[] outData = new byte[bufferInfo.size];
+ int dataLength = 0;
+
+ outBuffer.get(outData);
+
+ dataLength = outData.length - 2;
+ frame.frameData = new byte[dataLength];
+ // skipping 0x00 0x80 while copying
+ System.arraycopy(outData, 2, frame.frameData, 0, dataLength);
+
+ try {
+ if (bufferInfo.offset != 0) {
+ mFOS.write(outData, bufferInfo.offset, outData.length - bufferInfo.offset);
+ } else {
+ mFOS.write(outData, 0, outData.length);
+ }
+ mFOS.flush();
+ } catch (IOException e) {
+ Log.e("Encoding", e.toString());
+ }
+
+ try {
+ queue.put(frame);
+ } catch (InterruptedException e) {
+ Log.e("EncodeDecode", "interrupted while waiting");
+ e.printStackTrace();
+ } catch (NullPointerException e) {
+ Log.e("EncodeDecode", "frame is null");
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ Log.e("EncodeDecode", "problem inserting in the queue");
+ e.printStackTrace();
+ }
+ Log.d("EncodeDecode", "H263 frame enqueued. queue size now: " + queue.size());
+
+ frameID++;
+ mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
+ outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
+
+ } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ outputBuffers = mMediaCodec.getOutputBuffers();
+ Log.e("EncodeDecode", "output buffer of encoder : info changed");
+ } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ Log.e("EncodeDecode", "output buffer of encoder : format changed");
+ } else {
+ Log.e("EncodeDecode", "unknown value of outputBufferIndex : " + outputBufferIndex);
+ }
+ } while (outputBufferIndex >= 0);
+ }
+
+
+}
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/AspectRatio.java b/camerakit/src/main/java/com/flurgle/camerakit/utils/AspectRatio.java
similarity index 99%
rename from camerakit/src/main/java/com/flurgle/camerakit/AspectRatio.java
rename to camerakit/src/main/java/com/flurgle/camerakit/utils/AspectRatio.java
index 42c72f6d..c12b2b90 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/AspectRatio.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/utils/AspectRatio.java
@@ -1,4 +1,4 @@
-package com.flurgle.camerakit;
+package com.flurgle.camerakit.utils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/DisplayOrientationDetector.java b/camerakit/src/main/java/com/flurgle/camerakit/utils/DisplayOrientationDetector.java
similarity index 96%
rename from camerakit/src/main/java/com/flurgle/camerakit/DisplayOrientationDetector.java
rename to camerakit/src/main/java/com/flurgle/camerakit/utils/DisplayOrientationDetector.java
index 128c74f1..29d98a4b 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/DisplayOrientationDetector.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/utils/DisplayOrientationDetector.java
@@ -1,4 +1,4 @@
-package com.flurgle.camerakit;
+package com.flurgle.camerakit.utils;
import android.content.Context;
import android.util.SparseIntArray;
@@ -6,7 +6,7 @@ import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
-abstract class DisplayOrientationDetector {
+public abstract class DisplayOrientationDetector {
private final OrientationEventListener mOrientationEventListener;
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/Size.java b/camerakit/src/main/java/com/flurgle/camerakit/utils/Size.java
similarity index 96%
rename from camerakit/src/main/java/com/flurgle/camerakit/Size.java
rename to camerakit/src/main/java/com/flurgle/camerakit/utils/Size.java
index 1a03668d..dd011363 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/Size.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/utils/Size.java
@@ -1,4 +1,4 @@
-package com.flurgle.camerakit;
+package com.flurgle.camerakit.utils;
import android.support.annotation.NonNull;
diff --git a/camerakit/src/main/java/com/flurgle/camerakit/YuvUtils.java b/camerakit/src/main/java/com/flurgle/camerakit/utils/YuvUtils.java
similarity index 71%
rename from camerakit/src/main/java/com/flurgle/camerakit/YuvUtils.java
rename to camerakit/src/main/java/com/flurgle/camerakit/utils/YuvUtils.java
index 0aa54f6f..d09ea710 100644
--- a/camerakit/src/main/java/com/flurgle/camerakit/YuvUtils.java
+++ b/camerakit/src/main/java/com/flurgle/camerakit/utils/YuvUtils.java
@@ -1,9 +1,10 @@
-package com.flurgle.camerakit;
+package com.flurgle.camerakit.utils;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.media.Image;
+import android.support.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -11,7 +12,22 @@ import java.nio.ByteBuffer;
public class YuvUtils {
- public static byte[] createRGB(Image image) {
+ public static byte[] createRGB(Image image, @Nullable Rect crop) {
+ byte[] data = getYUVData(image);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
+
+ if (crop == null) {
+ crop = new Rect(0, 0, image.getWidth(), image.getHeight());
+ }
+
+ yuvImage.compressToJpeg(crop, 50, out);
+
+ return out.toByteArray();
+ }
+
+ public static byte[] getYUVData(Image image) {
ByteBuffer bufferY = image.getPlanes()[0].getBuffer();
byte[] y = new byte[bufferY.remaining()];
bufferY.get(y);
@@ -32,13 +48,7 @@ public class YuvUtils {
} catch (IOException e) {
}
- byte data[] = outputStream.toByteArray();
-
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
- yuvImage.compressToJpeg(new Rect(0, 0, image.getWidth(), image.getHeight()), 50, out);
-
- return out.toByteArray();
+ return outputStream.toByteArray();
}
}
diff --git a/camerakit/src/main/res/values/attrs.xml b/camerakit/src/main/res/values/attrs.xml
index 5b474faa..154736ac 100644
--- a/camerakit/src/main/res/values/attrs.xml
+++ b/camerakit/src/main/res/values/attrs.xml
@@ -28,6 +28,8 @@
+
+
diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml
index ffa019b8..513e9ebf 100644
--- a/demo/src/main/AndroidManifest.xml
+++ b/demo/src/main/AndroidManifest.xml
@@ -9,13 +9,16 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
-
+
+
-
+
\ No newline at end of file
diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
index a6496705..6d620278 100644
--- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
+++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
@@ -18,6 +18,8 @@ import com.flurgle.camerakit.CameraKit;
import com.flurgle.camerakit.CameraListener;
import com.flurgle.camerakit.CameraView;
+import java.io.File;
+
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnCheckedChanged;
@@ -110,7 +112,22 @@ public class MainActivity extends AppCompatActivity {
@OnClick(R.id.captureVideo)
void captureVideo() {
+ camera.startRecordingVideo();
+ camera.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ camera.stopRecordingVideo();
+ }
+ }, 3000);
+ camera.setCameraListener(new CameraListener() {
+ @Override
+ public void onVideoTaken(File video) {
+ super.onVideoTaken(video);
+ PreviewDialog previewDialog = new PreviewDialog(MainActivity.this, video);
+ previewDialog.show();
+ }
+ });
}
@OnClick(R.id.toggleCamera)
@@ -163,7 +180,7 @@ public class MainActivity extends AppCompatActivity {
invalidateParameters();
}
- @OnFocusChange({ R.id.width, R.id.height })
+ @OnFocusChange({R.id.width, R.id.height})
void inputFocusChanged(View view, boolean f) {
blockInvalidation = view.isFocused();
}
diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/PreviewDialog.java b/demo/src/main/java/com/flurgle/camerakit/demo/PreviewDialog.java
index a017e7ad..68713925 100644
--- a/demo/src/main/java/com/flurgle/camerakit/demo/PreviewDialog.java
+++ b/demo/src/main/java/com/flurgle/camerakit/demo/PreviewDialog.java
@@ -3,9 +3,14 @@ package com.flurgle.camerakit.demo;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
+import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.view.View;
import android.widget.ImageView;
+import android.widget.VideoView;
+
+import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
@@ -13,22 +18,44 @@ import butterknife.ButterKnife;
public class PreviewDialog extends Dialog {
@BindView(R.id.image)
- ImageView image;
+ ImageView imageView;
+
+ @BindView(R.id.video)
+ VideoView videoView;
private Bitmap bitmap;
+ private File video;
public PreviewDialog(@NonNull Context context, Bitmap bitmap) {
super(context);
this.bitmap = bitmap;
}
+ public PreviewDialog(@NonNull Context context, File video) {
+ super(context);
+ this.video = video;
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_preview);
ButterKnife.bind(this);
- image.setImageBitmap(bitmap);
+ if (bitmap != null) {
+ imageView.setImageBitmap(bitmap);
+ } else if (video != null) {
+ imageView.setVisibility(View.GONE);
+ videoView.setVideoPath(video.getAbsolutePath());
+ videoView.setMediaController(null);
+ videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ mediaPlayer.setLooping(true);
+ }
+ });
+ videoView.start();
+ }
}
}
diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml
index fde00098..73b889be 100644
--- a/demo/src/main/res/layout/activity_main.xml
+++ b/demo/src/main/res/layout/activity_main.xml
@@ -16,12 +16,12 @@
+ app:ckPictureMode="quality" />