1 /*
2 * Copyright (C) 2020 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 #define LOG_TAG "Camera3-RotCropMapper"
18 #define ATRACE_TAG ATRACE_TAG_CAMERA
19 //#define LOG_NDEBUG 0
20
21 #include <algorithm>
22 #include <cmath>
23
24 #include "device3/RotateAndCropMapper.h"
25
26 namespace android {
27
28 namespace camera3 {
29
isNeeded(const CameraMetadata * deviceInfo)30 bool RotateAndCropMapper::isNeeded(const CameraMetadata* deviceInfo) {
31 auto entry = deviceInfo->find(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES);
32 for (size_t i = 0; i < entry.count; i++) {
33 if (entry.data.u8[i] == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return true;
34 }
35 return false;
36 }
37
RotateAndCropMapper(const CameraMetadata * deviceInfo)38 RotateAndCropMapper::RotateAndCropMapper(const CameraMetadata* deviceInfo) {
39 auto entry = deviceInfo->find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
40 if (entry.count != 4) return;
41
42 mArrayWidth = entry.data.i32[2];
43 mArrayHeight = entry.data.i32[3];
44 mArrayAspect = static_cast<float>(mArrayWidth) / mArrayHeight;
45 mRotateAspect = 1.f/mArrayAspect;
46 }
47
48 /**
49 * Adjust capture request when rotate and crop AUTO is enabled
50 */
updateCaptureRequest(CameraMetadata * request)51 status_t RotateAndCropMapper::updateCaptureRequest(CameraMetadata *request) {
52 auto entry = request->find(ANDROID_SCALER_ROTATE_AND_CROP);
53 if (entry.count == 0) return OK;
54 uint8_t rotateMode = entry.data.u8[0];
55 if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK;
56
57 int32_t cx = 0;
58 int32_t cy = 0;
59 int32_t cw = mArrayWidth;
60 int32_t ch = mArrayHeight;
61 entry = request->find(ANDROID_SCALER_CROP_REGION);
62 if (entry.count == 4) {
63 cx = entry.data.i32[0];
64 cy = entry.data.i32[1];
65 cw = entry.data.i32[2];
66 ch = entry.data.i32[3];
67 }
68
69 // User inputs are relative to the rotated-and-cropped view, so convert back
70 // to active array coordinates. To be more specific, the application is
71 // calculating coordinates based on the crop rectangle and the active array,
72 // even though the view the user sees is the cropped-and-rotated one. So we
73 // need to adjust the coordinates so that a point that would be on the
74 // top-left corner of the crop region is mapped to the top-left corner of
75 // the rotated-and-cropped fov within the crop region, and the same for the
76 // bottom-right corner.
77 //
78 // Since the zoom ratio control scales everything uniformly (so an app does
79 // not need to adjust anything if it wants to put a metering region on the
80 // top-left quadrant of the preview FOV, when changing zoomRatio), it does
81 // not need to be factored into this calculation at all.
82 //
83 // ->+x active array aw
84 // |+--------------------------------------------------------------------+
85 // v| |
86 // +y| a 1 cw 2 b |
87 // | +=========*HHHHHHHHHHHHHHH*===========+ |
88 // | I H rw H I |
89 // | I H H I |
90 // | I H H I |
91 //ah | ch I H rh H I crop region |
92 // | I H H I |
93 // | I H H I |
94 // | I H rotate region H I |
95 // | +=========*HHHHHHHHHHHHHHH*===========+ |
96 // | d 4 3 c |
97 // | |
98 // +--------------------------------------------------------------------+
99 //
100 // aw , ah = active array width,height
101 // cw , ch = crop region width,height
102 // rw , rh = rotated-and-cropped region width,height
103 // aw / ah = array aspect = rh / rw = 1 / rotated aspect
104 // Coordinate mappings:
105 // ROTATE_AND_CROP_90: point a -> point 2
106 // point c -> point 4 = +x -> +y, +y -> -x
107 // ROTATE_AND_CROP_180: point a -> point c
108 // point c -> point a = +x -> -x, +y -> -y
109 // ROTATE_AND_CROP_270: point a -> point 4
110 // point c -> point 2 = +x -> -y, +y -> +x
111
112 float cropAspect = static_cast<float>(cw) / ch;
113 float transformMat[4] = {0, 0,
114 0, 0};
115 float xShift = 0;
116 float yShift = 0;
117
118 if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) {
119 transformMat[0] = -1;
120 transformMat[3] = -1;
121 xShift = cw;
122 yShift = ch;
123 } else {
124 float rw = cropAspect > mRotateAspect ?
125 ch * mRotateAspect : // pillarbox, not full width
126 cw; // letterbox or 1:1, full width
127 float rh = cropAspect >= mRotateAspect ?
128 ch : // pillarbox or 1:1, full height
129 cw / mRotateAspect; // letterbox, not full height
130 switch (rotateMode) {
131 case ANDROID_SCALER_ROTATE_AND_CROP_90:
132 transformMat[1] = -rw / ch; // +y -> -x
133 transformMat[2] = rh / cw; // +x -> +y
134 xShift = (cw + rw) / 2; // left edge of crop to right edge of rotated
135 yShift = (ch - rh) / 2; // top edge of crop to top edge of rotated
136 break;
137 case ANDROID_SCALER_ROTATE_AND_CROP_270:
138 transformMat[1] = rw / ch; // +y -> +x
139 transformMat[2] = -rh / cw; // +x -> -y
140 xShift = (cw - rw) / 2; // left edge of crop to left edge of rotated
141 yShift = (ch + rh) / 2; // top edge of crop to bottom edge of rotated
142 break;
143 default:
144 ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode);
145 return BAD_VALUE;
146 }
147 }
148
149 for (auto regionTag : kMeteringRegionsToCorrect) {
150 entry = request->find(regionTag);
151 for (size_t i = 0; i < entry.count; i += 5) {
152 int32_t weight = entry.data.i32[i + 4];
153 if (weight == 0) {
154 continue;
155 }
156 transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, cx, cy);
157 swapRectToMinFirst(entry.data.i32 + i);
158 }
159 }
160
161 return OK;
162 }
163
164 /**
165 * Adjust capture result when rotate and crop AUTO is enabled
166 */
updateCaptureResult(CameraMetadata * result)167 status_t RotateAndCropMapper::updateCaptureResult(CameraMetadata *result) {
168 auto entry = result->find(ANDROID_SCALER_ROTATE_AND_CROP);
169 if (entry.count == 0) return OK;
170 uint8_t rotateMode = entry.data.u8[0];
171 if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK;
172
173 int32_t cx = 0;
174 int32_t cy = 0;
175 int32_t cw = mArrayWidth;
176 int32_t ch = mArrayHeight;
177 entry = result->find(ANDROID_SCALER_CROP_REGION);
178 if (entry.count == 4) {
179 cx = entry.data.i32[0];
180 cy = entry.data.i32[1];
181 cw = entry.data.i32[2];
182 ch = entry.data.i32[3];
183 }
184
185 // HAL inputs are relative to the full active array, so convert back to
186 // rotated-and-cropped coordinates for apps. To be more specific, the
187 // application is calculating coordinates based on the crop rectangle and
188 // the active array, even though the view the user sees is the
189 // cropped-and-rotated one. So we need to adjust the coordinates so that a
190 // point that would be on the top-left corner of the rotate-and-cropped
191 // region is mapped to the top-left corner of the crop region, and the same
192 // for the bottom-right corner.
193 //
194 // Since the zoom ratio control scales everything uniformly (so an app does
195 // not need to adjust anything if it wants to put a metering region on the
196 // top-left quadrant of the preview FOV, when changing zoomRatio), it does
197 // not need to be factored into this calculation at all.
198 //
199 // Also note that round-tripping between original request and final result
200 // fields can't be perfect, since the intermediate values have to be
201 // integers on a smaller range than the original crop region range. That
202 // means that multiple input values map to a single output value in
203 // adjusting a request, so when adjusting a result, the original answer may
204 // not be obtainable. Given that aspect ratios are rarely > 16/9, the
205 // round-trip values should generally only be off by 1 at most.
206 //
207 // ->+x active array aw
208 // |+--------------------------------------------------------------------+
209 // v| |
210 // +y| a 1 cw 2 b |
211 // | +=========*HHHHHHHHHHHHHHH*===========+ |
212 // | I H rw H I |
213 // | I H H I |
214 // | I H H I |
215 //ah | ch I H rh H I crop region |
216 // | I H H I |
217 // | I H H I |
218 // | I H rotate region H I |
219 // | +=========*HHHHHHHHHHHHHHH*===========+ |
220 // | d 4 3 c |
221 // | |
222 // +--------------------------------------------------------------------+
223 //
224 // aw , ah = active array width,height
225 // cw , ch = crop region width,height
226 // rw , rh = rotated-and-cropped region width,height
227 // aw / ah = array aspect = rh / rw = 1 / rotated aspect
228 // Coordinate mappings:
229 // ROTATE_AND_CROP_90: point 2 -> point a
230 // point 4 -> point c = +x -> -y, +y -> +x
231 // ROTATE_AND_CROP_180: point c -> point a
232 // point a -> point c = +x -> -x, +y -> -y
233 // ROTATE_AND_CROP_270: point 4 -> point a
234 // point 2 -> point c = +x -> +y, +y -> -x
235
236 float cropAspect = static_cast<float>(cw) / ch;
237 float transformMat[4] = {0, 0,
238 0, 0};
239 float xShift = 0;
240 float yShift = 0;
241 float rx = 0; // top-left corner of rotated region
242 float ry = 0;
243 if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) {
244 transformMat[0] = -1;
245 transformMat[3] = -1;
246 xShift = cw;
247 yShift = ch;
248 rx = cx;
249 ry = cy;
250 } else {
251 float rw = cropAspect > mRotateAspect ?
252 ch * mRotateAspect : // pillarbox, not full width
253 cw; // letterbox or 1:1, full width
254 float rh = cropAspect >= mRotateAspect ?
255 ch : // pillarbox or 1:1, full height
256 cw / mRotateAspect; // letterbox, not full height
257 rx = cx + (cw - rw) / 2;
258 ry = cy + (ch - rh) / 2;
259 switch (rotateMode) {
260 case ANDROID_SCALER_ROTATE_AND_CROP_90:
261 transformMat[1] = ch / rw; // +y -> +x
262 transformMat[2] = -cw / rh; // +x -> -y
263 xShift = -(cw - rw) / 2; // left edge of rotated to left edge of cropped
264 yShift = ry - cy + ch; // top edge of rotated to bottom edge of cropped
265 break;
266 case ANDROID_SCALER_ROTATE_AND_CROP_270:
267 transformMat[1] = -ch / rw; // +y -> -x
268 transformMat[2] = cw / rh; // +x -> +y
269 xShift = (cw + rw) / 2; // left edge of rotated to left edge of cropped
270 yShift = (ch - rh) / 2; // top edge of rotated to bottom edge of cropped
271 break;
272 default:
273 ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode);
274 return BAD_VALUE;
275 }
276 }
277
278 for (auto regionTag : kMeteringRegionsToCorrect) {
279 entry = result->find(regionTag);
280 for (size_t i = 0; i < entry.count; i += 5) {
281 int32_t weight = entry.data.i32[i + 4];
282 if (weight == 0) {
283 continue;
284 }
285 transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, rx, ry);
286 swapRectToMinFirst(entry.data.i32 + i);
287 }
288 }
289
290 for (auto pointsTag: kResultPointsToCorrectNoClamp) {
291 entry = result->find(pointsTag);
292 transformPoints(entry.data.i32, entry.count / 2, transformMat, xShift, yShift, rx, ry);
293 if (pointsTag == ANDROID_STATISTICS_FACE_RECTANGLES) {
294 for (size_t i = 0; i < entry.count; i += 4) {
295 swapRectToMinFirst(entry.data.i32 + i);
296 }
297 }
298 }
299
300 return OK;
301 }
302
transformPoints(int32_t * pts,size_t count,float transformMat[4],float xShift,float yShift,float ox,float oy)303 void RotateAndCropMapper::transformPoints(int32_t* pts, size_t count, float transformMat[4],
304 float xShift, float yShift, float ox, float oy) {
305 for (size_t i = 0; i < count * 2; i += 2) {
306 float x0 = pts[i] - ox;
307 float y0 = pts[i + 1] - oy;
308 int32_t nx = std::round(transformMat[0] * x0 + transformMat[1] * y0 + xShift + ox);
309 int32_t ny = std::round(transformMat[2] * x0 + transformMat[3] * y0 + yShift + oy);
310
311 pts[i] = std::min(std::max(nx, 0), mArrayWidth);
312 pts[i + 1] = std::min(std::max(ny, 0), mArrayHeight);
313 }
314 }
315
swapRectToMinFirst(int32_t * rect)316 void RotateAndCropMapper::swapRectToMinFirst(int32_t* rect) {
317 if (rect[0] > rect[2]) {
318 auto tmp = rect[0];
319 rect[0] = rect[2];
320 rect[2] = tmp;
321 }
322 if (rect[1] > rect[3]) {
323 auto tmp = rect[1];
324 rect[1] = rect[3];
325 rect[3] = tmp;
326 }
327 }
328
329 } // namespace camera3
330
331 } // namespace android
332