mirror of
https://gitlab.freedesktop.org/mesa/mesa.git
synced 2026-06-04 06:38:19 +02:00
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 <eric.smith@collabora.com> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/40175>
This commit is contained in:
parent
1f93e1b831
commit
f6f2e16e35
3 changed files with 679 additions and 0 deletions
|
|
@ -466,6 +466,7 @@ if with_tests
|
||||||
'tests/u_memstream_test.cpp',
|
'tests/u_memstream_test.cpp',
|
||||||
'tests/u_printf_test.cpp',
|
'tests/u_printf_test.cpp',
|
||||||
'tests/u_qsort_test.cpp',
|
'tests/u_qsort_test.cpp',
|
||||||
|
'tests/u_ycbcr_test.cpp',
|
||||||
'tests/vector_test.cpp',
|
'tests/vector_test.cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
520
src/util/tests/u_ycbcr_test.cpp
Normal file
520
src/util/tests/u_ycbcr_test.cpp
Normal file
|
|
@ -0,0 +1,520 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Collabora Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
158
src/util/u_ycbcr.h
Normal file
158
src/util/u_ycbcr.h
Normal file
|
|
@ -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 */
|
||||||
Loading…
Add table
Reference in a new issue