Use single Overlay with three targets

pull/502/head
Mattia Iavarone 6 years ago
parent c3711743c5
commit e4a63339aa
  1. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  3. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  4. 49
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  5. 16
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/Overlay.java
  6. 83
      cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayLayout.java
  7. 24
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  8. 24
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  9. 12
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  10. 1
      demo/src/main/res/layout/activity_camera.xml

@ -244,13 +244,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
* {@link #setEngine(Engine)} is called. * {@link #setEngine(Engine)} is called.
*/ */
private void doInstantiateEngine() { private void doInstantiateEngine() {
if (mCameraEngine != null) {
mCameraEngine.removePictureOverlay(mOverlayLayout);
mCameraEngine.removeVideoOverlay(mOverlayLayout);
}
mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks); mCameraEngine = instantiateCameraEngine(mEngine, mCameraCallbacks);
mCameraEngine.addPictureOverlay(mOverlayLayout); mCameraEngine.setOverlay(mOverlayLayout);
mCameraEngine.addVideoOverlay(mOverlayLayout);
} }
/** /**

@ -312,7 +312,7 @@ public class Camera1Engine extends CameraEngine implements
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays()); mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getOverlay());
} else { } else {
mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio); 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); stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Start. // Start.
mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getVideoOverlays()); mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview, getOverlay());
mVideoRecorder.start(stub); mVideoRecorder.start(stub);
} }

@ -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. 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; AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio;
if (mPreview instanceof GlCameraPreview) { if (mPreview instanceof GlCameraPreview) {
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getPictureOverlays()); mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio, getOverlay());
} else { } else {
throw new RuntimeException("takePictureSnapshot with Camera2 is only supported with Preview.GL_SURFACE"); 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); stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
// Start. // Start.
mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getVideoOverlays()); mVideoRecorder = new SnapshotVideoRecorder(this, glPreview, getOverlay());
mVideoRecorder.start(stub); mVideoRecorder.start(stub);
} }

@ -185,8 +185,7 @@ public abstract class CameraEngine implements
private long mAutoFocusResetDelayMillis; private long mAutoFocusResetDelayMillis;
private int mSnapshotMaxWidth = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors 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 int mSnapshotMaxHeight = Integer.MAX_VALUE; // in REF_VIEW for consistency with SizeSelectors
private final List<Overlay> pictureOverlays = new ArrayList<>(); private Overlay overlay;
private final List<Overlay> videoOverlays = new ArrayList<>();
// Steps // Steps
private final Step.Callback mStepCallback = new Step.Callback() { private final Step.Callback mStepCallback = new Step.Callback() {
@ -784,6 +783,15 @@ public abstract class CameraEngine implements
//region Final setters and getters //region Final setters and getters
public final void setOverlay(@Nullable Overlay overlay) {
this.overlay = overlay;
}
@Nullable
public final Overlay getOverlay() {
return overlay;
}
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public final Angles getAngles() { public final Angles getAngles() {
return mAngles; return mAngles;
@ -1389,41 +1397,4 @@ public abstract class CameraEngine implements
} }
//endregion //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<Overlay> getPictureOverlays() {
return pictureOverlays;
}
@SuppressWarnings("WeakerAccess")
@NonNull
protected List<Overlay> getVideoOverlays() {
return videoOverlays;
}
//endregion
} }

@ -4,7 +4,19 @@ import android.graphics.Canvas;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
/**
* Base interface for overlays.
*/
public interface Overlay { public interface Overlay {
void drawOnPicture(@NonNull Canvas canvas);
void drawOnVideo(@NonNull Canvas canvas); enum Target {
PREVIEW, PICTURE_SNAPSHOT, VIDEO_SNAPSHOT
}
/**
* Called for this overlay to draw itself on the specified target and canvas.
* @param target target
* @param canvas target canvas
*/
void draw(@NonNull Target target, @NonNull Canvas canvas);
} }

