diff options
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibGfx/ICCProfile.cpp | 81 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICCProfile.h | 61 | ||||
-rw-r--r-- | 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<TagSignature> tag_signature; + BigEndian<u32> offset_to_beginning_of_tag_data_element; + BigEndian<u32> size_of_tag_data_element; +}; +static_assert(sizeof(Detail::TagTableEntry) == 12); +namespace { ErrorOr<u32> parse_size(ICCHeader const& header, ReadonlyBytes icc_bytes) { // ICC v4, 7.2.2 Profile size field @@ -539,14 +549,79 @@ ErrorOr<void> Profile::read_header(ReadonlyBytes bytes) return {}; } +ErrorOr<NonnullRefPtr<TagData>> 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<BigEndian<TagTypeSignature> 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<void> 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<BigEndian<u32> 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<Detail::TagTableEntry const*>(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<NonnullRefPtr<Profile>> 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 <AK/Error.h> #include <AK/Format.h> +#include <AK/HashMap.h> #include <AK/NonnullRefPtr.h> #include <AK/RefCounted.h> #include <AK/Span.h> @@ -23,6 +24,8 @@ enum class FourCCType { DeviceManufacturer, DeviceModel, Creator, + TagSignature, + TagTypeSignature, }; template<FourCCType type> @@ -47,6 +50,8 @@ using PreferredCMMType = DistinctFourCC<FourCCType::PreferredCMMType>; // IC using DeviceManufacturer = DistinctFourCC<FourCCType::DeviceManufacturer>; // ICC v4, "7.2.12 Device manufacturer field" using DeviceModel = DistinctFourCC<FourCCType::DeviceModel>; // ICC v4, "7.2.13 Device model field" using Creator = DistinctFourCC<FourCCType::Creator>; // ICC v4, "7.2.17 Profile creator field" +using TagSignature = DistinctFourCC<FourCCType::TagSignature>; // ICC v4, "9.2 Tag listing" +using TagTypeSignature = DistinctFourCC<FourCCType::TagTypeSignature>; // 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<TagData> { +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<Profile> { public: static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes); @@ -245,8 +282,17 @@ public: static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes); + template<typename Callback> + void for_each_tag(Callback callback) const + { + for (auto const& tag : m_tag_table) + callback(tag.key, tag.value); + } + private: ErrorOr<void> read_header(ReadonlyBytes); + ErrorOr<NonnullRefPtr<TagData>> read_tag(ReadonlyBytes, Detail::TagTableEntry const&); + ErrorOr<void> read_tag_table(ReadonlyBytes); u32 m_on_disk_size { 0 }; Optional<PreferredCMMType> m_preferred_cmm_type; @@ -264,6 +310,8 @@ private: XYZ m_pcs_illuminant; Optional<Creator> m_creator; Optional<Crypto::Hash::MD5::DigestType> m_id; + + OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> m_tag_table; }; } @@ -298,4 +346,17 @@ struct Formatter<Gfx::ICC::XYZ> : Formatter<FormatString> { return Formatter<FormatString>::format(builder, "X = {}, Y = {}, Z = {}"sv, xyz.x, xyz.y, xyz.z); } }; + +template<Gfx::ICC::FourCCType Type> +struct Traits<Gfx::ICC::DistinctFourCC<Type>> : public GenericTraits<Gfx::ICC::DistinctFourCC<Type>> { + static unsigned hash(Gfx::ICC::DistinctFourCC<Type> const& key) + { + return int_hash(key.value); + } + + static bool equals(Gfx::ICC::DistinctFourCC<Type> const& a, Gfx::ICC::DistinctFourCC<Type> 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<int> 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; } |