ScaleTypeTransform.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.view;

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

import androidx.annotation.NonNull;

import java.math.BigDecimal;

final class ScaleTypeTransform {
    /**
     * Produce the transform {@link Matrix} to do a center crop, which can be used by
     * {@link android.view.TextureView#setTransform(Matrix)}.
     *
     * @param resolution The resolution of the source {@link android.graphics.SurfaceTexture}
     * @param view The TextureView where the SurfaceTexture will be drawn into.
     * @param displayRotation The rotation of the display relative to the source
     * {@link android.view.Surface}
     */
    static Matrix transformCenterCrop(@NonNull Size resolution, @NonNull View view,
            int displayRotation) {
        if (resolution.getWidth() == 0 || resolution.getHeight() == 0) {
            throw new IllegalArgumentException("Input resolution can not be zero sized");
        }

        if (view.getWidth() == 0 || view.getHeight() == 0) {
            throw new IllegalArgumentException("View resolution can not be zero sized");
        }

        Matrix matrix = new Matrix();

        int viewWidth = view.getWidth();
        int viewHeight = view.getHeight();

        int displayRotationDegrees =
                SurfaceRotation.rotationDegreesFromSurfaceRotation(displayRotation);
        Size scaled = calculateCenterCropDimension(resolution.getWidth(), resolution.getHeight(),
                viewWidth, viewHeight, displayRotationDegrees);

        // Compute the center of the view.
        int centerX = viewWidth / 2;
        int centerY = viewHeight / 2;

        // Do corresponding rotation to correct the preview direction
        matrix.postRotate(-displayRotationDegrees, centerX, centerY);

        // Compute the scale value for center crop mode
        float xScale = scaled.getWidth() / (float) viewWidth;
        float yScale = scaled.getHeight() / (float) viewHeight;

        if (displayRotationDegrees == 90 || displayRotationDegrees == 270) {
            xScale = scaled.getWidth() / (float) viewHeight;
            yScale = scaled.getHeight() / (float) viewWidth;
        }

        // Only two digits after the decimal point are valid for postScale. Need to get ceiling of
        // two digits floating value to do the scale operation. Otherwise, the result may be
        // scaled not large enough and will have some blank lines on the screen.
        xScale = new BigDecimal(xScale).setScale(2, BigDecimal.ROUND_CEILING).floatValue();
        yScale = new BigDecimal(yScale).setScale(2, BigDecimal.ROUND_CEILING).floatValue();

        // Do corresponding scale to resolve the deformation problem
        matrix.postScale(xScale, yScale, centerX, centerY);

        return matrix;
    }

    /**
     * Produces the dimensions for the center cropped surface.
     *
     * @param sourceWidth The height of the source {@link android.view.Surface}.
     * @param sourceHeight The width of the source {@link android.view.Surface}.
     * @param parentWidth The width of the view that is drawn into.
     * @param parentHeight The height of the view that is drawn into.
     * @param displayRotation The rotation of the display relative to the source Surface.
     */
    private static Size calculateCenterCropDimension(int sourceWidth, int sourceHeight,
            int parentWidth,
            int parentHeight, int displayRotation) {
        int inWidth = sourceWidth;
        int inHeight = sourceHeight;
        if (displayRotation == 0 || displayRotation == 180) {
            // Need to reverse the width and height since we're in landscape orientation.
            inWidth = sourceHeight;
            inHeight = sourceWidth;
        }

        int outWidth;
        int outHeight;

        float vfRatio = inWidth / (float) inHeight;
        float parentRatio = parentWidth / (float) parentHeight;

        // Match shortest sides together.
        if (vfRatio < parentRatio) {
            outWidth = parentWidth;
            outHeight = Math.round(parentWidth / vfRatio);
        } else {
            outWidth = Math.round(parentHeight * vfRatio);
            outHeight = parentHeight;
        }

        return new Size(outWidth, outHeight);
    }

    // Prevent creating an instance
    private ScaleTypeTransform() {
    }
}