| /* | 
 |  * Copyright (C) 2020 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. | 
 |  */ | 
 |  | 
 | #define LOG_TAG "Camera3-RotCropMapper" | 
 | #define ATRACE_TAG ATRACE_TAG_CAMERA | 
 | //#define LOG_NDEBUG 0 | 
 |  | 
 | #include <algorithm> | 
 | #include <cmath> | 
 |  | 
 | #include "device3/RotateAndCropMapper.h" | 
 |  | 
 | namespace android { | 
 |  | 
 | namespace camera3 { | 
 |  | 
 | void RotateAndCropMapper::initRemappedKeys() { | 
 |     mRemappedKeys.insert( | 
 |             kMeteringRegionsToCorrect.begin(), | 
 |             kMeteringRegionsToCorrect.end()); | 
 |     mRemappedKeys.insert( | 
 |             kResultPointsToCorrectNoClamp.begin(), | 
 |             kResultPointsToCorrectNoClamp.end()); | 
 |  | 
 |     mRemappedKeys.insert(ANDROID_SCALER_ROTATE_AND_CROP); | 
 |     mRemappedKeys.insert(ANDROID_SCALER_CROP_REGION); | 
 | } | 
 |  | 
 | bool RotateAndCropMapper::isNeeded(const CameraMetadata* deviceInfo) { | 
 |     auto entry = deviceInfo->find(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES); | 
 |     for (size_t i = 0; i < entry.count; i++) { | 
 |         if (entry.data.u8[i] == ANDROID_SCALER_ROTATE_AND_CROP_AUTO) return true; | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | RotateAndCropMapper::RotateAndCropMapper(const CameraMetadata* deviceInfo) { | 
 |     initRemappedKeys(); | 
 |  | 
 |     auto entry = deviceInfo->find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); | 
 |     if (entry.count != 4) return; | 
 |  | 
 |     mArrayWidth = entry.data.i32[2]; | 
 |     mArrayHeight = entry.data.i32[3]; | 
 |     mArrayAspect = static_cast<float>(mArrayWidth) / mArrayHeight; | 
 |     mRotateAspect = 1.f/mArrayAspect; | 
 | } | 
 |  | 
 | /** | 
 |  * Adjust capture request when rotate and crop AUTO is enabled | 
 |  */ | 
 | status_t RotateAndCropMapper::updateCaptureRequest(CameraMetadata *request) { | 
 |     auto entry = request->find(ANDROID_SCALER_ROTATE_AND_CROP); | 
 |     if (entry.count == 0) return OK; | 
 |     uint8_t rotateMode = entry.data.u8[0]; | 
 |     if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK; | 
 |  | 
 |     int32_t cx = 0; | 
 |     int32_t cy = 0; | 
 |     int32_t cw = mArrayWidth; | 
 |     int32_t ch = mArrayHeight; | 
 |     entry = request->find(ANDROID_SCALER_CROP_REGION); | 
 |     if (entry.count == 4) { | 
 |         cx = entry.data.i32[0]; | 
 |         cy = entry.data.i32[1]; | 
 |         cw = entry.data.i32[2]; | 
 |         ch = entry.data.i32[3]; | 
 |     } | 
 |  | 
 |     // User inputs are relative to the rotated-and-cropped view, so convert back | 
 |     // to active array coordinates. To be more specific, the application is | 
 |     // calculating coordinates based on the crop rectangle and the active array, | 
 |     // even though the view the user sees is the cropped-and-rotated one. So we | 
 |     // need to adjust the coordinates so that a point that would be on the | 
 |     // top-left corner of the crop region is mapped to the top-left corner of | 
 |     // the rotated-and-cropped fov within the crop region, and the same for the | 
 |     // bottom-right corner. | 
 |     // | 
 |     // Since the zoom ratio control scales everything uniformly (so an app does | 
 |     // not need to adjust anything if it wants to put a metering region on the | 
 |     // top-left quadrant of the preview FOV, when changing zoomRatio), it does | 
 |     // not need to be factored into this calculation at all. | 
 |     // | 
 |     //   ->+x                       active array  aw | 
 |     //  |+--------------------------------------------------------------------+ | 
 |     //  v|                                                                    | | 
 |     // +y|         a         1       cw        2           b                  | | 
 |     //   |          +=========*HHHHHHHHHHHHHHH*===========+                   | | 
 |     //   |          I         H      rw       H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //ah |       ch I         H rh            H           I crop region       | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H rotate region H           I                   | | 
 |     //   |          +=========*HHHHHHHHHHHHHHH*===========+                   | | 
 |     //   |         d         4                 3           c                  | | 
 |     //   |                                                                    | | 
 |     //   +--------------------------------------------------------------------+ | 
 |     // | 
 |     // aw , ah = active array width,height | 
 |     // cw , ch = crop region width,height | 
 |     // rw , rh = rotated-and-cropped region width,height | 
 |     // aw / ah = array aspect = rh / rw = 1 / rotated aspect | 
 |     // Coordinate mappings: | 
 |     //    ROTATE_AND_CROP_90: point a -> point 2 | 
 |     //                        point c -> point 4 = +x -> +y, +y -> -x | 
 |     //    ROTATE_AND_CROP_180: point a -> point c | 
 |     //                         point c -> point a = +x -> -x, +y -> -y | 
 |     //    ROTATE_AND_CROP_270: point a -> point 4 | 
 |     //                         point c -> point 2 = +x -> -y, +y -> +x | 
 |  | 
 |     float cropAspect = static_cast<float>(cw) / ch; | 
 |     float transformMat[4] = {0, 0, | 
 |                              0, 0}; | 
 |     float xShift = 0; | 
 |     float yShift = 0; | 
 |  | 
 |     if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) { | 
 |         transformMat[0] = -1; | 
 |         transformMat[3] = -1; | 
 |         xShift = cw; | 
 |         yShift = ch; | 
 |     } else { | 
 |         float rw = cropAspect > mRotateAspect ? | 
 |                    ch * mRotateAspect : // pillarbox, not full width | 
 |                    cw;                  // letterbox or 1:1, full width | 
 |         float rh = cropAspect >= mRotateAspect ? | 
 |                    ch :                 // pillarbox or 1:1, full height | 
 |                    cw / mRotateAspect;  // letterbox, not full height | 
 |         switch (rotateMode) { | 
 |             case ANDROID_SCALER_ROTATE_AND_CROP_270: | 
 |                 transformMat[1] = -rw / ch; // +y -> -x | 
 |                 transformMat[2] =  rh / cw; // +x -> +y | 
 |                 xShift = (cw + rw) / 2; // left edge of crop to right edge of rotated | 
 |                 yShift = (ch - rh) / 2; // top edge of crop to top edge of rotated | 
 |                 break; | 
 |             case ANDROID_SCALER_ROTATE_AND_CROP_90: | 
 |                 transformMat[1] =  rw / ch; // +y -> +x | 
 |                 transformMat[2] = -rh / cw; // +x -> -y | 
 |                 xShift = (cw - rw) / 2; // left edge of crop to left edge of rotated | 
 |                 yShift = (ch + rh) / 2; // top edge of crop to bottom edge of rotated | 
 |                 break; | 
 |             default: | 
 |                 ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode); | 
 |                 return BAD_VALUE; | 
 |         } | 
 |     } | 
 |  | 
 |     for (auto regionTag : kMeteringRegionsToCorrect) { | 
 |         entry = request->find(regionTag); | 
 |         for (size_t i = 0; i < entry.count; i += 5) { | 
 |             int32_t weight = entry.data.i32[i + 4]; | 
 |             if (weight == 0) { | 
 |                 continue; | 
 |             } | 
 |             transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, cx, cy); | 
 |             swapRectToMinFirst(entry.data.i32 + i); | 
 |         } | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | /** | 
 |  * Adjust capture result when rotate and crop AUTO is enabled | 
 |  */ | 
 | status_t RotateAndCropMapper::updateCaptureResult(CameraMetadata *result) { | 
 |     auto entry = result->find(ANDROID_SCALER_ROTATE_AND_CROP); | 
 |     if (entry.count == 0) return OK; | 
 |     uint8_t rotateMode = entry.data.u8[0]; | 
 |     if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_NONE) return OK; | 
 |  | 
 |     int32_t cx = 0; | 
 |     int32_t cy = 0; | 
 |     int32_t cw = mArrayWidth; | 
 |     int32_t ch = mArrayHeight; | 
 |     entry = result->find(ANDROID_SCALER_CROP_REGION); | 
 |     if (entry.count == 4) { | 
 |         cx = entry.data.i32[0]; | 
 |         cy = entry.data.i32[1]; | 
 |         cw = entry.data.i32[2]; | 
 |         ch = entry.data.i32[3]; | 
 |     } | 
 |  | 
 |     // HAL inputs are relative to the full active array, so convert back to | 
 |     // rotated-and-cropped coordinates for apps. To be more specific, the | 
 |     // application is calculating coordinates based on the crop rectangle and | 
 |     // the active array, even though the view the user sees is the | 
 |     // cropped-and-rotated one. So we need to adjust the coordinates so that a | 
 |     // point that would be on the top-left corner of the rotate-and-cropped | 
 |     // region is mapped to the top-left corner of the crop region, and the same | 
 |     // for the bottom-right corner. | 
 |     // | 
 |     // Since the zoom ratio control scales everything uniformly (so an app does | 
 |     // not need to adjust anything if it wants to put a metering region on the | 
 |     // top-left quadrant of the preview FOV, when changing zoomRatio), it does | 
 |     // not need to be factored into this calculation at all. | 
 |     // | 
 |     // Also note that round-tripping between original request and final result | 
 |     // fields can't be perfect, since the intermediate values have to be | 
 |     // integers on a smaller range than the original crop region range. That | 
 |     // means that multiple input values map to a single output value in | 
 |     // adjusting a request, so when adjusting a result, the original answer may | 
 |     // not be obtainable.  Given that aspect ratios are rarely > 16/9, the | 
 |     // round-trip values should generally only be off by 1 at most. | 
 |     // | 
 |     //   ->+x                       active array  aw | 
 |     //  |+--------------------------------------------------------------------+ | 
 |     //  v|                                                                    | | 
 |     // +y|         a         1       cw        2           b                  | | 
 |     //   |          +=========*HHHHHHHHHHHHHHH*===========+                   | | 
 |     //   |          I         H      rw       H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //ah |       ch I         H rh            H           I crop region       | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H               H           I                   | | 
 |     //   |          I         H rotate region H           I                   | | 
 |     //   |          +=========*HHHHHHHHHHHHHHH*===========+                   | | 
 |     //   |         d         4                 3           c                  | | 
 |     //   |                                                                    | | 
 |     //   +--------------------------------------------------------------------+ | 
 |     // | 
 |     // aw , ah = active array width,height | 
 |     // cw , ch = crop region width,height | 
 |     // rw , rh = rotated-and-cropped region width,height | 
 |     // aw / ah = array aspect = rh / rw = 1 / rotated aspect | 
 |     // Coordinate mappings: | 
 |     //    ROTATE_AND_CROP_90: point 2 -> point a | 
 |     //                        point 4 -> point c = +x -> -y, +y -> +x | 
 |     //    ROTATE_AND_CROP_180: point c -> point a | 
 |     //                         point a -> point c = +x -> -x, +y -> -y | 
 |     //    ROTATE_AND_CROP_270: point 4 -> point a | 
 |     //                         point 2 -> point c = +x -> +y, +y -> -x | 
 |  | 
 |     float cropAspect = static_cast<float>(cw) / ch; | 
 |     float transformMat[4] = {0, 0, | 
 |                              0, 0}; | 
 |     float xShift = 0; | 
 |     float yShift = 0; | 
 |     float rx = 0; // top-left corner of rotated region | 
 |     float ry = 0; | 
 |     if (rotateMode == ANDROID_SCALER_ROTATE_AND_CROP_180) { | 
 |         transformMat[0] = -1; | 
 |         transformMat[3] = -1; | 
 |         xShift = cw; | 
 |         yShift = ch; | 
 |         rx = cx; | 
 |         ry = cy; | 
 |     } else { | 
 |         float rw = cropAspect > mRotateAspect ? | 
 |                    ch * mRotateAspect : // pillarbox, not full width | 
 |                    cw;                  // letterbox or 1:1, full width | 
 |         float rh = cropAspect >= mRotateAspect ? | 
 |                    ch :                 // pillarbox or 1:1, full height | 
 |                    cw / mRotateAspect;  // letterbox, not full height | 
 |         rx = cx + (cw - rw) / 2; | 
 |         ry = cy + (ch - rh) / 2; | 
 |         switch (rotateMode) { | 
 |             case ANDROID_SCALER_ROTATE_AND_CROP_270: | 
 |                 transformMat[1] =  ch / rw; // +y -> +x | 
 |                 transformMat[2] = -cw / rh; // +x -> -y | 
 |                 xShift = -(cw - rw) / 2; // left edge of rotated to left edge of cropped | 
 |                 yShift = ry - cy + ch;   // top edge of rotated to bottom edge of cropped | 
 |                 break; | 
 |             case ANDROID_SCALER_ROTATE_AND_CROP_90: | 
 |                 transformMat[1] = -ch / rw; // +y -> -x | 
 |                 transformMat[2] =  cw / rh; // +x -> +y | 
 |                 xShift = (cw + rw) / 2; // left edge of rotated to left edge of cropped | 
 |                 yShift = (ch - rh) / 2; // top edge of rotated to bottom edge of cropped | 
 |                 break; | 
 |             default: | 
 |                 ALOGE("%s: Unexpected rotate mode: %d", __FUNCTION__, rotateMode); | 
 |                 return BAD_VALUE; | 
 |         } | 
 |     } | 
 |  | 
 |     for (auto regionTag : kMeteringRegionsToCorrect) { | 
 |         entry = result->find(regionTag); | 
 |         for (size_t i = 0; i < entry.count; i += 5) { | 
 |             int32_t weight = entry.data.i32[i + 4]; | 
 |             if (weight == 0) { | 
 |                 continue; | 
 |             } | 
 |             transformPoints(entry.data.i32 + i, 2, transformMat, xShift, yShift, rx, ry); | 
 |             swapRectToMinFirst(entry.data.i32 + i); | 
 |         } | 
 |     } | 
 |  | 
 |     for (auto pointsTag: kResultPointsToCorrectNoClamp) { | 
 |         entry = result->find(pointsTag); | 
 |         transformPoints(entry.data.i32, entry.count / 2, transformMat, xShift, yShift, rx, ry); | 
 |         if (pointsTag == ANDROID_STATISTICS_FACE_RECTANGLES) { | 
 |             for (size_t i = 0; i < entry.count; i += 4) { | 
 |                 swapRectToMinFirst(entry.data.i32 + i); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | void RotateAndCropMapper::transformPoints(int32_t* pts, size_t count, float transformMat[4], | 
 |         float xShift, float yShift, float ox, float oy) { | 
 |     for (size_t i = 0; i < count * 2; i += 2) { | 
 |         float x0 = pts[i] - ox; | 
 |         float y0 = pts[i + 1] - oy; | 
 |         int32_t nx = std::round(transformMat[0] * x0 + transformMat[1] * y0 + xShift + ox); | 
 |         int32_t ny = std::round(transformMat[2] * x0 + transformMat[3] * y0 + yShift + oy); | 
 |  | 
 |         pts[i] = std::min(std::max(nx, 0), mArrayWidth); | 
 |         pts[i + 1] = std::min(std::max(ny, 0), mArrayHeight); | 
 |     } | 
 | } | 
 |  | 
 | void RotateAndCropMapper::swapRectToMinFirst(int32_t* rect) { | 
 |     if (rect[0] > rect[2]) { | 
 |         auto tmp = rect[0]; | 
 |         rect[0] = rect[2]; | 
 |         rect[2] = tmp; | 
 |     } | 
 |     if (rect[1] > rect[3]) { | 
 |         auto tmp = rect[1]; | 
 |         rect[1] = rect[3]; | 
 |         rect[3] = tmp; | 
 |     } | 
 | } | 
 |  | 
 | } // namespace camera3 | 
 |  | 
 | } // namespace android |