Skip to content

Commit f28b052

Browse files
nicolinusg
authored andcommitted
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.
1 parent 5017d8f commit f28b052

File tree

3 files changed

+146
-3
lines changed

3 files changed

+146
-3
lines changed

Userland/Libraries/LibGfx/ICCProfile.cpp

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,17 @@ struct ICCHeader {
115115
u8 reserved[28];
116116
};
117117
static_assert(sizeof(ICCHeader) == 128);
118+
}
119+
120+
// ICC V4, 7.3 Tag table, Table 24 - Tag table structure
121+
struct Detail::TagTableEntry {
122+
BigEndian<TagSignature> tag_signature;
123+
BigEndian<u32> offset_to_beginning_of_tag_data_element;
124+
BigEndian<u32> size_of_tag_data_element;
125+
};
126+
static_assert(sizeof(Detail::TagTableEntry) == 12);
118127

128+
namespace {
119129
ErrorOr<u32> parse_size(ICCHeader const& header, ReadonlyBytes icc_bytes)
120130
{
121131
// ICC v4, 7.2.2 Profile size field
@@ -539,14 +549,79 @@ ErrorOr<void> Profile::read_header(ReadonlyBytes bytes)
539549
return {};
540550
}
541551

552+
ErrorOr<NonnullRefPtr<TagData>> Profile::read_tag(ReadonlyBytes bytes, Detail::TagTableEntry const& entry)
553+
{
554+
if (entry.offset_to_beginning_of_tag_data_element + entry.size_of_tag_data_element > bytes.size())
555+
return Error::from_string_literal("ICC::Profile: Tag data out of bounds");
556+
557+
auto tag_bytes = bytes.slice(entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element);
558+
559+
// ICC v4, 9 Tag definitions
560+
// ICC v4, 9.1 General
561+
// "All tags, including private tags, have as their first four bytes a tag signature to identify to profile readers
562+
// what kind of data is contained within a tag."
563+
if (tag_bytes.size() < sizeof(u32))
564+
return Error::from_string_literal("ICC::Profile: Not enough data for tag type");
565+
auto tag_type = *bit_cast<BigEndian<TagTypeSignature> const*>(tag_bytes.data());
566+
567+
switch ((u32)(TagTypeSignature)tag_type) {
568+
default:
569+
// FIXME: optionally ignore tags of unknown type
570+
return adopt_ref(*new UnknownTagData(entry.offset_to_beginning_of_tag_data_element, entry.size_of_tag_data_element, tag_type));
571+
}
572+
}
573+
574+
ErrorOr<void> Profile::read_tag_table(ReadonlyBytes bytes)
575+
{
576+
// ICC v4, 7.3 Tag table
577+
// ICC v4, 7.3.1 Overview
578+
// "The tag table acts as a table of contents for the tags and an index into the tag data element in the profiles. It
579+
// 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-
580+
// byte entries with one entry for each tag. The tag table therefore contains 4+12n bytes where n is the number of
581+
// tags contained in the profile. The entries for the tags within the table are not required to be in any particular
582+
// order nor are they required to match the sequence of tag data element within the profile.
583+
// Each 12-byte tag entry following the tag count shall consist of a 4-byte tag signature, a 4-byte offset to define
584+
// the beginning of the tag data element, and a 4-byte entry identifying the length of the tag data element in bytes.
585+
// [...]
586+
// The tag table shall define a contiguous sequence of unique tag elements, with no gaps between the last byte
587+
// of any tag data element referenced from the tag table (inclusive of any necessary additional pad bytes required
588+
// to reach a four-byte boundary) and the byte offset of the following tag element, or the end of the file.
589+
// Duplicate tag signatures shall not be included in the tag table.
590+
// Tag data elements shall not partially overlap, so there shall be no part of any tag data element that falls within
591+
// the range defined for another tag in the tag table.
592+
// The tag table may contain multiple tags signatures that all reference the same tag data element offset, allowing
593+
// efficient reuse of tag data elements. In such cases, both the offset and size of the tag data elements in the tag
594+
// table shall be the same."
595+
596+
ReadonlyBytes tag_table_bytes = bytes.slice(sizeof(ICCHeader));
597+
598+
if (tag_table_bytes.size() < sizeof(u32))
599+
return Error::from_string_literal("ICC::Profile: Not enough data for tag count");
600+
auto tag_count = *bit_cast<BigEndian<u32> const*>(tag_table_bytes.data());
601+
602+
tag_table_bytes = tag_table_bytes.slice(sizeof(u32));
603+
if (tag_table_bytes.size() < tag_count * sizeof(Detail::TagTableEntry))
604+
return Error::from_string_literal("ICC::Profile: Not enough data for tag table entries");
605+
auto tag_table_entries = bit_cast<Detail::TagTableEntry const*>(tag_table_bytes.data());
606+
607+
for (u32 i = 0; i < tag_count; ++i) {
608+
// FIXME: optionally ignore tags with unknown signature
609+
// FIXME: dedupe identical offset/sizes
610+
auto tag_data = TRY(read_tag(bytes, tag_table_entries[i]));
611+
// "Duplicate tag signatures shall not be included in the tag table."
612+
if (TRY(m_tag_table.try_set(tag_table_entries[i].tag_signature, move(tag_data))) != AK::HashSetResult::InsertedNewEntry)
613+
return Error::from_string_literal("ICC::Profile: duplicate tag signature");
614+
}
615+
616+
return {};
617+
}
618+
542619
ErrorOr<NonnullRefPtr<Profile>> Profile::try_load_from_externally_owned_memory(ReadonlyBytes bytes)
543620
{
544621
auto profile = adopt_ref(*new Profile());
545622
TRY(profile->read_header(bytes));
546-
547623
bytes = bytes.trim(profile->on_disk_size());
548-
bytes = bytes.slice(sizeof(ICCHeader));
549-
// FIXME: Read tag table.
624+
TRY(profile->read_tag_table(bytes));
550625

551626
return profile;
552627
}

Userland/Libraries/LibGfx/ICCProfile.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <AK/Error.h>
1010
#include <AK/Format.h>
11+
#include <AK/HashMap.h>
1112
#include <AK/NonnullRefPtr.h>
1213
#include <AK/RefCounted.h>
1314
#include <AK/Span.h>
@@ -23,6 +24,8 @@ enum class FourCCType {
2324
DeviceManufacturer,
2425
DeviceModel,
2526
Creator,
27+
TagSignature,
28+
TagTypeSignature,
2629
};
2730

2831
template<FourCCType type>
@@ -47,6 +50,8 @@ using PreferredCMMType = DistinctFourCC<FourCCType::PreferredCMMType>; // IC
4750
using DeviceManufacturer = DistinctFourCC<FourCCType::DeviceManufacturer>; // ICC v4, "7.2.12 Device manufacturer field"
4851
using DeviceModel = DistinctFourCC<FourCCType::DeviceModel>; // ICC v4, "7.2.13 Device model field"
4952
using Creator = DistinctFourCC<FourCCType::Creator>; // ICC v4, "7.2.17 Profile creator field"
53+
using TagSignature = DistinctFourCC<FourCCType::TagSignature>; // ICC v4, "9.2 Tag listing"
54+
using TagTypeSignature = DistinctFourCC<FourCCType::TagTypeSignature>; // ICC v4, "10 Tag type definitions"
5055

5156
// ICC v4, 7.2.4 Profile version field
5257
class Version {
@@ -219,6 +224,38 @@ struct XYZ {
219224
double z { 0 };
220225
};
221226

227+
class TagData : public RefCounted<TagData> {
228+
public:
229+
u32 offset() const { return m_offset; }
230+
u32 size() const { return m_size; }
231+
TagTypeSignature type() const { return m_type; }
232+
233+
protected:
234+
TagData(u32 offset, u32 size, TagTypeSignature type)
235+
: m_offset(offset)
236+
, m_size(size)
237+
, m_type(type)
238+
{
239+
}
240+
241+
private:
242+
u32 m_offset;
243+
u32 m_size;
244+
TagTypeSignature m_type;
245+
};
246+
247+
class UnknownTagData : public TagData {
248+
public:
249+
UnknownTagData(u32 offset, u32 size, TagTypeSignature type)
250+
: TagData(offset, size, type)
251+
{
252+
}
253+
};
254+
255+
namespace Detail {
256+
struct TagTableEntry;
257+
}
258+
222259
class Profile : public RefCounted<Profile> {
223260
public:
224261
static ErrorOr<NonnullRefPtr<Profile>> try_load_from_externally_owned_memory(ReadonlyBytes);
@@ -245,8 +282,17 @@ class Profile : public RefCounted<Profile> {
245282

246283
static Crypto::Hash::MD5::DigestType compute_id(ReadonlyBytes);
247284

285+
template<typename Callback>
286+
void for_each_tag(Callback callback) const
287+
{
288+
for (auto const& tag : m_tag_table)
289+
callback(tag.key, tag.value);
290+
}
291+
248292
private:
249293
ErrorOr<void> read_header(ReadonlyBytes);
294+
ErrorOr<NonnullRefPtr<TagData>> read_tag(ReadonlyBytes, Detail::TagTableEntry const&);
295+
ErrorOr<void> read_tag_table(ReadonlyBytes);
250296

251297
u32 m_on_disk_size { 0 };
252298
Optional<PreferredCMMType> m_preferred_cmm_type;
@@ -264,6 +310,8 @@ class Profile : public RefCounted<Profile> {
264310
XYZ m_pcs_illuminant;
265311
Optional<Creator> m_creator;
266312
Optional<Crypto::Hash::MD5::DigestType> m_id;
313+
314+
OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> m_tag_table;
267315
};
268316

269317
}
@@ -298,4 +346,17 @@ struct Formatter<Gfx::ICC::XYZ> : Formatter<FormatString> {
298346
return Formatter<FormatString>::format(builder, "X = {}, Y = {}, Z = {}"sv, xyz.x, xyz.y, xyz.z);
299347
}
300348
};
349+
350+
template<Gfx::ICC::FourCCType Type>
351+
struct Traits<Gfx::ICC::DistinctFourCC<Type>> : public GenericTraits<Gfx::ICC::DistinctFourCC<Type>> {
352+
static unsigned hash(Gfx::ICC::DistinctFourCC<Type> const& key)
353+
{
354+
return int_hash(key.value);
355+
}
356+
357+
static bool equals(Gfx::ICC::DistinctFourCC<Type> const& a, Gfx::ICC::DistinctFourCC<Type> const& b)
358+
{
359+
return a == b;
360+
}
361+
};
301362
}

Userland/Utilities/icc.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
7373
outln("{} trailing bytes after profile data", profile_disk_size - profile->on_disk_size());
7474
}
7575

76+
outln("");
77+
78+
outln("tags:");
79+
profile->for_each_tag([](auto tag_signature, auto tag_data) {
80+
outln("{}: {}, offset {}, size {}", tag_signature, tag_data->type(), tag_data->offset(), tag_data->size());
81+
});
82+
7683
return 0;
7784
}

0 commit comments

Comments
 (0)