summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2023-02-07 09:49:03 -0500
committerLinus Groh <mail@linusgroh.de>2023-02-08 16:34:24 +0000
commitcbcf8471a625b485748764886c27012ac6786570 (patch)
treecd8c1d770e1d01a146fdcad1688f21d52c661c37
parent1ce34805f81be6b5c544a7f9aa03505d1b46ff26 (diff)
downloadserenity-cbcf8471a625b485748764886c27012ac6786570.zip
LibGfx+icc: Read namedColor2Type
This is the type of namedColor2Tag, which is a required tag in NamedColor profiles. The implementation is pretty basic for now and only exposes the numbers stored in the file directly (after endian conversion).
-rw-r--r--Userland/Libraries/LibGfx/ICC/Profile.cpp11
-rw-r--r--Userland/Libraries/LibGfx/ICC/TagTypes.cpp70
-rw-r--r--Userland/Libraries/LibGfx/ICC/TagTypes.h76
-rw-r--r--Userland/Utilities/icc.cpp20
4 files changed, 176 insertions, 1 deletions
diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp
index e1a0d96c2d..b3f96177e8 100644
--- a/Userland/Libraries/LibGfx/ICC/Profile.cpp
+++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp
@@ -580,6 +580,8 @@ ErrorOr<NonnullRefPtr<TagData>> Profile::read_tag(ReadonlyBytes bytes, u32 offse
return Lut8TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case MultiLocalizedUnicodeTagData::Type:
return MultiLocalizedUnicodeTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
+ case NamedColor2TagData::Type:
+ return NamedColor2TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case ParametricCurveTagData::Type:
return ParametricCurveTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case S15Fixed16ArrayTagData::Type:
@@ -1137,7 +1139,14 @@ ErrorOr<void> Profile::check_tag_types()
// ICC v4, 9.2.37 namedColor2Tag
// "Permitted tag types: namedColor2Type"
- // FIXME
+ if (auto type = m_tag_table.get(namedColor2Tag); type.has_value()) {
+ if (type.value()->type() != NamedColor2TagData::Type)
+ return Error::from_string_literal("ICC::Profile: namedColor2Tag has unexpected type");
+ // ICC v4, 10.17 namedColor2Type
+ // "The device representation corresponds to the header’s “data colour space” field.
+ // This representation should be consistent with the “number of device coordinates” field in the namedColor2Type."
+ // FIXME: check that
+ }
// ICC v4, 9.2.38 outputResponseTag
// "Permitted tag types: responseCurveSet16Type"
diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp
index 65f879a98e..7ddf2db61d 100644
--- a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp
+++ b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp
@@ -230,6 +230,7 @@ ErrorOr<NonnullRefPtr<MultiLocalizedUnicodeTagData>> MultiLocalizedUnicodeTagDat
BigEndian<u32> string_length_in_bytes;
BigEndian<u32> string_offset_in_bytes;
};
+ static_assert(AssertSize<RawRecord, 12>());
for (u32 i = 0; i < number_of_records; ++i) {
size_t offset = 16 + i * record_size;
@@ -268,6 +269,75 @@ unsigned ParametricCurveTagData::parameter_count(FunctionType function_type)
VERIFY_NOT_REACHED();
}
+ErrorOr<NonnullRefPtr<NamedColor2TagData>> NamedColor2TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size)
+{
+ // ICC v4, 10.17 namedColor2Type
+ VERIFY(tag_type(bytes) == Type);
+ TRY(check_reserved(bytes));
+
+ // Table 66 — namedColor2Type encoding
+ struct NamedColorHeader {
+ BigEndian<u32> vendor_specific_flag;
+ BigEndian<u32> count_of_named_colors;
+ BigEndian<u32> number_of_device_coordinates_of_each_named_color;
+ u8 prefix_for_each_color_name[32]; // null-terminated
+ u8 suffix_for_each_color_name[32]; // null-terminated
+ };
+ static_assert(AssertSize<NamedColorHeader, 76>());
+
+ if (bytes.size() < 2 * sizeof(u32) + sizeof(NamedColorHeader))
+ return Error::from_string_literal("ICC::Profile: namedColor2Type has not enough data");
+
+ auto& header = *bit_cast<NamedColorHeader const*>(bytes.data() + 8);
+
+ unsigned const record_byte_size = 32 + sizeof(u16) * (3 + header.number_of_device_coordinates_of_each_named_color);
+ if (bytes.size() < 2 * sizeof(u32) + sizeof(NamedColorHeader) + header.count_of_named_colors * record_byte_size)
+ return Error::from_string_literal("ICC::Profile: namedColor2Type has not enough color data");
+
+ auto buffer_to_string = [](u8 const* buffer) -> ErrorOr<String> {
+ size_t length = strnlen((char const*)buffer, 32);
+ if (length == 32)
+ return Error::from_string_literal("ICC::Profile: namedColor2Type string not \\0-terminated");
+ for (size_t i = 0; i < length; ++i)
+ if (buffer[i] >= 128)
+ return Error::from_string_literal("ICC::Profile: namedColor2Type not 7-bit ASCII");
+ return String::from_utf8({ buffer, length });
+ };
+
+ String prefix = TRY(buffer_to_string(header.prefix_for_each_color_name));
+ String suffix = TRY(buffer_to_string(header.suffix_for_each_color_name));
+
+ Vector<String> root_names;
+ Vector<XYZOrLAB> pcs_coordinates;
+ Vector<u16> device_coordinates;
+
+ TRY(root_names.try_resize(header.count_of_named_colors));
+ TRY(pcs_coordinates.try_resize(header.count_of_named_colors));
+ TRY(device_coordinates.try_resize(header.count_of_named_colors * header.number_of_device_coordinates_of_each_named_color));
+
+ for (unsigned i = 0; i < header.count_of_named_colors; ++i) {
+ u8 const* root_name = bytes.data() + 8 + sizeof(NamedColorHeader) + i * record_byte_size;
+ auto* components = bit_cast<BigEndian<u16> const*>(root_name + 32);
+
+ root_names[i] = TRY(buffer_to_string(root_name));
+ pcs_coordinates[i] = { { { components[0], components[1], components[2] } } };
+ for (unsigned j = 0; j < header.number_of_device_coordinates_of_each_named_color; ++j)
+ device_coordinates[i * header.number_of_device_coordinates_of_each_named_color + j] = components[3 + j];
+ }
+
+ return adopt_ref(*new NamedColor2TagData(offset, size, header.vendor_specific_flag, header.number_of_device_coordinates_of_each_named_color,
+ move(prefix), move(suffix), move(root_names), move(pcs_coordinates), move(device_coordinates)));
+}
+
+ErrorOr<String> NamedColor2TagData::color_name(u32 index)
+{
+ StringBuilder builder;
+ builder.append(prefix());
+ builder.append(root_name(index));
+ builder.append(suffix());
+ return builder.to_string();
+}
+
ErrorOr<NonnullRefPtr<ParametricCurveTagData>> ParametricCurveTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size)
{
// ICC v4, 10.18 parametricCurveType
diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h
index dc3a6b2283..9fd56484c0 100644
--- a/Userland/Libraries/LibGfx/ICC/TagTypes.h
+++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h
@@ -247,6 +247,82 @@ private:
Vector<Record> m_records;
};
+// ICC v4, 10.17 namedColor2Type
+class NamedColor2TagData : public TagData {
+public:
+ static constexpr TagTypeSignature Type { 0x6E636C32 }; // 'ncl2'
+
+ static ErrorOr<NonnullRefPtr<NamedColor2TagData>> from_bytes(ReadonlyBytes, u32 offset, u32 size);
+
+ // "The encoding is the same as the encodings for the PCS colour spaces
+ // as described in 6.3.4.2 and 10.8. Only PCSXYZ and
+ // legacy 16-bit PCSLAB encodings are permitted. PCS
+ // values shall be relative colorimetric."
+ // (Which I suppose implies this type must not be used in DeviceLink profiles unless
+ // the device's PCS happens to be PCSXYZ or PCSLAB.)
+ struct XYZOrLAB {
+ union {
+ struct {
+ u16 x, y, z;
+ } xyz;
+ struct {
+ u16 L, a, b;
+ } lab;
+ };
+ };
+
+ NamedColor2TagData(u32 offset, u32 size, u32 vendor_specific_flag, u32 number_of_device_coordinates, String prefix, String suffix,
+ Vector<String> root_names, Vector<XYZOrLAB> pcs_coordinates, Vector<u16> device_coordinates)
+ : TagData(offset, size, Type)
+ , m_vendor_specific_flag(vendor_specific_flag)
+ , m_number_of_device_coordinates(number_of_device_coordinates)
+ , m_prefix(move(prefix))
+ , m_suffix(move(suffix))
+ , m_root_names(move(root_names))
+ , m_pcs_coordinates(move(pcs_coordinates))
+ , m_device_coordinates(move(device_coordinates))
+ {
+ VERIFY(root_names.size() == pcs_coordinates.size());
+ VERIFY(root_names.size() * number_of_device_coordinates == device_coordinates.size());
+ }
+
+ // "(least-significant 16 bits reserved for ICC use)"
+ u32 vendor_specific_flag() const { return m_vendor_specific_flag; }
+
+ // "If this field is 0, device coordinates are not provided."
+ u32 number_of_device_coordinates() const { return m_number_of_device_coordinates; }
+
+ u32 size() { return m_root_names.size(); }
+
+ // "In order to maintain maximum portability, it is strongly recommended that
+ // special characters of the 7-bit ASCII set not be used."
+ String const& prefix() const { return m_prefix; } // "7-bit ASCII"
+ String const& suffix() const { return m_suffix; } // "7-bit ASCII"
+ String const& root_name(u32 index) const { return m_root_names[index]; } // "7-bit ASCII"
+
+ // Returns 7-bit ASCII.
+ ErrorOr<String> color_name(u32 index);
+
+ // "The PCS representation corresponds to the header’s PCS field."
+ XYZOrLAB const& pcs_coordinates(u32 index) { return m_pcs_coordinates[index]; }
+
+ // "The device representation corresponds to the header’s “data colour space” field."
+ u16 const* device_coordinates(u32 index)
+ {
+ VERIFY((index + 1) * m_number_of_device_coordinates <= m_device_coordinates.size());
+ return m_device_coordinates.data() + index * m_number_of_device_coordinates;
+ }
+
+private:
+ u32 m_vendor_specific_flag;
+ u32 m_number_of_device_coordinates;
+ String m_prefix;
+ String m_suffix;
+ Vector<String> m_root_names;
+ Vector<XYZOrLAB> m_pcs_coordinates;
+ Vector<u16> m_device_coordinates;
+};
+
// ICC v4, 10.18 parametricCurveType
class ParametricCurveTagData : public TagData {
public:
diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp
index 40e4c7d969..3f3f67107a 100644
--- a/Userland/Utilities/icc.cpp
+++ b/Userland/Utilities/icc.cpp
@@ -163,6 +163,26 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
record.iso_3166_1_country_code >> 8, record.iso_3166_1_country_code & 0xff,
record.text);
}
+ } else if (tag_data->type() == Gfx::ICC::NamedColor2TagData::Type) {
+ auto& named_colors = static_cast<Gfx::ICC::NamedColor2TagData&>(*tag_data);
+ outln(" vendor specific flag: 0x{:08x}", named_colors.vendor_specific_flag());
+ outln(" common name prefix: \"{}\"", named_colors.prefix());
+ outln(" common name suffix: \"{}\"", named_colors.suffix());
+ outln(" {} colors:", named_colors.size());
+ for (size_t i = 0; i < min(named_colors.size(), 5u); ++i) {
+ const auto& pcs = named_colors.pcs_coordinates(i);
+
+ // FIXME: Display decoded values? (See ICC v4 6.3.4.2 and 10.8.)
+ out(" \"{}\", PCS coordinates: 0x{:04x} 0x{:04x} 0x{:04x}", MUST(named_colors.color_name(i)), pcs.xyz.x, pcs.xyz.y, pcs.xyz.z);
+ if (auto number_of_device_coordinates = named_colors.number_of_device_coordinates(); number_of_device_coordinates > 0) {
+ out(", device coordinates:");
+ for (size_t j = 0; j < number_of_device_coordinates; ++j)
+ out(" 0x{:04x}", named_colors.device_coordinates(i)[j]);
+ }
+ outln();
+ }
+ if (named_colors.size() > 5u)
+ outln(" ...");
} else if (tag_data->type() == Gfx::ICC::ParametricCurveTagData::Type) {
auto& parametric_curve = static_cast<Gfx::ICC::ParametricCurveTagData&>(*tag_data);
switch (parametric_curve.function_type()) {