| /* Copyright 2017 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include <tuple> |
| #include <vector> |
| |
| #include <linux/videodev2.h> |
| |
| #include <libyuv.h> |
| |
| #include "cros-camera/common.h" |
| #include "hal/usb/test_pattern.h" |
| |
| namespace cros { |
| |
| TestPattern::TestPattern(Size sensor_pixel_array_size, Size resolution) |
| : sensor_pixel_array_size_(sensor_pixel_array_size), |
| resolution_(resolution), |
| pattern_mode_(ANDROID_SENSOR_TEST_PATTERN_MODE_OFF) {} |
| |
| TestPattern::~TestPattern() {} |
| |
| bool TestPattern::SetTestPatternMode(int32_t pattern_mode) { |
| if (pattern_mode != pattern_mode_) { |
| pattern_image_rgb_.reset(); |
| pattern_image_yuv_.reset(); |
| VLOGF(1) << "Set test pattern mode: " << pattern_mode; |
| } |
| |
| switch (pattern_mode) { |
| case ANDROID_SENSOR_TEST_PATTERN_MODE_OFF: |
| case ANDROID_SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY: |
| pattern_mode_ = pattern_mode; |
| break; |
| default: |
| LOGF(ERROR) << "Unsupported pattern mode: " << pattern_mode; |
| return false; |
| } |
| return true; |
| } |
| |
| bool TestPattern::IsTestPatternEnabled() const { |
| return pattern_mode_ != ANDROID_SENSOR_TEST_PATTERN_MODE_OFF; |
| } |
| |
| FrameBuffer* TestPattern::GetTestPattern() { |
| if (pattern_image_yuv_ && pattern_image_yuv_->GetDataSize()) { |
| return pattern_image_yuv_.get(); |
| } |
| |
| if (resolution_.width == 0 || resolution_.height == 0) { |
| LOGF(ERROR) << "Invalid resolution: " << resolution_.width << "x" |
| << resolution_.height; |
| return nullptr; |
| } |
| if (!GenerateTestPattern()) { |
| return nullptr; |
| } |
| return pattern_image_yuv_.get(); |
| } |
| |
| bool TestPattern::GenerateTestPattern() { |
| switch (pattern_mode_) { |
| case ANDROID_SENSOR_TEST_PATTERN_MODE_COLOR_BARS_FADE_TO_GRAY: |
| return GenerateColorBarFadeToGray(); |
| default: |
| return false; |
| } |
| } |
| |
| bool TestPattern::GenerateColorBar() { |
| std::vector<std::tuple<uint8_t, uint8_t, uint8_t>> color_bar = { |
| // Color map: R , G , B |
| std::make_tuple(0xFF, 0xFF, 0xFF), // White |
| std::make_tuple(0xFF, 0xFF, 0x00), // Yellow |
| std::make_tuple(0x00, 0xFF, 0xFF), // Cyan |
| std::make_tuple(0x00, 0xFF, 0x00), // Green |
| std::make_tuple(0xFF, 0x00, 0xFF), // Magenta |
| std::make_tuple(0xFF, 0x00, 0x00), // Red |
| std::make_tuple(0x00, 0x00, 0xFF), // Blue |
| std::make_tuple(0x00, 0x00, 0x00), // Black |
| }; |
| size_t argb_size = |
| sensor_pixel_array_size_.width * sensor_pixel_array_size_.height * 4; |
| pattern_image_rgb_.reset(new SharedFrameBuffer(argb_size)); |
| |
| uint8_t* data = pattern_image_rgb_->GetData(); |
| int color_bar_width = sensor_pixel_array_size_.width / color_bar.size(); |
| for (size_t h = 0; h < sensor_pixel_array_size_.height; h++) { |
| for (size_t w = 0; w < sensor_pixel_array_size_.width; w++) { |
| int index = (w / color_bar_width) % color_bar.size(); |
| *data++ = std::get<2>(color_bar[index]); // B |
| *data++ = std::get<1>(color_bar[index]); // G |
| *data++ = std::get<0>(color_bar[index]); // R |
| *data++ = 0x00; // A |
| } |
| } |
| |
| return ConvertToYU12(); |
| } |
| |
| bool TestPattern::GenerateColorBarFadeToGray() { |
| std::vector<std::tuple<uint8_t, uint8_t, uint8_t>> color_bar = { |
| // Color map: R , G , B |
| std::make_tuple(0xFF, 0xFF, 0xFF), // White |
| std::make_tuple(0xFF, 0xFF, 0x00), // Yellow |
| std::make_tuple(0x00, 0xFF, 0xFF), // Cyan |
| std::make_tuple(0x00, 0xFF, 0x00), // Green |
| std::make_tuple(0xFF, 0x00, 0xFF), // Magenta |
| std::make_tuple(0xFF, 0x00, 0x00), // Red |
| std::make_tuple(0x00, 0x00, 0xFF), // Blue |
| std::make_tuple(0x00, 0x00, 0x00), // Black |
| }; |
| size_t argb_size = |
| sensor_pixel_array_size_.width * sensor_pixel_array_size_.height * 4; |
| pattern_image_rgb_.reset(new SharedFrameBuffer(argb_size)); |
| |
| uint8_t* data = pattern_image_rgb_->GetData(); |
| int color_bar_width = sensor_pixel_array_size_.width / color_bar.size(); |
| int color_bar_height = sensor_pixel_array_size_.height / 128 * 128; |
| if (color_bar_height == 0) { |
| color_bar_height = sensor_pixel_array_size_.height; |
| } |
| for (size_t h = 0; h < sensor_pixel_array_size_.height; h++) { |
| float gray_factor = |
| static_cast<float>(color_bar_height - (h % color_bar_height)) / |
| color_bar_height; |
| for (size_t w = 0; w < sensor_pixel_array_size_.width; w++) { |
| int index = (w / color_bar_width) % color_bar.size(); |
| auto get_fade_color = [&](uint8_t base_color) { |
| uint8_t color = base_color * gray_factor; |
| if ((w / (color_bar_width / 2)) % 2) { |
| color = (color & 0xF0) | (color >> 4); |
| } |
| return color; |
| }; |
| *data++ = get_fade_color(std::get<2>(color_bar[index])); // B |
| *data++ = get_fade_color(std::get<1>(color_bar[index])); // G |
| *data++ = get_fade_color(std::get<0>(color_bar[index])); // R |
| *data++ = 0x00; // A |
| } |
| } |
| |
| return ConvertToYU12(); |
| } |
| |
| bool TestPattern::ConvertToYU12() { |
| int cropped_width; |
| int cropped_height; |
| int crop_x = 0; |
| int crop_y = 0; |
| if (sensor_pixel_array_size_.width * resolution_.height > |
| resolution_.width * sensor_pixel_array_size_.height) { |
| cropped_width = sensor_pixel_array_size_.height * resolution_.width / |
| resolution_.height; |
| cropped_height = sensor_pixel_array_size_.height; |
| if (cropped_width % 2 == 1) { |
| // Make cropped_width to the closest even number. |
| cropped_width++; |
| } |
| crop_x = (sensor_pixel_array_size_.width - cropped_width) / 2; |
| } else { |
| cropped_width = sensor_pixel_array_size_.width; |
| cropped_height = |
| sensor_pixel_array_size_.width * resolution_.height / resolution_.width; |
| if (cropped_height % 2 == 1) { |
| // Make cropped_height to the closest even number. |
| cropped_height++; |
| } |
| crop_y = (sensor_pixel_array_size_.height - cropped_height) / 2; |
| } |
| auto cropped_image_yuv = |
| std::make_unique<SharedFrameBuffer>(cropped_width * cropped_height * 1.5); |
| int ret = libyuv::ConvertToI420( |
| pattern_image_rgb_->GetData(), pattern_image_rgb_->GetDataSize(), |
| cropped_image_yuv->GetData(), cropped_width, |
| cropped_image_yuv->GetData() + cropped_width * cropped_height, |
| cropped_width / 2, |
| cropped_image_yuv->GetData() + cropped_width * cropped_height * 5 / 4, |
| cropped_width / 2, crop_x, crop_y, sensor_pixel_array_size_.width, |
| sensor_pixel_array_size_.height, cropped_width, cropped_height, |
| libyuv::RotationMode::kRotate0, libyuv::FourCC::FOURCC_ARGB); |
| LOGF_IF(ERROR, ret) << "ConvertToI420() returns " << ret; |
| pattern_image_rgb_.reset(); |
| if (!ret) { |
| size_t yuv_size = resolution_.width * resolution_.height * 1.5; |
| pattern_image_yuv_.reset(new SharedFrameBuffer(yuv_size)); |
| |
| ret = libyuv::I420Scale( |
| cropped_image_yuv->GetData(), cropped_width, |
| cropped_image_yuv->GetData() + cropped_width * cropped_height, |
| cropped_width / 2, |
| cropped_image_yuv->GetData() + cropped_width * cropped_height * 5 / 4, |
| cropped_width / 2, cropped_width, cropped_height, |
| pattern_image_yuv_->GetData(), resolution_.width, |
| pattern_image_yuv_->GetData() + resolution_.width * resolution_.height, |
| resolution_.width / 2, |
| pattern_image_yuv_->GetData() + |
| resolution_.width * resolution_.height * 5 / 4, |
| resolution_.width / 2, resolution_.width, resolution_.height, |
| libyuv::FilterMode::kFilterNone); |
| LOGF_IF(ERROR, ret) << "I420Scale() returns " << ret; |
| if (!ret) { |
| pattern_image_yuv_->SetFourcc(V4L2_PIX_FMT_YUV420); |
| pattern_image_yuv_->SetWidth(resolution_.width); |
| pattern_image_yuv_->SetHeight(resolution_.height); |
| pattern_image_yuv_->SetDataSize(yuv_size); |
| } |
| } |
| return ret == 0; |
| } |
| |
| } // namespace cros |