mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 00:50:22 +00:00
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.
This commit is contained in:
parent
5017d8fdcb
commit
f28b052590
Notes:
sideshowbarker
2024-07-17 01:27:16 +09:00
Author: https://github.com/nico Commit: https://github.com/SerenityOS/serenity/commit/f28b052590 Pull-request: https://github.com/SerenityOS/serenity/pull/17108 Reviewed-by: https://github.com/linusg
3 changed files with 146 additions and 3 deletions
|
@ -115,7 +115,17 @@ struct ICCHeader {
|
||||||
u8 reserved[28];
|
u8 reserved[28];
|
||||||
};
|
};
|
||||||
static_assert(sizeof(ICCHeader) == 128);
|
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)
|
ErrorOr<u32> parse_size(ICCHeader const& header, ReadonlyBytes icc_bytes)
|
||||||
{
|
{
|
||||||
// ICC v4, 7.2.2 Profile size field
|
// ICC v4, 7.2.2 Profile size field
|
||||||
|
@ -539,14 +549,79 @@ ErrorOr<void> Profile::read_header(ReadonlyBytes bytes)
|
||||||
return {};
|
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)
|
ErrorOr<NonnullRefPtr<Profile>> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes)
|
||||||
{
|
{
|
||||||
auto profile = adopt_ref(*new Profile());
|
auto profile = adopt_ref(*new Profile());
|
||||||
TRY(profile->read_header(bytes));
|
TRY(profile->read_header(bytes));
|
||||||
|
|
||||||
bytes = bytes.trim(profile->on_disk_size());
|
bytes = bytes.trim(profile->on_disk_size());
|
||||||
bytes = bytes.slice(sizeof(ICCHeader));
|
TRY(profile->read_tag_table(bytes));
|
||||||
// FIXME: Read tag table.
|
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <AK/Error.h>
|
#include <AK/Error.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
|
#include <AK/HashMap.h>
|
||||||
#include <AK/NonnullRefPtr.h>
|
#include <AK/NonnullRefPtr.h>
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <AK/Span.h>
|
#include <AK/Span.h>
|
||||||
|
@ -23,6 +24,8 @@ enum class FourCCType {
|
||||||
DeviceManufacturer,
|
DeviceManufacturer,
|
||||||
DeviceModel,
|
DeviceModel,
|
||||||
Creator,
|
Creator,
|
||||||
|
TagSignature,
|
||||||
|
TagTypeSignature,
|
||||||
};
|
};
|
||||||
|
|
||||||
template<FourCCType type>
|
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 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 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 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
|
// ICC v4, 7.2.4 Profile version field
|
||||||
class Version {
|
class Version {
|
||||||
|
@ -219,6 +224,38 @@ struct XYZ {
|
||||||
double z { 0 };
|
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> {
|
class Profile : public RefCounted<Profile> {
|
||||||
public:
|
public:
|
||||||
static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes);
|
static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes);
|
||||||
|
@ -245,8 +282,17 @@ public:
|
||||||
|
|
||||||
static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes);
|
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:
|
private:
|
||||||
ErrorOr<void> read_header(ReadonlyBytes);
|
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 };
|
u32 m_on_disk_size { 0 };
|
||||||
Optional<PreferredCMMType> m_preferred_cmm_type;
|
Optional<PreferredCMMType> m_preferred_cmm_type;
|
||||||
|
@ -264,6 +310,8 @@ private:
|
||||||
XYZ m_pcs_illuminant;
|
XYZ m_pcs_illuminant;
|
||||||
Optional<Creator> m_creator;
|
Optional<Creator> m_creator;
|
||||||
Optional<Crypto::Hash::MD5::DigestType> m_id;
|
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);
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("{} 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue