From 3a6666fdbc697c0d9d92d2374a11036557e7ec66 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sat, 5 Aug 2017 13:51:48 +0200 Subject: [PATCH] Built in support for drawing grids --- README.md | 47 ++++------ .../java/com/flurgle/camerakit/CameraKit.java | 6 ++ .../com/flurgle/camerakit/CameraView.java | 30 ++++++ .../flurgle/camerakit/GridLinesLayout.java | 94 +++++++++++++++++++ camerakit/src/main/res/values/attrs.xml | 6 ++ .../types/com/flurgle/camerakit/Grid.java | 16 ++++ .../flurgle/camerakit/demo/MainActivity.java | 21 +++++ demo/src/main/res/layout/activity_main.xml | 82 +++++++++++++++- 8 files changed, 269 insertions(+), 33 deletions(-) create mode 100644 camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java create mode 100644 camerakit/src/main/types/com/flurgle/camerakit/Grid.java diff --git a/README.md b/README.md index 32cbd94e..47da8dd5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - *introduced a smart measuring and sizing behavior, replacing bugged `adjustViewBounds`* - *measure `CameraView` as center crop or center inside* - *add multiple `CameraListener`s for events* +- *new `setGrid` APIs, to draw 3x3, 4x4 or golden ratio grids *Feel free to open issues with suggestions or contribute.* @@ -40,8 +41,7 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt - [cameraCropOutput](#cameracropoutput) - [cameraJpegQuality](#camerajpegquality) - [cameraWhiteBalance](#camerawhitebalance) - - [Deprecated: cameraCaptureMethod](#cameracapturemethod) - - [Deprecated: cameraPermissionsPolicy](#camerapermissionpolicy) + - [cameraGrid](#cameragrid) - [Permissions Behavior](#permissions-behavior) - [Manifest file](#manifest-file) - [Roadmap](#roadmap) @@ -57,6 +57,7 @@ CameraKit is an easy to use utility to work with the Android Camera APIs. Everyt - Multiple capture methods - Take high-resolution pictures with `capturePicture` - Take quick snapshots as a freeze frame of the preview with `captureSnapshot` (similar to SnapChat and Instagram) +- Built-in grid drawing (3x3, 4x4, golden ratio) - Built-in tap to focus - `CameraUtils` to help with Bitmaps and orientations - EXIF support @@ -220,6 +221,7 @@ This means that part of the preview is hidden, and the image output will contain app:cameraFlash="off" app:cameraFocus="continuous" app:cameraZoom="pinch" + app:cameraGrid="off" app:cameraSessionType="picture" app:cameraCropOutput="true" app:cameraJpegQuality="100" @@ -235,12 +237,11 @@ This means that part of the preview is hidden, and the image output will contain |[`cameraFlash`](#cameraflash)|`setFlash()`|`off` `on` `auto` `torch`|`off`| |[`cameraFocus`](#camerafocus)|`setFocus()`|`off` `continuous` `tap` `tapWithMarker`|`continuous`| |[`cameraZoomMode`](#camerazoommode)|`setZoom()`|`off` `pinch`|`off`| +|[`cameraGrid`](#cameragrid)|`setGrid()`|`off` `grid3x3` `grid4x4` `phi`|`off`| |[`cameraCropOutput`](#cameracropoutput)|`setCropOutput()`|`true` `false`|`false`| |[`cameraJpegQuality`](#camerajpegquality)|`setJpegQuality()`|`0 <= n <= 100`|`100`| |[`cameraVideoQuality`](#cameravideoquality)|`setVideoQuality()`|`max480p` `max720p` `max1080p` `max2160p` `highest` `lowest`|`max480p`| |[`cameraWhiteBalance`](#camerawhitebalance)|`setWhiteBalance()`|`auto` `incandescent` `fluorescent` `daylight` `cloudy`|`auto`| -|[`cameraCaptureMethod`](#cameracapturemethod) (Deprecated)|`setCaptureMethod()`|`standard` `frame`|`standard`| -|[`cameraPermissionPolicy`](#camerapermissionpolicy) (Deprecated)|`setPermissionPolicy()`|`picture` `video`|`picture`| ### cameraSessionType @@ -296,10 +297,20 @@ cameraView.setZoom(CameraKit.Constants.ZOOM_OFF); cameraView.setZoom(CameraKit.Constants.ZOOM_PINCH); ``` +### cameraGrid + +Lets you draw grids over the camera preview. Supported values are `off`, `grid3x3` and `grid4x4` for regular grids, and `phi` for a grid based on the golden ratio constant, often used in photography. + +```java +cameraView.setZoom(CameraKit.Constants.GRID_OFF); +cameraView.setZoom(CameraKit.Constants.GRID_3X3); +cameraView.setZoom(CameraKit.Constants.GRID_4X4); +cameraView.setZoom(CameraKit.Constants.GRID_PHI); +``` ### cameraCropOutput -Wheter the output picture should be cropped to fit the aspect ratio of the preview surface. +Whether the output picture should be cropped to fit the aspect ratio of the preview surface. This can guarantee consistency between what the user sees and the final output, if you fixed the camera view dimensions. This does not support videos. ### cameraJpegQuality @@ -337,28 +348,6 @@ cameraView.setWhiteBalance(CameraKit.Constants.WHITE_BALANCE_DAYLIGHT); cameraView.setWhiteBalance(CameraKit.Constants.WHITE_BALANCE_CLOUDY); ``` -### cameraCaptureMethod -*Deprecated. Use cameraSessionType instead* - -How to capture pictures, either standard or frame. The frame option lets you capture and save a preview frame, which can be better with slower camera sensors, though the captured image can be blurry or noisy. - -```java -cameraView.setMethod(CameraKit.Constants.CAPTURE_METHOD_STANDARD); -cameraView.setMethod(CameraKit.Constants.CAPTURE_METHOD_FRAME); -``` - -### cameraPermissionPolicy -*Deprecated. Use cameraSessionType instead* - -Either picture or video. This tells the library which permissions should be asked before starting the camera session. In the case of 'picture', we require the camera permissions. In the case of 'video', the record audio permission is asked as well. - -Please note that, if needed, the latter should be added to your manifest file or the app will crash. - -```java -cameraView.setPermissionPolicy(CameraKit.Constants.PERMISSIONS_PICTURE); -cameraView.setPermissionPolicy(CameraKit.Constants.PERMISSIONS_VIDEO); -``` - ## Permissions behavior `CameraView` needs two permissions: @@ -394,7 +383,9 @@ These are things that need to be done, off the top of my head: - [x] fix CropOutput class presumably not working on rotated pictures - [x] test video and 'frame' capture behavior, I expect some bugs there -- [ ] simple APIs to draw grid lines +- [x] simple APIs to draw grid lines +- [ ] animate grid lines similar to stock camera app +- [ ] check focus, not sure it exposes the right part of the image - [x] replace setCameraListener() with addCameraListener() - [ ] add a `sizingMethod` API to choose the capture size? Could be `max`, `4:3`, `16:9`... Right now it's `max` - [ ] pinch to zoom support diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java index 650ed030..0abbde84 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java @@ -26,6 +26,11 @@ public class CameraKit { public static final int ZOOM_OFF = 0; public static final int ZOOM_PINCH = 1; + public static final int GRID_OFF = 0; + public static final int GRID_3X3 = 1; + public static final int GRID_4X4 = 2; + public static final int GRID_PHI = 3; + public static final int SESSION_TYPE_PICTURE = 0; public static final int SESSION_TYPE_VIDEO = 1; @@ -79,6 +84,7 @@ public class CameraKit { static final int DEFAULT_WHITE_BALANCE = Constants.WHITE_BALANCE_AUTO; static final int DEFAULT_SESSION_TYPE = Constants.SESSION_TYPE_PICTURE; static final int DEFAULT_JPEG_QUALITY = 100; + static final int DEFAULT_GRID = Constants.GRID_OFF; static final boolean DEFAULT_CROP_OUTPUT = false; } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index 42fc8e79..a90f9279 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -94,6 +94,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { private Lifecycle mLifecycle; private FocusMarkerLayout mFocusMarkerLayout; + private GridLinesLayout mGridLinesLayout; private boolean mIsStarted; private boolean mKeepScreenOn; @@ -116,6 +117,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { int sessionType = a.getInteger(R.styleable.CameraView_cameraSessionType, CameraKit.Defaults.DEFAULT_SESSION_TYPE); int whiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE); int videoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY); + int grid = a.getInteger(R.styleable.CameraView_cameraGrid, CameraKit.Defaults.DEFAULT_GRID); mZoom = a.getInteger(R.styleable.CameraView_cameraZoomMode, CameraKit.Defaults.DEFAULT_ZOOM); mJpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, CameraKit.Defaults.DEFAULT_JPEG_QUALITY); mCropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, CameraKit.Defaults.DEFAULT_CROP_OUTPUT); @@ -125,7 +127,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mPreviewImpl = new TextureViewPreview(context, this); mCameraImpl = new Camera1(mCameraCallbacks, mPreviewImpl); mFocusMarkerLayout = new FocusMarkerLayout(context); + mGridLinesLayout = new GridLinesLayout(context); addView(mFocusMarkerLayout); + addView(mGridLinesLayout); mIsStarted = false; setFacing(facing); @@ -135,6 +139,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { setZoomMode(mZoom); setVideoQuality(videoQuality); setWhiteBalance(whiteBalance); + setGrid(grid); if (!isInEditMode()) { mOrientationHelper = new OrientationHelper(context) { @@ -319,6 +324,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { // And dispatch to everyone. mFocusMarkerLayout.onTouchEvent(event); // For drawing focus marker. mCameraImpl.onTouchEvent(event); // For focus behavior. + mGridLinesLayout.onTouchEvent(event); // For grid drawing. return true; } @@ -471,6 +477,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * Controls the grids to be drawn over the current layout. + * + * @see CameraKit.Constants#GRID_OFF + * @see CameraKit.Constants#GRID_3X3 + * @see CameraKit.Constants#GRID_4X4 + * + * @param gridMode desired grid mode + */ + public void setGrid(@Grid int gridMode) { + mGridLinesLayout.setGridMode(gridMode); + } + + + /** + * Gets the current grid mode. + * @return the current grid mode + */ + @Grid + public int getGrid() { + return mGridLinesLayout.getGridMode(); + } + + /** * Set location coordinates to be found later in the jpeg EXIF header * diff --git a/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java b/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java new file mode 100644 index 00000000..8760f255 --- /dev/null +++ b/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java @@ -0,0 +1,94 @@ +package com.flurgle.camerakit; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; + +// TODO animate lines! +public class GridLinesLayout extends View { + + @Grid private int gridMode; + + private final Drawable horiz; + private final Drawable vert; + private final float width; + + private final static float GOLDEN_RATIO_INV = 0.61803398874989f; + + public GridLinesLayout(@NonNull Context context) { + this(context, null); + } + + public GridLinesLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + horiz = new ColorDrawable(Color.WHITE); horiz.setAlpha(160); + vert = new ColorDrawable(Color.WHITE); vert.setAlpha(160); + width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.9f, context.getResources().getDisplayMetrics()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + horiz.setBounds(left, 0, right, (int) width); + vert.setBounds(0, top, (int) width, bottom); + } + + @Grid + int getGridMode() { + return gridMode; + } + + void setGridMode(@Grid int gridMode) { + this.gridMode = gridMode; + invalidate(); + } + + private int getLineCount() { + switch (gridMode) { + case CameraKit.Constants.GRID_OFF: return 0; + case CameraKit.Constants.GRID_3X3: return 2; + case CameraKit.Constants.GRID_PHI: return 2; + case CameraKit.Constants.GRID_4X4: return 3; + } + return 0; + } + + private float getLinePosition(int lineNumber) { + int lineCount = getLineCount(); + if (gridMode == CameraKit.Constants.GRID_PHI) { + // 1 = 2x + GRIx + // 1 = x(2+GRI) + // x = 1/(2+GRI) + float delta = 1f/(2+GOLDEN_RATIO_INV); + return lineNumber == 1 ? delta : (1 - delta); + } else { + return (1f / (lineCount + 1)) * (lineNumber + 1f); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + int count = getLineCount(); + for (int n = 0; n < count; n++) { + float pos = getLinePosition(n); + + // Draw horizontal line + canvas.translate(0, pos * getHeight()); + horiz.draw(canvas); + canvas.translate(0, - pos * getHeight()); + + // Draw vertical line + canvas.translate(pos * getWidth(), 0); + vert.draw(canvas); + canvas.translate(- pos * getWidth(), 0); + } + } +} diff --git a/camerakit/src/main/res/values/attrs.xml b/camerakit/src/main/res/values/attrs.xml index 97e58b73..e13ad4f7 100644 --- a/camerakit/src/main/res/values/attrs.xml +++ b/camerakit/src/main/res/values/attrs.xml @@ -37,6 +37,12 @@ + + + + + + diff --git a/camerakit/src/main/types/com/flurgle/camerakit/Grid.java b/camerakit/src/main/types/com/flurgle/camerakit/Grid.java new file mode 100644 index 00000000..037bf220 --- /dev/null +++ b/camerakit/src/main/types/com/flurgle/camerakit/Grid.java @@ -0,0 +1,16 @@ +package com.flurgle.camerakit; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static com.flurgle.camerakit.CameraKit.Constants.GRID_OFF; +import static com.flurgle.camerakit.CameraKit.Constants.GRID_3X3; +import static com.flurgle.camerakit.CameraKit.Constants.GRID_4X4; +import static com.flurgle.camerakit.CameraKit.Constants.GRID_PHI; + +@IntDef({GRID_OFF, GRID_3X3, GRID_4X4, GRID_PHI}) +@Retention(RetentionPolicy.SOURCE) +public @interface Grid { +} \ 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 0a2ee82c..11008199 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java @@ -44,6 +44,10 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan @BindView(R.id.videoQualityRadioGroup) RadioGroup videoQualityRadioGroup; + // Grid mode: + @BindView(R.id.gridModeRadioGroup) + RadioGroup gridModeRadioGroup; + // Width: @BindView(R.id.screenWidth) TextView screenWidth; @@ -91,6 +95,7 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan widthModeRadioGroup.setOnCheckedChangeListener(widthModeChangedListener); heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener); videoQualityRadioGroup.setOnCheckedChangeListener(videoQualityChangedListener); + gridModeRadioGroup.setOnCheckedChangeListener(gridModeChangedListener); } private void message(String content, boolean important) { @@ -249,6 +254,22 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan } }; + + RadioGroup.OnCheckedChangeListener gridModeChangedListener = new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + int grid = CameraKit.Constants.GRID_OFF; + switch (checkedId) { + case R.id.gridModeOff: grid = CameraKit.Constants.GRID_OFF; break; + case R.id.gridMode3x3: grid = CameraKit.Constants.GRID_3X3; break; + case R.id.gridMode4x4: grid = CameraKit.Constants.GRID_4X4; break; + case R.id.gridModeGolden: grid = CameraKit.Constants.GRID_PHI; break; + } + camera.setGrid(grid); + message("Grid mode changed!", false); + } + }; + @OnClick(R.id.widthUpdate) void widthUpdateClicked() { if (mCapturingPicture) return; diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index 7975b5d4..40c5b126 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -8,29 +8,28 @@ android:descendantFocusability="beforeDescendants" android:focusable="true" android:focusableInTouchMode="true"> - + - - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + -