PreviewCorrector.java

/*
 * Copyright 2020 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.preview.transform;

import android.util.Pair;
import android.util.Size;
import android.view.Surface;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.camera.view.preview.transform.transformation.PreviewCorrectionTransformation;

/** Corrects a camera preview by scaling and/or rotating it so that it matches the display. */
final class PreviewCorrector {

    private PreviewCorrector() {
    }

    /**
     * Corrects a camera preview by scaling and rotating it.
     *
     * @param container                 Preview container
     * @param preview                   Preview view (a {@link android.view.TextureView} or
     *                                  {@link android.view.SurfaceView})
     * @param bufferSize                Camera output size
     * @param sensorDimensionFlipNeeded True if the sensor x and y dimensions need to be flipped.
     * @param deviceRotation            If the app is not running in remote display mode, set the
     *                                  parameter as {@link RotationTransform#ROTATION_AUTOMATIC}.
     *                                  Then, the rotation value queried from the preview will be
     *                                  used to do the transformation calculations. If the app is
     *                                  running in remote display mode, the device rotation value
     *                                  needs to be provided to make the result be rotated into
     *                                  correct orientation. The device rotation should be obtained
     *                                  from {@link android.view.OrientationEventListener} and
     *                                  needs to be converted into {@link Surface#ROTATION_0},
     *                                  {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}
     *                                  , or {@link Surface#ROTATION_270}.
     */
    @NonNull
    static PreviewCorrectionTransformation getCorrectionTransformation(
            @NonNull final View container, @NonNull final View preview,
            @NonNull final Size bufferSize, final boolean sensorDimensionFlipNeeded,
            final int deviceRotation) {
        final int rotation = (int) RotationTransform.getRotationDegrees(preview, deviceRotation);
        final Pair<Float, Float> scaleXY = getCorrectionScale(container, preview,
                bufferSize, sensorDimensionFlipNeeded);
        return new PreviewCorrectionTransformation(scaleXY.first, scaleXY.second, -rotation);
    }

    /**
     * Computes the scales on both the x and y axes so that the preview can be corrected.
     *
     * @param container                 Preview container
     * @param preview                   Preview view (a {@link android.view.TextureView} or
     *                                  {@link android.view.SurfaceView})
     * @param bufferSize                Camera output size
     * @param sensorDimensionFlipNeeded True if the sensor x and y dimensions need to be flipped.
     * @return The scales on both the x and y axes so that the preview can be corrected.
     */
    private static Pair<Float, Float> getCorrectionScale(@NonNull final View container,
            @NonNull final View preview, @NonNull final Size bufferSize,
            final boolean sensorDimensionFlipNeeded) {

        // Scaling only makes sense when none of the dimensions are equal to zero. In the
        // opposite case, a default scale of 1 is returned,
        if (container.getWidth() == 0 || container.getHeight() == 0 || preview.getWidth() == 0
                || preview.getHeight() == 0 || bufferSize.getWidth() == 0
                || bufferSize.getHeight() == 0) {
            return new Pair<>(1F, 1F);
        }

        final int bufferWidth;
        final int bufferHeight;
        if (sensorDimensionFlipNeeded) {
            bufferWidth = bufferSize.getHeight();
            bufferHeight = bufferSize.getWidth();
        } else {
            bufferWidth = bufferSize.getWidth();
            bufferHeight = bufferSize.getHeight();
        }

        // Scale the buffers back to the original output size.
        float scaleX = bufferWidth / (float) preview.getWidth();
        float scaleY = bufferHeight / (float) preview.getHeight();
        return new Pair<>(scaleX, scaleY);
    }
}