public class

OverlayEffect

extends CameraEffect

implements java.lang.AutoCloseable

 java.lang.Object

androidx.camera.core.CameraEffect

↳androidx.camera.effects.OverlayEffect

Gradle dependencies

compile group: 'androidx.camera', name: 'camera-effects', version: '1.5.0-alpha01'

  • groupId: androidx.camera
  • artifactId: camera-effects
  • version: 1.5.0-alpha01

Artifact androidx.camera:camera-effects:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A CameraEffect for drawing overlay on top of the camera frames.

This class manages and processes camera frames with OpenGL. Upon arrival, frames are enqueued into an array of GL textures for deferred rendering. Calling OverlayEffect.drawFrameAsync(long) dequeues frames and renders them to the output. Additionally, when the texture queue reaches its capacity, the oldest frame is automatically dequeued and rendered. The size of the texture queue can be defined in the constructor.

The queuing mechanism provides the flexibility to postpone frame rendering until analysis results are available. For instance, to highlight on a QR code in a preview, one can apply a QR code detection algorithm using ImageAnalysis. Once the frame's analysis result is ready, invoke OverlayEffect.drawFrameAsync(long) and pass in the ImageInfo.getTimestamp() to release the frame and draw overlay. If the app doesn't render real-time analysis results, set the queue depth to 0 to avoid unnecessary buffer copies. For example, when laying over a static watermark.

Prior to rendering a frame, the OverlayEffect invokes the listener set in OverlayEffect.setOnDrawListener(Function). This listener provides a Frame object, which contains both a object for drawing the overlay and the metadata like crop rect, rotation degrees, etc to calculate how the overlay should be positioned. Once the listener returns, OverlayEffect updates the SurfaceTexture behind the and blends it with the camera frame before rendering to the output.

This class is thread-safe. The methods can be invoked on any thread. The app provides a Handler object in the constructor which is used for listening for Surface updates, performing OpenGL operations and invoking app provided listeners.

Summary

Fields
public static final intRESULT_CANCELLED_BY_CALLER

The OverlayEffect.drawFrameAsync(long) call failed because the caller cancelled the drawing.

public static final intRESULT_FRAME_NOT_FOUND

The OverlayEffect.drawFrameAsync(long) call failed because the frame with the exact timestamp was not found in the queue.

public static final intRESULT_INVALID_SURFACE

The OverlayEffect.drawFrameAsync(long) call failed because the output surface is missing, or the output surface no longer matches the frame.

public static final intRESULT_SUCCESS

The OverlayEffect.drawFrameAsync(long) call was successful.

from CameraEffectIMAGE_CAPTURE, OUTPUT_OPTION_ONE_FOR_ALL_TARGETS, OUTPUT_OPTION_ONE_FOR_EACH_TARGET, PREVIEW, TRANSFORMATION_ARBITRARY, TRANSFORMATION_CAMERA_AND_SURFACE_ROTATION, TRANSFORMATION_PASSTHROUGH, VIDEO_CAPTURE
Constructors
publicOverlayEffect(int targets, int queueDepth, Handler handler, Consumer<java.lang.Throwable> errorListener)

Creates an OverlayEffect.

Methods
public voidclearOnDrawListener()

Clears the listener set in OverlayEffect.setOnDrawListener(Function).

public voidclose()

Closes the OverlayEffect.

public <any>drawFrameAsync(long timestampNs)

Draws the queued frame with the given timestamp.

public HandlergetHandler()

Gets the Handler set in the constructor.

public intgetQueueDepth()

Gets the depth of the queue set in the constructor.

public voidsetOnDrawListener(Function<Frame, java.lang.Boolean> onDrawListener)

Sets the listener for drawing overlay.

from CameraEffectcreateSurfaceProcessorInternal, getErrorListener, getExecutor, getImageProcessor, getOutputOption, getSurfaceProcessor, getTargets, getTransformation
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int RESULT_SUCCESS

The OverlayEffect.drawFrameAsync(long) call was successful. The frame with the exact timestamp was drawn to the output surface.

public static final int RESULT_FRAME_NOT_FOUND

The OverlayEffect.drawFrameAsync(long) call failed because the frame with the exact timestamp was not found in the queue. It could be one of the following reasons:

  • the timestamp provided was incorrect, or
  • the frame was removed because OverlayEffect.drawFrameAsync(long) had been called with a newer timestamp, or
  • the frame was removed due to the queue being full.
