#pragma once #include "color-management-v1.hpp" #include #include "../../helpers/memory/Memory.hpp" #include "../../helpers/math/Math.hpp" #include #include #include #define SDR_MIN_LUMINANCE 0.2 #define SDR_MAX_LUMINANCE 80.0 #define SDR_REF_LUMINANCE 80.0 #define HDR_MIN_LUMINANCE 0.005 #define HDR_MAX_LUMINANCE 10000.0 #define HDR_REF_LUMINANCE 203.0 #define HLG_MAX_LUMINANCE 1000.0 class ITexture; namespace NColorManagement { enum eNoShader : uint8_t { CM_NS_DISABLE = 0, CM_NS_ALWAYS = 1, CM_NS_ONDEMAND = 2, CM_NS_IGNORE = 3, }; enum ePrimaries : uint8_t { CM_PRIMARIES_SRGB = 1, CM_PRIMARIES_PAL_M = 2, CM_PRIMARIES_PAL = 3, CM_PRIMARIES_NTSC = 4, CM_PRIMARIES_GENERIC_FILM = 5, CM_PRIMARIES_BT2020 = 6, CM_PRIMARIES_CIE1931_XYZ = 7, CM_PRIMARIES_DCI_P3 = 8, CM_PRIMARIES_DISPLAY_P3 = 9, CM_PRIMARIES_ADOBE_RGB = 10, }; enum eTransferFunction : uint8_t { CM_TRANSFER_FUNCTION_BT1886 = 1, CM_TRANSFER_FUNCTION_GAMMA22 = 2, CM_TRANSFER_FUNCTION_GAMMA28 = 3, CM_TRANSFER_FUNCTION_ST240 = 4, CM_TRANSFER_FUNCTION_EXT_LINEAR = 5, CM_TRANSFER_FUNCTION_LOG_100 = 6, CM_TRANSFER_FUNCTION_LOG_316 = 7, CM_TRANSFER_FUNCTION_XVYCC = 8, CM_TRANSFER_FUNCTION_SRGB = 9, CM_TRANSFER_FUNCTION_EXT_SRGB = 10, CM_TRANSFER_FUNCTION_ST2084_PQ = 11, CM_TRANSFER_FUNCTION_ST428 = 12, CM_TRANSFER_FUNCTION_HLG = 13, }; // NOTE should be ok this way. unsupported primaries/tfs must be rejected earlier. supported enum values should be in sync with proto. // might need a proper switch-case and additional INVALID enum value. inline wpColorManagerV1Primaries convertPrimaries(ePrimaries primaries) { return sc(primaries); } inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) { return sc(primaries); } inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) { return sc(tf); } inline eTransferFunction convertTransferFunction(wpColorManagerV1TransferFunction tf) { return sc(tf); } using SPCPRimaries = Hyprgraphics::SPCPRimaries; namespace NColorPrimaries { static const auto BT709 = SPCPRimaries{ .red = {.x = 0.64, .y = 0.33}, .green = {.x = 0.30, .y = 0.60}, .blue = {.x = 0.15, .y = 0.06}, .white = {.x = 0.3127, .y = 0.3290}, }; static const auto DEFAULT_PRIMARIES = BT709; static const auto PAL_M = SPCPRimaries{ .red = {.x = 0.67, .y = 0.33}, .green = {.x = 0.21, .y = 0.71}, .blue = {.x = 0.14, .y = 0.08}, .white = {.x = 0.310, .y = 0.316}, }; static const auto PAL = SPCPRimaries{ .red = {.x = 0.640, .y = 0.330}, .green = {.x = 0.290, .y = 0.600}, .blue = {.x = 0.150, .y = 0.060}, .white = {.x = 0.3127, .y = 0.3290}, }; static const auto NTSC = SPCPRimaries{ .red = {.x = 0.630, .y = 0.340}, .green = {.x = 0.310, .y = 0.595}, .blue = {.x = 0.155, .y = 0.070}, .white = {.x = 0.3127, .y = 0.3290}, }; static const auto GENERIC_FILM = SPCPRimaries{ .red = {.x = 0.243, .y = 0.692}, .green = {.x = 0.145, .y = 0.049}, .blue = {.x = 0.681, .y = 0.319}, // NOLINT(modernize-use-std-numbers) .white = {.x = 0.310, .y = 0.316}, }; static const auto BT2020 = SPCPRimaries{ .red = {.x = 0.708, .y = 0.292}, .green = {.x = 0.170, .y = 0.797}, .blue = {.x = 0.131, .y = 0.046}, .white = {.x = 0.3127, .y = 0.3290}, }; static const auto CIE1931_XYZ = SPCPRimaries{ .red = {.x = 1.0, .y = 0.0}, .green = {.x = 0.0, .y = 1.0}, .blue = {.x = 0.0, .y = 0.0}, .white = {.x = 1.0 / 3.0, .y = 1.0 / 3.0}, }; static const auto DCI_P3 = SPCPRimaries{ .red = {.x = 0.680, .y = 0.320}, .green = {.x = 0.265, .y = 0.690}, .blue = {.x = 0.150, .y = 0.060}, .white = {.x = 0.314, .y = 0.351}, }; static const auto DISPLAY_P3 = SPCPRimaries{ .red = {.x = 0.680, .y = 0.320}, .green = {.x = 0.265, .y = 0.690}, .blue = {.x = 0.150, .y = 0.060}, .white = {.x = 0.3127, .y = 0.3290}, }; static const auto ADOBE_RGB = SPCPRimaries{ .red = {.x = 0.6400, .y = 0.3300}, .green = {.x = 0.2100, .y = 0.7100}, .blue = {.x = 0.1500, .y = 0.0600}, .white = {.x = 0.3127, .y = 0.3290}, }; } struct SVCGTTable16 { uint16_t channels = 0; uint16_t entries = 0; uint16_t entrySize = 0; std::array, 3> ch; }; const SPCPRimaries& getPrimaries(ePrimaries name); std::optional rgbToXYZFromPrimaries(SPCPRimaries pr); Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW); class CPrimaries { public: static WP from(const SPCPRimaries& primaries); static WP from(const ePrimaries name); static WP from(const uint primariesId); const SPCPRimaries& value() const; uint id() const; const Hyprgraphics::CMatrix3& toXYZ() const; // toXYZ() * rgb -> xyz const Hyprgraphics::CMatrix3& convertMatrix(const WP dst) const; // convertMatrix(dst) * rgb with "this" primaries -> rgb with dst primaries private: CPrimaries(const SPCPRimaries& primaries, const uint primariesId); uint m_id; SPCPRimaries m_primaries; Hyprgraphics::CMatrix3 m_primaries2XYZ; }; struct SImageDescription { static std::expected fromICC(const std::filesystem::path& file); // std::vector rawICC; eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; float transferFunctionPower = 1.0f; bool windowsScRGB = false; bool primariesNameSet = false; ePrimaries primariesNamed = CM_PRIMARIES_SRGB; // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0) // wayland protocol expects int32_t values multiplied by 1000000 // drm expects uint16_t values multiplied by 50000 SPCPRimaries primaries, masteringPrimaries; // luminances in cd/m² // protos and drm expect min * 10000 struct SPCLuminances { float min = 0.2; // 0.2 cd/m² uint32_t max = 80; // 80 cd/m² uint32_t reference = 80; // 80 cd/m² bool operator==(const SPCLuminances& l2) const { return min == l2.min && max == l2.max && reference == l2.reference; } } luminances; struct SPCMasteringLuminances { float min = 0; uint32_t max = 0; bool operator==(const SPCMasteringLuminances& l2) const { return min == l2.min && max == l2.max; } } masteringLuminances; // Matrix data from ICC struct SICCData { bool present = false; size_t lutSize = 33; std::vector lutDataPacked; SP lutTexture; std::optional vcgt; } icc; uint32_t maxCLL = 0; uint32_t maxFALL = 0; bool operator==(const SImageDescription& d2) const { if (icc.present || d2.icc.present) return false; return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && (primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) && masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL && maxFALL == d2.maxFALL; } const SPCPRimaries& getPrimaries() const { if (primariesNameSet || primaries == SPCPRimaries{}) return NColorManagement::getPrimaries(primariesNamed); return primaries; } float getTFMinLuminance(float sdrMinLuminance = -1.0f) const { switch (transferFunction) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0; case CM_TRANSFER_FUNCTION_ST2084_PQ: case CM_TRANSFER_FUNCTION_HLG: return HDR_MIN_LUMINANCE; case CM_TRANSFER_FUNCTION_BT1886: return 0.01; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: case CM_TRANSFER_FUNCTION_XVYCC: case CM_TRANSFER_FUNCTION_EXT_SRGB: case CM_TRANSFER_FUNCTION_ST428: case CM_TRANSFER_FUNCTION_SRGB: default: return sdrMinLuminance >= 0 ? sdrMinLuminance : SDR_MIN_LUMINANCE; } }; float getTFMaxLuminance(int sdrMaxLuminance = -1) const { switch (transferFunction) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000) case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_BT1886: return 100; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: case CM_TRANSFER_FUNCTION_XVYCC: case CM_TRANSFER_FUNCTION_EXT_SRGB: case CM_TRANSFER_FUNCTION_ST428: case CM_TRANSFER_FUNCTION_SRGB: default: return sdrMaxLuminance >= 0 ? sdrMaxLuminance : SDR_MAX_LUMINANCE; } }; float getTFRefLuminance(int sdrRefLuminance = -1) const { switch (transferFunction) { case CM_TRANSFER_FUNCTION_EXT_LINEAR: case CM_TRANSFER_FUNCTION_ST2084_PQ: case CM_TRANSFER_FUNCTION_HLG: return HDR_REF_LUMINANCE; case CM_TRANSFER_FUNCTION_BT1886: return 100; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: case CM_TRANSFER_FUNCTION_XVYCC: case CM_TRANSFER_FUNCTION_EXT_SRGB: case CM_TRANSFER_FUNCTION_ST428: case CM_TRANSFER_FUNCTION_SRGB: default: return sdrRefLuminance >= 0 ? sdrRefLuminance : SDR_REF_LUMINANCE; } }; }; class CImageDescription { public: static WP from(const SImageDescription& imageDescription); static WP from(const uint32_t imageDescriptionId); WP with(const SImageDescription::SPCLuminances& luminances) const; const SImageDescription& value() const; uint32_t id() const; WP getPrimaries() const; private: CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId); uint32_t m_id = 0; uint32_t m_primariesId = 0; SImageDescription m_imageDescription; }; using PImageDescription = WP; static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB), .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, }); static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), .luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}, }); static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, .windowsScRGB = true, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, .primaries = NColorPrimaries::BT709, .luminances = {.reference = 203}, }); static const auto LINEAR_IMAGE_DESCRIPTION = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different? }