From b232281d1510378834c7b971b441480789082727 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Thu, 9 Feb 2023 12:26:43 -0500 Subject: [PATCH] LibGfx+icc: Read chromaticityTag This isn't terribly useful. But some profiles, for example the ones at https://vpifg.com/help/icc-profiles/, do contain this tag and it seems nice to be able to dump it, just for completeness. I haven't seen any files that contain a phosphor or colorant type different from "Unknown", even for the Rec2020 profile on that page. (It has x,y coordinates that match the values required for Rec2020, but it doesn't set the phosphor or colorant type to that.) --- Userland/Libraries/LibGfx/ICC/Profile.cpp | 5 +- Userland/Libraries/LibGfx/ICC/TagTypes.cpp | 66 ++++++++++++++++++++++ Userland/Libraries/LibGfx/ICC/TagTypes.h | 40 +++++++++++++ Userland/Utilities/icc.cpp | 7 ++- 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 7afa21c88ea..9c797a5a05d 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -572,6 +572,8 @@ ErrorOr> Profile::read_tag(ReadonlyBytes bytes, u32 offse auto type = tag_type(tag_bytes); switch (type) { + case ChromaticityTagData::Type: + return ChromaticityTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); case CicpTagData::Type: return CicpTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); case CurveTagData::Type: @@ -988,7 +990,8 @@ ErrorOr Profile::check_tag_types() // ICC v4, 9.2.16 chromaticityTag // "Permitted tag types: chromaticityType" - // FIXME + if (!has_type(chromaticityTag, { ChromaticityTagData::Type }, {})) + return Error::from_string_literal("ICC::Profile: ChromaticityTagData has unexpected type"); // ICC v4, 9.2.17 cicpTag // "Permitted tag types: cicpType" diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp index bcb6e8b63f3..94d0ec70d7d 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp @@ -83,6 +83,72 @@ TagTypeSignature tag_type(ReadonlyBytes tag_bytes) return *bit_cast const*>(tag_bytes.data()); } +StringView ChromaticityTagData::phosphor_or_colorant_type_name(PhosphorOrColorantType phosphor_or_colorant_type) +{ + switch (phosphor_or_colorant_type) { + case PhosphorOrColorantType::Unknown: + return "Unknown"sv; + case PhosphorOrColorantType::ITU_R_BT_709_2: + return "ITU-R BT.709-2"sv; + case PhosphorOrColorantType::SMPTE_RP145: + return "SMPTE RP145"sv; + case PhosphorOrColorantType::EBU_Tech_3213_E: + return "EBU Tech. 3213-E"sv; + case PhosphorOrColorantType::P22: + return "P22"sv; + case PhosphorOrColorantType::P3: + return "P3"sv; + case PhosphorOrColorantType::ITU_R_BT_2020: + return "ITU-R BT.2020"sv; + } + VERIFY_NOT_REACHED(); +} + +ErrorOr> ChromaticityTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) +{ + // ICC v4, 10.2 chromaticityType + VERIFY(tag_type(bytes) == Type); + TRY(check_reserved(bytes)); + + if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16)) + return Error::from_string_literal("ICC::Profile: chromaticityType has not enough data"); + + u16 number_of_device_channels = *bit_cast const*>(bytes.data() + 8); + PhosphorOrColorantType phosphor_or_colorant_type = *bit_cast const*>(bytes.data() + 10); + + switch (phosphor_or_colorant_type) { + case PhosphorOrColorantType::Unknown: + case PhosphorOrColorantType::ITU_R_BT_709_2: + case PhosphorOrColorantType::SMPTE_RP145: + case PhosphorOrColorantType::EBU_Tech_3213_E: + case PhosphorOrColorantType::P22: + case PhosphorOrColorantType::P3: + case PhosphorOrColorantType::ITU_R_BT_2020: + break; + default: + return Error::from_string_literal("ICC::Profile: chromaticityType invalid phosphor_or_colorant_type"); + } + + // "If the value is 0001h to 0004h, the number of channels shall be three..." + if (phosphor_or_colorant_type != PhosphorOrColorantType::Unknown && number_of_device_channels != 3) + return Error::from_string_literal("ICC::Profile: chromaticityType unexpected number of channels for phosphor_or_colorant_type"); + + if (bytes.size() < 2 * sizeof(u32) + 2 * sizeof(u16) + number_of_device_channels * 2 * sizeof(u16Fixed16Number)) + return Error::from_string_literal("ICC::Profile: chromaticityType has not enough data for xy coordinates"); + + auto* raw_xy_coordinates = bit_cast const*>(bytes.data() + 12); + Vector xy_coordinates; + TRY(xy_coordinates.try_resize(number_of_device_channels)); + for (size_t i = 0; i < number_of_device_channels; ++i) { + xy_coordinates[i].x = U16Fixed16::create_raw(raw_xy_coordinates[2 * i]); + xy_coordinates[i].y = U16Fixed16::create_raw(raw_xy_coordinates[2 * i + 1]); + } + + // FIXME: Once I find files that have phosphor_or_colorant_type != Unknown, check that the values match the values in Table 31. + + return adopt_ref(*new ChromaticityTagData(offset, size, phosphor_or_colorant_type, move(xy_coordinates))); +} + ErrorOr> CicpTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) { // ICC v4, 10.3 cicpType diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index b8f46aebf1f..75a6e2dd2db 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -59,6 +59,46 @@ public: } }; +// ICC v4, 10.2 chromaticityType +class ChromaticityTagData : public TagData { +public: + static constexpr TagTypeSignature Type { 0x6368726D }; // 'chrm' + + static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); + + // ICC v4, Table 31 — Colorant and phosphor encoding + enum class PhosphorOrColorantType : u16 { + Unknown = 0, + ITU_R_BT_709_2 = 1, + SMPTE_RP145 = 2, + EBU_Tech_3213_E = 3, + P22 = 4, + P3 = 5, + ITU_R_BT_2020 = 6, + }; + + static StringView phosphor_or_colorant_type_name(PhosphorOrColorantType); + + struct xyCoordinate { + U16Fixed16 x; + U16Fixed16 y; + }; + + ChromaticityTagData(u32 offset, u32 size, PhosphorOrColorantType phosphor_or_colorant_type, Vector xy_coordinates) + : TagData(offset, size, Type) + , m_phosphor_or_colorant_type(phosphor_or_colorant_type) + , m_xy_coordinates(move(xy_coordinates)) + { + } + + PhosphorOrColorantType phosphor_or_colorant_type() const { return m_phosphor_or_colorant_type; } + Vector xy_coordinates() const { return m_xy_coordinates; } + +private: + PhosphorOrColorantType m_phosphor_or_colorant_type; + Vector m_xy_coordinates; +}; + // ICC v4, 10.3 cicpType // "The cicpType specifies Coding-independent code points for video signal type identification." // See presentations at https://www.color.org/events/HDR_experts.xalter for background. diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp index 60b33391b9b..df4777da0fd 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -126,7 +126,12 @@ ErrorOr serenity_main(Main::Arguments arguments) } tag_data_to_first_signature.set(tag_data, tag_signature); - if (tag_data->type() == Gfx::ICC::CicpTagData::Type) { + if (tag_data->type() == Gfx::ICC::ChromaticityTagData::Type) { + auto& chromaticity = static_cast(*tag_data); + outln(" phosphor or colorant type: {}", Gfx::ICC::ChromaticityTagData::phosphor_or_colorant_type_name(chromaticity.phosphor_or_colorant_type())); + for (auto const& xy : chromaticity.xy_coordinates()) + outln(" x, y: {}, {}", xy.x, xy.y); + } else if (tag_data->type() == Gfx::ICC::CicpTagData::Type) { auto& cicp = static_cast(*tag_data); outln(" color primaries: {} - {}", cicp.color_primaries(), Video::color_primaries_to_string((Video::ColorPrimaries)cicp.color_primaries()));