1 /*
2  * Copyright 2024 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.core;
18 
19 import androidx.annotation.FloatRange;
20 import androidx.camera.core.ConcurrentCamera.SingleCameraConfig;
21 import androidx.core.util.Pair;
22 
23 import org.jspecify.annotations.NonNull;
24 
25 /**
26  * Composition settings for dual concurrent camera. It includes alpha value for blending,
27  * offset in x, y coordinates, scale of width and height. The offset, and scale of width and height
28  * are specified in normalized device coordinates(NDCs). The offset is applied after scale.
29  * The origin of normalized device coordinates is at the center of the viewing volume. The positive
30  * X-axis extends to the right, the positive Y-axis extends upwards.The x, y values range from -1
31  * to 1. E.g. scale with {@code (0.5f, 0.5f)} and offset with {@code (0.5f, 0.5f)} is the
32  * bottom-right quadrant of the output device.
33  *
34  * <p>Composited dual camera frames preview and recording can be supported using
35  * {@link CompositionSettings} and {@link SingleCameraConfig}. The z-order of composition is
36  * determined by the order of camera configs to bind. Currently the background color will be black
37  * by default. The resolution of camera frames for preview and recording will be determined by
38  * resolution selection strategy configured for each use case and the scale of width and height set
39  * in {@link CompositionSettings}, so it is recommended to use 16:9 aspect ratio strategy for
40  * preview if 16:9 quality selector is configured for video capture. The mirroring and rotation of
41  * the camera frame will be applied after composition because both cameras are using the same use
42  * cases.
43  *
44  * <p>The following code snippet demonstrates how to display in Picture-in-Picture mode:
45  * <pre>
46  *                        16
47  *         --------------------------------
48  *         |               c0             |
49  *         |                              |
50  *         |                              |
51  *         |                              |
52  *         |  ---------                   |  9
53  *         |  |       |                   |
54  *         |  |   c1  |                   |
55  *         |  |       |                   |
56  *         |  ---------                   |
57  *         --------------------------------
58  *         c0: primary camera
59  *         c1: secondary camera
60  *     {@code
61  *         ResolutionSelector resolutionSelector = new ResolutionSelector.Builder()
62  *                 .setAspectRatioStrategy(
63  *                         AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY)
64  *                 .build();
65  *         Preview preview = new Preview.Builder()
66  *                 .setResolutionSelector(resolutionSelector)
67  *                 .build();
68  *         preview.setSurfaceProvider(mSinglePreviewView.getSurfaceProvider());
69  *         UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
70  *                 .addUseCase(preview)
71  *                 .addUseCase(mVideoCapture)
72  *                 .build();
73  *         SingleCameraConfig primary = new SingleCameraConfig(
74  *                 cameraSelectorPrimary,
75  *                 useCaseGroup,
76  *                 new CompositionSettings.Builder()
77  *                         .setAlpha(1.0f)
78  *                         .setOffset(0.0f, 0.0f)
79  *                         .setScale(1.0f, 1.0f)
80  *                         .build(),
81  *                 lifecycleOwner);
82  *         SingleCameraConfig secondary = new SingleCameraConfig(
83  *                 cameraSelectorSecondary,
84  *                 useCaseGroup,
85  *                 new CompositionSettings.Builder()
86  *                         .setAlpha(1.0f)
87  *                         .setOffset(-0.3f, -0.4f)
88  *                         .setScale(0.3f, 0.3f)
89  *                         .build(),
90  *                 lifecycleOwner);
91  *         cameraProvider.bindToLifecycle(ImmutableList.of(primary, secondary));
92  * }</pre>
93  */
94 public class CompositionSettings {
95 
96     /**
97      * Default composition settings, which will display in full screen with no offset and scale.
98      */
99     public static final CompositionSettings DEFAULT = new Builder()
100             .setAlpha(1.0f)
101             .setOffset(0.0f, 0.0f)
102             .setScale(1.0f, 1.0f)
103             .build();
104 
105     private final float mAlpha;
106     private final Pair<Float, Float> mOffset;
107     private final Pair<Float, Float> mScale;
108 
CompositionSettings( float alpha, Pair<Float, Float> offset, Pair<Float, Float> scale)109     private CompositionSettings(
110             float alpha,
111             Pair<Float, Float> offset,
112             Pair<Float, Float> scale) {
113         mAlpha = alpha;
114         mOffset = offset;
115         mScale = scale;
116     }
117 
118     /**
119      * Gets the alpha.
120      *
121      * @return alpha value.
122      */
getAlpha()123     public float getAlpha() {
124         return mAlpha;
125     }
126 
127     /**
128      * Gets the offset.
129      *
130      * @return offset value.
131      */
getOffset()132     public @NonNull Pair<Float, Float> getOffset() {
133         return mOffset;
134     }
135 
136     /**
137      * Gets the scale. Negative value means mirroring in X or Y direction.
138      *
139      * @return scale value.
140      */
getScale()141     public @NonNull Pair<Float, Float> getScale() {
142         return mScale;
143     }
144 
145     /** A builder for {@link CompositionSettings} instances. */
146     public static final class Builder {
147         private float mAlpha;
148         private Pair<Float, Float> mOffset;
149         private Pair<Float, Float> mScale;
150 
151         /**
152          * Creates a new {@link Builder}.
153          *
154          * <p>The default alpha is 1.0f, the default offset is (0.0f, 0.0f), the default scale is
155          * (1.0f, 1.0f).
156          */
Builder()157         public Builder() {
158             mAlpha = 1.0f;
159             mOffset = Pair.create(0.0f, 0.0f);
160             mScale = Pair.create(1.0f, 1.0f);
161         }
162 
163         /**
164          * Sets the alpha. 0 means fully transparent, 1 means fully opaque.
165          *
166          * @param alpha alpha value.
167          * @return Builder instance.
168          */
setAlpha(@loatRangefrom = 0, to = 1) float alpha)169         public @NonNull Builder setAlpha(@FloatRange(from = 0, to = 1) float alpha) {
170             mAlpha = alpha;
171             return this;
172         }
173 
174         /**
175          * Sets the offset.
176          *
177          * @param offsetX offset X value.
178          * @param offsetY offset Y value.
179          * @return Builder instance.
180          */
setOffset( @loatRangefrom = -1, to = 1) float offsetX, @FloatRange(from = -1, to = 1) float offsetY)181         public @NonNull Builder setOffset(
182                 @FloatRange(from = -1, to = 1) float offsetX,
183                 @FloatRange(from = -1, to = 1) float offsetY) {
184             mOffset = Pair.create(offsetX, offsetY);
185             return this;
186         }
187 
188         /**
189          * Sets the scale.
190          *
191          * @param scaleX scale X value.
192          * @param scaleY scale Y value.
193          * @return Builder instance.
194          */
setScale(float scaleX, float scaleY)195         public @NonNull Builder setScale(float scaleX, float scaleY) {
196             mScale = Pair.create(scaleX, scaleY);
197             return this;
198         }
199 
200         /**
201          * Builds the {@link CompositionSettings}.
202          *
203          * @return {@link CompositionSettings}.
204          */
build()205         public @NonNull CompositionSettings build() {
206             return new CompositionSettings(
207                     mAlpha,
208                     mOffset,
209                     mScale);
210         }
211     }
212 }
213