From f28b052590ddc4590855e60e820a56639a23b6ac Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Fri, 20 Jan 2023 14:37:25 -0500 Subject: [PATCH] LibGfx: Add scaffolding for reading ICC tag table The idea is that we'll have one type for each tag type. For now, this treats all tag types as unknown, but it puts most of the infrastructure for reading tags in place. --- Userland/Libraries/LibGfx/ICCProfile.cpp | 81 +++++++++++++++++++++++- Userland/Libraries/LibGfx/ICCProfile.h | 61 ++++++++++++++++++ Userland/Utilities/icc.cpp | 7 ++ 3 files changed, 146 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibGfx/ICCProfile.cpp b/Userland/Libraries/LibGfx/ICCProfile.cpp index 22d8b57e21..36f5ed9d69 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.cpp +++ b/Userland/Libraries/LibGfx/ICCProfile.cpp @@ -115,7 +115,17 @@ struct ICCHeader { u8 reserved[28]; }; static_assert(sizeof(ICCHeader) == 128); +} +// ICC V4, 7.3 Tag table, Table 24 - Tag table structure +struct Detail::TagTableEntry { + BigEndian tag_signature; + BigEndian offset_to_beginning_of_tag_data_element; + BigEndian size_of_tag_data_element; +}; +static_assert(sizeof(Detail::TagTableEntry) == 12); + +namespace { ErrorOr parse_size(ICCHeader const& header, ReadonlyBytes icc_bytes) { // ICC v4, 7.2.2 Profile size field @@ -539,14 +549,79 @@ ErrorOr Profile::read_header(ReadonlyBytes bytes) return {}; } +ErrorOr> Profile::read_tag(ReadonlyBytes bytes, Detail::TagTableEntry const& entry) +{ + if (entry.offset_to_beginning_of_tag_data_element + entry.size_of_tag_data_element > bytes.size()) + return Error::from_string_literal("ICC::Profile: Tag data out of bounds"); + + auto tag_bytes = bytes.slice(entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element); + + // ICC v4, 9 Tag definitions + // ICC v4, 9.1 General + // "All tags, including private tags, have as their first four bytes a tag signature to identify to profile readers + // what kind of data is contained within a tag." + if (tag_bytes.size() < sizeof(u32)) + return Error::from_string_literal("ICC::Profile: Not enough data for tag type"); + auto tag_type = *bit_cast const*>(tag_bytes.data()); + + switch ((u32)(TagTypeSignature)tag_type) { + default: + // FIXME: optionally ignore tags of unknown type + return adopt_ref(*new UnknownTagData(entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element, tag_type)); + } +} + +ErrorOr Profile::read_tag_table(ReadonlyBytes bytes) +{ + // ICC v4, 7.3 Tag table + // ICC v4, 7.3.1 Overview + // "The tag table acts as a table of contents for the tags and an index into the tag data element in the profiles. It + // shall consist of a 4-byte entry that contains a count of the number of tags in the table followed by a series of 12- + // byte entries with one entry for each tag. The tag table therefore contains 4+12n bytes where n is the number of + // tags contained in the profile. The entries for the tags within the table are not required to be in any particular + // order nor are they required to match the sequence of tag data element within the profile. + // Each 12-byte tag entry following the tag count shall consist of a 4-byte tag signature, a 4-byte offset to define + // the beginning of the tag data element, and a 4-byte entry identifying the length of the tag data element in bytes. + // [...] + // The tag table shall define a contiguous sequence of unique tag elements, with no gaps between the last byte + // of any tag data element referenced from the tag table (inclusive of any necessary additional pad bytes required + // to reach a four-byte boundary) and the byte offset of the following tag element, or the end of the file. + // Duplicate tag signatures shall not be included in the tag table. + // Tag data elements shall not partially overlap, so there shall be no part of any tag data element that falls within + // the range defined for another tag in the tag table. + // The tag table may contain multiple tags signatures that all reference the same tag data element offset, allowing + // efficient reuse of tag data elements. In such cases, both the offset and size of the tag data elements in the tag + // table shall be the same." + + ReadonlyBytes tag_table_bytes = bytes.slice(sizeof(ICCHeader)); + + if (tag_table_bytes.size() < sizeof(u32)) + return Error::from_string_literal("ICC::Profile: Not enough data for tag count"); + auto tag_count = *bit_cast const*>(tag_table_bytes.data()); + + tag_table_bytes = tag_table_bytes.slice(sizeof(u32)); + if (tag_table_bytes.size() < tag_count * sizeof(Detail::TagTableEntry)) + return Error::from_string_literal("ICC::Profile: Not enough data for tag table entries"); + auto tag_table_entries = bit_cast(tag_table_bytes.data()); + + for (u32 i = 0; i < tag_count; ++i) { + // FIXME: optionally ignore tags with unknown signature + // FIXME: dedupe identical offset/sizes + auto tag_data = TRY(read_tag(bytes, tag_table_entries[i])); + // "Duplicate tag signatures shall not be included in the tag table." + if (TRY(m_tag_table.try_set(tag_table_entries[i].tag_signature, move(tag_data))) != AK::HashSetResult::InsertedNewEntry) + return Error::from_string_literal("ICC::Profile: duplicate tag signature"); + } + + return {}; +} + ErrorOr> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes) { auto profile = adopt_ref(*new Profile()); TRY(profile->read_header(bytes)); - bytes = bytes.trim(profile->on_disk_size()); - bytes = bytes.slice(sizeof(ICCHeader)); - // FIXME: Read tag table. + TRY(profile->read_tag_table(bytes)); return profile; } diff --git a/Userland/Libraries/LibGfx/ICCProfile.h b/Userland/Libraries/LibGfx/ICCProfile.h index 7c997a4cb6..7771f5d4fc 100644 --- a/Userland/Libraries/LibGfx/ICCProfile.h +++ b/Userland/Libraries/LibGfx/ICCProfile.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,8 @@ enum class FourCCType { DeviceManufacturer, DeviceModel, Creator, + TagSignature, + TagTypeSignature, }; template @@ -47,6 +50,8 @@ using PreferredCMMType = DistinctFourCC; // IC using DeviceManufacturer = DistinctFourCC; // ICC v4, "7.2.12 Device manufacturer field" using DeviceModel = DistinctFourCC; // ICC v4, "7.2.13 Device model field" using Creator = DistinctFourCC; // ICC v4, "7.2.17 Profile creator field" +using TagSignature = DistinctFourCC; // ICC v4, "9.2 Tag listing" +using TagTypeSignature = DistinctFourCC; // ICC v4, "10 Tag type definitions" // ICC v4, 7.2.4 Profile version field class Version { @@ -219,6 +224,38 @@ struct XYZ { double z { 0 }; }; +class TagData : public RefCounted { +public: + u32 offset() const { return m_offset; } + u32 size() const { return m_size; } + TagTypeSignature type() const { return m_type; } + +protected: + TagData(u32 offset, u32 size, TagTypeSignature type) + : m_offset(offset) + , m_size(size) + , m_type(type) + { + } + +private: + u32 m_offset; + u32 m_size; + TagTypeSignature m_type; +}; + +class UnknownTagData : public TagData { +public: + UnknownTagData(u32 offset, u32 size, TagTypeSignature type) + : TagData(offset, size, type) + { + } +}; + +namespace Detail { +struct TagTableEntry; +} + class Profile : public RefCounted { public: static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes); @@ -245,8 +282,17 @@ public: static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes); + template + void for_each_tag(Callback callback) const + { + for (auto const& tag : m_tag_table) + callback(tag.key, tag.value); + } + private: ErrorOr read_header(ReadonlyBytes); + ErrorOr> read_tag(ReadonlyBytes, Detail::TagTableEntry const&); + ErrorOr read_tag_table(ReadonlyBytes); u32 m_on_disk_size { 0 }; Optional m_preferred_cmm_type; @@ -264,6 +310,8 @@ private: XYZ m_pcs_illuminant; Optional m_creator; Optional m_id; + + OrderedHashMap> m_tag_table; }; } @@ -298,4 +346,17 @@ struct Formatter : Formatter { return Formatter::format(builder, "X = {}, Y = {}, Z = {}"sv, xyz.x, xyz.y, xyz.z); } }; + +template +struct Traits> : public GenericTraits> { + static unsigned hash(Gfx::ICC::DistinctFourCC const& key) + { + return int_hash(key.value); + } + + static bool equals(Gfx::ICC::DistinctFourCC const& a, Gfx::ICC::DistinctFourCC const& b) + { + return a == b; + } +}; } diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp index 5e22e04e60..1543c746a9 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -73,5 +73,12 @@ ErrorOr serenity_main(Main::Arguments arguments) outln("{} trailing bytes after profile data", profile_disk_size - profile->on_disk_size()); } + outln(""); + + outln("tags:"); + profile->for_each_tag([](auto tag_signature, auto tag_data) { + outln("{}: {}, offset {}, size {}", tag_signature, tag_data->type(), tag_data->offset(), tag_data->size()); + }); + return 0; }