Feature/overlays (#502)
* Overlays (#421) * get overlay working * fix overlay drawing * allow disabling overlay in pictures or videos * Fix picture snapshot colors when there is an overlay * Bug fixes * Update example with watermark * Fix bug * Fix overlay orientation in pictures * Fix overlay orientation in videos * Fix overlay when changing preview size * Fix bug * Experiment * Refactor EglViewport * Refactor SnapshotPictureRecorder * Use single EglViewport * Refactor SnapshotVideoRecorder * Bug fix * fix some of the requested changes * clean adding View to OverlayLayout * Specify where to draw the overlay * Refactor * Remove unnecessary variable from CameraPreview * Use mWithOverlay in SnapshotVideoRecorder * Use multiple OverlayLayout * Add explanation for OverlayLayoutManager * override removeView * Remove DisableOverlayFor * Reorder to overlay package * Address issues * Draw selectively on preview, picture or video * Use single Overlay with three targets * Fix picture snapshots * Add demo app control * Fix video snapshot rotation for Camera2 * Fix video snapshot overlay rotation for Camera2 only * Fix tests, improve performance * Add animating watermark * Add tests in CameraViewTest * Add integration tests * Fix race condition * Improve README * Remove isOverlay * Remove isOverlay from docs * Add documentation empty page * Add documentation links * Add real documentation * Remove isOverlay from attrs * Add doc links to main README * Fix tests and logs * Small changes in AudioMediaEncoder * Add changelog linepull/513/head
parent
7d87d4af61
commit
ea952d1497
@ -0,0 +1,197 @@ |
|||||||
|
package com.otaliastudios.cameraview.overlay; |
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Resources; |
||||||
|
import android.content.res.TypedArray; |
||||||
|
import android.content.res.XmlResourceParser; |
||||||
|
import android.graphics.Canvas; |
||||||
|
import android.util.AttributeSet; |
||||||
|
import android.util.Xml; |
||||||
|
import android.view.Gravity; |
||||||
|
import android.view.LayoutInflater; |
||||||
|
import android.view.View; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.test.annotation.UiThreadTest; |
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||||
|
import androidx.test.filters.SmallTest; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.BaseTest; |
||||||
|
import com.otaliastudios.cameraview.R; |
||||||
|
|
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.ArgumentCaptor; |
||||||
|
import org.mockito.ArgumentMatcher; |
||||||
|
import org.mockito.invocation.InvocationOnMock; |
||||||
|
import org.mockito.stubbing.Answer; |
||||||
|
import org.w3c.dom.Attr; |
||||||
|
import org.xmlpull.v1.XmlPullParser; |
||||||
|
import org.xmlpull.v1.XmlPullParserException; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertFalse; |
||||||
|
import static org.junit.Assert.assertNotEquals; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
import static org.mockito.ArgumentMatchers.any; |
||||||
|
import static org.mockito.ArgumentMatchers.anyFloat; |
||||||
|
import static org.mockito.ArgumentMatchers.anyLong; |
||||||
|
import static org.mockito.ArgumentMatchers.argThat; |
||||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||||
|
import static org.mockito.ArgumentMatchers.notNull; |
||||||
|
import static org.mockito.Mockito.doNothing; |
||||||
|
import static org.mockito.Mockito.mock; |
||||||
|
import static org.mockito.Mockito.never; |
||||||
|
import static org.mockito.Mockito.reset; |
||||||
|
import static org.mockito.Mockito.spy; |
||||||
|
import static org.mockito.Mockito.times; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class) |
||||||
|
@SmallTest |
||||||
|
public class OverlayLayoutTest extends BaseTest { |
||||||
|
|
||||||
|
private OverlayLayout overlayLayout; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
overlayLayout = spy(new OverlayLayout(context())); |
||||||
|
} |
||||||
|
|
||||||
|
@After |
||||||
|
public void tearDown() { |
||||||
|
overlayLayout = null; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testIsOverlay_LayoutParams() { |
||||||
|
ViewGroup.LayoutParams params; |
||||||
|
|
||||||
|
params = new ViewGroup.LayoutParams(10, 10); |
||||||
|
assertFalse(overlayLayout.isOverlay(params)); |
||||||
|
|
||||||
|
params = new OverlayLayout.LayoutParams(10, 10); |
||||||
|
assertTrue(overlayLayout.isOverlay(params)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testIsOverlay_attributeSet() throws Exception { |
||||||
|
int layout1 = com.otaliastudios.cameraview.test.R.layout.overlay; |
||||||
|
int layout2 = com.otaliastudios.cameraview.test.R.layout.not_overlay; |
||||||
|
|
||||||
|
AttributeSet set1 = getAttributeSet(layout1); |
||||||
|
assertTrue(overlayLayout.isOverlay(set1)); |
||||||
|
|
||||||
|
AttributeSet set2 = getAttributeSet(layout2); |
||||||
|
assertFalse(overlayLayout.isOverlay(set2)); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private AttributeSet getAttributeSet(int layout) throws Exception { |
||||||
|
// Get the attribute set in the correct state: use a parser and move to START_TAG
|
||||||
|
XmlResourceParser parser = context().getResources().getLayout(layout); |
||||||
|
//noinspection StatementWithEmptyBody
|
||||||
|
while (parser.next() != XmlResourceParser.START_TAG) {} |
||||||
|
return Xml.asAttributeSet(parser); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testLayoutParams_drawsOn() { |
||||||
|
OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); |
||||||
|
|
||||||
|
assertFalse(params.drawsOn(Overlay.Target.PREVIEW)); |
||||||
|
assertFalse(params.drawsOn(Overlay.Target.PICTURE_SNAPSHOT)); |
||||||
|
assertFalse(params.drawsOn(Overlay.Target.VIDEO_SNAPSHOT)); |
||||||
|
|
||||||
|
params.drawOnPreview = true; |
||||||
|
assertTrue(params.drawsOn(Overlay.Target.PREVIEW)); |
||||||
|
params.drawOnPictureSnapshot = true; |
||||||
|
assertTrue(params.drawsOn(Overlay.Target.PICTURE_SNAPSHOT)); |
||||||
|
params.drawOnVideoSnapshot = true; |
||||||
|
assertTrue(params.drawsOn(Overlay.Target.VIDEO_SNAPSHOT)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testLayoutParams_toString() { |
||||||
|
OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); |
||||||
|
String string = params.toString(); |
||||||
|
assertTrue(string.contains("drawOnPreview")); |
||||||
|
assertTrue(string.contains("drawOnPictureSnapshot")); |
||||||
|
assertTrue(string.contains("drawOnVideoSnapshot")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testDrawChild() { |
||||||
|
Canvas canvas = new Canvas(); |
||||||
|
OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); |
||||||
|
View child = new View(context()); |
||||||
|
child.setLayoutParams(params); |
||||||
|
when(overlayLayout.doDrawChild(canvas, child, 0)).thenReturn(true); |
||||||
|
|
||||||
|
overlayLayout.currentTarget = Overlay.Target.PREVIEW; |
||||||
|
assertFalse(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
params.drawOnPreview = true; |
||||||
|
assertTrue(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
|
||||||
|
overlayLayout.currentTarget = Overlay.Target.PICTURE_SNAPSHOT; |
||||||
|
assertFalse(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
params.drawOnPictureSnapshot = true; |
||||||
|
assertTrue(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
|
||||||
|
overlayLayout.currentTarget = Overlay.Target.VIDEO_SNAPSHOT; |
||||||
|
assertFalse(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
params.drawOnVideoSnapshot = true; |
||||||
|
assertTrue(overlayLayout.drawChild(canvas, child, 0)); |
||||||
|
} |
||||||
|
|
||||||
|
@UiThreadTest |
||||||
|
@Test |
||||||
|
public void testDraw() { |
||||||
|
Canvas canvas = new Canvas(); |
||||||
|
when(overlayLayout.drawsOn(Overlay.Target.PREVIEW)).thenReturn(false); |
||||||
|
overlayLayout.draw(canvas); |
||||||
|
verify(overlayLayout, never()).drawOn(Overlay.Target.PREVIEW, canvas); |
||||||
|
|
||||||
|
when(overlayLayout.drawsOn(Overlay.Target.PREVIEW)).thenReturn(true); |
||||||
|
overlayLayout.draw(canvas); |
||||||
|
verify(overlayLayout, times(1)).drawOn(Overlay.Target.PREVIEW, canvas); |
||||||
|
} |
||||||
|
|
||||||
|
@UiThreadTest |
||||||
|
@Test |
||||||
|
public void testDrawOn() { |
||||||
|
Canvas canvas = spy(new Canvas()); |
||||||
|
View child = new View(context()); |
||||||
|
OverlayLayout.LayoutParams params = new OverlayLayout.LayoutParams(10, 10); |
||||||
|
params.drawOnPreview = true; |
||||||
|
params.drawOnPictureSnapshot = true; |
||||||
|
params.drawOnVideoSnapshot = true; |
||||||
|
overlayLayout.addView(child, params); |
||||||
|
|
||||||
|
overlayLayout.drawOn(Overlay.Target.PREVIEW, canvas); |
||||||
|
verify(canvas, never()).scale(anyFloat(), anyFloat()); |
||||||
|
verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); |
||||||
|
reset(canvas); |
||||||
|
reset(overlayLayout); |
||||||
|
|
||||||
|
overlayLayout.drawOn(Overlay.Target.PICTURE_SNAPSHOT, canvas); |
||||||
|
verify(canvas, times(1)).scale(anyFloat(), anyFloat()); |
||||||
|
verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); |
||||||
|
reset(canvas); |
||||||
|
reset(overlayLayout); |
||||||
|
|
||||||
|
overlayLayout.drawOn(Overlay.Target.VIDEO_SNAPSHOT, canvas); |
||||||
|
verify(canvas, times(1)).scale(anyFloat(), anyFloat()); |
||||||
|
verify(overlayLayout, times(1)).doDrawChild(eq(canvas), eq(child), anyLong()); |
||||||
|
reset(canvas); |
||||||
|
reset(overlayLayout); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"/> |
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
app:layout_drawOnPreview="true" |
||||||
|
app:layout_drawOnPictureSnapshot="true" |
||||||
|
app:layout_drawOnVideoSnapshot="true" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent"/> |
@ -0,0 +1,33 @@ |
|||||||
|
package com.otaliastudios.cameraview.overlay; |
||||||
|
|
||||||
|
import android.graphics.Canvas; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base interface for overlays. |
||||||
|
*/ |
||||||
|
public interface Overlay { |
||||||
|
|
||||||
|
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 drawOn(@NonNull Target target, @NonNull Canvas canvas); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called to understand if this overlay would like to draw onto the given |
||||||
|
* target or not. If true is returned, {@link #drawOn(Target, Canvas)} can be |
||||||
|
* called at a future time. |
||||||
|
* |
||||||
|
* @param target the target |
||||||
|
* @return true to draw on it |
||||||
|
*/ |
||||||
|
boolean drawsOn(@NonNull Target target); |
||||||
|
} |
@ -0,0 +1,211 @@ |
|||||||
|
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; |
||||||
|
import android.view.ViewGroup; |
||||||
|
import android.widget.FrameLayout; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
import androidx.annotation.Nullable; |
||||||
|
import androidx.annotation.VisibleForTesting; |
||||||
|
|
||||||
|
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); |
||||||
|
|
||||||
|
@VisibleForTesting Target currentTarget = Target.PREVIEW; |
||||||
|
|
||||||
|
/** |
||||||
|
* We set {@link #setWillNotDraw(boolean)} to false even if we don't draw anything. |
||||||
|
* This ensures that the View system will call {@link #draw(Canvas)} on us instead |
||||||
|
* of short-circuiting to {@link #dispatchDraw(Canvas)}. |
||||||
|
* |
||||||
|
* That would be a problem for us since we use {@link #draw(Canvas)} to understand if |
||||||
|
* we are currently drawing on the preview or not. |
||||||
|
* |
||||||
|
* @param context a context |
||||||
|
*/ |
||||||
|
public OverlayLayout(@NonNull Context context) { |
||||||
|
super(context); |
||||||
|
setWillNotDraw(false); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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.hasValue(R.styleable.CameraView_Layout_layout_drawOnPreview) |
||||||
|
|| a.hasValue(R.styleable.CameraView_Layout_layout_drawOnPictureSnapshot) |
||||||
|
|| a.hasValue(R.styleable.CameraView_Layout_layout_drawOnVideoSnapshot); |
||||||
|
a.recycle(); |
||||||
|
return isOverlay; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 OverlayLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { |
||||||
|
return new LayoutParams(getContext(), attrs); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This is called by the View hierarchy, so at this point we are |
||||||
|
* likely drawing on the preview. |
||||||
|
* @param canvas View canvas |
||||||
|
*/ |
||||||
|
@SuppressLint("MissingSuperCall") |
||||||
|
@Override |
||||||
|
public void draw(Canvas canvas) { |
||||||
|
LOG.i("normal draw called."); |
||||||
|
if (drawsOn(Target.PREVIEW)) { |
||||||
|
drawOn(Target.PREVIEW, canvas); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean drawsOn(@NonNull Target target) { |
||||||
|
for (int i = 0; i < getChildCount(); i++) { |
||||||
|
LayoutParams params = (LayoutParams) getChildAt(i).getLayoutParams(); |
||||||
|
if (params.drawsOn(target)) return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* For {@link Target#PREVIEW}, this method is called by the View hierarchy. We will |
||||||
|
* just forward the call to super. |
||||||
|
* |
||||||
|
* For {@link Target#PICTURE_SNAPSHOT} and {@link Target#VIDEO_SNAPSHOT}, |
||||||
|
* this method is called by the overlay drawer. We call {@link #dispatchDraw(Canvas)} |
||||||
|
* to draw our children only. |
||||||
|
* |
||||||
|
* @param target the draw target |
||||||
|
* @param canvas the canvas |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void drawOn(@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(); |
||||||
|
// The input canvas size is that of the preview stream, cropped to match
|
||||||
|
// the view aspect ratio (this op is done by picture & video recorder).
|
||||||
|
// So the aspect ratio is guaranteed to be the same, but we might have
|
||||||
|
// to apply some scale (typically > 1).
|
||||||
|
float widthScale = canvas.getWidth() / (float) getWidth(); |
||||||
|
float heightScale = canvas.getHeight() / (float) getHeight(); |
||||||
|
LOG.i("draw", |
||||||
|
"target:", target, |
||||||
|
"canvas:", canvas.getWidth() + "x" + canvas.getHeight(), |
||||||
|
"view:", getWidth() + "x" + getHeight(), |
||||||
|
"widthScale:", widthScale, |
||||||
|
"heightScale:", heightScale |
||||||
|
); |
||||||
|
canvas.scale(widthScale, heightScale); |
||||||
|
dispatchDraw(canvas); |
||||||
|
canvas.restore(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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(); |
||||||
|
if (params.drawsOn(currentTarget)) { |
||||||
|
LOG.v("Performing drawing for view:", child.getClass().getSimpleName(), |
||||||
|
"target:", currentTarget, |
||||||
|
"params:", params); |
||||||
|
return doDrawChild(canvas, child, drawingTime); |
||||||
|
} else { |
||||||
|
LOG.v("Skipping drawing for view:", child.getClass().getSimpleName(), |
||||||
|
"target:", currentTarget, |
||||||
|
"params:", params); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
boolean doDrawChild(Canvas canvas, View child, long drawingTime) { |
||||||
|
return super.drawChild(canvas, child, drawingTime); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") |
||||||
|
public static class LayoutParams extends FrameLayout.LayoutParams { |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public boolean drawOnPreview = false; |
||||||
|
public boolean drawOnPictureSnapshot = false; |
||||||
|
public boolean drawOnVideoSnapshot = false; |
||||||
|
|
||||||
|
public LayoutParams(int width, int height) { |
||||||
|
super(width, height); |
||||||
|
} |
||||||
|
|
||||||
|
public LayoutParams(@NonNull Context context, @NonNull AttributeSet attrs) { |
||||||
|
super(context, attrs); |
||||||
|
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView_Layout); |
||||||
|
try { |
||||||
|
drawOnPreview = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPreview, false); |
||||||
|
drawOnPictureSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnPictureSnapshot, false); |
||||||
|
drawOnVideoSnapshot = a.getBoolean(R.styleable.CameraView_Layout_layout_drawOnVideoSnapshot, false); |
||||||
|
} finally { |
||||||
|
a.recycle(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
boolean drawsOn(@NonNull Target target) { |
||||||
|
return ((target == Target.PREVIEW && drawOnPreview) |
||||||
|
|| (target == Target.VIDEO_SNAPSHOT && drawOnVideoSnapshot) |
||||||
|
|| (target == Target.PICTURE_SNAPSHOT && drawOnPictureSnapshot)); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getName() + "[" |
||||||
|
+ "drawOnPreview:" + drawOnPreview |
||||||
|
+ ",drawOnPictureSnapshot:" + drawOnPictureSnapshot |
||||||
|
+ ",drawOnVideoSnapshot:" + drawOnVideoSnapshot |
||||||
|
+ "]"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
--- |
||||||
|
layout: page |
||||||
|
title: "Watermarks and Overlays" |
||||||
|
subtitle: "Static and animated overlays" |
||||||
|
description: "Static and animated overlays" |
||||||
|
category: docs |
||||||
|
order: 10 |
||||||
|
date: 2019-07-14 20:14:31 |
||||||
|
disqus: 1 |
||||||
|
--- |
||||||
|
|
||||||
|
CameraView offers a simple yet powerful framework for watermarks and overlays of any kind. |
||||||
|
These overlays can be shown on the live camera preview, plus they appear on the media results |
||||||
|
taken with `takePictureSnapshot()` or `takeVideoSnapshot()`. |
||||||
|
|
||||||
|
### Simple Usage |
||||||
|
|
||||||
|
```xml |
||||||
|
<com.otaliastudios.cameraview.CameraView |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content"> |
||||||
|
|
||||||
|
<!-- Watermark in bottom/end corner --> |
||||||
|
<ImageView |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="bottom|end" |
||||||
|
android:src="@drawable/watermark" |
||||||
|
app:layout_drawOnPreview="true|false" |
||||||
|
app:layout_drawOnPictureSnapshot="true|false" |
||||||
|
app:layout_drawOnVideoSnapshot="true|false"/> |
||||||
|
|
||||||
|
<!-- More overlays here... --> |
||||||
|
|
||||||
|
</com.otaliastudios.cameraview.CameraView> |
||||||
|
``` |
||||||
|
|
||||||
|
As you can see, the overlay system is View-based - each overlay is just a real `View` attached |
||||||
|
into the hierarchy. This is a powerful and creative tool. You can, for instance, retrieve the |
||||||
|
overlay with `findViewById` and: |
||||||
|
|
||||||
|
- Animate it! |
||||||
|
- Change its visibility |
||||||
|
- Change its position or appearance |
||||||
|
- Do so while video is being recorded |
||||||
|
|
||||||
|
Any changes in the overlay appearance will be recorded in real-time in the picture snapshot |
||||||
|
or video snapshot that you are capturing. |
||||||
|
|
||||||
|
As you can see in the example, you can also selectively choose, for each overlay, whether it |
||||||
|
will draw on the preview (`layout_drawOnPreview`), on picture snapshots (`layout_drawOnPreview`), |
||||||
|
on video snapshots (`layout_drawOnPreview`). |
||||||
|
|
||||||
|
### Advanced Usage |
||||||
|
|
||||||
|
If you need to change these flags at runtime, you should cast the overlay `LayoutParams` as follows: |
||||||
|
|
||||||
|
```java |
||||||
|
// Cast to OverlayLayout.LayoutParams |
||||||
|
View overlay = findViewById(R.id.watermark); |
||||||
|
OverlayLayout.LayoutParams params = (OverlayLayout.LayoutParams) overlay.getLayoutParams(); |
||||||
|
|
||||||
|
// Perform changes |
||||||
|
params.drawOnPreview = true; // draw on preview |
||||||
|
params.drawOnPreview = false; // do not draw on preview |
||||||
|
params.drawOnPictureSnapshot = true; // draw on picture snapshots |
||||||
|
params.drawOnPictureSnapshot = false; // do not draw on picture snapshots |
||||||
|
params.drawOnVideoSnapshot = true; // draw on video snapshots |
||||||
|
params.drawOnVideoSnapshot = false; // do not draw on video snapshots |
||||||
|
|
||||||
|
// When done, apply |
||||||
|
overlay.setLayoutParams(params); |
||||||
|
``` |
Loading…
Reference in new issue