1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:04:59 +00:00

LibGfx+icc: Implement half of lutAToBType and lutBToAType

These are among the permitted tag types of ATo0Tag and BToA0Tag,
which are among the required tags of most profiles. They are the
last permitted tag types for those profiles (the other are
lut8Type or lut16Type, which are already implemented).

They are pretty chonky types though, so this only implements
support for the E matrix and the CLUT. Support for the various
curves will be in a future PR.
This commit is contained in:
Nico Weber 2023-01-28 21:41:19 -05:00 committed by Linus Groh
parent 913119b492
commit 2ff11bac3d
4 changed files with 355 additions and 2 deletions

View file

@ -580,6 +580,10 @@ ErrorOr<NonnullRefPtr<TagData>> Profile::read_tag(ReadonlyBytes bytes, u32 offse
return Lut16TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case Lut8TagData::Type:
return Lut8TagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case LutAToBTagData::Type:
return LutAToBTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element);
case LutBToATagData::Type:
return LutBToATagData::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:

View file

@ -38,6 +38,29 @@ struct LUTHeader {
};
static_assert(AssertSize<LUTHeader, 40>());
// Common bits of ICC v4, Table 45 — lutAToBType encoding and Table 47 — lutBToAType encoding
struct AdvancedLUTHeader {
u8 number_of_input_channels;
u8 number_of_output_channels;
BigEndian<u16> reserved_for_padding;
BigEndian<u32> offset_to_b_curves;
BigEndian<u32> offset_to_matrix;
BigEndian<u32> offset_to_m_curves;
BigEndian<u32> offset_to_clut;
BigEndian<u32> offset_to_a_curves;
};
static_assert(AssertSize<AdvancedLUTHeader, 24>());
// ICC v4, Table 46 — lutAToBType CLUT encoding
// ICC v4, Table 48 — lutBToAType CLUT encoding
// (They're identical.)
struct CLUTHeader {
u8 number_of_grid_points_in_dimension[16];
u8 precision_of_data_elements; // 1 for u8 entries, 2 for u16 entries.
u8 reserved_for_padding[3];
};
static_assert(AssertSize<CLUTHeader, 20>());
ErrorOr<void> check_reserved(ReadonlyBytes tag_bytes)
{
if (tag_bytes.size() < 2 * sizeof(u32))
@ -206,6 +229,209 @@ ErrorOr<NonnullRefPtr<Lut8TagData>> Lut8TagData::from_bytes(ReadonlyBytes bytes,
move(input_tables), move(clut_values), move(output_tables)));
}
static ErrorOr<CLUTData> read_clut_data(ReadonlyBytes bytes, AdvancedLUTHeader const& header)
{
// Reads a CLUT as described in ICC v4, 10.12.3 CLUT and 10.13.5 CLUT (the two sections are virtually identical).
if (header.offset_to_clut + sizeof(CLUTHeader) > bytes.size())
return Error::from_string_literal("ICC::Profile: clut out of bounds");
if (header.number_of_input_channels >= sizeof(CLUTHeader::number_of_grid_points_in_dimension))
return Error::from_string_literal("ICC::Profile: clut has too many input channels");
auto& clut_header = *bit_cast<CLUTHeader const*>(bytes.data() + header.offset_to_clut);
// "Number of grid points in each dimension. Only the first i entries are used, where i is the number of input channels."
Vector<u8, 4> number_of_grid_points_in_dimension;
TRY(number_of_grid_points_in_dimension.try_resize(header.number_of_input_channels));
for (size_t i = 0; i < header.number_of_input_channels; ++i)
number_of_grid_points_in_dimension[i] = clut_header.number_of_grid_points_in_dimension[i];
// "Unused entries shall be set to 00h."
for (size_t i = header.number_of_input_channels; i < sizeof(CLUTHeader::number_of_grid_points_in_dimension); ++i) {
if (clut_header.number_of_grid_points_in_dimension[i] != 0)
return Error::from_string_literal("ICC::Profile: unused clut grid point not 0");
}
// "Precision of data elements in bytes. Shall be either 01h or 02h."
if (clut_header.precision_of_data_elements != 1 && clut_header.precision_of_data_elements != 2)
return Error::from_string_literal("ICC::Profile: clut invalid data element precision");
// "Reserved for padding, shall be set to 0"
for (size_t i = 0; i < sizeof(CLUTHeader::reserved_for_padding); ++i) {
if (clut_header.reserved_for_padding[i] != 0)
return Error::from_string_literal("ICC::Profile: clut reserved for padding not 0");
}
// "The size of the CLUT in bytes is (nGrid1 x nGrid2 x…x nGridN) x number of output channels (o) x size of (channel component)."
u32 clut_size = header.number_of_output_channels;
for (u8 grid_size_in_dimension : number_of_grid_points_in_dimension)
clut_size *= grid_size_in_dimension;
if (header.offset_to_clut + sizeof(CLUTHeader) + clut_size * clut_header.precision_of_data_elements > bytes.size())
return Error::from_string_literal("ICC::Profile: clut data out of bounds");
if (clut_header.precision_of_data_elements == 1) {
auto* raw_values = bytes.data() + header.offset_to_clut + sizeof(CLUTHeader);
Vector<u8> values;
TRY(values.try_resize(clut_size));
for (u32 i = 0; i < clut_size; ++i)
values[i] = raw_values[i];
return CLUTData { move(number_of_grid_points_in_dimension), move(values) };
}
VERIFY(clut_header.precision_of_data_elements == 2);
auto* raw_values = bit_cast<BigEndian<u16> const*>(bytes.data() + header.offset_to_clut + sizeof(CLUTHeader));
Vector<u16> values;
TRY(values.try_resize(clut_size));
for (u32 i = 0; i < clut_size; ++i)
values[i] = raw_values[i];
return CLUTData { move(number_of_grid_points_in_dimension), move(values) };
}
ErrorOr<NonnullRefPtr<LutAToBTagData>> LutAToBTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size)
{
// ICC v4, 10.12 lutAToBType
VERIFY(tag_type(bytes) == Type);
TRY(check_reserved(bytes));
if (bytes.size() < 2 * sizeof(u32) + sizeof(AdvancedLUTHeader))
return Error::from_string_literal("ICC::Profile: lutAToBType has not enough data");
auto& header = *bit_cast<AdvancedLUTHeader const*>(bytes.data() + 8);
if (header.reserved_for_padding != 0)
return Error::from_string_literal("ICC::Profile: lutAToBType reserved_for_padding not 0");
// "Curve data elements may be shared. For example, the offsets for A, B and M curves can be identical."
// 10.12.2 “A” curves
// "There are the same number of “A” curves as there are input channels. The “A” curves may only be used when
// the CLUT is used. The curves are stored sequentially, with 00h bytes used for padding between them if needed.
// Each “A” curve is stored as an embedded curveType or a parametricCurveType (see 10.5 or 10.16). The length
// is as indicated by the convention of the respective curve type. Note that the entire tag type, including the tag
// type signature and reserved bytes, is included for each curve."
if (header.offset_to_a_curves) {
// FIXME
}
// 10.12.3 CLUT
Optional<CLUTData> clut_data;
if (header.offset_to_clut) {
clut_data = TRY(read_clut_data(bytes, header));
} else if (header.number_of_input_channels != header.number_of_output_channels) {
// "If the number of input channels does not equal the number of output channels, the CLUT shall be present."
return Error::from_string_literal("ICC::Profile: lutAToBType no CLUT despite different number of input and output channels");
}
// 10.12.4 “M” curves
// "There are the same number of “M” curves as there are output channels. The curves are stored sequentially,
// with 00h bytes used for padding between them if needed. Each “M” curve is stored as an embedded curveType
// or a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the respective
// curve type. Note that the entire tag type, including the tag type signature and reserved bytes, is included for
// each curve. The “M” curves may only be used when the matrix is used."
if (header.offset_to_m_curves) {
// FIXME
}
// 10.12.5 Matrix
// "The matrix is organized as a 3 x 4 array. The elements appear in order from e1-e12. The matrix elements are
// each s15Fixed16Numbers."
Optional<EMatrix3x4> e;
if (header.offset_to_matrix) {
if (header.offset_to_matrix + 12 * sizeof(s15Fixed16Number) > bytes.size())
return Error::from_string_literal("ICC::Profile: lutAToBType matrix out of bounds");
e = EMatrix3x4 {};
auto* raw_e = bit_cast<BigEndian<s15Fixed16Number> const*>(bytes.data() + header.offset_to_matrix);
for (int i = 0; i < 12; ++i)
e->e[i] = S15Fixed16::create_raw(raw_e[i]);
}
// 10.12.6 “B” curves
// "There are the same number of “B” curves as there are output channels. The curves are stored sequentially, with
// 00h bytes used for padding between them if needed. Each “B” curve is stored as an embedded curveType or a
// parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the respective curve
// type. Note that the entire tag type, including the tag type signature and reserved bytes, are included for each
// curve."
if (header.offset_to_b_curves) {
// FIXME
}
// FIXME: Pass curve data once it's read above.
return adopt_ref(*new LutAToBTagData(offset, size, header.number_of_input_channels, header.number_of_output_channels, move(clut_data), e));
}
ErrorOr<NonnullRefPtr<LutBToATagData>> LutBToATagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size)
{
// ICC v4, 10.13 lutBToAType
VERIFY(tag_type(bytes) == Type);
TRY(check_reserved(bytes));
if (bytes.size() < 2 * sizeof(u32) + sizeof(AdvancedLUTHeader))
return Error::from_string_literal("ICC::Profile: lutBToAType has not enough data");
auto& header = *bit_cast<AdvancedLUTHeader const*>(bytes.data() + 8);
if (header.reserved_for_padding != 0)
return Error::from_string_literal("ICC::Profile: lutBToAType reserved_for_padding not 0");
// "Curve data elements may be shared. For example, the offsets for A, B and M curves may be identical."
// 10.13.2 “B” curves
// "There are the same number of “B” curves as there are input channels. The curves are stored sequentially, with
// 00h bytes used for padding between them if needed. Each “B” curve is stored as an embedded curveType tag
// or a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the proper curve
// type. Note that the entire tag type, including the tag type signature and reserved bytes, is included for each
// curve."
if (header.offset_to_b_curves) {
// FIXME
}
// 10.13.3 Matrix
// "The matrix is organized as a 3 x 4 array. The elements of the matrix appear in the type in order from e1 to e12.
// The matrix elements are each s15Fixed16Numbers"
Optional<EMatrix3x4> e;
if (header.offset_to_matrix) {
if (header.offset_to_matrix + 12 * sizeof(s15Fixed16Number) > bytes.size())
return Error::from_string_literal("ICC::Profile: lutBToAType matrix out of bounds");
e = EMatrix3x4 {};
auto* raw_e = bit_cast<BigEndian<s15Fixed16Number> const*>(bytes.data() + header.offset_to_matrix);
for (int i = 0; i < 12; ++i)
e->e[i] = S15Fixed16::create_raw(raw_e[i]);
}
// 10.13.4 “M” curves
// "There are the same number of “M” curves as there are input channels. The curves are stored sequentially, with
// 00h bytes used for padding between them if needed. Each “M” curve is stored as an embedded curveType or
// a parametricCurveType (see 10.5 or 10.16). The length is as indicated by the convention of the proper curve
// type. Note that the entire tag type, including the tag type signature and reserved bytes, are included for each
// curve. The “M” curves may only be used when the matrix is used."
if (header.offset_to_m_curves) {
// FIXME
}
// 10.13.5 CLUT
Optional<CLUTData> clut_data;
if (header.offset_to_clut) {
clut_data = TRY(read_clut_data(bytes, header));
} else if (header.number_of_input_channels != header.number_of_output_channels) {
// "If the number of input channels does not equal the number of output channels, the CLUT shall be present."
return Error::from_string_literal("ICC::Profile: lutAToBType no CLUT despite different number of input and output channels");
}
// 10.13.6 “A” curves
// "There are the same number of “A” curves as there are output channels. The “A” curves may only be used when
// the CLUT is used. The curves are stored sequentially, with 00h bytes used for padding between them if needed.
// Each “A” curve is stored as an embedded curveType or a parametricCurveType (see 10.5 or 10.16). The length
// is as indicated by the convention of the proper curve type. Note that the entire tag type, including the tag type
// signature and reserved bytes, is included for each curve."
if (header.offset_to_a_curves) {
// FIXME
}
// FIXME: Pass curve data once it's read above.
return adopt_ref(*new LutBToATagData(offset, size, header.number_of_input_channels, header.number_of_output_channels, e, move(clut_data)));
}
ErrorOr<NonnullRefPtr<MultiLocalizedUnicodeTagData>> MultiLocalizedUnicodeTagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size)
{
// ICC v4, 10.15 multiLocalizedUnicodeType

View file

@ -122,6 +122,10 @@ private:
};
struct EMatrix3x3 {
// A row-major 3x3 matrix:
// [ e[0] e[1] e[2] ]
// [ e[3] e[4] e[5] ] * v
// ] e[6] e[7] e[8] ]
S15Fixed16 e[9];
S15Fixed16 const& operator[](unsigned i) const
@ -239,12 +243,58 @@ private:
Vector<u8> m_output_tables;
};
struct EMatrix3x4 {
// A row-major 3x3 matrix followed by a translation vector:
// [ e[0] e[1] e[2] ] [ e[9] ]
// [ e[3] e[4] e[5] ] * v + [ e[10] ]
// [ e[6] e[7] e[8] ] [ e[11] ]
S15Fixed16 e[12];
S15Fixed16 const& operator[](unsigned i) const
{
VERIFY(i < array_size(e));
return e[i];
}
};
struct CLUTData {
Vector<u8, 4> number_of_grid_points_in_dimension;
Variant<Vector<u8>, Vector<u16>> values;
};
// ICC v4, 10.12 lutAToBType
class LutAToBTagData : public TagData {
public:
static constexpr TagTypeSignature Type { 0x6D414220 }; // 'mAB '
// FIXME: Implement!
static ErrorOr<NonnullRefPtr<LutAToBTagData>> from_bytes(ReadonlyBytes, u32 offset, u32 size);
LutAToBTagData(u32 offset, u32 size, u8 number_of_input_channels, u8 number_of_output_channels, Optional<CLUTData> clut, Optional<EMatrix3x4> e)
: TagData(offset, size, Type)
, m_number_of_input_channels(number_of_input_channels)
, m_number_of_output_channels(number_of_output_channels)
, m_clut(move(clut))
, m_e(e)
{
}
u8 number_of_input_channels() const { return m_number_of_input_channels; }
u8 number_of_output_channels() const { return m_number_of_output_channels; }
Optional<CLUTData> const& clut() const { return m_clut; }
Optional<EMatrix3x4> const& e_matrix() const { return m_e; }
private:
u8 m_number_of_input_channels;
u8 m_number_of_output_channels;
// "Only the following combinations are permitted:
// - B;
// - M, Matrix, B;
// - A, CLUT, B;
// - A, CLUT, M, Matrix, B."
Optional<CLUTData> m_clut;
Optional<EMatrix3x4> m_e;
};
// ICC v4, 10.13 lutBToAType
@ -252,7 +302,34 @@ class LutBToATagData : public TagData {
public:
static constexpr TagTypeSignature Type { 0x6D424120 }; // 'mBA '
// FIXME: Implement!
static ErrorOr<NonnullRefPtr<LutBToATagData>> from_bytes(ReadonlyBytes, u32 offset, u32 size);
LutBToATagData(u32 offset, u32 size, u8 number_of_input_channels, u8 number_of_output_channels, Optional<EMatrix3x4> e, Optional<CLUTData> clut)
: TagData(offset, size, Type)
, m_number_of_input_channels(number_of_input_channels)
, m_number_of_output_channels(number_of_output_channels)
, m_e(e)
, m_clut(move(clut))
{
}
u8 number_of_input_channels() const { return m_number_of_input_channels; }
u8 number_of_output_channels() const { return m_number_of_output_channels; }
Optional<EMatrix3x4> const& e_matrix() const { return m_e; }
Optional<CLUTData> const& clut() const { return m_clut; }
private:
u8 m_number_of_input_channels;
u8 m_number_of_output_channels;
// "Only the following combinations are permitted:
// - B;
// - B, Matrix, M;
// - B, CLUT, A;
// - B, Matrix, M, CLUT, A."
Optional<EMatrix3x4> m_e;
Optional<CLUTData> m_clut;
};
// ICC v4, 10.15 multiLocalizedUnicodeType

