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