diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index e1a0d96c2d2..b3f96177e85 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -580,6 +580,8 @@ ErrorOr> 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 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 65f879a98e7..7ddf2db61d5 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp @@ -230,6 +230,7 @@ ErrorOr> MultiLocalizedUnicodeTagDat BigEndian string_length_in_bytes; BigEndian string_offset_in_bytes; }; + static_assert(AssertSize()); 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> 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 vendor_specific_flag; + BigEndian count_of_named_colors; + BigEndian 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()); + + if (bytes.size() < 2 * sizeof(u32) + sizeof(NamedColorHeader)) + return Error::from_string_literal("ICC::Profile: namedColor2Type has not enough data"); + + auto& header = *bit_cast(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 { + 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 root_names; + Vector pcs_coordinates; + Vector 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 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 NamedColor2TagData::color_name(u32 index) +{ + StringBuilder builder; + builder.append(prefix()); + builder.append(root_name(index)); + builder.append(suffix()); + return builder.to_string(); +} + ErrorOr> 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 dc3a6b22830..9fd56484c0f 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -247,6 +247,82 @@ private: Vector m_records; }; +// ICC v4, 10.17 namedColor2Type +class NamedColor2TagData : public TagData { +public: + static constexpr TagTypeSignature Type { 0x6E636C32 }; // 'ncl2' + + static ErrorOr> 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 root_names, Vector pcs_coordinates, Vector 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 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 m_root_names; + Vector m_pcs_coordinates; + Vector 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 40e4c7d969e..3f3f67107a7 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -163,6 +163,26 @@ ErrorOr 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(*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(*tag_data); switch (parametric_curve.function_type()) {