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