CoordinateTransform.java

/*
 * Copyright 2021 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.view.transform;

import static androidx.camera.view.TransformUtils.isAspectRatioMatchingWithRoundingError;

import android.graphics.Matrix;

import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.Logger;
import androidx.camera.core.UseCase;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.core.ViewPort;
import androidx.camera.view.PreviewView;
import androidx.camera.view.TransformExperimental;

/**
 * This class represents the transform from one {@link OutputTransform} to another.
 *
 * <p> This class can be used to map the coordinates of one {@link OutputTransform} to another,
 * given that they are both from the same {@link UseCaseGroup}. {@link OutputTransform} can
 * represent the output of a {@link UseCase} or {@link PreviewView}. For example, mapping the
 * coordinates of detected objects from {@link ImageAnalysis} output to the drawing location in
 * {@link PreviewView}.
 *
 * TODO(b/179827713): add code samples when more {@link OutputTransform} subclasses are available.
 * TODO(b/179827713): unhide this class once all transform utils are done.
 *
 * @hide
 */
@TransformExperimental
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class CoordinateTransform {

    private static final String TAG = "CoordinateTransform";
    private static final String MISMATCH_MSG = "The source viewport (%s) does not match the target "
            + "viewport (%s). Please make sure they are from the same UseCaseGroup.";

    private final Matrix mMatrix;

    /**
     * Creates the transform between the {@code source} and the {@code target}.
     *
     * <p> The source and the target must be from the same {@link UseCaseGroup}.
     *
     * @param source the source
     * @see UseCaseGroup
     * @see ViewPort
     */
    public CoordinateTransform(@NonNull OutputTransform source,
            @NonNull OutputTransform target) {
        // TODO(b/137515129): This is a poor way to check if the two outputs are based on
        //  the same viewport. A better way is to add a matrix in use case output that represents
        //  the transform from sensor to surface. But it will require the view artifact to
        //  depend on a new internal API in the core artifact, which we can't do at the
        //  moment because of the version mismatch between view and core.
        if (!isAspectRatioMatchingWithRoundingError(
                source.getViewPortSize(), /* isAccurate1= */ false,
                target.getViewPortSize(), /* isAccurate2= */ false)) {
            // Mismatched aspect ratio means the outputs are not from the same UseCaseGroup
            Logger.w(TAG, String.format(MISMATCH_MSG, source.getViewPortSize(),
                    target.getViewPortSize()));
        }

        // Concatenate the source transform with the target transform.
        mMatrix = new Matrix();
        source.getMatrix().invert(mMatrix);
        mMatrix.postConcat(target.getMatrix());
    }

    /**
     * Gets the transform matrix.
     *
     * @param matrix a {@link android.graphics.Matrix} that represents the transform from source
     *               to target.
     */
    public void getTransform(@NonNull Matrix matrix) {
        matrix.set(mMatrix);
    }

    /**
     * Apply this transform to the array of 2D points, and write the transformed points back into
     * the array
     *
     * @param points The array [x0, y0, x1, y1, ...] of points to transform.
     * @see Matrix#mapPoints(float[])
     */
    public void mapPoints(@NonNull float[] points) {
        mMatrix.mapPoints(points);
    }

    // TODO(b/179827713): add overloading mapPoints method for other data types.
}