summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibGfx/ICCProfile.cpp81
-rw-r--r--Userland/Libraries/LibGfx/ICCProfile.h61
-rw-r--r--Userland/Utilities/icc.cpp7
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;
}