@ -23,11 +23,7 @@ public class OverlayLayout extends FrameLayout implements Overlay {
private static final String TAG = OverlayLayout.class.getSimpleName(); private static final String TAG = OverlayLayout.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
private static final int DRAWING_PREVIEW = 0; private Target currentTarget = Target.PREVIEW;
private static final int DRAWING_PICTURE = 1;
private static final int DRAWING_VIDEO = 2;
private int target = DRAWING_PREVIEW;
/** /**
* We set {@link #setWillNotDraw(boolean)} to false even if we don't draw anything. * We set {@link #setWillNotDraw(boolean)} to false even if we don't draw anything.
@ -81,63 +77,52 @@ public class OverlayLayout extends FrameLayout implements Overlay {
* likely drawing on the preview. * likely drawing on the preview.
* @param canvas View canvas * @param canvas View canvas
*/ */
@SuppressLint("MissingSuperCall")
@Override @Override
public void draw(Canvas canvas) { public void draw(Canvas canvas) {
LOG.i("draw called. Setting DRAWING_PREVIEW and calling super."); LOG.i("normal draw called.");
synchronized (this) { draw(Target.PREVIEW, canvas);
target = DRAWING_PREVIEW;
super.draw(canvas);
}
} }
/** /**
* This is called by the overlay drawer. * For {@link Target#PREVIEW}, this method is called by the View hierarchy. We will
* We call {@link #dispatchDraw(Canvas)} to draw our children. * just forward the call to super.
* *
* The input canvas has the Surface dimensions which means it is guaranteed * For {@link Target#PICTURE_SNAPSHOT} and {@link Target#VIDEO_SNAPSHOT},
* to have our own aspect ratio. But we might still have to apply some scale. * this method is called by the overlay drawer. We call {@link #dispatchDraw(Canvas)}
* to draw our children only.
* *
* @param canvas the overlay canvas * @param target the draw target
* @param canvas the canvas
*/ */
@Override @Override
public void drawOnPicture(@NonNull Canvas canvas) { public void draw(@NonNull Target target, @NonNull Canvas canvas) {
synchronized (this) {
currentTarget = target;
switch (target) {
case PREVIEW:
super.draw(canvas);
break;
case VIDEO_SNAPSHOT:
case PICTURE_SNAPSHOT:
canvas.save(); canvas.save();
// 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.
float widthScale = canvas.getWidth() / (float) getWidth(); float widthScale = canvas.getWidth() / (float) getWidth();
float heightScale = canvas.getHeight() / (float) getHeight(); float heightScale = canvas.getHeight() / (float) getHeight();
LOG.i("drawOnPicture", LOG.i("draw",
"target:", target,
"canvas:", canvas.getWidth() + "x" + canvas.getHeight(),
"view:", getWidth() + "x" + getHeight(),
"widthScale:", widthScale, "widthScale:", widthScale,
"heightScale:", heightScale, "heightScale:", heightScale
"canvasWidth:", canvas.getWidth(), );
"canvasHeight:", canvas.getHeight());
canvas.scale(widthScale, heightScale); canvas.scale(widthScale, heightScale);
synchronized (this) {
target = DRAWING_PICTURE;
dispatchDraw(canvas); dispatchDraw(canvas);
}
canvas.restore(); canvas.restore();
break;
} }
/**
* This is called by the overlay drawer.
* We call {@link #dispatchDraw(Canvas)} to draw our children.
* @param canvas the overlay canvas
*/
@Override
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);
} }
canvas.restore();
} }
/** /**
@ -147,18 +132,18 @@ public class OverlayLayout extends FrameLayout implements Overlay {
@Override @Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
LayoutParams params = (LayoutParams) child.getLayoutParams(); LayoutParams params = (LayoutParams) child.getLayoutParams();
boolean draw = ((target == DRAWING_PREVIEW && params.drawOnPreview) boolean draw = ((currentTarget == Target.PREVIEW && params.drawOnPreview)
|| (target == DRAWING_VIDEO && params.drawOnVideoSnapshot) || (currentTarget == Target.VIDEO_SNAPSHOT && params.drawOnVideoSnapshot)
|| (target == DRAWING_PICTURE && params.drawOnPictureSnapshot) || (currentTarget == Target.PICTURE_SNAPSHOT && params.drawOnPictureSnapshot)
); );
if (draw) { if (draw) {
LOG.v("Performing drawing for view:", child.getClass().getSimpleName(), LOG.v("Performing drawing for view:", child.getClass().getSimpleName(),
"target:", target, "target:", currentTarget,
"params:", params); "params:", params);
return super.drawChild(canvas, child, drawingTime); return super.drawChild(canvas, child, drawingTime);
} else { } else {
LOG.v("Skipping drawing for view:", child.getClass().getSimpleName(), LOG.v("Skipping drawing for view:", child.getClass().getSimpleName(),
"target:", target, "target:", currentTarget,
"params:", params); "params:", params);
return false; return false;
} }

@ -47,23 +47,21 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
private GlCameraPreview mPreview; private GlCameraPreview mPreview;
private AspectRatio mOutputRatio; private AspectRatio mOutputRatio;
private List<Overlay> mOverlays; private Overlay mOverlay;
private boolean mHasOverlays; private boolean mHasOverlay;
public SnapshotGlPictureRecorder( public SnapshotGlPictureRecorder(
@NonNull PictureResult.Stub stub, @NonNull PictureResult.Stub stub,
@NonNull CameraEngine engine, @NonNull CameraEngine engine,
@NonNull GlCameraPreview preview, @NonNull GlCameraPreview preview,
@NonNull AspectRatio outputRatio, @NonNull AspectRatio outputRatio,
@Nullable List<Overlay> overlays) { @Nullable Overlay overlay) {
super(stub, engine); super(stub, engine);
mEngine = engine; mEngine = engine;
mPreview = preview; mPreview = preview;
mOutputRatio = outputRatio; mOutputRatio = outputRatio;
mHasOverlays = overlays != null && !overlays.isEmpty(); mOverlay = overlay;
if (mHasOverlays) { mHasOverlay = overlay != null;
mOverlays = new ArrayList<>(overlays);
}
} }
@TargetApi(Build.VERSION_CODES.KITKAT) @TargetApi(Build.VERSION_CODES.KITKAT)
@ -93,7 +91,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
mSurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); mSurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
mTransform = new float[16]; mTransform = new float[16];
if (mHasOverlays) { if (mHasOverlay) {
mOverlayTextureId = mViewport.createTexture(); mOverlayTextureId = mViewport.createTexture();
mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true); mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId, true);
mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight()); mOverlaySurfaceTexture.setDefaultBufferSize(mResult.size.getWidth(), mResult.size.getHeight());
@ -165,14 +163,12 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
// 7. Do pretty much the same for overlays, though with // 7. Do pretty much the same for overlays, though with
// some differences. // some differences.
if (mHasOverlays) { if (mHasOverlay) {
// 1. First we must draw on the texture and get latest image. // 1. First we must draw on the texture and get latest image.
try { try {
final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null); final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null);
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Overlay overlay : mOverlays) { mOverlay.draw(Overlay.Target.PICTURE_SNAPSHOT, surfaceCanvas);
overlay.drawOnPicture(surfaceCanvas);
}
mOverlaySurface.unlockCanvasAndPost(surfaceCanvas); mOverlaySurface.unlockCanvasAndPost(surfaceCanvas);
} catch (Surface.OutOfResourcesException e) { } catch (Surface.OutOfResourcesException e) {
LOG.w("Got Surface.OutOfResourcesException while drawing picture overlays", e); LOG.w("Got Surface.OutOfResourcesException while drawing picture overlays", e);
@ -195,7 +191,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
// 8. Draw and save // 8. Draw and save
mViewport.drawFrame(mTextureId, mTransform); mViewport.drawFrame(mTextureId, mTransform);
if (mHasOverlays) mViewport.drawFrame(mOverlayTextureId, mOverlayTransform); if (mHasOverlay) mViewport.drawFrame(mOverlayTextureId, mOverlayTransform);
// don't - surface.swapBuffers(); // don't - surface.swapBuffers();
mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG); mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG);
mResult.format = PictureResult.FORMAT_JPEG; mResult.format = PictureResult.FORMAT_JPEG;
@ -205,7 +201,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
surface.release(); surface.release();
mViewport.release(); mViewport.release();
mSurfaceTexture.release(); mSurfaceTexture.release();
if (mHasOverlays) { if (mHasOverlay) {
mOverlaySurface.release(); mOverlaySurface.release();
mOverlaySurfaceTexture.release(); mOverlaySurfaceTexture.release();
} }

@ -60,20 +60,17 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
private int mOverlayTextureId = 0; private int mOverlayTextureId = 0;
private SurfaceTexture mOverlaySurfaceTexture; private SurfaceTexture mOverlaySurfaceTexture;
private Surface mOverlaySurface; private Surface mOverlaySurface;
private List<Overlay> mOverlays; private Overlay mOverlay;
private boolean mHasOverlays; private boolean mHasOverlay;
public SnapshotVideoRecorder(@NonNull CameraEngine engine, public SnapshotVideoRecorder(@NonNull CameraEngine engine,
@NonNull GlCameraPreview preview, @NonNull GlCameraPreview preview,
@Nullable List<Overlay> overlays) { @Nullable Overlay overlay) {
super(engine); super(engine);
mPreview = preview; mPreview = preview;
mEngine = engine; mEngine = engine;
mOverlay = overlay;
mHasOverlays = overlays != null && !overlays.isEmpty(); mHasOverlay = overlay != null;
if (mHasOverlays) {
mOverlays = new ArrayList<>(overlays);
}
} }
@Override @Override
@ -93,7 +90,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@Override @Override
public void onRendererTextureCreated(int textureId) { public void onRendererTextureCreated(int textureId) {
mTextureId = textureId; mTextureId = textureId;
if (mHasOverlays) { if (mHasOverlay) {
EglViewport temp = new EglViewport(); EglViewport temp = new EglViewport();
mOverlayTextureId = temp.createTexture(); mOverlayTextureId = temp.createTexture();
mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId); mOverlaySurfaceTexture = new SurfaceTexture(mOverlayTextureId);
@ -131,7 +128,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
mResult.videoFrameRate, mResult.videoFrameRate,
mResult.rotation, mResult.rotation,
type, mTextureId, type, mTextureId,
mHasOverlays ? mOverlayTextureId : TextureMediaEncoder.NO_TEXTURE, mHasOverlay ? mOverlayTextureId : TextureMediaEncoder.NO_TEXTURE,
scaleX, scaleY, scaleX, scaleY,
mFlipped, mFlipped,
EGL14.eglGetCurrentContext() EGL14.eglGetCurrentContext()
@ -160,14 +157,11 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
surfaceTexture.getTransformMatrix(textureFrame.transform); surfaceTexture.getTransformMatrix(textureFrame.transform);
// get overlay // get overlay
if (mHasOverlays) { if (mHasOverlay) {
try { try {
final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null); final Canvas surfaceCanvas = mOverlaySurface.lockCanvas(null);
surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); surfaceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Overlay overlay : mOverlays) { mOverlay.draw(Overlay.Target.VIDEO_SNAPSHOT, surfaceCanvas);
overlay.drawOnVideo(surfaceCanvas);
}
mOverlaySurface.unlockCanvasAndPost(surfaceCanvas); mOverlaySurface.unlockCanvasAndPost(surfaceCanvas);
} catch (Surface.OutOfResourcesException e) { } catch (Surface.OutOfResourcesException e) {
LOG.w("Got Surface.OutOfResourcesException while drawing video overlays", e); LOG.w("Got Surface.OutOfResourcesException while drawing video overlays", e);

@ -83,8 +83,6 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
b.setState(BottomSheetBehavior.STATE_HIDDEN); b.setState(BottomSheetBehavior.STATE_HIDDEN);
} }
}); });
syncWatermark();
} }
private void message(String content, boolean important) { private void message(String content, boolean important) {
@ -220,16 +218,6 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
message("Switched to front camera!", false); message("Switched to front camera!", false);
break; break;
} }
syncWatermark();
}
@SuppressLint("SetTextI18n")
private void syncWatermark() {
TextView watermark = findViewById(R.id.watermark_title);
switch (camera.getFacing()) {
case FRONT: watermark.setText("Front Camera"); break;
case BACK: watermark.setText("Back Camera"); break;
}
} }
@Override @Override

@ -51,6 +51,7 @@
android:id="@+id/watermark_title" android:id="@+id/watermark_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="CameraView"
android:textColor="@android:color/white" /> android:textColor="@android:color/white" />
</LinearLayout> </LinearLayout>

Loading…
Cancel
Save