diff --git a/.repo/demo2.png b/.repo/demo2.png index 52889c20..e6a51cb7 100644 Binary files a/.repo/demo2.png and b/.repo/demo2.png differ diff --git a/.repo/demo3.png b/.repo/demo3.png index dad4acb7..897288b8 100644 Binary files a/.repo/demo3.png and b/.repo/demo3.png differ diff --git a/.repo/demo4.png b/.repo/demo4.png index a1edba4b..4dd497a2 100644 Binary files a/.repo/demo4.png and b/.repo/demo4.png differ diff --git a/.repo/permissions.gif b/.repo/permissions.gif index 79916a36..bccf3352 100644 Binary files a/.repo/permissions.gif and b/.repo/permissions.gif differ diff --git a/.repo/sizing.gif b/.repo/sizing.gif new file mode 100644 index 00000000..7c4a123a Binary files /dev/null and b/.repo/sizing.gif differ diff --git a/README.md b/README.md index 908bcf19..5a143852 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,20 @@ Try out all the unique features using the CameraKit Demo from the Google Play st - [Features](#features) - [Setup](#setup) - [Usage](#usage) - - [Events](#events) - - [Extra Attributes](#extra-attributes) - - [`ckFacing`](#ckfacing) - - [`ckFlash`](#ckflash) - - [`ckFocus`](#ckfocus) - - [`ckMethod`](#ckmethod) - - [`ckZoom`](#ckzoom) - - [`ckCropOutput`](#ckcropoutput) - - [`ckJpegQuality`](#ckjpegquality) - [Capturing Images](#capturing-images) - [Capturing Video](#capturing-video) +- [Extra Attributes](#extra-attributes) + - [`ckFacing`](#ckfacing) + - [`ckFlash`](#ckflash) + - [`ckFocus`](#ckfocus) + - [`ckMethod`](#ckmethod) + - [`ckZoom`](#ckzoom) + - [`ckCropOutput`](#ckcropoutput) + - [`ckJpegQuality`](#ckjpegquality) - [Automatic Permissions Behavior](#automatic-permissions-behavior) - [Dynamic Sizing Behavior](#dynamic-sizing-behavior) - - [Output Cropping](#output-cropping) - [`adjustViewBounds`](#adjustviewbounds) -- [Capture Methods](#capture-methods) - - [Standard](#standard) - - [Still](#still) - - [Auto](#auto) -- [Focus](#focus) +- [Events](#events) - [Credits](#credits) - [License](#license) @@ -52,7 +46,7 @@ Try out all the unique features using the CameraKit Demo from the Google Play st - Multiple capture methods. - `METHOD_STANDARD`: an image captured normally using the camera APIs. - `METHOD_STILL`: a freeze frame of the `CameraView` preview (similar to SnapChat and Instagram) for devices with slower cameras. - - Automatic picture mode determination based on measured speed. + - `METHOD_AUTO`: automatic capture method determination based on measured speed. - Built-in tap to focus and auto focus. - Built-in pinch to zoom. @@ -92,37 +86,47 @@ protected void onPause() { } ``` -### Events +### Capturing Images -Make sure you can react to different camera events by setting up a `CameraListener` instance. +To capture an image just call `CameraView.captureImage()`. Make sure you setup a `CameraListener` to handle the image callback. ```java camera.setCameraListener(new CameraListener() { - - @Override - public void onCameraOpened() { - super.onCameraOpened(); - } - - @Override - public void onCameraClosed() { - super.onCameraClosed(); - } - @Override public void onPictureTaken(byte[] picture) { super.onPictureTaken(picture); + + // Create a bitmap + Bitmap result = BitmapFactory.decodeByteArray(picture, 0, picture.length); } +}); + +camera.captureImage(); +``` +### Capturing Video + +To capture video just call `CameraView.startRecordingVideo()` to start, and `CameraView.stopRecordingVideo()` to finish. Make sure you setup a `CameraListener` to handle the video callback. + +```java +camera.setCameraListener(new CameraListener() { @Override public void onVideoTaken(File video) { super.onVideoTaken(video); + // The File parameter is an MP4 file. } - }); + +camera.startRecordingVideo(); +camera.postDelayed(new Runnable() { + @Override + public void run() { + camera.stopRecordingVideo(); + } +}, 2500); ``` -### Extra Attributes +## Extra Attributes ```xml ``` -#### `ckFacing` +|Attribute|Values| +|---------|------| +|[`ckFacing`](#ckfacing)|[`back`](#back) [`front`](#front)| +|[`ckFlash`](#ckflash)|[`off`](#off) [`on`](#on) [`auto`](#auto)| +|[`ckFocus`](#ckfocus)|[`off`](#off-1) [`continuous`](#continuous) [`tap`](#tap)| +|[`ckMethod`](#ckmethod)|[`standard`](#standard) [`still`](#still) [`speed`](#speed)| +|[`ckZoom`](#ckzoom)|[`off`](#off-2) [`pinch`](#pinch)| +|[`ckCropOutput`](#ckcropoutput)|[`true`](#true) [`false`](#false)| +|[`ckJpegQuality`](#ckjpegquality)|[`0 <= n <= 100`](#ckjpegquality)| -| Value | Description | -| --------------| -------------| -| `back` | Default `CameraView` preview to back camera. | -| `front` | Default `CameraView` preview to front camera. | +- - - -#### `ckFlash` +### `ckFacing` +[`back`](#back) [`front`](#front) -| Value | Description | -| --------------| -------------| -| `off` | Default `CameraView` flash to off. | -| `on` | Default `CameraView` flash to on. | -| `auto` | Default `CameraView` flash to automatic. | +#### `back` -#### `ckFocus` +```java +cameraView.setFacing(CameraKit.Constants.FACING_BACK); +``` -| Value | Description | -| --------------| -------------| -| `off` | Tap to focus is enabled and a visible focus circle appears when tapped, similar to the Android built-in camera. | -| `continuous` | Tap to focus is enabled, but no focus circle appears. | -| `tap` | Tap to focus is off. | +#### `front` + +```java +cameraView.setFacing(CameraKit.Constants.FACING_FRONT); +``` -#### `ckMethod` +- - - -| Value | Description | -| --------------| -------------| -| `standard` | Use normal Android Camera API image capturing. | -| `still` | Freeze the `CameraView` preview and grab a `Bitmap` of the frame. | -| `auto` (coming soon) | Default picture mode to `standard`, but fallback to `still` if capturing is determined to be too slow. | +### `ckFlash` +[`off`](#off) [`on`](#on) [`auto`](#auto) -#### `ckZoom` +#### `off` -| Value | Description | -| --------------| -------------| -| `off` | User can zoom using pinching gestures with their fingers. | -| `pinch` | User can zoom in and out using pinching gestures on the `CameraView`. | +```java +cameraView.setFlash(CameraKit.Constants.FLASH_OFF); +``` -#### `ckCropOutput` +#### `on` -| Value | Description | -| --------------| -------------| -| `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. | +```java +cameraView.setFlash(CameraKit.Constants.FLASH_ON); +``` -#### `ckJpegQuality` +#### `auto` -| Value | Description | -| --------------| -------------| -| `0 <= n <= 100`| Percent quality for returned JPEG data. | +```java +cameraView.setFlash(CameraKit.Constants.FLASH_AUTO); +``` +- - - -### Capturing Images +### `ckFocus` +[`off`](#off-1) [`continuous`](#continuous) [`tap`](#tap) -To capture an image just call `CameraView.captureImage()`. Make sure you setup a `CameraListener` to handle the image callback. +#### `off` ```java -camera.setCameraListener(new CameraListener() { - @Override - public void onPictureTaken(byte[] picture) { - super.onPictureTaken(picture); +cameraView.setFocus(CameraKit.Constants.FOCUS_OFF); +``` - // Create a bitmap - Bitmap result = BitmapFactory.decodeByteArray(picture, 0, picture.length); - } -}); +#### `continuous` -camera.captureImage(); +```java +cameraView.setFocus(CameraKit.Constants.FOCUS_CONTINUOUS); ``` -### Capturing Video - -To capture video just call `CameraView.startRecordingVideo()` to start, and `CameraView.stopRecordingVideo()` to finish. Make sure you setup a `CameraListener` to handle the video callback. +#### `tap` ```java -camera.setCameraListener(new CameraListener() { - @Override - public void onVideoTaken(File video) { - super.onVideoTaken(video); - // The File parameter is an MP4 file. - } -}); - -camera.startRecordingVideo(); -camera.postDelayed(new Runnable() { - @Override - public void run() { - camera.stopRecordingVideo(); - } -}, 2500); +cameraView.setFocus(CameraKit.Constants.FOCUS_TAP); ``` -## Automatic Permissions Behavior +- - - -You can handle permissions yourself in whatever way you want, but if you make a call to `CameraView.start()` without the `android.permission.CAMERA` permission, an exception would normally be thrown and your app would crash. +### `ckMethod` +[`standard`](#standard) [`still`](#still) [`speed`](#speed) -With CameraKit, we will automatically prompt for the `android.permission.CAMERA` permission if it's not available. If you want to handle it yourself, just make sure you don't call `CameraView.start()` until you acquire the permissions. +#### `standard` -![Permissions behavior gif](.repo/permissions.gif) +```java +cameraView.setMethod(CameraKit.Constants.METHOD_STANDARD); +``` -## Dynamic Sizing Behavior +When you use `METHOD_STANDARD` (`camerakit:ckMethod="standard"`), images will be captured using the normal camera API capture method using the shutter. -You can setup the `CameraView` dimensions however you want. When your dimensions don't match the aspect ratio of the internal preview surface, the surface will be cropped minimally to fill the view. The behavior is the same as the `android:scaleType="centerCrop"` on an `ImageView`. +[Insert GIF] + +#### `still` + +```java +cameraView.setMethod(CameraKit.Constants.METHOD_STILL); +``` + +When you use `METHOD_STILL` (`camerakit:ckMethod="still"`), images will be captured by grabbing a single frame from the preview. This behavior is the same as SnapChat and Instagram. This method has a higher rate of motion blur but can be a better experience for users with slower cameras. [Insert GIF] -### Output Cropping +#### `speed` + +```java +cameraView.setMethod(CameraKit.Constants.METHOD_SPEED); +``` -When you capture output you can either capture everything - even what is not visible to the user on the `CameraView` - or just the visible preview. See [`ckCropOutput`](#ckcropoutput) above for usage. +When you use `METHOD_SPEED` (`camerakit:ckMethod="speed"`), images will be first be captured using the [standard](#standard) method. If capture consistently takes a long amount of time, the picture mode will fallback to [still](#still) capture. [Insert GIF] -### `adjustViewBounds` +- - - -You can use a mix of a fixed dimension (a set value or `match_parent`) as well as `wrap_content`. When you do this make sure you set `android:adjustViewBounds="true"` on the `CameraView`. +### `ckZoom` +[`off`](#off-2) [`pinch`](#pinch) -When you do this the dimension set to `wrap_content` will automatically align with the true aspect ratio of the preview surface. In this case the whole preview will be visible with no cropping. +#### `off` -## Capture Methods +```java +cameraView.setZoom(CameraKit.Constants.ZOOM_OFF); +``` -We decided to add multiple capture modes to CameraKit to allow you to give a better image capturing experience to users with slower cameras when appropriate. +#### `pinch` -See [`ckMethod`](#ckmethod) above for usage. +```java +cameraView.setZoom(CameraKit.Constants.ZOOM_PINCH); +``` -### Standard +- - - -When you use `METHOD_STANDARD` (`camerakit:ckMethod="standard"`), images will be captured using the normal camera API capture method using the shutter. +### `ckCropOutput` +[`true`](#true) [`false`](#false) -[Insert GIF] +#### `true` -### Still +```java +cameraView.setCropOutput(true); +``` -When you use `METHOD_STILL` (`camerakit:ckMethod="still"`), images will be captured by grabbing a single frame from the preview. This behavior is the same as SnapChat and Instagram. This method has a higher rate of motion blur but can be a better experience for users with slower cameras. +#### `false` -[Insert GIF] +```java +cameraView.setCropOutput(false); +``` -### Auto +- - - -When you use `METHOD_AUTO` (`camerakit:ckMethod="auto"`), images will be first be captured using the [standard](#standard) method. If capture consistently takes a long amount of time, the picture mode will fallback to [still](#still) capture. +### `ckJpegQuality` -[Insert GIF] +```java +cameraView.setJpegQuality(100); +``` -## Focus +- - - -Along with the always on auto-focus, you can enable tap to focus in your `CameraView`. See [`ckFocus`](#ckFocus) for usage. You have the option of having it on with a visible focus marker, on with no marker, or off. +## Automatic Permissions Behavior -[Insert GIF] +You can handle permissions yourself in whatever way you want, but if you make a call to `CameraView.start()` without the `android.permission.CAMERA` permission, an exception would normally be thrown and your app would crash. + +With CameraKit, we will automatically prompt for the `android.permission.CAMERA` permission if it's not available. If you want to handle it yourself, just make sure you don't call `CameraView.start()` until you acquire the permissions. + +![Permissions behavior gif](.repo/permissions.gif) + +## Dynamic Sizing Behavior + +You can setup the `CameraView` dimensions however you want. When your dimensions don't match the aspect ratio of the internal preview surface, the surface will be cropped minimally to fill the view. The behavior is the same as the `android:scaleType="centerCrop"` on an `ImageView`. + +![Dynamic sizing gif](.repo/sizing.gif) + +### `adjustViewBounds` + +You can use a mix of a fixed dimension (a set value or `match_parent`) as well as `wrap_content`. When you do this make sure you set `android:adjustViewBounds="true"` on the `CameraView`. + +When you do this the dimension set to `wrap_content` will automatically align with the true aspect ratio of the preview surface. In this case the whole preview will be visible with no cropping. + +## Events + +Make sure you can react to different camera events by setting up a `CameraListener` instance. + +```java +camera.setCameraListener(new CameraListener() { + + @Override + public void onCameraOpened() { + super.onCameraOpened(); + } + + @Override + public void onCameraClosed() { + super.onCameraClosed(); + } + + @Override + public void onPictureTaken(byte[] picture) { + super.onPictureTaken(picture); + } + + @Override + public void onVideoTaken(File video) { + super.onVideoTaken(video); + } + +}); +``` ## Credits diff --git a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java index 25823927..789f04b6 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/PreviewImpl.java @@ -16,8 +16,8 @@ abstract class PreviewImpl { private int mWidth; private int mHeight; - private int mTrueWidth; - private int mTrueHeight; + protected int mTrueWidth; + protected int mTrueHeight; void setCallback(Callback callback) { mCallback = callback; @@ -76,9 +76,11 @@ abstract class PreviewImpl { } if (scaleY > 1) { + getView().setScaleX(1); getView().setScaleY(scaleY); } else { getView().setScaleX(1 / scaleY); + getView().setScaleY(1); } } } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java index a0d5e57b..4df13abf 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/TextureViewPreview.java @@ -33,6 +33,7 @@ class TextureViewPreview extends PreviewImpl { setSize(width, height); configureTransform(); dispatchSurfaceChanged(); + setTruePreviewSize(mTrueWidth, mTrueHeight); } @Override diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java index a580eb92..6cae659d 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java @@ -24,7 +24,7 @@ public class CameraKit { public static final int METHOD_STANDARD = 0; public static final int METHOD_STILL = 1; - public static final int METHOD_AUTO = 2; + public static final int METHOD_SPEED = 2; public static final int FOCUS_OFF = 0; public static final int FOCUS_CONTINUOUS = 1; diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 4ddae2d1..20ff48a3 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -130,6 +130,7 @@ public class CameraView extends FrameLayout { MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), heightMeasureSpec ); + return; } else if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { int width = MeasureSpec.getSize(widthMeasureSpec); float ratio = (float) width / (float) previewSize.getHeight(); @@ -138,10 +139,11 @@ public class CameraView extends FrameLayout { widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) ); + return; } - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); } + + super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public void start() { diff --git a/camerakit/src/main/res/values/attrs.xml b/camerakit/src/main/res/values/attrs.xml index a1029817..2508bc13 100644 --- a/camerakit/src/main/res/values/attrs.xml +++ b/camerakit/src/main/res/values/attrs.xml @@ -35,7 +35,7 @@ - + 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 59d1cc3c..6adcad8f 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java @@ -24,7 +24,7 @@ import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -public class MainActivity extends AppCompatActivity { +public class MainActivity extends AppCompatActivity implements View.OnLayoutChangeListener { @BindView(R.id.activity_main) ViewGroup parent; @@ -42,11 +42,6 @@ public class MainActivity extends AppCompatActivity { @BindView(R.id.cropModeRadioGroup) RadioGroup cropModeRadioGroup; - // Tap to Focus: - - @BindView(R.id.tapToFocusModeRadioGroup) - RadioGroup tapToFocusModeRadioGroup; - // Width: @BindView(R.id.screenWidth) @@ -86,20 +81,10 @@ public class MainActivity extends AppCompatActivity { } }); - camera.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - mCameraWidth = right - left; - mCameraHeight = bottom - top; - - width.setText(String.valueOf(mCameraWidth)); - height.setText(String.valueOf(mCameraHeight)); - } - }); + camera.addOnLayoutChangeListener(this); captureModeRadioGroup.setOnCheckedChangeListener(captureModeChangedListener); cropModeRadioGroup.setOnCheckedChangeListener(cropModeChangedListener); - tapToFocusModeRadioGroup.setOnCheckedChangeListener(tapToFocusModeChangedListener); widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener); heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener); } @@ -128,7 +113,7 @@ public class MainActivity extends AppCompatActivity { ResultHolder.dispose(); ResultHolder.setImage(bitmap); ResultHolder.setNativeCaptureSize( - captureModeRadioGroup.getCheckedRadioButtonId() == R.id.modeCaptureQuality ? + captureModeRadioGroup.getCheckedRadioButtonId() == R.id.modeCaptureStandard ? camera.getCaptureSize() : camera.getPreviewSize() ); ResultHolder.setTimeToCallback(callbackTime - startTime); @@ -192,12 +177,12 @@ public class MainActivity extends AppCompatActivity { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { camera.setMethod( - checkedId == R.id.modeCaptureQuality ? + checkedId == R.id.modeCaptureStandard ? CameraKit.Constants.METHOD_STANDARD : CameraKit.Constants.METHOD_STILL ); - Toast.makeText(MainActivity.this, "Picture capture set to" + (checkedId == R.id.modeCaptureQuality ? " quality!" : " speed!"), Toast.LENGTH_SHORT).show(); + Toast.makeText(MainActivity.this, "Picture capture set to" + (checkedId == R.id.modeCaptureStandard ? " quality!" : " speed!"), Toast.LENGTH_SHORT).show(); } }; @@ -212,21 +197,6 @@ public class MainActivity extends AppCompatActivity { } }; - RadioGroup.OnCheckedChangeListener tapToFocusModeChangedListener = new RadioGroup.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(RadioGroup group, int checkedId) { - camera.setFocus( - checkedId == R.id.modeTapToFocusVisible ? - CameraKit.Constants.FOCUS_CONTINUOUS : - checkedId == R.id.modeTapToFocusInvisible ? - CameraKit.Constants.FOCUS_TAP : - CameraKit.Constants.FOCUS_OFF - ); - - Toast.makeText(MainActivity.this, "Tap to focus is" + (checkedId == R.id.modeTapToFocusOff ? " off!" : (checkedId == R.id.modeTapToFocusVisible) ? " on and visible!" : " on and invisible!"), Toast.LENGTH_SHORT).show(); - } - }; - @OnClick(R.id.widthUpdate) void widthUpdateClicked() { if (widthUpdate.getAlpha() >= 1) { @@ -322,9 +292,20 @@ public class MainActivity extends AppCompatActivity { cameraLayoutParams.width = width; cameraLayoutParams.height = height; + camera.addOnLayoutChangeListener(this); camera.setLayoutParams(cameraLayoutParams); Toast.makeText(this, (updateWidth && updateHeight ? "Width and height" : updateWidth ? "Width" : "Height") + " updated!", Toast.LENGTH_SHORT).show(); } + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + mCameraWidth = right - left; + mCameraHeight = bottom - top; + + width.setText(String.valueOf(mCameraWidth)); + height.setText(String.valueOf(mCameraHeight)); + + camera.removeOnLayoutChangeListener(this); + } } diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index bb4e82c7..feac6a53 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -18,12 +18,13 @@ android:layout_width="match_parent" android:layout_height="500dp" android:layout_gravity="center_horizontal" - app:ckCaptureMode="standard" + android:adjustViewBounds="true" app:ckCropOutput="false" app:ckFacing="back" app:ckFlash="off" app:ckFocus="continuous" - app:ckJpegQuality="100" /> + app:ckJpegQuality="100" + app:ckMethod="standard" /> @@ -116,21 +117,21 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" - android:checkedButton="@+id/modeCaptureQuality" + android:checkedButton="@+id/modeCaptureStandard" android:orientation="horizontal"> + android:text="Standard" /> + android:text="Still" /> @@ -158,7 +159,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="3dp" - android:text="OUTPUT" + android:text="CROP OUTPUT" android:textColor="@android:color/black" android:textSize="14dp" android:textStyle="bold" /> @@ -175,73 +176,14 @@ android:id="@+id/modeCropFullSize" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="Full size" /> + android:text="Off" /> - - - - - - - - - - - - - - - - - - - - - - + android:text="On" />