PreviewTransform.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 static androidx.camera.view.preview.transform.transformation.Transformation.getTransformation;

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

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.camera.core.impl.ImageOutputConfig;
import androidx.camera.view.PreviewView;
import androidx.camera.view.preview.transform.transformation.Transformation;

/**
 * Transforms the camera preview using a supported {@link PreviewView.ScaleType}.
 * <p>
 * Holds a reference to the {@link PreviewView.ScaleType} that's being applied to the preview.
 * This attribute is used by both {@link PreviewView} (see {@link PreviewView#getScaleType()} and
 * {@link PreviewView#setScaleType(PreviewView.ScaleType)}) and its implementation (when
 * correcting the preview and updating the {@link PreviewView.ScaleType}).
 *
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
public final class PreviewTransform {

    private static final PreviewView.ScaleType DEFAULT_SCALE_TYPE =
            PreviewView.ScaleType.FILL_CENTER;

    @NonNull
    private PreviewView.ScaleType mScaleType = DEFAULT_SCALE_TYPE;

    @Nullable
    private Transformation mCurrentTransformation;

    private boolean mSensorDimensionFlipNeeded = true;

    private int mDeviceRotation = RotationTransform.ROTATION_AUTOMATIC;

    /** Returns the {@link PreviewView.ScaleType} currently applied to the preview. */
    @NonNull
    public PreviewView.ScaleType getScaleType() {
        return mScaleType;
    }

    /** Sets the {@link PreviewView.ScaleType} to be applied to the preview. */
    public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
        mScaleType = scaleType;
    }

    /**
     * Returns the current {@link Transformation} applied to the preview, or {@code null} if the
     * preview hasn't started yet.
     */
    @Nullable
    public Transformation getCurrentTransformation() {
        return mCurrentTransformation;
    }

    /** Returns whether the device sensor x and y dimensions need to be flipped. */
    public boolean isSensorDimensionFlipNeeded() {
        return mSensorDimensionFlipNeeded;
    }

    /**
     * Sets whether the target device sensor dimensions need to be flipped when calculating the
     * transform.
     */
    public void setSensorDimensionFlipNeeded(boolean sensorDimensionFlipNeeded) {
        mSensorDimensionFlipNeeded = sensorDimensionFlipNeeded;
    }

    /** Returns current device rotation value. */
    public int getDeviceRotation() {
        return mDeviceRotation;
    }

    /**
     * Sets the device rotation value that will affect the transform calculations.
     */
    public void setDeviceRotation(@ImageOutputConfig.RotationValue int deviceRotation) {
        mDeviceRotation = deviceRotation;
    }

    /** Applies the current {@link PreviewView.ScaleType} on the passed in preview. */
    public void applyCurrentScaleType(@NonNull final View container, @NonNull final View view,
            @NonNull final Size bufferSize) {
        resetPreview(view);
        correctPreview(container, view, bufferSize);
        applyScaleTypeInternal(container, view, mScaleType, mDeviceRotation);
    }

    private void resetPreview(@NonNull View view) {
        final Transformation reset = new Transformation();
        applyTransformation(view, reset);
    }

    /** Corrects the preview. */
    private void correctPreview(@NonNull final View container, @NonNull final View view,
            @NonNull final Size bufferSize) {
        final Transformation correct = PreviewCorrector.getCorrectionTransformation(container, view,
                bufferSize, mSensorDimensionFlipNeeded, mDeviceRotation);
        applyTransformation(view, correct);
    }

    /** Applies the specified {@link PreviewView.ScaleType} on top of the corrected preview. */
    private void applyScaleTypeInternal(@NonNull final View container,
            @NonNull final View view, @NonNull final PreviewView.ScaleType scaleType,
            final int deviceRotation) {
        final Transformation current = getTransformation(view);
        final Transformation transformation = ScaleTypeTransform.getTransformation(container, view,
                scaleType, deviceRotation);
        applyTransformation(view, current.add(transformation));
    }

    /**
     * Applies a {@link Transformation} on the passed in preview while overriding any previous
     * preview {@linkplain Transformation transformations}
     */
    private void applyTransformation(@NonNull final View view,
            @NonNull final Transformation transformation) {
        view.setX(0);
        view.setY(0);
        view.setScaleX(transformation.getScaleX());
        view.setScaleY(transformation.getScaleY());
        view.setTranslationX(transformation.getTransX());
        view.setTranslationY(transformation.getTransY());
        view.setRotation(transformation.getRotation());

        mCurrentTransformation = transformation;
    }
}