diff options
author | Nico Weber <thakis@chromium.org> | 2023-01-28 21:41:19 -0500 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2023-02-08 19:44:03 +0000 |
commit | 2ff11bac3d4042c72a645e75e5c762e346fd8343 (patch) | |
tree | a4d426501b2330f2c732d34e3834983db5363fbb /Userland/Libraries | |
parent | 913119b49223c54061806e8bd56bf9baf3342cd2 (diff) | |
download | serenity-2ff11bac3d4042c72a645e75e5c762e346fd8343.zip |
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.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibGfx/ICC/Profile.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICC/TagTypes.cpp | 226 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICC/TagTypes.h | 81 |
3 files changed, 309 insertions, 2 deletions
diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 9a37d49357..f7d1537db7 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -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: diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp index c7a7635093..8bbcb4f762 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp @@ -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 diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index fc4c1b0060..7096643fd6 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -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 |