If it's the last case, the caller may avoid this issue by increasing the queue depth.

public static final int RESULT_INVALID_SURFACE

The OverlayEffect.drawFrameAsync(long) call failed because the output surface is missing, or the output surface no longer matches the frame. It can happen when the output Surface is replaced or disabled. For example, when the UseCase was unbound.

public static final int RESULT_CANCELLED_BY_CALLER

The OverlayEffect.drawFrameAsync(long) call failed because the caller cancelled the drawing. This happens when the listener in OverlayEffect.setOnDrawListener(Function) returned false.

Constructors

public OverlayEffect(int targets, int queueDepth, Handler handler, Consumer<java.lang.Throwable> errorListener)

Creates an OverlayEffect.

Parameters:

targets: The targets the effect applies to. For example, CameraEffect.PREVIEW | CameraEffect.VIDEO_CAPTURE. See for supported targets combinations.
queueDepth: The depth of the queue. This value indicates how many frames can be queued before the oldest frame being automatically released. OverlayEffect allocates an array of OpenGL 2D textures that matches this size. The maximum value of the queueDepth depends on the size of the image and the device capabilities. Set a larger value if an ImageAnalysis processing takes a long time to produce a result to be used for overlay, so the frame is not auto-released before the result is ready. If the queue depth is 0, the input frames are rendered immediately without queuing.
handler: The Handler for listening for the input Surface updates and for performing OpenGL operations.
errorListener: invoked if the effect runs into unrecoverable errors. The java.lang.Throwable will be the error thrown by this CameraEffect. For example, ProcessingException. This is invoked on the provided Handler.

Methods

public <any> drawFrameAsync(long timestampNs)

Draws the queued frame with the given timestamp.

Once invoked, OverlayEffect retrieves the queued frame with the given timestamp and draws it to the output Surface. If the frame is successfully drawn, completes with OverlayEffect.RESULT_SUCCESS. Otherwise, it completes with one of the following results: OverlayEffect.RESULT_FRAME_NOT_FOUND, OverlayEffect.RESULT_INVALID_SURFACE or OverlayEffect.RESULT_CANCELLED_BY_CALLER. If this method is called after the OverlayEffect is released, the completes with an java.lang.IllegalStateException.

This method is thread safe. When calling from the OverlayEffect.getHandler() thread, it's executed right away; otherwise, it posts the execution on the OverlayEffect.getHandler(). It's recommended to call this method from the OverlayEffect.getHandler() thread to avoid thread hopping.

public void setOnDrawListener(Function<Frame, java.lang.Boolean> onDrawListener)

Sets the listener for drawing overlay.

Each time before OverlayEffect draws a frame to the output, the listener receives a Frame object, which contains the necessary APIs for drawing overlay.

To draw an overlay, first call Frame.getOverlayCanvas() ()} to get a object. The object is backed by a SurfaceTexture with the size of Frame.getSize(). Frame.getSensorToBufferTransform() represents the mapping from camera sensor coordinates to the frame's coordinates. To draw objects in the sensor coordinates, call with the value of Frame.getSensorToBufferTransform().

Once the drawing is done, the listener should return true for the OverlayEffect to draw it to the output Surface. If it returns false, the frame will be dropped.

OverlayEffect invokes the listener on the OverlayEffect.getHandler() thread.

See also: Frame

public void clearOnDrawListener()

Clears the listener set in OverlayEffect.setOnDrawListener(Function).

public void close()

Closes the OverlayEffect.

Once closed, the OverlayEffect can no longer be used.

public int getQueueDepth()

Gets the depth of the queue set in the constructor.

public Handler getHandler()

Gets the Handler set in the constructor.

Source

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.camera.effects;

import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.os.Handler;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.arch.core.util.Function;
import androidx.camera.core.CameraEffect;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageInfo;
import androidx.camera.core.ProcessingException;
import androidx.camera.core.SurfaceProcessor;
import androidx.camera.core.UseCase;
import androidx.camera.effects.internal.SurfaceProcessorImpl;
import androidx.core.util.Consumer;

