From 9b2a7728b2b0b26065ba79cfbbd20f783f4a9988 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Mon, 3 Feb 2025 11:34:48 +0200 Subject: kms++util: Add color16.h Signed-off-by: Tomi Valkeinen --- kms++util/inc/kms++util/color16.h | 163 ++++++++++++++++++++++++++++++++++++ kms++util/inc/kms++util/kms++util.h | 1 + kms++util/meson.build | 1 + 3 files changed, 165 insertions(+) create mode 100644 kms++util/inc/kms++util/color16.h (limited to 'kms++util') diff --git a/kms++util/inc/kms++util/color16.h b/kms++util/inc/kms++util/color16.h new file mode 100644 index 0000000..307b138 --- /dev/null +++ b/kms++util/inc/kms++util/color16.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include + +namespace kms +{ + +enum class ColorRange { Limited, Full }; + +enum class RecStandard { BT601, BT709, BT2020 }; + +class YUV16; +class RGB16; + +// Conversion coefficients for different standards +struct ConversionCoefficients { + double kr; + double kg; + double kb; + + static constexpr ConversionCoefficients get(RecStandard rec) noexcept + { + switch (rec) { + case RecStandard::BT601: + return { 0.299, 0.587, 0.114 }; + default: // Default to BT709 + case RecStandard::BT709: + return { 0.2126, 0.7152, 0.0722 }; + case RecStandard::BT2020: + return { 0.2627, 0.6780, 0.0593 }; + } + } +}; + +// Range scaling factors +struct RangeScaling { + double y_min; + double y_max; + double c_min; + double c_max; + + static constexpr RangeScaling get(ColorRange range) noexcept + { + switch (range) { + case ColorRange::Limited: + return { + 16.0 / 255.0, // y_min + 235.0 / 255.0, // y_max + 16.0 / 255.0, // c_min + 240.0 / 255.0 // c_max + }; + case ColorRange::Full: + default: + return { 0.0, 1.0, 0.0, 1.0 }; + } + } +}; + +class RGB16 +{ +public: + uint16_t r = 0; + uint16_t g = 0; + uint16_t b = 0; + uint16_t a = 0; + + constexpr RGB16() noexcept = default; + constexpr RGB16(uint16_t r, uint16_t g, uint16_t b) noexcept : r(r), g(g), b(b), a(max_value) {} + constexpr RGB16(uint16_t r, uint16_t g, uint16_t b, uint16_t a) noexcept : r(r), g(g), b(b), a(a) {} + + static constexpr RGB16 from_8(uint8_t r, uint8_t g, uint8_t b) + { + return RGB16 { + static_cast((r << 8) | r), + static_cast((g << 8) | g), + static_cast((b << 8) | b), + }; + } + + [[nodiscard]] + constexpr YUV16 to_yuv(RecStandard rec = RecStandard::BT709, + ColorRange range = ColorRange::Limited) const noexcept; + + static constexpr uint16_t max_value = 0xffff; +}; + +class YUV16 +{ +public: + uint16_t y = 0; + uint16_t u = 0; + uint16_t v = 0; + uint16_t a = 0; + + constexpr YUV16() noexcept = default; + constexpr YUV16(uint16_t y, uint16_t u, uint16_t v) noexcept : y(y), u(u), v(v), a(max_value) {} + constexpr YUV16(uint16_t y, uint16_t u, uint16_t v, uint16_t a) noexcept : y(y), u(u), v(v), a(a) {} + + [[nodiscard]] + constexpr RGB16 to_rgb(RecStandard rec = RecStandard::BT709, + ColorRange range = ColorRange::Limited) const noexcept; + + static constexpr uint16_t max_value = 0xffff; +}; + +constexpr YUV16 RGB16::to_yuv(RecStandard rec, ColorRange range) const noexcept +{ + const auto coeff = ConversionCoefficients::get(rec); + const auto scaling = RangeScaling::get(range); + + // Normalize RGB values to [0,1] + const double r_norm = static_cast(r) / max_value; + const double g_norm = static_cast(g) / max_value; + const double b_norm = static_cast(b) / max_value; + + // Calculate Y + double y = coeff.kr * r_norm + coeff.kg * g_norm + coeff.kb * b_norm; + + // Scale Y to target range + y = y * (scaling.y_max - scaling.y_min) + scaling.y_min; + + // Calculate U and V + double u = (b_norm - y) / (2.0 * (1.0 - coeff.kb)); + double v = (r_norm - y) / (2.0 * (1.0 - coeff.kr)); + + // Scale U and V to target range + u = u * (scaling.c_max - scaling.c_min) + (scaling.c_max + scaling.c_min) / 2.0; + v = v * (scaling.c_max - scaling.c_min) + (scaling.c_max + scaling.c_min) / 2.0; + + // Convert back to 16-bit values + return YUV16(static_cast(y * max_value), static_cast(u * max_value), + static_cast(v * max_value)); +} + +constexpr RGB16 YUV16::to_rgb(RecStandard rec, ColorRange range) const noexcept +{ + const auto coeff = ConversionCoefficients::get(rec); + const auto scaling = RangeScaling::get(range); + + // Normalize YUV values to [0,1] + double y_norm = static_cast(y) / max_value; + double u_norm = static_cast(u) / max_value; + double v_norm = static_cast(v) / max_value; + + // Rescale from target range to [0,1] + y_norm = (y_norm - scaling.y_min) / (scaling.y_max - scaling.y_min); + u_norm = (u_norm - (scaling.c_max + scaling.c_min) / 2.0) / (scaling.c_max - scaling.c_min); + v_norm = (v_norm - (scaling.c_max + scaling.c_min) / 2.0) / (scaling.c_max - scaling.c_min); + + // Convert to RGB + const double r = y_norm + 2.0 * v_norm * (1.0 - coeff.kr); + const double g = y_norm - 2.0 * u_norm * (1.0 - coeff.kb) * coeff.kb / coeff.kg - + 2.0 * v_norm * (1.0 - coeff.kr) * coeff.kr / coeff.kg; + const double b = y_norm + 2.0 * u_norm * (1.0 - coeff.kb); + + // Clamp and convert back to 16-bit values + return RGB16(static_cast(std::clamp(r, 0.0, 1.0) * max_value), + static_cast(std::clamp(g, 0.0, 1.0) * max_value), + static_cast(std::clamp(b, 0.0, 1.0) * max_value)); +} + +} // namespace kms diff --git a/kms++util/inc/kms++util/kms++util.h b/kms++util/inc/kms++util/kms++util.h index 52b6ce9..f046a78 100644 --- a/kms++util/inc/kms++util/kms++util.h +++ b/kms++util/inc/kms++util/kms++util.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/kms++util/meson.build b/kms++util/meson.build index 61512ca..5dd91a7 100644 --- a/kms++util/meson.build +++ b/kms++util/meson.build @@ -19,6 +19,7 @@ libkmsxxutil_sources = files([ public_headers = [ 'inc/kms++util/color.h', + 'inc/kms++util/color16.h', 'inc/kms++util/kms++util.h', 'inc/kms++util/stopwatch.h', 'inc/kms++util/cpuframebuffer.h', -- cgit v1.2.3