Built in support for drawing grids

pull/1/head
Mattia Iavarone 7 years ago
parent 1feb1c868b
commit 3a6666fdbc
  1. 47
      README.md
  2. 6
      camerakit/src/main/java/com/flurgle/camerakit/CameraKit.java
  3. 30
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  4. 94
      camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java
  5. 6
      camerakit/src/main/res/values/attrs.xml
  6. 16
      camerakit/src/main/types/com/flurgle/camerakit/Grid.java
  7. 21
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
  8. 82
      demo/src/main/res/layout/activity_main.xml

@ -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

@ -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;
}

@ -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
*

@ -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);
}
}
}

@ -37,6 +37,12 @@
<enum name="pinch" value="0" />
</attr>
<attr name="cameraGrid" format="enum">
<enum name="off" value="0" />
<enum name="grid3x3" value="1" />
<enum name="grid4x4" value="2" />
<enum name="phi" value="3" />
</attr>
<attr name="cameraCaptureMethod" format="enum">
<enum name="standard" value="0" />

@ -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 {
}

@ -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;

@ -8,29 +8,28 @@
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Camera -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.flurgle.camerakit.CameraView xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/camera"
android:layout_width="match_parent"
android:layout_height="400dp"
android:layout_gravity="center_horizontal"
android:keepScreenOn="true"
app:cameraGrid="off"
app:cameraCropOutput="false"
app:cameraFacing="back"
app:cameraFlash="off"
app:cameraFocus="tapWithMarker"
app:cameraJpegQuality="100"
app:cameraSessionType="picture" />
</RelativeLayout>
<LinearLayout
@ -39,6 +38,7 @@
android:orientation="vertical"
android:padding="16dp">
<!-- Controls -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -87,6 +87,7 @@
</LinearLayout>
<!-- Session type -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -139,6 +140,7 @@
</FrameLayout>
<!-- Crop output -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -191,6 +193,7 @@
</FrameLayout>
<!-- Width -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -290,6 +293,7 @@
</FrameLayout>
<!-- Height -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -389,7 +393,7 @@
</FrameLayout>
<!-- Video Quality -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -468,8 +472,76 @@
</FrameLayout>
<!-- Grid -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="GRID MODE"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/gridModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/gridModeOff"
android:orientation="horizontal">
<RadioButton
android:id="@+id/gridModeOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Off" />
<RadioButton
android:id="@+id/gridMode3x3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="3X3" />
<RadioButton
android:id="@+id/gridMode4x4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="4x4" />
<RadioButton
android:id="@+id/gridModeGolden"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Golden ratio" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

Loading…
Cancel
Save