import com.google.common.util.concurrent.ListenableFuture;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * A {@link CameraEffect} for drawing overlay on top of the camera frames.
 *
 * <p>This class manages and processes camera frames with OpenGL. Upon arrival, frames are
 * enqueued into an array of GL textures for deferred rendering. Calling
 * {@link #drawFrameAsync(long)} dequeues frames and renders them to the output. Additionally, when
 * the texture queue reaches its capacity, the oldest frame is automatically dequeued and
 * rendered. The size of the texture queue can be defined in the constructor.
 *
 * <p>The queuing mechanism provides the flexibility to postpone frame rendering until analysis
 * results are available. For instance, to highlight on a QR code in a preview, one can apply a
 * QR code detection algorithm using {@link ImageAnalysis}. Once the frame's analysis result
 * is ready, invoke {@link #drawFrameAsync(long)} and pass in the
 * {@link ImageInfo#getTimestamp()} to release the frame and draw overlay. If the app
 * doesn't render real-time analysis results, set the queue depth to 0 to avoid unnecessary
 * buffer copies. For example, when laying over a static watermark.
 *
 * <p>Prior to rendering a frame, the {@link OverlayEffect} invokes the listener set in
 * {@link #setOnDrawListener(Function)}. This listener provides a {@link Frame} object, which
 * contains both a {@link Canvas} object for drawing the overlay and the metadata like crop rect,
 * rotation degrees, etc to calculate how the overlay should be positioned. Once the listener
 * returns, {@link OverlayEffect} updates the {@link SurfaceTexture} behind the {@link Canvas}
 * and blends it with the camera frame before rendering to the output.
 *
 * <p>This class is thread-safe. The methods can be invoked on any thread. The app provides a
 * {@link Handler} object in the constructor which is used for listening for Surface updates,
 * performing OpenGL operations and invoking app provided listeners.
 */
public class OverlayEffect extends CameraEffect implements AutoCloseable {

    /**
     * {@link #drawFrameAsync(long)} result code.
     */
    @Retention(RetentionPolicy.SOURCE)
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @IntDef(value = {
            RESULT_SUCCESS,
            RESULT_FRAME_NOT_FOUND,
            RESULT_INVALID_SURFACE,
            RESULT_CANCELLED_BY_CALLER})
    public @interface DrawFrameResult {
    }

    /**
     * The {@link #drawFrameAsync(long)} call was successful. The frame with the exact timestamp was
     * drawn to the output surface.
     */
    public static final int RESULT_SUCCESS = 1;

    /**
     * The {@link #drawFrameAsync(long)} call failed because the frame with the exact timestamp was
     * not found in the queue. It could be one of the following reasons:
     *
     * <ul>
     * <li>the timestamp provided was incorrect, or
     * <li>the frame was removed because {@link #drawFrameAsync} had been called with a newer
     * timestamp, or
     * <li>the frame was removed due to the queue being full.
     * </ul>
     *
     * If it's the last case, the caller may avoid this issue by increasing the queue depth.
     */
    public static final int RESULT_FRAME_NOT_FOUND = 2;

    /**
     * The {@link #drawFrameAsync(long)} call failed because the output surface is missing, or the
     * output surface no longer matches the frame. It can happen when the output Surface is
     * replaced or disabled. For example, when the {@link UseCase} was unbound.
     */
    public static final int RESULT_INVALID_SURFACE = 3;

    /**
     * The {@link #drawFrameAsync(long)} call failed because the caller cancelled the drawing. This
     * happens when the listener in {@link #setOnDrawListener(Function)} returned false.
     */
    public static final int RESULT_CANCELLED_BY_CALLER = 4;

    /**
     * Creates an {@link OverlayEffect}.
     *
     * @param targets       The targets the effect applies to. For example,
     *                      {@link CameraEffect#PREVIEW} | {@link CameraEffect#VIDEO_CAPTURE}. See
     *                      {@link UseCaseGroup.Builder#addEffect} for supported targets
     *                      combinations.
     * @param queueDepth    The depth of the queue. This value indicates how many frames can be
     *                      queued before the oldest frame being automatically released.
     *                      {@link OverlayEffect} allocates an array of OpenGL 2D textures that
     *                      matches this size. The maximum value of the queueDepth depends on the
     *                      size of the image and the device capabilities. Set a larger value if
     *                      an ImageAnalysis processing takes a long time to produce a result to
     *                      be used for overlay, so the frame is not auto-released before the
     *                      result is ready. If the queue depth is 0, the input frames are
     *                      rendered immediately without queuing.
     * @param handler       The Handler for listening for the input Surface updates and for
     *                      performing OpenGL operations.
     * @param errorListener invoked if the effect runs into unrecoverable errors. The
     *                      {@link Throwable} will be the error thrown by this
     *                      {@link CameraEffect}. For example, {@link ProcessingException}.
     *                      This is invoked on the provided {@param Handler}.
     */
    public OverlayEffect(int targets, int queueDepth, @NonNull Handler handler,
            @NonNull Consumer<Throwable> errorListener) {
        this(targets, new SurfaceProcessorImpl(queueDepth, handler), errorListener);
    }

    private OverlayEffect(int targets, @NonNull SurfaceProcessorImpl surfaceProcessor,
            @NonNull Consumer<Throwable> errorListener) {
        this(targets, surfaceProcessor.getGlExecutor(), surfaceProcessor,
                errorListener);
    }

    private OverlayEffect(int targets, @NonNull Executor executor,
            @NonNull SurfaceProcessor surfaceProcessor,
            @NonNull Consumer<Throwable> errorListener) {
        super(targets, executor, surfaceProcessor, errorListener);
    }

    /**
     * Draws the queued frame with the given timestamp.
     *
     * <p>Once invoked, {@link OverlayEffect} retrieves the queued frame with the given timestamp
     * and draws it to the output Surface. If the frame is successfully drawn,
     * {@link ListenableFuture} completes with {@link #RESULT_SUCCESS}. Otherwise, it completes
     * with one of the following results: {@link #RESULT_FRAME_NOT_FOUND},
     * {@link #RESULT_INVALID_SURFACE} or {@link #RESULT_CANCELLED_BY_CALLER}. If this method is
     * called after the {@link OverlayEffect} is released, the {@link ListenableFuture} completes
     * with an {@link IllegalStateException}.
     *
     * <p>This method is thread safe. When calling from the {@link #getHandler()} thread, it's
     * executed right away; otherwise, it posts the execution on the {@link #getHandler()}. It's
     * recommended to call this method from the {@link #getHandler()} thread to avoid thread
     * hopping.
     */
    @NonNull
    public ListenableFuture<Integer> drawFrameAsync(long timestampNs) {
        return getSurfaceProcessorImpl().drawFrameAsync(timestampNs);
    }

    /**
     * Sets the listener for drawing overlay.
     *
     * <p>Each time before {@link OverlayEffect} draws a frame to the output, the listener
     * receives a {@link Frame} object, which contains the necessary APIs for drawing overlay.
     *
     * <p>To draw an overlay, first call {@link Frame#getOverlayCanvas()} ()} to get a
     * {@link Canvas} object. The {@link Canvas} object is backed by a {@link SurfaceTexture}
     * with the size of {@link Frame#getSize()}. {@link Frame#getSensorToBufferTransform()}
     * represents the mapping from camera sensor coordinates to the frame's coordinates. To draw
     * objects in the sensor coordinates, call {@link Canvas#setMatrix(Matrix)} with the value of
     * {@link Frame#getSensorToBufferTransform()}.
     *
     * <p>Once the drawing is done, the listener should return true for the {@link OverlayEffect}
     * to draw it to the output Surface. If it returns false, the frame will be dropped.
     *
     * <p>{@link OverlayEffect} invokes the listener on the {@link #getHandler()} thread.
     *
     * @see Frame
     */
    public void setOnDrawListener(@NonNull Function<Frame, Boolean> onDrawListener) {
        getSurfaceProcessorImpl().setOnDrawListener(onDrawListener);
    }

    /**
     * Clears the listener set in {@link #setOnDrawListener(Function)}.
     */
    public void clearOnDrawListener() {
        getSurfaceProcessorImpl().setOnDrawListener(null);
    }

    /**
     * Closes the {@link OverlayEffect}.
     *
     * <p>Once closed, the {@link OverlayEffect} can no longer be used.
     */
    @Override
    public void close() {
        getSurfaceProcessorImpl().release();
    }

    /**
     * Gets the depth of the queue set in the constructor.
     */
    public int getQueueDepth() {
        return getSurfaceProcessorImpl().getQueueDepth();
    }

    /**
     * Gets the {@link Handler} set in the constructor.
     */
    @NonNull
    public Handler getHandler() {
        return getSurfaceProcessorImpl().getGlHandler();
    }

    @NonNull
    private SurfaceProcessorImpl getSurfaceProcessorImpl() {
        return (SurfaceProcessorImpl) Objects.requireNonNull(getSurfaceProcessor());
    }
}