From 573415323fe736855ef71f57db7a7262f185246a Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sat, 13 Jul 2019 11:14:03 -0300 Subject: [PATCH] Address issues --- .../otaliastudios/cameraview/CameraView.java | 122 ++------- .../cameraview/engine/Camera1Engine.java | 4 +- .../cameraview/engine/Camera2Engine.java | 4 +- .../cameraview/engine/CameraEngine.java | 73 +++--- .../cameraview/overlay/Overlay.java | 10 + .../cameraview/overlay/OverlayLayout.java | 245 ++++++++++-------- .../overlay/OverlayLayoutInner.java | 27 -- .../cameraview/overlay/SurfaceDrawer.java | 8 - .../picture/SnapshotGlPictureRecorder.java | 150 +++++------ .../video/SnapshotVideoRecorder.java | 28 +- .../video/encoding/TextureMediaEncoder.java | 16 +- cameraview/src/main/res/values/attrs.xml | 8 +- demo/src/main/res/layout/activity_camera.xml | 6 +- 13 files changed, 309 insertions(+), 392 deletions(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayoutInner.java delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/overlay/SurfaceDrawer.java diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 989e5a37..12afbd77 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -134,8 +134,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { private boolean mExperimental; // Overlays - private OverlayLayout mOverlayLayoutManager; // see OverlayLayoutManager for why having two of them - private OverlayLayout mOverlayLayoutManagerBelow; + private OverlayLayout mOverlayLayout; // Threading private Handler mUiHandler; @@ -193,13 +192,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver { // Views mGridLinesLayout = new GridLinesLayout(context); - mOverlayLayoutManager = new OverlayLayout(context); - mOverlayLayoutManagerBelow = new OverlayLayout(context); + mOverlayLayout = new OverlayLayout(context); mMarkerLayout = new MarkerLayout(context); addView(mGridLinesLayout); addView(mMarkerLayout); - addView(mOverlayLayoutManager); - addView(mOverlayLayoutManagerBelow, 0); // put it at the bottom of the FrameLayout + addView(mOverlayLayout); // Create the engine doInstantiateEngine(); @@ -248,16 +245,12 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ private void doInstantiateEngine() { if (mCameraEngine != null) { - mCameraEngine.removePictureSurfaceDrawer(mOverlayLayoutManager); - mCameraEngine.removePictureSurfaceDrawer(mOverlayLayoutManagerBelow); - mCameraEngine.removeVideoSurfaceDrawer(mOverlayLayoutManager); - mCameraEngine.removeVideoSurfaceDrawer(mOverlayLayoutManagerBelow); + mCameraEngine.removePictureOverlay(mOverlayLayout); + mCameraEngine.removeVideoOverlay(mOverlayLayout); } mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks); - mCameraEngine.addPictureSurfaceDrawer(mOverlayLayoutManager); - mCameraEngine.addPictureSurfaceDrawer(mOverlayLayoutManagerBelow); - mCameraEngine.addVideoSurfaceDrawer(mOverlayLayoutManager); - mCameraEngine.addVideoSurfaceDrawer(mOverlayLayoutManagerBelow); + mCameraEngine.addPictureOverlay(mOverlayLayout); + mCameraEngine.addVideoOverlay(mOverlayLayout); } /** @@ -2103,106 +2096,21 @@ public class CameraView extends FrameLayout implements LifecycleObserver { //region Overlays - @Override - public void addView(View child, ViewGroup.LayoutParams params) { - if (params instanceof OverlayLayoutParams) { - if (((OverlayLayoutParams) params).drawInPreview) { - mOverlayLayoutManager.addView(child, params); - } else { - mOverlayLayoutManagerBelow.addView(child, params); - } - } else { - super.addView(child, params); - } - } - - @Override - public void removeView(View child) { - if (child.getLayoutParams() instanceof OverlayLayoutParams) { - if (((OverlayLayoutParams) child.getLayoutParams()).drawInPreview) { - mOverlayLayoutManager.removeView(child); - } else { - mOverlayLayoutManagerBelow.removeView(child); - } - } else { - super.removeView(child); - } - } - @Override public LayoutParams generateLayoutParams(AttributeSet attributeSet) { - OverlayLayoutParams toBeChecked = new OverlayLayoutParams(this.getContext(), attributeSet); - if (toBeChecked.isOverlay()) { - return toBeChecked; + if (mOverlayLayout.isOverlay(attributeSet)) { + return mOverlayLayout.generateLayoutParams(attributeSet); } return super.generateLayoutParams(attributeSet); } + // We don't support removeView on overlays for now. @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - if (p instanceof OverlayLayoutParams) { - return p; - } - return super.generateLayoutParams(p); - } - - - public static class OverlayLayoutParams extends FrameLayout.LayoutParams { - - private boolean drawInPreview = false; - private boolean drawInPictureSnapshot = false; - private boolean drawInVideoSnapshot = false; - - public OverlayLayoutParams(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - this.readStyleParameters(context, attributeSet); - } - - public OverlayLayoutParams(int width, int height) { - super(width, height); - } - - public OverlayLayoutParams(ViewGroup.LayoutParams layoutParams) { - super(layoutParams); - } - - private void readStyleParameters(Context context, AttributeSet attributeSet) { - TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.CameraView_Layout); - try { - this.drawInPreview = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInPreview, false); - this.drawInPictureSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInPictureSnapshot, false); - this.drawInVideoSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawInVideoSnapshot, false); - } finally { - a.recycle(); - } - } - - public boolean isDrawInPreview() { - return drawInPreview; - } - - public void setDrawInPreview(boolean drawInPreview) { - this.drawInPreview = drawInPreview; - } - - public boolean isDrawInPictureSnapshot() { - return drawInPictureSnapshot; - } - - public void setDrawInPictureSnapshot(boolean drawInPictureSnapshot) { - this.drawInPictureSnapshot = drawInPictureSnapshot; - } - - public boolean isDrawInVideoSnapshot() { - return drawInVideoSnapshot; - } - - public void setDrawInVideoSnapshot(boolean drawInVideoSnapshot) { - this.drawInVideoSnapshot = drawInVideoSnapshot; - } - - public boolean isOverlay() { - return drawInPreview || drawInPictureSnapshot || drawInVideoSnapshot; + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (mOverlayLayout.isOverlay(params)) { + mOverlayLayout.addView(child, params); + } else { + super.addView(child, index, params); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index c7e6cec2..6438055c 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -312,7 +312,7 @@ public class Camera1Engine extends CameraEngine implements AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureSurfaceDrawers()); + mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays()); } else { mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio); } @@ -362,7 +362,7 @@ public class Camera1Engine extends CameraEngine implements stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); // Start. - mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getVideoSurfaceDrawers()); + mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getVideoOverlays()); mVideoRecorder.start(stub); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 4e397f2c..7fe8b9af 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -615,7 +615,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0. AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; if (mPreview instanceof GlCameraPreview) { - mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureSurfaceDrawers()); + mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays()); } else { throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE"); } @@ -709,7 +709,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); // Start. - mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getVideoSurfaceDrawers()); + mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getVideoOverlays()); mVideoRecorder.start(stub); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 21b5967a..c9f26c93 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -18,7 +18,7 @@ import com.otaliastudios.cameraview.CameraException; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.overlay.SurfaceDrawer; +import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.engine.offset.Angles; import com.otaliastudios.cameraview.engine.offset.Reference; @@ -185,38 +185,8 @@ public abstract class CameraEngine implements private long mAutoFocusResetDelayMillis; private int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors private int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors - private final List pictureSurfaceDrawers = new ArrayList<>(); - private final List videoSurfaceDrawers = new ArrayList<>(); - - public void addPictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { - if (!pictureSurfaceDrawers.contains(surfaceDrawer)) { - pictureSurfaceDrawers.add(surfaceDrawer); - } - } - - public void removePictureSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { - pictureSurfaceDrawers.remove(surfaceDrawer); - } - - @NonNull - protected List getPictureSurfaceDrawers() { - return pictureSurfaceDrawers; - } - - public void addVideoSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { - if (!videoSurfaceDrawers.contains(surfaceDrawer)) { - videoSurfaceDrawers.add(surfaceDrawer); - } - } - - public void removeVideoSurfaceDrawer(@NonNull SurfaceDrawer surfaceDrawer) { - videoSurfaceDrawers.remove(surfaceDrawer); - } - - @NonNull - protected List getVideoSurfaceDrawers() { - return videoSurfaceDrawers; - } + private final List pictureOverlays = new ArrayList<>(); + private final List videoOverlays = new ArrayList<>(); // Steps private final Step.Callback mStepCallback = new Step.Callback() { @@ -1419,4 +1389,41 @@ public abstract class CameraEngine implements } //endregion + + //region Overlays + + + public void addPictureOverlay(@NonNull Overlay overlay) { + if (!pictureOverlays.contains(overlay)) { + pictureOverlays.add(overlay); + } + } + + public void removePictureOverlay(@NonNull Overlay overlay) { + pictureOverlays.remove(overlay); + } + + public void addVideoOverlay(@NonNull Overlay overlay) { + if (!videoOverlays.contains(overlay)) { + videoOverlays.add(overlay); + } + } + + public void removeVideoOverlay(@NonNull Overlay overlay) { + videoOverlays.remove(overlay); + } + + @SuppressWarnings("WeakerAccess") + @NonNull + protected List getPictureOverlays() { + return pictureOverlays; + } + + @SuppressWarnings("WeakerAccess") + @NonNull + protected List getVideoOverlays() { + return videoOverlays; + } + + //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java new file mode 100644 index 00000000..d614a875 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java @@ -0,0 +1,10 @@ +package com.otaliastudios.cameraview.overlay; + +import android.graphics.Canvas; + +import androidx.annotation.NonNull; + +public interface Overlay { + void drawOnPicture(@NonNull Canvas canvas); + void drawOnVideo(@NonNull Canvas canvas); +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java index cb6e66a5..79195221 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java @@ -1,7 +1,9 @@ package com.otaliastudios.cameraview.overlay; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.View; @@ -11,137 +13,172 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.otaliastudios.cameraview.CameraView; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class manages {@link OverlayLayoutInner}s. - * The necessity for this class comes from two features of {@link View}s: - * - a {@link View} can only have one parent - * - the View framework does not provide a straightforward way for a {@link ViewGroup} to draw - * only a subset of it's children. - * We have three possible target for a overlay {@link View} to be drawn on: - * - camera preview - * - picture snapshot - * - video snapshot - * Given the two constraints above in order to draw exclusively on a subset of targets we need a - * different {@link OverlayLayoutInner} for each subset of targets. This class manages those different - * {@link OverlayLayoutInner}s. - * - * A problem remains: the views are drawn on preview when {@link #draw(Canvas)} is called on this - * class, for not drawing on the preview but drawing on picture snapshot, for instance, we cannot - * change the child's visibility. - * One way to solve this problem is to have two instances of {@link OverlayLayout} and layer - * them so that the one below is covered and hidden by the camera preview. This way only the top - * {@link OverlayLayout} is shown on top of the camera preview and we can still access the - * bottom one's {@link OverlayLayoutInner#draw(Canvas)} for drawing on picture snapshots. - */ -public class OverlayLayout extends FrameLayout implements SurfaceDrawer { - - private Map mLayouts = new HashMap<>(); +import com.otaliastudios.cameraview.CameraLogger; +import com.otaliastudios.cameraview.R; + + +@SuppressLint("CustomViewStyleable") +public class OverlayLayout extends FrameLayout implements Overlay { + + private static final String TAG = OverlayLayout.class.getSimpleName(); + private static final CameraLogger LOG = CameraLogger.create(TAG); + + private static final int DRAWING_PREVIEW = 0; + private static final int DRAWING_PICTURE = 1; + private static final int DRAWING_VIDEO = 2; + + private int target = DRAWING_PREVIEW; public OverlayLayout(@NonNull Context context) { super(context); } - public OverlayLayout(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); + /** + * Returns true if this {@link AttributeSet} belongs to an overlay. + * @param set an attribute set + * @return true if overlay + */ + public boolean isOverlay(@Nullable AttributeSet set) { + if (set == null) return false; + TypedArray a = getContext().obtainStyledAttributes(set, R.styleable.CameraView_Layout); + boolean isOverlay = a.getBoolean(R.styleable.CameraView_Layout_layout_isOverlay, false); + a.recycle(); + return isOverlay; } - @Override - public void addView(View child, ViewGroup.LayoutParams params) { - // params must be instance of OverlayLayoutParams - if (!(params instanceof CameraView.OverlayLayoutParams)) { - return; - } - - OverlayType viewOverlayType = new OverlayType((CameraView.OverlayLayoutParams) params); - if (mLayouts.containsKey(viewOverlayType)) { - mLayouts.get(viewOverlayType).addView(child, params); - } else { - OverlayLayoutInner newLayout = new OverlayLayoutInner(getContext()); - newLayout.addView(child, params); - super.addView(newLayout); - mLayouts.put(viewOverlayType, newLayout); - } + /** + * Returns true if this {@link ViewGroup.LayoutParams} belongs to an overlay. + * @param params a layout params + * @return true if overlay + */ + public boolean isOverlay(@NonNull ViewGroup.LayoutParams params) { + return params instanceof LayoutParams; } + /** + * Generates our own overlay layout params. + * @param attrs input attrs + * @return our params + */ @Override - public void removeView(View child) { - // params must be instance of OverlayLayoutParams - if (!(child.getLayoutParams() instanceof CameraView.OverlayLayoutParams)) { - return; - } + public OverlayLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } - OverlayType viewOverlayType = new OverlayType((CameraView.OverlayLayoutParams) child.getLayoutParams()); - if (mLayouts.containsKey(viewOverlayType)) { - mLayouts.get(viewOverlayType).removeView(child); + /** + * This is called by the View hierarchy, so at this point we are + * likely drawing on the preview. + * @param canvas View canvas + */ + @Override + public void draw(Canvas canvas) { + synchronized (this) { + target = DRAWING_PREVIEW; + super.draw(canvas); } } + /** + * This is called by the overlay drawer. + * We call {@link #dispatchDraw(Canvas)} to draw our children. + * + * The input canvas has the Surface dimensions which means it is guaranteed + * to have our own aspect ratio. But we might still have to apply some scale. + * + * @param canvas the overlay canvas + */ @Override - public void drawOnSurfaceForPictureSnapshot(Canvas surfaceCanvas) { - surfaceCanvas.save(); - // scale factor between canvas width and this View's width - float widthScale = surfaceCanvas.getWidth() / (float) getWidth(); - // scale factor between canvas height and this View's height - float heightScale = surfaceCanvas.getHeight() / (float) getHeight(); - surfaceCanvas.scale(widthScale, heightScale); - for (Map.Entry entry : mLayouts.entrySet()) { - if (entry.getKey().pictureSnapshot) { - entry.getValue().drawOverlay(surfaceCanvas); - } + public void drawOnPicture(@NonNull Canvas canvas) { + canvas.save(); + float widthScale = canvas.getWidth() / (float) getWidth(); + float heightScale = canvas.getHeight() / (float) getHeight(); + LOG.i("drawOnPicture", + "widthScale:", widthScale, + "heightScale:", heightScale, + "canvasWidth:", canvas.getWidth(), + "canvasHeight:", canvas.getHeight()); + canvas.scale(widthScale, heightScale); + synchronized (this) { + target = DRAWING_PICTURE; + dispatchDraw(canvas); } - surfaceCanvas.restore(); + canvas.restore(); } + /** + * This is called by the overlay drawer. + * We call {@link #dispatchDraw(Canvas)} to draw our children. + * @param canvas the overlay canvas + */ @Override - public void drawOnSurfaceForVideoSnapshot(Canvas surfaceCanvas) { - surfaceCanvas.save(); - // scale factor between canvas width and this View's width - float widthScale = surfaceCanvas.getWidth() / (float) getWidth(); - // scale factor between canvas height and this View's height - float heightScale = surfaceCanvas.getHeight() / (float) getHeight(); - surfaceCanvas.scale(widthScale, heightScale); - for (Map.Entry entry : mLayouts.entrySet()) { - if (entry.getKey().videoSnapshot) { - entry.getValue().drawOverlay(surfaceCanvas); - } + public void drawOnVideo(@NonNull Canvas canvas) { + canvas.save(); + float widthScale = canvas.getWidth() / (float) getWidth(); + float heightScale = canvas.getHeight() / (float) getHeight(); + LOG.i("drawOnVideo", + "widthScale:", widthScale, + "heightScale:", heightScale, + "canvasWidth:", canvas.getWidth(), + "canvasHeight:", canvas.getHeight()); + canvas.scale(widthScale, heightScale); + synchronized (this) { + target = DRAWING_VIDEO; + dispatchDraw(canvas); } - surfaceCanvas.restore(); + canvas.restore(); } - private class OverlayType { - boolean preview = false; - boolean pictureSnapshot = false; - boolean videoSnapshot = false; - - OverlayType(CameraView.OverlayLayoutParams params) { - this.preview = params.isDrawInPreview(); - this.pictureSnapshot = params.isDrawInPictureSnapshot(); - this.videoSnapshot = params.isDrawInVideoSnapshot(); + /** + * We end up here in all three cases, and should filter out + * views that are not meant to be drawn on that specific surface. + */ + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + LayoutParams params = (LayoutParams) child.getLayoutParams(); + boolean draw = ((target == DRAWING_PREVIEW && params.drawOnPreview) + || (target == DRAWING_VIDEO && params.drawOnVideoSnapshot) + || (target == DRAWING_PICTURE && params.drawOnPictureSnapshot) + ); + if (draw) { + return super.drawChild(canvas, child, drawingTime); + } else { + LOG.v("Skipping drawing for view:", child.getClass().getSimpleName(), + "target:", target, + "params:", params); + return false; } + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - OverlayType that = (OverlayType) o; - return preview == that.preview && - pictureSnapshot == that.pictureSnapshot && - videoSnapshot == that.videoSnapshot; + @SuppressWarnings("WeakerAccess") + public static class LayoutParams extends FrameLayout.LayoutParams { + + @SuppressWarnings("unused") + private boolean isOverlay; + public boolean drawOnPreview; + public boolean drawOnPictureSnapshot; + public boolean drawOnVideoSnapshot; + + public LayoutParams(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView_Layout); + try { + this.isOverlay = a.getBoolean(R.styleable.CameraView_Layout_layout_isOverlay, false); + this.drawOnPreview = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPreview, false); + this.drawOnPictureSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPictureSnapshot, false); + this.drawOnVideoSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnVideoSnapshot, false); + } finally { + a.recycle(); + } } + @NonNull @Override - public int hashCode() { - int result = 0; - result = 31*result + (preview ? 1 : 0); - result = 31*result + (pictureSnapshot ? 1 : 0); - result = 31*result + (videoSnapshot ? 1 : 0); - return result; + public String toString() { + return getClass().getName() + "[isOverlay:" + isOverlay + + ",drawOnPreview:" + drawOnPreview + + ",drawOnPictureSnapshot:" + drawOnPictureSnapshot + + ",drawOnVideoSnapshot:" + drawOnVideoSnapshot + + "]"; } } - } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayoutInner.java b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayoutInner.java deleted file mode 100644 index 3948f7f3..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayoutInner.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.otaliastudios.cameraview.overlay; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class OverlayLayoutInner extends FrameLayout { - - public OverlayLayoutInner(@NonNull Context context) { - super(context); - setWillNotDraw(false); - } - - public OverlayLayoutInner(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - setWillNotDraw(false); - } - - public void drawOverlay(Canvas canvas) { - super.draw(canvas); - } - -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/SurfaceDrawer.java b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/SurfaceDrawer.java deleted file mode 100644 index 11aaa6a2..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/SurfaceDrawer.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.otaliastudios.cameraview.overlay; - -import android.graphics.Canvas; - -public interface SurfaceDrawer { - void drawOnSurfaceForPictureSnapshot(Canvas surfaceCanvas); - void drawOnSurfaceForVideoSnapshot(Canvas surfaceCanvas); -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java index 01c7f21f..ddca7f86 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java @@ -14,7 +14,7 @@ import android.os.Build; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.overlay.SurfaceDrawer; +import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.offset.Axis; @@ -31,9 +31,11 @@ import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import android.view.Surface; +import java.util.ArrayList; import java.util.List; public class SnapshotGlPictureRecorder extends PictureRecorder { @@ -45,21 +47,23 @@ public class SnapshotGlPictureRecorder extends PictureRecorder { private GlCameraPreview mPreview; private AspectRatio mOutputRatio; - private List mSurfaceDrawerList; - private boolean mWithOverlay; + private List mOverlays; + private boolean mHasOverlays; public SnapshotGlPictureRecorder( @NonNull PictureResult.Stub stub, @NonNull CameraEngine engine, @NonNull GlCameraPreview preview, @NonNull AspectRatio outputRatio, - @NonNull List surfaceDrawerList) { + @Nullable List overlays) { super(stub, engine); mEngine = engine; mPreview = preview; mOutputRatio = outputRatio; - mWithOverlay = true; - mSurfaceDrawerList = surfaceDrawerList; + mHasOverlays = overlays != null && !overlays.isEmpty(); + if (mHasOverlays) { + mOverlays = new ArrayList<>(overlays); + } } @TargetApi(Build.VERSION_CODES.KITKAT) @@ -68,33 +72,32 @@ public class SnapshotGlPictureRecorder extends PictureRecorder { mPreview.addRendererFrameCallback(new RendererFrameCallback() { int mTextureId; - int mOverlayTextureId = 0; SurfaceTexture mSurfaceTexture; - SurfaceTexture mOverlaySurfaceTexture; float[] mTransform; + + int mOverlayTextureId = 0; + SurfaceTexture mOverlaySurfaceTexture; + Surface mOverlaySurface; float[] mOverlayTransform; - EglViewport viewport; + + EglViewport mViewport; @RendererThread public void onRendererTextureCreated(int textureId) { mTextureId = textureId; - viewport = new EglViewport(); - if (mWithOverlay) { - mOverlayTextureId = viewport.createTexture(); - } + mViewport = new EglViewport(); mSurfaceTexture = new SurfaceTexture(mTextureId, true); - if (mWithOverlay) { - mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true); - } // Need to crop the size. Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio); mResult.size = new Size(crop.width(), crop.height()); mSurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); - if (mWithOverlay) { - mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); - } mTransform = new float[16]; - if (mWithOverlay) { + + if (mHasOverlays) { + mOverlayTextureId = mViewport.createTexture(); + mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true); + mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); + mOverlaySurface = new Surface(mOverlaySurfaceTexture); mOverlayTransform = new float[16]; } } @@ -128,30 +131,14 @@ public class SnapshotGlPictureRecorder extends PictureRecorder { WorkerHandler.execute(new Runnable() { @Override public void run() { + // 1. Get latest texture EglWindowSurface surface = new EglWindowSurface(core, mSurfaceTexture); surface.makeCurrent(); -// EglViewport viewport = new EglViewport(); mSurfaceTexture.updateTexImage(); mSurfaceTexture.getTransformMatrix(mTransform); - Surface drawOnto = new Surface(mOverlaySurfaceTexture); - if (mWithOverlay) { - try { - final Canvas surfaceCanvas = drawOnto.lockCanvas(null); - surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - for (SurfaceDrawer surfaceDrawer : mSurfaceDrawerList) { - surfaceDrawer.drawOnSurfaceForPictureSnapshot(surfaceCanvas); - } - drawOnto.unlockCanvasAndPost(surfaceCanvas); - } catch (Surface.OutOfResourcesException e) { - e.printStackTrace(); - } - mOverlaySurfaceTexture.updateTexImage(); - mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransform); - } - - // Apply scale and crop: - // NOTE: scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR. + // 2. Apply scale and crop: + // scaleX and scaleY are in REF_VIEW, while our input appears to be in REF_SENSOR. boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR); float realScaleX = flip ? scaleY : scaleX; float realScaleY = flip ? scaleX : scaleY; @@ -160,65 +147,66 @@ public class SnapshotGlPictureRecorder extends PictureRecorder { Matrix.translateM(mTransform, 0, scaleTranslX, scaleTranslY, 0); Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1); - // Fix rotation: - // Not sure why we need the minus here... It makes no sense to me. - LOG.w("Recording frame. Rotation:", mResult.rotation, "Actual:", -mResult.rotation); - int rotation = -mResult.rotation; - int overlayRotation = mEngine.getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); // TODO check axis - // apparently with front facing camera with don't need the minus sign - if (mResult.facing == Facing.FRONT) { - overlayRotation = -overlayRotation; - } - mResult.rotation = 0; - - // Go back to 0,0 so that rotate and flip work well. + // 3. Go back to 0,0 so that rotate and flip work well. Matrix.translateM(mTransform, 0, 0.5F, 0.5F, 0); - if (mOverlayTransform != null) { - Matrix.translateM(mOverlayTransform, 0, 0.5F, 0.5F, 0); - } - // Apply rotation: - Matrix.rotateM(mTransform, 0, rotation, 0, 0, 1); - if (mOverlayTransform != null) { - Matrix.rotateM(mOverlayTransform, 0, overlayRotation, 0, 0, 1); - } + // 4. Apply rotation: + // Not sure why we need the minus here. + Matrix.rotateM(mTransform, 0, -mResult.rotation, 0, 0, 1); + mResult.rotation = 0; - // Flip horizontally for front camera: + // 5. Flip horizontally for front camera: if (mResult.facing == Facing.FRONT) { Matrix.scaleM(mTransform, 0, -1, 1, 1); - if (mOverlayTransform != null) { - // not sure why we have to flip the mirror the y axis - Matrix.scaleM(mOverlayTransform, 0, -1, -1, 1); - } - } - if (mOverlayTransform != null) { - // not sure why we have to flip the mirror the y axis - Matrix.scaleM(mOverlayTransform, 0, 1, -1, 1); } - // Go back to old position. + // 6. Go back to old position. Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0); - if (mOverlayTransform != null) { + + // 7. Do pretty much the same for overlays, though with + // some differences. + if (mHasOverlays) { + // 1. First we must draw on the texture and get latest image. + try { + final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null); + surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + for (Overlay overlay : mOverlays) { + overlay.drawOnPicture(surfaceCanvas); + } + mOverlaySurface.unlockCanvasAndPost(surfaceCanvas); + } catch (Surface.OutOfResourcesException e) { + LOG.w("Got Surface.OutOfResourcesException while drawing picture overlays", e); + } + mOverlaySurfaceTexture.updateTexImage(); + mOverlaySurfaceTexture.getTransformMatrix(mOverlayTransform); + + // 2. Then we can apply the transformations. + // TODO check the sign of this angle. + int rotation = mEngine.getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); + Matrix.translateM(mOverlayTransform, 0, 0.5F, 0.5F, 0); + Matrix.rotateM(mOverlayTransform, 0, -rotation, 0, 0, 1); + // Not sure why we have to flip the y axis (twice?). + if (mResult.facing == Facing.FRONT) { + Matrix.scaleM(mOverlayTransform, 0, -1, -1, 1); + } + Matrix.scaleM(mOverlayTransform, 0, 1, -1, 1); Matrix.translateM(mOverlayTransform, 0, -0.5F, -0.5F, 0); } - // Future note: passing scale values to the viewport? - // They are simply realScaleX and realScaleY. - viewport.drawFrame(mTextureId, mTransform); - if (mWithOverlay) { - viewport.drawFrame(mOverlayTextureId, mOverlayTransform); - } + // 8. Draw and save + mViewport.drawFrame(mTextureId, mTransform); + if (mHasOverlays) mViewport.drawFrame(mOverlayTextureId, mOverlayTransform); // don't - surface.swapBuffers(); mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG); mResult.format = PictureResult.FORMAT_JPEG; - mSurfaceTexture.releaseTexImage(); - // EGL14.eglMakeCurrent(oldDisplay, oldSurface, oldSurface, eglContext); + // 9. Cleanup + mSurfaceTexture.releaseTexImage(); surface.release(); - viewport.release(); - drawOnto.release(); + mViewport.release(); mSurfaceTexture.release(); - if (mOverlaySurfaceTexture != null) { + if (mHasOverlays) { + mOverlaySurface.release(); mOverlaySurfaceTexture.release(); } core.release(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java index 7a2d9879..2dd585c6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java @@ -8,10 +8,11 @@ import android.opengl.EGL14; import android.os.Build; import android.view.Surface; +import java.util.ArrayList; import java.util.List; import com.otaliastudios.cameraview.CameraLogger; -import com.otaliastudios.cameraview.overlay.SurfaceDrawer; +import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.engine.CameraEngine; @@ -59,18 +60,20 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram private int mOverlayTextureId = 0; private SurfaceTexture mOverlaySurfaceTexture; private Surface mOverlaySurface; - private List mSurfaceDrawerList; - private boolean mWithOverlay; + private List mOverlays; + private boolean mHasOverlays; public SnapshotVideoRecorder(@NonNull CameraEngine engine, @NonNull GlCameraPreview preview, - @NonNull List surfaceDrawerList) { + @Nullable List overlays) { super(engine); mPreview = preview; mEngine = engine; - mWithOverlay = true; - mSurfaceDrawerList = surfaceDrawerList; + mHasOverlays = overlays != null && !overlays.isEmpty(); + if (mHasOverlays) { + mOverlays = new ArrayList<>(overlays); + } } @Override @@ -90,10 +93,9 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram @Override public void onRendererTextureCreated(int textureId) { mTextureId = textureId; - if (mWithOverlay) { + if (mHasOverlays) { EglViewport temp = new EglViewport(); mOverlayTextureId = temp.createTexture(); - mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId); mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); mOverlaySurface = new Surface(mOverlaySurfaceTexture); @@ -129,7 +131,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram mResult.videoFrameRate, mResult.rotation, type, mTextureId, - mWithOverlay ? mOverlayTextureId : 0, + mHasOverlays ? mOverlayTextureId : TextureMediaEncoder.NO_TEXTURE, scaleX, scaleY, mFlipped, EGL14.eglGetCurrentContext() @@ -158,17 +160,17 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram surfaceTexture.getTransformMatrix(textureFrame.transform); // get overlay - if (mWithOverlay) { + if (mHasOverlays) { try { final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null); surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - for (SurfaceDrawer surfaceDrawer : mSurfaceDrawerList) { - surfaceDrawer.drawOnSurfaceForVideoSnapshot(surfaceCanvas); + for (Overlay overlay : mOverlays) { + overlay.drawOnVideo(surfaceCanvas); } mOverlaySurface.unlockCanvasAndPost(surfaceCanvas); } catch (Surface.OutOfResourcesException e) { - e.printStackTrace(); + LOG.w("Got Surface.OutOfResourcesException while drawing video overlays", e); } mOverlaySurfaceTexture.updateTexImage(); mOverlaySurfaceTexture.getTransformMatrix(textureFrame.overlayTransform); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java index 0b7b59fe..f4eefeff 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java @@ -24,6 +24,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder - - - - + + + + \ No newline at end of file diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index 87721e2b..622e8d3a 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -37,9 +37,9 @@ android:layout_height="wrap_content" android:orientation="vertical" android:layout_gravity="bottom|end" - app:layout_drawInPreview="false" - app:layout_drawInVideoSnapshot="true" - app:layout_drawInPictureSnapshot="true" + app:layout_drawOnPreview="false" + app:layout_drawOnVideoSnapshot="true" + app:layout_drawOnPictureSnapshot="true" android:gravity="center" android:padding="8dp">