From f6f2e16e35195f0a7f8ec1f7e60b19b8e8b10f09 Mon Sep 17 00:00:00 2001 From: Erik Faye-Lund Date: Mon, 16 Feb 2026 13:56:49 +0100 Subject: [PATCH] util: add common ycbcr coefficient math code This adds some reusable math to set up YCbCr to RGB color transforms. It covers ITU BT.601, ITU BT.709 and ITU BT.2020 YUV <-> RGB conversion, as well as "narrow"" and "full" range. This code is intended to replace three different implementations of YUV-transforms already present in Mesa, all of them with different parameterizations and differences in data-formats. These implementations are: nir_lower_tex.c, vk_nir_convert_ycbcr.c and vl_csc.c. None of the exising implementations seems to fully cover all of the needs of the others. The one that comes the closest is the one in vl_csc.c, but it has a few issues: 1. It doesn't differentiate between per-channel bit-sizes, which the Vulkan code needs. 2. It uses enums from p_video_enums.h in Gallium to paremeterize the behavior. 3. It's written in a monolithic way, handling up to two range-remappings, which the other implementations doesn't need. While it could be possible to entangle all of that, that would likely end up being a more or less a new implementation anyway. So let's instead try to pick the best of all three implementations into one new one, that's broken into smaller pieces that can be assembled into either of the three. In addition, this implementation has a bunch of unit-tests, to make sure we don't introduce subtle breakages down the line. Reviewed-by: Eric R. Smith Part-of: --- src/util/meson.build | 1 + src/util/tests/u_ycbcr_test.cpp | 520 ++++++++++++++++++++++++++++++++ src/util/u_ycbcr.h | 158 ++++++++++ 3 files changed, 679 insertions(+) create mode 100644 src/util/tests/u_ycbcr_test.cpp create mode 100644 src/util/u_ycbcr.h diff --git a/src/util/meson.build b/src/util/meson.build index 62fc844f07f..4f3b71dd730 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -466,6 +466,7 @@ if with_tests 'tests/u_memstream_test.cpp', 'tests/u_printf_test.cpp', 'tests/u_qsort_test.cpp', + 'tests/u_ycbcr_test.cpp', 'tests/vector_test.cpp', ) diff --git a/src/util/tests/u_ycbcr_test.cpp b/src/util/tests/u_ycbcr_test.cpp new file mode 100644 index 00000000000..e3b1a48292d --- /dev/null +++ b/src/util/tests/u_ycbcr_test.cpp @@ -0,0 +1,520 @@ +/** + * Copyright (c) 2026 Collabora Ltd. + * + * SPDX-License-Identifier: MIT + */ + +#include + +#include + +#include "util/u_ycbcr.h" + +#include "macros.h" + +static void +test_to_rgb_coeffs(const float coeffs[3]) +{ + float mat[3][4]; + util_get_ycbcr_to_rgb_matrix(mat, coeffs); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 0.0f, 0.0f}, + { 1.0f, 1.0f, 1.0f }, + }, { + { 0.0f, 0.0f, 0.0f}, + { 0.0f, 0.0f, 0.0f }, + }, { + { 0.5f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + }, { + { a, -a / d, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + }, { + { b, -b / d, -b / e }, + { 0.0f, 1.0f, 0.0f }, + }, { + { c, 0.5f, -c / e }, + { 0.0f, 0.0f, 1.0f }, + }, { + { 1 - c, -0.5f, c / e }, + { 1.0f, 1.0f, 0.0f }, + }, { + { 1 - a, a / d, (a - 1) / e }, + { 0.0f, 1.0f, 1.0f }, + }, { + { 1 - b, b / d, b / e }, + { 1.0f, 0.0f, 1.0f }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + + /* verify with reference equation */ + float Y = test_data[i].input[0]; + float Cb = test_data[i].input[1]; + float Cr = test_data[i].input[2]; + + result[0] = Y + e * Cr; + result[1] = Y - (a * e / b) * Cr - (c * d / b) * Cb; + result[2] = Y + d * Cb; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-6); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-6); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, to_rgb) +{ + test_to_rgb_coeffs(util_ycbcr_bt601_coeffs); + test_to_rgb_coeffs(util_ycbcr_bt709_coeffs); + test_to_rgb_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_coeffs(const float coeffs[3]) +{ + float mat[3][4]; + util_get_rgb_to_ycbcr_matrix(mat, coeffs); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 0.0f}, + }, { + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f}, + }, { + { 0.5f, 0.5f, 0.5f }, + { 0.5f, 0.0f, 0.0f }, + }, { + { 1.0f, 0.0f, 0.0f }, + { a, -a / d, 0.5f }, + }, { + { 0.0f, 1.0f, 0.0f }, + { b, -b / d, -b / e }, + }, { + { 0.0f, 0.0f, 1.0f }, + { c, 0.5f, -c / e }, + }, { + { 1.0f, 1.0f, 0.0f }, + { 1 - c, -0.5f, c / e }, + }, { + { 0.0f, 1.0f, 1.0f }, + { 1 - a, a / d, (a - 1) / e }, + }, { + { 1.0f, 0.0f, 1.0f }, + { 1 - b, b / d, b / e }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + + /* verify with reference equation */ + float R = test_data[i].input[0]; + float G = test_data[i].input[1]; + float B = test_data[i].input[2]; + + float Y = a * R + b * G + c * B;; + result[0] = Y; + result[1] = (B - Y) / d; + result[2] = (R - Y) / e; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +TEST(u_ycbcr_test, to_ycbcr) +{ + test_to_ycbcr_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_and_back_coeffs(const float coeffs[3]) +{ + float to_ycbcr[3][4], to_rgb[3][4]; + util_get_rgb_to_ycbcr_matrix(to_ycbcr, coeffs); + util_get_ycbcr_to_rgb_matrix(to_rgb, coeffs); + + float inputs[][3] = { + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(inputs); ++i) { + float ycbcr[3]; + for (int c = 0; c < 3; ++c) { + ycbcr[c] = inputs[i][0] * to_ycbcr[c][0] + + inputs[i][1] * to_ycbcr[c][1] + + inputs[i][2] * to_ycbcr[c][2]; + + } + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = ycbcr[0] * to_rgb[c][0] + + ycbcr[1] * to_rgb[c][1] + + ycbcr[2] * to_rgb[c][2]; + + } + + EXPECT_NEAR(inputs[i][0], result[0], 1e-6); + EXPECT_NEAR(inputs[i][1], result[1], 1e-6); + EXPECT_NEAR(inputs[i][2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, to_ycbcr_and_back) +{ + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_and_back_coeffs(util_ycbcr_bt2020_coeffs); +} + +TEST(u_ycbcr_test, full_range) +{ + float range[3][2]; + unsigned bpc[3] = {8, 8, 8}; + util_get_full_range_coeffs(range, bpc); + + const struct { + uint8_t input[3]; + float expected[3]; + } test_data[] = { { + { 0, 128, 128 }, + { 0.0f, 0.0f, 0.0f }, + }, { + { 255, 128, 128 }, + { 1.0f, 0.0f, 0.0f }, + }, { + { 0, 0, 255 }, + { 0.0f, -128.0f / 255, 127.0f / 255 }, + }, { + { 255, 255, 0 }, + { 1.0f, 127.0f / 255, -128.0f / 255 }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + test_data[i].input[0] / 255.0f, + test_data[i].input[1] / 255.0f, + test_data[i].input[2] / 255.0f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) + result[c] = input[c] * range[c][0] + range[c][1]; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +TEST(u_ycbcr_test, narrow_range) +{ + float range[3][2]; + unsigned bpc[3] = {8, 8, 8}; + util_get_narrow_range_coeffs(range, bpc); + + const struct { + uint8_t input[3]; + float expected[3]; + } test_data[] = { { + { 16, 128, 128 }, + { 0.0f, 0.0f, 0.0f }, + }, { + { 235, 128, 128 }, + { 1.0f, 0.0f, 0.0f }, + }, { + { 16, 16, 240 }, + { 0.0f, -0.5f, 0.5f }, + }, { + { 235, 240, 16 }, + { 1.0f, 0.5f, -0.5f }, + } + }; + + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + test_data[i].input[0] / 255.0f, + test_data[i].input[1] / 255.0f, + test_data[i].input[2] / 255.0f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) + result[c] = input[c] * range[c][0] + range[c][1]; + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-7); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-7); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-7); + } +} + +static void +test_to_rgb_narrow_range_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float mat[3][4]; + util_get_ycbcr_to_rgb_matrix(mat, coeffs); + util_ycbcr_adjust_from_range(mat, range); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 0.0f, 0.0f}, + { 1.0f, 1.0f, 1.0f }, + }, { + { 0.0f, 0.0f, 0.0f}, + { 0.0f, 0.0f, 0.0f }, + }, { + { 0.5f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + }, { + { a, -a / d, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + }, { + { b, -b / d, -b / e }, + { 0.0f, 1.0f, 0.0f }, + }, { + { c, 0.5f, -c / e }, + { 0.0f, 0.0f, 1.0f }, + }, { + { 1 - c, -0.5f, c / e }, + { 1.0f, 1.0f, 0.0f }, + }, { + { 1 - a, a / d, (a - 1) / e }, + { 0.0f, 1.0f, 1.0f }, + }, { + { 1 - b, b / d, b / e }, + { 1.0f, 0.0f, 1.0f }, + } + }; + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float input[3] = { + (16 + test_data[i].input[0] * (235 - 16)) / 255.0f, + (16 + (test_data[i].input[1] + 0.5f) * (240 - 16)) / 255.0f, + (16 + (test_data[i].input[2] + 0.5f) * (240 - 16)) / 255.75f, + }; + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = input[0] * mat[c][0] + + input[1] * mat[c][1] + + input[2] * mat[c][2] + + mat[c][3]; + + } + + EXPECT_NEAR(test_data[i].expected[0], result[0], 1e-6); + EXPECT_NEAR(test_data[i].expected[1], result[1], 1e-6); + EXPECT_NEAR(test_data[i].expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, bt601_to_rgb_narrow_range) +{ + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt601_coeffs); + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt709_coeffs); + test_to_rgb_narrow_range_coeffs(util_ycbcr_bt2020_coeffs); +} + + +static void +test_to_ycbcr_narrow_range_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float mat[3][4]; + util_get_rgb_to_ycbcr_matrix(mat, coeffs); + util_ycbcr_adjust_to_range(mat, range); + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + + const struct { + float input[3]; + float expected[3]; + } test_data[] = { { + { 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 0.0f}, + }, { + { 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f}, + }, { + { 0.5f, 0.5f, 0.5f }, + { 0.5f, 0.0f, 0.0f }, + }, { + { 1.0f, 0.0f, 0.0f }, + { a, -a / d, 0.5f }, + }, { + { 0.0f, 1.0f, 0.0f }, + { b, -b / d, -b / e }, + }, { + { 0.0f, 0.0f, 1.0f }, + { c, 0.5f, -c / e }, + }, { + { 1.0f, 1.0f, 0.0f }, + { 1 - c, -0.5f, c / e }, + }, { + { 0.0f, 1.0f, 1.0f }, + { 1 - a, a / d, (a - 1) / e }, + }, { + { 1.0f, 0.0f, 1.0f }, + { 1 - b, b / d, b / e }, + } + }; + for (size_t i = 0; i < ARRAY_SIZE(test_data); ++i) { + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = test_data[i].input[0] * mat[c][0] + + test_data[i].input[1] * mat[c][1] + + test_data[i].input[2] * mat[c][2] + + mat[c][3]; + } + + float expected[3] = { + (16 + test_data[i].expected[0] * (235 - 16)) / 255.0f, + (16 + (test_data[i].expected[1] + 0.5f) * (240 - 16)) / 255.0f, + (16 + (test_data[i].expected[2] + 0.5f) * (240 - 16)) / 255.75f, + }; + + EXPECT_NEAR(expected[0], result[0], 1e-6); + EXPECT_NEAR(expected[1], result[1], 1e-6); + EXPECT_NEAR(expected[2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, bt601_to_ycbcr_narrow_range) +{ + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_narrow_range_coeffs(util_ycbcr_bt2020_coeffs); +} + +static void +test_to_ycbcr_range_and_back_coeffs(const float coeffs[3]) +{ + const unsigned bpc[3] = {8, 8, 10}; + + float range[3][2]; + util_get_narrow_range_coeffs(range, bpc); + + float to_ycbcr[3][4]; + util_get_rgb_to_ycbcr_matrix(to_ycbcr, coeffs); + util_ycbcr_adjust_to_range(to_ycbcr, range); + + float to_rgb[3][4]; + util_get_ycbcr_to_rgb_matrix(to_rgb, coeffs); + util_ycbcr_adjust_from_range(to_rgb, range); + + float inputs[][3] = { + { 1.0f, 1.0f, 1.0f }, + { 0.0f, 0.0f, 0.0f }, + { 0.5f, 0.5f, 0.5f }, + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f }, + { 0.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(inputs); ++i) { + float ycbcr[3]; + for (int c = 0; c < 3; ++c) { + ycbcr[c] = inputs[i][0] * to_ycbcr[c][0] + + inputs[i][1] * to_ycbcr[c][1] + + inputs[i][2] * to_ycbcr[c][2]; + + } + + float result[3]; + for (int c = 0; c < 3; ++c) { + result[c] = ycbcr[0] * to_rgb[c][0] + + ycbcr[1] * to_rgb[c][1] + + ycbcr[2] * to_rgb[c][2]; + + } + + EXPECT_NEAR(inputs[i][0], result[0], 1e-6); + EXPECT_NEAR(inputs[i][1], result[1], 1e-6); + EXPECT_NEAR(inputs[i][2], result[2], 1e-6); + } +} + +TEST(u_ycbcr_test, to_ycbcr_range_and_back) +{ + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt601_coeffs); + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt709_coeffs); + test_to_ycbcr_range_and_back_coeffs(util_ycbcr_bt2020_coeffs); +} diff --git a/src/util/u_ycbcr.h b/src/util/u_ycbcr.h new file mode 100644 index 00000000000..448da7f12d0 --- /dev/null +++ b/src/util/u_ycbcr.h @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2026 Collabora Ltd. + * + * SPDX-License-Identifier: MIT + */ + +#ifndef U_YCBCR_H +#define U_YCBCR_H + +/* BT.601 coefficients */ +static const float util_ycbcr_bt601_coeffs[3] = { + 0.299f, 0.587f, 0.114f +}; + +/* BT.701 coefficients */ +static const float util_ycbcr_bt709_coeffs[3] = { + 0.2126f, 0.7152f, 0.0722f +}; + +/* BT.2020 coefficients */ +static const float util_ycbcr_bt2020_coeffs[3] = { + 0.2627f, 0.6780f, 0.0593f +}; + +/* SMPTE 240M coefficients */ +static const float util_ycbcr_smpte240m_coeffs[3] = { + 0.2122f, 0.7013f, 0.0865f +}; + +static inline void +util_get_ycbcr_to_rgb_matrix(float m[3][4], const float coeffs[3]) +{ + /** + * Sets up a 3x4 matrix that computes: + * + * R = Y + e * Cr + * G = Y - (a * e / b) * Cr - (c * d / b) * Cb + * B = Y + d * Cb + */ + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 2 - 2 * c; + float e = 2 - 2 * a; + float f = 1.0f / b; + + m[0][0] = 1; m[0][1] = 0; m[0][2] = e; m[0][3] = 0; + m[1][0] = 1; m[1][1] = -c * d * f; m[1][2] = -a * e * f; m[1][3] = 0; + m[2][0] = 1; m[2][1] = d; m[2][2] = 0; m[2][3] = 0; +} + +static inline void +util_get_rgb_to_ycbcr_matrix(float m[3][4], const float coeffs[3]) +{ + /** + * Sets up a 3x4 matrix that computes: + * + * Y = a * R + b * G + c * B + * Cb = (B - Y) / d + * Cr = (R - Y) / e + */ + + float a = coeffs[0]; + float b = coeffs[1]; + float c = coeffs[2]; + float d = 0.5f / (c - 1); + float e = 0.5f / (a - 1); + + m[0][0] = a; m[0][1] = b; m[0][2] = c; m[0][3] = 0; + m[1][0] = d * a; m[1][1] = d * b; m[1][2] = 0.5f; m[1][3] = 0; + m[2][0] = 0.5f; m[2][1] = e * b; m[2][2] = e * c; m[2][3] = 0; +} + +static inline float +util_get_full_range_chroma_bias(unsigned bpc) +{ + return -(1 << (bpc - 1)) / ((1 << bpc) - 1.0f); +} + +static inline void +util_get_full_range_coeffs(float out[3][2], const unsigned bpc[3]) +{ + out[0][0] = 1; out[0][1] = 0; + out[1][0] = 1; out[1][1] = util_get_full_range_chroma_bias(bpc[1]); + out[2][0] = 1; out[2][1] = util_get_full_range_chroma_bias(bpc[2]); +} + +static inline float +util_get_narrow_range(unsigned bpc) +{ + return 1 - 1.0f / (1 << bpc); +} + +static inline float +util_get_narrow_range_luma_factor(unsigned bpc) +{ + return util_get_narrow_range(bpc) * (256.0f / 219); +} + +static inline float +util_get_narrow_range_chroma_factor(unsigned bpc) +{ + return util_get_narrow_range(bpc) * (256.0f / 224); +} + +static inline void +util_get_narrow_range_coeffs(float out[3][2], const unsigned bpc[3]) +{ + float y_factor = util_get_narrow_range_luma_factor(bpc[0]); + float cb_factor = util_get_narrow_range_chroma_factor(bpc[1]); + float cr_factor = util_get_narrow_range_chroma_factor(bpc[2]); + + float y_bias = -16.0f / 219; + float c_bias = -128.0f / 224; + + out[0][0] = y_factor; out[0][1] = y_bias; + out[1][0] = cb_factor; out[1][1] = c_bias; + out[2][0] = cr_factor; out[2][1] = c_bias; +} + +static inline void +util_get_identity_range_coeffs(float out[3][2]) +{ + out[0][0] = out[1][0] = out[2][0] = 1.0f; + out[0][1] = out[1][1] = out[2][1] = 0.0f; +} + +static inline void +util_ycbcr_adjust_from_range(float mat[3][4], + const float range[3][2]) +{ + for (int i = 0; i < 3; ++i) { + mat[i][3] = range[0][1] * mat[i][0] + + range[1][1] * mat[i][1] + + range[2][1] * mat[i][2] + + mat[i][3]; + + mat[i][0] = mat[i][0] * range[0][0]; + mat[i][1] = mat[i][1] * range[1][0]; + mat[i][2] = mat[i][2] * range[2][0]; + } +} + +static inline void +util_ycbcr_adjust_to_range(float mat[3][4], + const float range[3][2]) +{ + for (int i = 0; i < 3; ++i) { + float tmp = 1.0f / range[i][0]; + mat[i][0] = mat[i][0] * tmp; + mat[i][1] = mat[i][1] * tmp; + mat[i][2] = mat[i][2] * tmp; + mat[i][3] -= range[i][1] * tmp; + } +} + +#endif /* U_YCBCR_H */