diff options
Diffstat (limited to 'kms++util/src/testpat.cpp')
| -rw-r--r-- | kms++util/src/testpat.cpp | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/kms++util/src/testpat.cpp b/kms++util/src/testpat.cpp new file mode 100644 index 0000000..b9f1e56 --- /dev/null +++ b/kms++util/src/testpat.cpp @@ -0,0 +1,577 @@ + +#include <cstring> +#include <functional> +#include <optional> +#include <span> +#include <stdexcept> +#include <vector> + +#ifdef HAS_PTHREAD +#include <thread> +#endif + +#include <kms++/kms++.h> +#include <kms++util/kms++util.h> + +#include "conv.h" + +using namespace std; + +namespace kms +{ +static RGB16 get_test_pattern_pixel_16(size_t w, size_t h, size_t x, size_t y) +{ + const unsigned mw = 20; + + const unsigned xm1 = mw; + const unsigned xm2 = w - mw - 1; + const unsigned ym1 = mw; + const unsigned ym2 = h - mw - 1; + + // white margin lines + if (x == xm1 || x == xm2 || y == ym1 || y == ym2) + return RGB16::from_8(255, 255, 255); + // white box in top left corner + else if (x < xm1 && y < ym1) + return RGB16::from_8(255, 255, 255); + // white box outlines to corners + else if ((x == 0 || x == w - 1) && (y < ym1 || y > ym2)) + return RGB16::from_8(255, 255, 255); + // white box outlines to corners + else if ((y == 0 || y == h - 1) && (x < xm1 || x > xm2)) + return RGB16::from_8(255, 255, 255); + // blue bar on the left + else if (x < xm1 && (y > ym1 && y < ym2)) + return RGB16::from_8(0, 0, 255); + // blue bar on the top + else if (y < ym1 && (x > xm1 && x < xm2)) + return RGB16::from_8(0, 0, 255); + // red bar on the right + else if (x > xm2 && (y > ym1 && y < ym2)) + return RGB16::from_8(255, 0, 0); + // red bar on the bottom + else if (y > ym2 && (x > xm1 && x < xm2)) + return RGB16::from_8(255, 0, 0); + // inside the margins + else if (x > xm1 && x < xm2 && y > ym1 && y < ym2) { + // diagonal line + if (x == y || w - x == h - y) + return RGB16::from_8(255, 255, 255); + // diagonal line + else if (w - x - 1 == y || x == h - y - 1) + return RGB16::from_8(255, 255, 255); + else { + int t = (x - xm1 - 1) * 8 / (xm2 - xm1 - 1); + unsigned r = 0, g = 0, b = 0; + + unsigned c = (y - ym1 - 1) % 256; + + switch (t) { + case 0: + r = c; + break; + case 1: + g = c; + break; + case 2: + b = c; + break; + case 3: + g = b = c; + break; + case 4: + r = b = c; + break; + case 5: + r = g = c; + break; + case 6: + r = g = b = c; + break; + case 7: + break; + } + + return RGB16::from_8(r, g, b); + } + } else { + // black corners + return RGB16::from_8(0, 0, 0); + } +} + +static void get_test_pattern_line(size_t w, size_t h, size_t row, std::span<RGB16> buf) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = get_test_pattern_pixel_16(w, h, x, row); +} + +static void get_test_pattern_line_yuv(size_t w, size_t h, size_t row, + std::span<YUV16> buf, + const TestPatternOptions& options) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = get_test_pattern_pixel_16(w, h, x, row) + .to_yuv(options.rec, options.range); +} + +// SMPTE RP 219-1:2014 +// High-Definition, Standard-Definition Compatible Color Bar Signal +// Limited range YUV +static YUV16 get_smpte_pixel(size_t w, size_t h, size_t x, size_t y) +{ + // Pattern colors (12-bit values, limited range) + constexpr YUV16 gray40 = YUV16::from_12(1658, 2048, 2048); // 40% Gray + constexpr YUV16 white75 = YUV16::from_12(2884, 2048, 2048); // 75% White + constexpr YUV16 yellow75 = YUV16::from_12(2694, 704, 2171); // 75% Yellow + constexpr YUV16 cyan75 = YUV16::from_12(2325, 2356, 704); // 75% Cyan + constexpr YUV16 green75 = YUV16::from_12(2136, 1012, 827); // 75% Green + constexpr YUV16 magenta75 = YUV16::from_12(1004, 3084, 3269); // 75% Magenta + constexpr YUV16 red75 = YUV16::from_12(815, 1740, 3392); // 75% Red + constexpr YUV16 blue75 = YUV16::from_12(446, 3392, 1925); // 75% Blue + constexpr YUV16 cyan100 = YUV16::from_12(3015, 2459, 256); // 100% Cyan + constexpr YUV16 blue100 = YUV16::from_12(509, 3840, 1884); // 100% Blue + constexpr YUV16 yellow100 = YUV16::from_12(3507, 256, 2212); // 100% Yellow + constexpr YUV16 black = YUV16::from_12(256, 2048, 2048); // 0% Black + constexpr YUV16 white100 = YUV16::from_12(3760, 2048, 2048); // 100% White + constexpr YUV16 red100 = YUV16::from_12(1001, 1637, 3840); // 100% Red + constexpr YUV16 gray15 = YUV16::from_12(782, 2048, 2048); // 15% Gray + + // PLUGE steps + constexpr YUV16 black_m2 = YUV16::from_12(186, 2048, 2048); // -2% Black + constexpr YUV16 black_p2 = YUV16::from_12(326, 2048, 2048); // +2% Black + constexpr YUV16 black_p4 = YUV16::from_12(396, 2048, 2048); // +4% Black + + // High precision for x-axis calculations + constexpr size_t M = 1024; + x = x * M; + const size_t a = w * M; + const size_t c = (a * 3 / 4) / 7; + const size_t d = a / 8; + + // Pattern heights + const size_t pattern1_height = (h * 7) / 12; + const size_t pattern2_height = pattern1_height + (h / 12); + const size_t pattern3_height = pattern2_height + (h / 12); + + // Pattern 1 (75% color bars) + if (y < pattern1_height) { + if (x < d || x >= (a - d)) + return gray40; + + size_t bar_index = (x - d) / c; + switch (bar_index) { + case 0: + return white75; + case 1: + return yellow75; + case 2: + return cyan75; + case 3: + return green75; + case 4: + return magenta75; + case 5: + return red75; + default: + return blue75; + } + } + + // Pattern 2 (Color difference reference) + if (y >= pattern1_height && y < pattern2_height) { + if (x < d) + return cyan100; + + if (x >= (a - d)) + return blue100; + + return white75; + } + + // Pattern 3 (Ramp) + if (y >= pattern2_height && y < pattern3_height) { + if (x < d) + return yellow100; + + if (x >= (a - d)) + return red100; + + const size_t ramp_width = a - 2 * d; + const size_t ramp_x = x - d; + + uint16_t y_val = 256 + (3760 - 256) * ramp_x / ramp_width; + return YUV16::from_12(y_val, 2048, 2048); + } + + // Pattern 4 (PLUGE) + if (y >= pattern3_height) { + const size_t c0 = d; + const size_t c1 = c0 + c * 3 / 2; + const size_t c2 = c1 + 2 * c; + const size_t c3 = c2 + c * 5 / 6; + + if (x < c0) + return gray15; + + if (x < c1) + return black; + + if (x < c2) + return white100; + + if (x < c3) + return black; + + if (x >= a - d) + return gray15; + + if (x >= a - d - c) + return black; + + const size_t step = (x - c3) / (c / 3); + + switch (step) { + case 0: + return black_m2; // -2% + case 1: + return black; // 0% + case 2: + return black_p2; // +2% + case 3: + return black; // 0% + default: + return black_p4; // +4% + } + } + + return black; +} + +static void get_smpte_line_rgb(size_t w, size_t h, size_t row, std::span<RGB16> buf, + const TestPatternOptions& options) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = get_smpte_pixel(w, h, x, row) + .to_rgb(RecStandard::BT709, ColorRange::Limited); +} + +static void get_smpte_line_yuv(size_t w, size_t h, size_t row, std::span<YUV16> buf, + const TestPatternOptions& options) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = get_smpte_pixel(w, h, x, row); +} + +static void get_plain_line_rgb(size_t w, const RGB16& color, std::span<RGB16> buf) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = color; +} + +static void get_plain_line_yuv(size_t w, const YUV16& color, std::span<YUV16> buf) +{ + for (size_t x = 0; x < w; ++x) + buf[x] = color; +} + +static void draw_test_pattern_part(IFramebuffer& fb, size_t start_y, size_t end_y, + const TestPatternOptions& options) +{ + std::optional<RGB16> solid; + + if (options.pattern == "red") + solid = RGB16(0xffff, 0, 0); + else if (options.pattern == "green") + solid = RGB16(0, 0xffff, 0); + else if (options.pattern == "blue") + solid = RGB16(0, 0, 0xffff); + else if (options.pattern == "white") + solid = RGB16(0xffff, 0xffff, 0xffff); + else if (options.pattern == "black") + solid = RGB16(0, 0, 0); + + std::function<void(size_t y, std::span<RGB16> span)> generate_line_rgb; + std::function<void(size_t y, std::span<YUV16> span)> generate_line_yuv; + + if (solid.has_value()) { + generate_line_rgb = [&fb, rgb = solid.value()](size_t y, + std::span<RGB16> span) { + get_plain_line_rgb(fb.width(), rgb, span); + }; + + generate_line_yuv = [&fb, rgb = solid.value(), + &options](size_t y, std::span<YUV16> span) { + get_plain_line_yuv(fb.width(), + rgb.to_yuv(options.rec, options.range), span); + }; + } else if (options.pattern == "smpte") { + generate_line_rgb = [&fb, &options](size_t y, std::span<RGB16> span) { + get_smpte_line_rgb(fb.width(), fb.height(), y, span, options); + }; + + generate_line_yuv = [&fb, &options](size_t y, std::span<YUV16> span) { + get_smpte_line_yuv(fb.width(), fb.height(), y, span, options); + }; + } else { + generate_line_rgb = [&fb](size_t y, std::span<RGB16> span) { + get_test_pattern_line(fb.width(), fb.height(), y, span); + }; + + generate_line_yuv = [&fb, &options](size_t y, std::span<YUV16> span) { + get_test_pattern_line_yuv(fb.width(), fb.height(), y, span, + options); + }; + } + +#define CASE_ARGB(x) \ + case PixelFormat::x: \ + ARGB_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_rgb); \ + break; + +#define CASE_YUV(x) \ + case PixelFormat::x: \ + YUV_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_YUV_PACKED(x) \ + case PixelFormat::x: \ + YUVPackedWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_YUV_SEMI(x) \ + case PixelFormat::x: \ + YUVSemiPlanarWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_YUV_PLANAR(x) \ + case PixelFormat::x: \ + YUVPlanarWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_Y_ONLY(x) \ + case PixelFormat::x: \ + Y_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_YUV_PLANAR_PACKED(x) \ + case PixelFormat::x: \ + YUVPlanarPackedWriter<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_yuv); \ + break; + +#define CASE_RAW(x) \ + case PixelFormat::x: \ + Bayer_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_rgb); \ + break; + +#define CASE_RAW_PACKED(x) \ + case PixelFormat::x: \ + BayerPacked_Writer<x##_Layout>::write_pattern(fb, start_y, end_y, \ + generate_line_rgb); \ + break; + + switch (fb.format()) { + CASE_YUV_SEMI(P230); + CASE_YUV_SEMI(P030); + CASE_YUV_SEMI(NV12); + CASE_YUV_SEMI(NV21); + CASE_YUV_SEMI(NV16); + CASE_YUV_SEMI(NV61); + + CASE_ARGB(RGB565); + CASE_ARGB(BGR565); + + CASE_ARGB(XRGB1555); + CASE_ARGB(ARGB1555); + CASE_ARGB(XRGB4444); + CASE_ARGB(ARGB4444); + + CASE_ARGB(RGB888); + CASE_ARGB(BGR888); + + CASE_ARGB(XRGB8888); + CASE_ARGB(ARGB8888); + CASE_ARGB(XBGR8888); + CASE_ARGB(ABGR8888); + CASE_ARGB(RGBX8888); + CASE_ARGB(RGBA8888); + CASE_ARGB(BGRX8888); + CASE_ARGB(BGRA8888); + CASE_ARGB(XRGB2101010); + CASE_ARGB(ARGB2101010); + CASE_ARGB(XBGR2101010); + CASE_ARGB(ABGR2101010); + CASE_ARGB(RGBX1010102); + CASE_ARGB(RGBA1010102); + CASE_ARGB(BGRX1010102); + CASE_ARGB(BGRA1010102); + + CASE_YUV_PACKED(YUYV); + CASE_YUV_PACKED(YVYU); + CASE_YUV_PACKED(UYVY); + CASE_YUV_PACKED(VYUY); + + CASE_YUV(XVUY2101010); + + CASE_YUV_PLANAR(YUV444); + CASE_YUV_PLANAR(YVU444); + CASE_YUV_PLANAR(YUV422); + CASE_YUV_PLANAR(YVU422); + CASE_YUV_PLANAR(YUV420); + CASE_YUV_PLANAR(YVU420); + + CASE_Y_ONLY(Y8); + CASE_Y_ONLY(XYYY2101010); + + CASE_YUV_PLANAR_PACKED(T430); + + CASE_RAW(SRGGB8); + CASE_RAW(SGBRG8); + CASE_RAW(SGRBG8); + CASE_RAW(SBGGR8); + + CASE_RAW(SRGGB10); + CASE_RAW(SGBRG10); + CASE_RAW(SGRBG10); + CASE_RAW(SBGGR10); + + CASE_RAW(SRGGB12); + CASE_RAW(SGBRG12); + CASE_RAW(SGRBG12); + CASE_RAW(SBGGR12); + + CASE_RAW(SRGGB16); + CASE_RAW(SGBRG16); + CASE_RAW(SGRBG16); + CASE_RAW(SBGGR16); + + CASE_RAW_PACKED(SRGGB10P); + CASE_RAW_PACKED(SGBRG10P); + CASE_RAW_PACKED(SGRBG10P); + CASE_RAW_PACKED(SBGGR10P); + + CASE_RAW_PACKED(SRGGB12P); + CASE_RAW_PACKED(SGBRG12P); + CASE_RAW_PACKED(SGRBG12P); + CASE_RAW_PACKED(SBGGR12P); + + default: + break; + } +} + +void draw_test_pattern_multi(IFramebuffer& fb, const TestPatternOptions& options) +{ + const auto& info = get_pixel_format_info(fb.format()); + uint8_t v_sub = 0; + for (size_t p = 0; p < info.num_planes; ++p) + v_sub = max(v_sub, info.planes[p].vsub); + + if (!v_sub || fb.height() % v_sub) + throw invalid_argument("FB height must be divisible with vsub"); + + // Create the mmaps before starting the threads + for (size_t i = 0; i < fb.num_planes(); ++i) + fb.map(i); + + size_t num_threads = thread::hardware_concurrency(); + + size_t part_height = fb.height() / num_threads; + + // round up to v_sub + part_height = (part_height + v_sub - 1) / v_sub * v_sub; + + vector<thread> workers; + + std::vector<std::exception_ptr> errors(num_threads); + + for (size_t n = 0; n < num_threads; ++n) { + size_t start = n * part_height; + size_t end = start + part_height - 1; + + if (n == num_threads - 1) + end = fb.height() - 1; + + workers.push_back(thread([&fb, start, end, &options, &error = errors[n]]() { + try { + draw_test_pattern_part(fb, start, end, options); + } catch (...) { + error = std::current_exception(); + } + })); + } + + for (thread& t : workers) + t.join(); + + auto i = std::find_if(errors.begin(), errors.end(), + [](const auto& e) { return e != nullptr; }); + if (i != errors.end()) + std::rethrow_exception(*i); +} + +void draw_test_pattern_single(IFramebuffer& fb, const TestPatternOptions& options) +{ + draw_test_pattern_part(fb, 0, fb.height() - 1, options); +} + +void draw_test_pattern(IFramebuffer& fb, const TestPatternOptions& options) +{ +#ifdef HAS_PTHREAD + draw_test_pattern_multi(fb, options); +#else + draw_test_pattern_single(fb, options); +#endif +} + +} // namespace kms + +extern "C" { + +int c_draw_test_pattern(struct CDrawTestPatternParameters* params) +{ + using namespace kms; + + try { + auto fmt = find_pixel_format_by_name(params->format_name); + + ExtCPUFramebuffer fb(params->width, + params->height, + fmt, + params->buffers, + params->sizes, + params->pitches, + params->offsets); + + RecStandard rec; + if (params->rec_standard == 0) + rec = RecStandard::BT601; + else if (params->rec_standard == 1) + rec = RecStandard::BT709; + else if (params->rec_standard == 2) + rec = RecStandard::BT2020; + else + return -1; + + TestPatternOptions options; + options.pattern = params->pattern; + options.rec = rec; + options.range = params->full_range ? ColorRange::Full : ColorRange::Limited; + + draw_test_pattern(fb, options); + } catch (const exception&) { + return -1; + } + + return 0; +} + +} |
