DisplayOrientedMeteringPointFactory.java

/*
 * Copyright 2019 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.core;

import android.content.Context;
import android.graphics.PointF;
import android.view.Display;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.camera.core.CameraX.LensFacing;

/**
 * A {@link MeteringPointFactory} that can create {@link MeteringPoint} by display oriented x, y.
 *
 * <p>This factory will consider the current display rotation and the lens facing to translate the
 * x/y correctly. Using this factory, apps do not need to handle the device rotation. They
 * can simply pass the x/y retrieved from their View. However if the camera preview is cropped,
 * scaled or rotated, it is apps' duty to transform the coordinates first.
 *
 * <p> The width/height of this factory is the logical width/height of the preview FoV and X/Y
 * is the logical XY inside the FOV. User can set the width and height to 1.0 which will make the
 * XY the normalized coordinates [0..1].
 */
public final class DisplayOrientedMeteringPointFactory extends MeteringPointFactory {
    /** The logical width of FoV in current display orientation */
    private final float mWidth;
    /** The logical height of FoV in current display orientation */
    private final float mHeight;
    /** Lens facing is required for correctly adjusted for front camera */
    private final LensFacing mLensFacing;
    /** {@link Display} used for detecting display orientation */
    @NonNull
    private final Display mDisplay;
    @NonNull
    private final CameraInfo mCameraInfo;

    /**
     * Creates the {@link MeteringPointFactory} with default display orientation.
     *
     * <p>The width/height is the logical width/height of the preview FoV and X/Y is the logical
     * XY inside the FOV. User can set the width and height to 1.0 which will make the XY the
     * normalized coordinates [0..1]. Or user can set the width/height to the View width/height and
     * then X/Y becomes the X/Y in the view.
     *
     * @param context    context to get the {@link WindowManager} for default display rotation.
     * @param lensFacing current lens facing.
     * @param width      the logical width of FoV in current display orientation.
     * @param height     the logical height of FoV in current display orientation.
     */
    public DisplayOrientedMeteringPointFactory(@NonNull Context context,
            @NonNull LensFacing lensFacing, float width, float height) {
        this(((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(),
                lensFacing, width, height);
    }

    /**
     * Creates the  {@link MeteringPointFactory}  with custom display orientation. This is used
     * in multi-display situation.
     *
     * <p>The width/height is the logical width/height of the preview FoV and X/Y is the logical
     * XY inside the FOV. User can set the width and height to 1.0 which will make the XY the
     * normalized coordinates [0..1]. Or user can set the width/height to the View width/height and
     * then X/Y becomes the X/Y in the view.
     * {@link Display} is used to dete
     * @param display    {@link Display} to get the orientation from.
     * @param lensFacing current lens facing.
     * @param width      the logical width of FoV in current display orientation.
     * @param height     the logical height of FoV in current display orientation.
     */
    public DisplayOrientedMeteringPointFactory(@NonNull Display display,
            @NonNull LensFacing lensFacing, float width, float height) {
        mWidth = width;
        mHeight = height;
        mLensFacing = lensFacing;
        mDisplay = display;
        try {
            String cameraId = CameraX.getCameraWithLensFacing(lensFacing);
            mCameraInfo = CameraX.getCameraInfo(cameraId);
        } catch (Exception e) {
            throw new IllegalArgumentException("Can not find CameraInfo : " + lensFacing);
        }
    }

    /**
     * {@inheritDoc}
     */
    @NonNull
    @Override
    protected PointF translatePoint(float x, float y) {
        float width = mWidth;
        float height = mHeight;

        boolean compensateForMirroring = (mLensFacing == LensFacing.FRONT);
        int relativeCameraOrientation = getRelativeCameraOrientation(compensateForMirroring);
        float outputX = x;
        float outputY = y;
        float outputWidth = width;
        float outputHeight = height;

        if (relativeCameraOrientation == 90 || relativeCameraOrientation == 270) {
            // We're horizontal. Swap width/height. Swap x/y.
            outputX = y;
            outputY = x;
            outputWidth = height;
            outputHeight = width;
        }

        switch (relativeCameraOrientation) {
            // Map to correct coordinates according to relativeCameraOrientation
            case 90:
                outputY = outputHeight - outputY;
                break;
            case 180:
                outputX = outputWidth - outputX;
                outputY = outputHeight - outputY;
                break;
            case 270:
                outputX = outputWidth - outputX;
                break;
            default:
                break;
        }

        // Swap x if it's a mirrored preview
        if (compensateForMirroring) {
            outputX = outputWidth - outputX;
        }

        // Normalized it to [0, 1]
        outputX = outputX / outputWidth;
        outputY = outputY / outputHeight;

        return new PointF(outputX, outputY);
    }

    private int getRelativeCameraOrientation(boolean compensateForMirroring) {
        int rotationDegrees;
        try {
            int displayRotation = mDisplay.getRotation();
            rotationDegrees = mCameraInfo.getSensorRotationDegrees(displayRotation);
            if (compensateForMirroring) {
                rotationDegrees = (360 - rotationDegrees) % 360;
            }
        } catch (Exception e) {
            rotationDegrees = 0;
        }
        return rotationDegrees;
    }
}