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).
This commit is contained in:
Nico Weber 2023-02-07 09:49:03 -05:00 committed by Linus Groh
parent 1ce34805f8
commit cbcf8471a6
4 changed files with 176 additions and 1 deletions

View file

@ -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 headers “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"

View file

@ -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

View file

@ -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 headers PCS field."
XYZOrLAB const& pcs_coordinates(u32 index) { return m_pcs_coordinates[index]; }
// "The device representation corresponds to the headers “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:

View file

@ -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()) {