You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
346 lines
15 KiB
346 lines
15 KiB
/*
|
|
* 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_90:
|
|
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_270:
|
|
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_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 = ry - cy + ch; // top edge of rotated to bottom edge of cropped
|
|
break;
|
|
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 = (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
|