blob: 9f86526a8c03c12ee278f779f8233dec0eff1312 [file] [log] [blame]
/*
* 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_NDEBUG 0
#define LOG_TAG "RotateAndCropMapperTest"
#include <functional>
#include <random>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "../device3/RotateAndCropMapper.h"
namespace rotateAndCropMapperTest {
using namespace android;
using namespace android::camera3;
using ::testing::ElementsAreArray;
using ::testing::Each;
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::Le;
#define EXPECT_EQUAL_WITHIN_N(vec, array, N, msg) \
{ \
for (size_t i = 0; i < vec.size(); i++) { \
EXPECT_THAT(vec[i] - array[i], AllOf(Ge(-N), Le(N))) << msg " failed at index:" << i; \
} \
}
int32_t testActiveArray[] = {100, 100, 4000, 3000};
std::vector<uint8_t> basicModes = {
ANDROID_SCALER_ROTATE_AND_CROP_NONE,
ANDROID_SCALER_ROTATE_AND_CROP_90,
ANDROID_SCALER_ROTATE_AND_CROP_AUTO
};
CameraMetadata setupDeviceInfo(int32_t activeArray[4], std::vector<uint8_t> availableCropModes ) {
CameraMetadata deviceInfo;
deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
activeArray, 4);
deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
availableCropModes.data(), availableCropModes.size());
return deviceInfo;
}
TEST(RotationMapperTest, Initialization) {
CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
{ANDROID_SCALER_ROTATE_AND_CROP_NONE});
ASSERT_FALSE(RotateAndCropMapper::isNeeded(&deviceInfo));
deviceInfo.update(ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES,
basicModes.data(), 3);
ASSERT_TRUE(RotateAndCropMapper::isNeeded(&deviceInfo));
}
TEST(RotationMapperTest, IdentityTransform) {
status_t res;
CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
basicModes);
RotateAndCropMapper mapper(&deviceInfo);
CameraMetadata request;
uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_NONE;
auto full_crop = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3]};
auto full_region = std::vector<int32_t>{0,0, testActiveArray[2], testActiveArray[3], 1};
request.update(ANDROID_SCALER_ROTATE_AND_CROP,
&mode, 1);
request.update(ANDROID_SCALER_CROP_REGION,
full_crop.data(), full_crop.size());
request.update(ANDROID_CONTROL_AE_REGIONS,
full_region.data(), full_region.size());
// Map to HAL
res = mapper.updateCaptureRequest(&request);
ASSERT_TRUE(res == OK);
auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
e = request.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
// Add fields in HAL
CameraMetadata result(request);
auto face = std::vector<int32_t> {300,300,500,500};
result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
face.data(), face.size());
// Map to app
res = mapper.updateCaptureResult(&result);
ASSERT_TRUE(res == OK);
e = result.find(ANDROID_CONTROL_AE_REGIONS);
EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count));
e = result.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count));
e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
EXPECT_THAT(face, ElementsAreArray(e.data.i32, e.count));
}
TEST(RotationMapperTest, Transform90) {
status_t res;
CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
basicModes);
RotateAndCropMapper mapper(&deviceInfo);
CameraMetadata request;
uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_90;
auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
request.update(ANDROID_SCALER_ROTATE_AND_CROP,
&mode, 1);
request.update(ANDROID_SCALER_CROP_REGION,
full_crop.data(), full_crop.size());
request.update(ANDROID_CONTROL_AE_REGIONS,
full_region.data(), full_region.size());
// Map to HAL
res = mapper.updateCaptureRequest(&request);
ASSERT_TRUE(res == OK);
auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
int32_t rw = full_crop[3] / aspectRatio;
int32_t rh = full_crop[3];
auto rotated_region = std::vector<int32_t> {
full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
1
};
EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
<< "Rotated AE region isn't right";
e = request.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
<< "Rotated crop region isn't right";
// Add fields in HAL
CameraMetadata result(request);
auto face = std::vector<int32_t> {
rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
face.data(), face.size());
auto landmarks = std::vector<int32_t> {
rotated_region[0], rotated_region[1],
rotated_region[2], rotated_region[3],
rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
};
result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
landmarks.data(), landmarks.size());
// Map to app
res = mapper.updateCaptureResult(&result);
ASSERT_TRUE(res == OK);
// Round-trip results can't be exact since we've gone from a large int range -> small int range
// and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
e = result.find(ANDROID_CONTROL_AE_REGIONS);
EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
e = result.find(ANDROID_SCALER_CROP_REGION);
EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
auto full_face = std::vector<int32_t> {
full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
auto full_landmarks = std::vector<int32_t> {
full_crop[0] + full_crop[2], full_crop[1],
full_crop[0], full_crop[1] + full_crop[3],
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4,
full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
}
TEST(RotationMapperTest, Transform270) {
status_t res;
CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
basicModes);
RotateAndCropMapper mapper(&deviceInfo);
CameraMetadata request;
uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_270;
auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
request.update(ANDROID_SCALER_ROTATE_AND_CROP,
&mode, 1);
request.update(ANDROID_SCALER_CROP_REGION,
full_crop.data(), full_crop.size());
request.update(ANDROID_CONTROL_AE_REGIONS,
full_region.data(), full_region.size());
// Map to HAL
res = mapper.updateCaptureRequest(&request);
ASSERT_TRUE(res == OK);
auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
float aspectRatio = static_cast<float>(full_crop[2]) / full_crop[3];
int32_t rw = full_crop[3] / aspectRatio;
int32_t rh = full_crop[3];
auto rotated_region = std::vector<int32_t> {
full_crop[0] + (full_crop[2] - rw) / 2, full_crop[1],
full_crop[0] + (full_crop[2] + rw) / 2, full_crop[1] + full_crop[3],
1
};
EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
<< "Rotated AE region isn't right";
e = request.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
<< "Rotated crop region isn't right";
// Add fields in HAL
CameraMetadata result(request);
auto face = std::vector<int32_t> {
rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
rotated_region[2] - rw / 4, rotated_region[3] - rh / 4};
result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
face.data(), face.size());
auto landmarks = std::vector<int32_t> {
rotated_region[0], rotated_region[1],
rotated_region[2], rotated_region[3],
rotated_region[0] + rw / 4, rotated_region[1] + rh / 4,
rotated_region[0] + rw / 2, rotated_region[1] + rh / 2,
rotated_region[2] - rw / 4, rotated_region[3] - rh / 4
};
result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
landmarks.data(), landmarks.size());
// Map to app
res = mapper.updateCaptureResult(&result);
ASSERT_TRUE(res == OK);
// Round-trip results can't be exact since we've gone from a large int range -> small int range
// and back, leading to quantization. For 4/3 aspect ratio, no more than +-1 error expected
e = result.find(ANDROID_CONTROL_AE_REGIONS);
EXPECT_EQUAL_WITHIN_N(full_region, e.data.i32, 1, "Round-tripped AE region isn't right");
e = result.find(ANDROID_SCALER_CROP_REGION);
EXPECT_EQUAL_WITHIN_N(full_crop, e.data.i32, 1, "Round-tripped crop region isn't right");
auto full_face = std::vector<int32_t> {
full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
auto full_landmarks = std::vector<int32_t> {
full_crop[0], full_crop[1] + full_crop[3],
full_crop[0] + full_crop[2], full_crop[1],
full_crop[0] + full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
}
TEST(RotationMapperTest, Transform180) {
status_t res;
CameraMetadata deviceInfo = setupDeviceInfo(testActiveArray,
basicModes);
RotateAndCropMapper mapper(&deviceInfo);
CameraMetadata request;
uint8_t mode = ANDROID_SCALER_ROTATE_AND_CROP_180;
auto full_crop = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3]};
auto full_region = std::vector<int32_t> {0,0, testActiveArray[2], testActiveArray[3], 1};
request.update(ANDROID_SCALER_ROTATE_AND_CROP,
&mode, 1);
request.update(ANDROID_SCALER_CROP_REGION,
full_crop.data(), full_crop.size());
request.update(ANDROID_CONTROL_AE_REGIONS,
full_region.data(), full_region.size());
// Map to HAL
res = mapper.updateCaptureRequest(&request);
ASSERT_TRUE(res == OK);
auto e = request.find(ANDROID_CONTROL_AE_REGIONS);
auto rotated_region = full_region;
EXPECT_THAT(rotated_region, ElementsAreArray(e.data.i32, e.count))
<< "Rotated AE region isn't right";
e = request.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
<< "Rotated crop region isn't right";
// Add fields in HAL
CameraMetadata result(request);
float rw = full_region[2] - full_region[0];
float rh = full_region[3] - full_region[1];
auto face = std::vector<int32_t> {
rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
};
result.update(ANDROID_STATISTICS_FACE_RECTANGLES,
face.data(), face.size());
auto landmarks = std::vector<int32_t> {
rotated_region[0], rotated_region[1],
rotated_region[2], rotated_region[3],
rotated_region[0] + (int)(rw / 4), rotated_region[1] + (int)(rh / 4),
rotated_region[0] + (int)(rw / 2), rotated_region[1] + (int)(rh / 2),
rotated_region[2] - (int)(rw / 4), rotated_region[3] - (int)(rh / 4)
};
result.update(ANDROID_STATISTICS_FACE_LANDMARKS,
landmarks.data(), landmarks.size());
// Map to app
res = mapper.updateCaptureResult(&result);
ASSERT_TRUE(res == OK);
e = result.find(ANDROID_CONTROL_AE_REGIONS);
EXPECT_THAT(full_region, ElementsAreArray(e.data.i32, e.count))
<< "Round-tripped AE region isn't right";
e = result.find(ANDROID_SCALER_CROP_REGION);
EXPECT_THAT(full_crop, ElementsAreArray(e.data.i32, e.count))
<< "Round-tripped crop region isn't right";
auto full_face = std::vector<int32_t> {
full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4,
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_RECTANGLES);
EXPECT_EQUAL_WITHIN_N(full_face, e.data.i32, 1, "App-side face rectangle isn't right");
auto full_landmarks = std::vector<int32_t> {
full_crop[0] + full_crop[2], full_crop[1] + full_crop[3],
full_crop[0], full_crop[1],
full_crop[0] + 3*full_crop[2]/4, full_crop[1] + 3*full_crop[3]/4,
full_crop[0] + full_crop[2]/2, full_crop[1] + full_crop[3]/2,
full_crop[0] + full_crop[2]/4, full_crop[1] + full_crop[3]/4
};
e = result.find(ANDROID_STATISTICS_FACE_LANDMARKS);
EXPECT_EQUAL_WITHIN_N(full_landmarks, e.data.i32, 1, "App-side face landmarks aren't right");
}
} // namespace rotateAndCropMapperTest