1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.view.transform;
18 
19 import static androidx.camera.core.impl.utils.TransformUtils.getNormalizedToBuffer;
20 import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect;
21 import static androidx.camera.core.impl.utils.TransformUtils.is90or270;
22 import static androidx.camera.core.impl.utils.TransformUtils.rectToSize;
23 
24 import android.graphics.Matrix;
25 import android.graphics.RectF;
26 
27 import androidx.camera.core.ImageAnalysis;
28 import androidx.camera.core.ImageCapture;
29 import androidx.camera.core.ImageInfo;
30 import androidx.camera.core.ImageProxy;
31 import androidx.camera.core.UseCase;
32 import androidx.camera.view.TransformExperimental;
33 
34 import org.jspecify.annotations.NonNull;
35 
36 /**
37  * Factory for extracting transform info from {@link ImageProxy}.
38  *
39  * <p> This class is for extracting a {@link OutputTransform} from an {@link ImageProxy} object. The
40  * {@link OutputTransform} represents the transform being applied to the original camera buffer,
41  * which can be used by {@link CoordinateTransform} to transform coordinates between
42  * {@link UseCase}s.
43  *
44  * @see OutputTransform
45  * @see CoordinateTransform
46  */
47 @TransformExperimental
48 public final class ImageProxyTransformFactory {
49 
50     private boolean mUsingCropRect;
51     private boolean mUsingRotationDegrees;
52 
ImageProxyTransformFactory()53     public ImageProxyTransformFactory() {
54     }
55 
56     /**
57      * Whether to use the crop rect of the {@link ImageProxy}.
58      *
59      * <p> By default, the value is false and the factory uses the {@link ImageProxy}'s
60      * entire buffer. Only set this value if the coordinates to be transformed respect the
61      * crop rect. For example, top-left corner of the crop rect is (0, 0).
62      */
setUsingCropRect(boolean usingCropRect)63     public void setUsingCropRect(boolean usingCropRect) {
64         mUsingCropRect = usingCropRect;
65     }
66 
67     /**
68      * Whether the factory respects the value of {@link ImageProxy#getCropRect()}.
69      *
70      * <p>By default, the value is false.
71      */
isUsingCropRect()72     public boolean isUsingCropRect() {
73         return mUsingCropRect;
74     }
75 
76     /**
77      * Whether to use the rotation degrees of the {@link ImageProxy}.
78      *
79      * <p> By default, the value is false and the factory uses a rotation degree of 0. Only
80      * set this value to true if the coordinates to be transformed is after the rotation degree is
81      * applied. For example, if the {@link ImageInfo#getRotationDegrees()} is 90 degrees, (0, 0) in
82      * the original buffer should be mapped to (height, 0) in the rotated image. Set this value
83      * to true if the input coordinates are based on the original image, and false if the
84      * coordinates are based on the rotated image.
85      */
setUsingRotationDegrees(boolean usingRotationDegrees)86     public void setUsingRotationDegrees(boolean usingRotationDegrees) {
87         mUsingRotationDegrees = usingRotationDegrees;
88     }
89 
90     /**
91      * Whether the factory respects the value of {@link ImageInfo#getRotationDegrees()}.
92      *
93      * <p>By default, the value is false.
94      */
isUsingRotationDegrees()95     public boolean isUsingRotationDegrees() {
96         return mUsingRotationDegrees;
97     }
98 
99     /**
100      * Extracts the transform from the given {@link ImageProxy}.
101      *
102      * <p> This method returns a {@link OutputTransform} that represents the
103      * transform applied to the buffer of a {@link ImageProxy} based on factory settings.  An
104      * {@link ImageProxy} can be the output of {@link ImageAnalysis} or in-memory
105      * {@link ImageCapture}.
106      */
getOutputTransform(@onNull ImageProxy imageProxy)107     public @NonNull OutputTransform getOutputTransform(@NonNull ImageProxy imageProxy) {
108         // Map the viewport to output.
109         int rotationDegrees = getRotationDegrees(imageProxy);
110         RectF source = getCropRect(imageProxy);
111         RectF target = getRotatedCropRect(source, rotationDegrees);
112         Matrix matrix = getRectToRect(source, target, rotationDegrees);
113 
114         // Map the normalized space to viewport.
115         matrix.preConcat(getNormalizedToBuffer(imageProxy.getCropRect()));
116 
117         return new OutputTransform(matrix, rectToSize(imageProxy.getCropRect()));
118     }
119 
120     /**
121      * Gets the crop rect based on factory settings.
122      */
getCropRect(@onNull ImageProxy imageProxy)123     private RectF getCropRect(@NonNull ImageProxy imageProxy) {
124         if (mUsingCropRect) {
125             return new RectF(imageProxy.getCropRect());
126         }
127         // The default crop rect is the full buffer.
128         return new RectF(0, 0, imageProxy.getWidth(), imageProxy.getHeight());
129     }
130 
131     /**
132      * Gets the rotation degrees based on factory settings.
133      */
getRotationDegrees(@onNull ImageProxy imageProxy)134     private int getRotationDegrees(@NonNull ImageProxy imageProxy) {
135         if (mUsingRotationDegrees) {
136             return imageProxy.getImageInfo().getRotationDegrees();
137         }
138         // The default is no rotation.
139         return 0;
140     }
141 
142     /**
143      * Rotates the rect and align it to (0, 0).
144      */
getRotatedCropRect(RectF rect, int rotationDegrees)145     static RectF getRotatedCropRect(RectF rect, int rotationDegrees) {
146         if (is90or270(rotationDegrees)) {
147             return new RectF(0, 0, rect.height(), rect.width());
148         }
149         return new RectF(0, 0, rect.width(), rect.height());
150     }
151 }
152