View file

@ -165,6 +165,52 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
outln(" e = [ {}, {}, {},", e[0], e[1], e[2]);
outln(" {}, {}, {},", e[3], e[4], e[5]);
outln(" {}, {}, {} ]", e[6], e[7], e[8]);
} else if (tag_data->type() == Gfx::ICC::LutAToBTagData::Type) {
auto& a_to_b = static_cast<Gfx::ICC::LutAToBTagData&>(*tag_data);
outln(" {} input channels, {} output channels", a_to_b.number_of_input_channels(), a_to_b.number_of_output_channels());
if (auto const& optional_clut = a_to_b.clut(); optional_clut.has_value()) {
auto const& clut = optional_clut.value();
outln(" color lookup table: {} grid points, {}",
MUST(String::join(" x "sv, clut.number_of_grid_points_in_dimension)),
MUST(clut.values.visit(
[](Vector<u8> const& v) { return String::formatted("{} u8 entries", v.size()); },
[](Vector<u16> const& v) { return String::formatted("{} u16 entries", v.size()); })));
} else {
outln(" color lookup table: (not set)");
}
if (auto const& optional_e = a_to_b.e_matrix(); optional_e.has_value()) {
auto const& e = optional_e.value();
outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]);
outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]);
outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]);
} else {
outln(" e = (not set)");
}
} else if (tag_data->type() == Gfx::ICC::LutBToATagData::Type) {
auto& b_to_a = static_cast<Gfx::ICC::LutBToATagData&>(*tag_data);
outln(" {} input channels, {} output channels", b_to_a.number_of_input_channels(), b_to_a.number_of_output_channels());
if (auto const& optional_e = b_to_a.e_matrix(); optional_e.has_value()) {
auto const& e = optional_e.value();
outln(" e = [ {}, {}, {}, {},", e[0], e[1], e[2], e[9]);
outln(" {}, {}, {}, {},", e[3], e[4], e[5], e[10]);
outln(" {}, {}, {}, {} ]", e[6], e[7], e[8], e[11]);
} else {
outln(" e = (not set)");
}
if (auto const& optional_clut = b_to_a.clut(); optional_clut.has_value()) {
auto const& clut = optional_clut.value();
outln(" color lookup table: {} grid points, {}",
MUST(String::join(" x "sv, clut.number_of_grid_points_in_dimension)),
MUST(clut.values.visit(
[](Vector<u8> const& v) { return String::formatted("{} u8 entries", v.size()); },
[](Vector<u16> const& v) { return String::formatted("{} u16 entries", v.size()); })));
} else {
outln(" color lookup table: (not set)");
}
} else if (tag_data->type() == Gfx::ICC::MultiLocalizedUnicodeTagData::Type) {
auto& multi_localized_unicode = static_cast<Gfx::ICC::MultiLocalizedUnicodeTagData&>(*tag_data);
for (auto& record : multi_localized_unicode.records()) {