diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 25efbebb40b..f9b40aad031 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -574,6 +574,10 @@ ErrorOr> Profile::read_tag(ReadonlyBytes bytes, u32 offse switch (type) { case CurveTagData::Type: return CurveTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); + case Lut16TagData::Type: + 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 MultiLocalizedUnicodeTagData::Type: return MultiLocalizedUnicodeTagData::from_bytes(tag_bytes, offset_to_beginning_of_tag_data_element, size_of_tag_data_element); case ParametricCurveTagData::Type: diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp index 47a69330034..0c94bc4d8b3 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.cpp +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.cpp @@ -28,6 +28,16 @@ struct XYZNumber { } }; +// Common bits of ICC v4, Table 40 — lut16Type encoding and Table 44 — lut8Type encoding +struct LUTHeader { + u8 number_of_input_channels; + u8 number_of_output_channels; + u8 number_of_clut_grid_points; + u8 reserved_for_padding; + BigEndian e_parameters[9]; +}; +static_assert(AssertSize()); + ErrorOr check_reserved(ReadonlyBytes tag_bytes) { if (tag_bytes.size() < 2 * sizeof(u32)) @@ -70,6 +80,107 @@ ErrorOr> CurveTagData::from_bytes(ReadonlyBytes byte return adopt_ref(*new CurveTagData(offset, size, move(values))); } +ErrorOr> Lut16TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) +{ + // ICC v4, 10.10 lut16Type + VERIFY(tag_type(bytes) == Type); + TRY(check_reserved(bytes)); + + if (bytes.size() < 2 * sizeof(u32) + sizeof(LUTHeader) + 2 + sizeof(u16)) + return Error::from_string_literal("ICC::Profile: lut16Type has not enough data"); + + auto& header = *bit_cast(bytes.data() + 8); + if (header.reserved_for_padding != 0) + return Error::from_string_literal("ICC::Profile: lut16Type reserved_for_padding not 0"); + + u16 number_of_input_table_entries = *bit_cast const*>(bytes.data() + 8 + sizeof(LUTHeader)); + u16 number_of_output_table_entries = *bit_cast const*>(bytes.data() + 8 + sizeof(LUTHeader) + 2); + ReadonlyBytes table_bytes = bytes.slice(8 + sizeof(LUTHeader) + 4); + + EMatrix e; + for (int i = 0; i < 9; ++i) + e.e[i] = S15Fixed16::create_raw(header.e_parameters[i]); + + u32 input_tables_size = number_of_input_table_entries * header.number_of_input_channels; + u32 output_tables_size = number_of_output_table_entries * header.number_of_output_channels; + u32 clut_values_size = header.number_of_output_channels; + for (int i = 0; i < header.number_of_input_channels; ++i) + clut_values_size *= header.number_of_clut_grid_points; + + if (table_bytes.size() < (input_tables_size + clut_values_size + output_tables_size) * sizeof(u16)) + return Error::from_string_literal("ICC::Profile: lut16Type has not enough data for tables"); + + auto* raw_table_data = bit_cast const*>(table_bytes.data()); + + Vector input_tables; + input_tables.resize(input_tables_size); + for (u32 i = 0; i < input_tables_size; ++i) + input_tables[i] = raw_table_data[i]; + + Vector clut_values; + clut_values.resize(clut_values_size); + for (u32 i = 0; i < clut_values_size; ++i) + clut_values[i] = raw_table_data[input_tables_size + i]; + + Vector output_tables; + output_tables.resize(output_tables_size); + for (u32 i = 0; i < output_tables_size; ++i) + output_tables[i] = raw_table_data[input_tables_size + clut_values_size + i]; + + return adopt_ref(*new Lut16TagData(offset, size, e, + header.number_of_input_channels, header.number_of_output_channels, header.number_of_clut_grid_points, + number_of_input_table_entries, number_of_output_table_entries, + move(input_tables), move(clut_values), move(output_tables))); +} + +ErrorOr> Lut8TagData::from_bytes(ReadonlyBytes bytes, u32 offset, u32 size) +{ + // ICC v4, 10.11 lut8Type + VERIFY(tag_type(bytes) == Type); + TRY(check_reserved(bytes)); + + if (bytes.size() < 8 + sizeof(LUTHeader)) + return Error::from_string_literal("ICC::Profile: lut8Type has not enough data"); + + auto& header = *bit_cast(bytes.data() + 8); + if (header.reserved_for_padding != 0) + return Error::from_string_literal("ICC::Profile: lut16Type reserved_for_padding not 0"); + + u16 number_of_input_table_entries = 256; + u16 number_of_output_table_entries = 256; + ReadonlyBytes table_bytes = bytes.slice(8 + sizeof(LUTHeader)); + + EMatrix e; + for (int i = 0; i < 9; ++i) + e.e[i] = S15Fixed16::create_raw(header.e_parameters[i]); + + u32 input_tables_size = number_of_input_table_entries * header.number_of_input_channels; + u32 output_tables_size = number_of_output_table_entries * header.number_of_output_channels; + u32 clut_values_size = header.number_of_output_channels; + for (int i = 0; i < header.number_of_input_channels; ++i) + clut_values_size *= header.number_of_clut_grid_points; + + if (table_bytes.size() < input_tables_size + clut_values_size + output_tables_size) + return Error::from_string_literal("ICC::Profile: lut8Type has not enough data for tables"); + + Vector input_tables; + input_tables.resize(input_tables_size); + memcpy(input_tables.data(), table_bytes.data(), input_tables_size); + + Vector clut_values; + clut_values.resize(clut_values_size); + memcpy(clut_values.data(), table_bytes.data() + input_tables_size, clut_values_size); + + Vector output_tables; + output_tables.resize(output_tables_size); + memcpy(output_tables.data(), table_bytes.data() + input_tables_size + clut_values_size, output_tables_size); + + return adopt_ref(*new Lut8TagData(offset, size, e, + header.number_of_input_channels, header.number_of_output_channels, header.number_of_clut_grid_points, + number_of_input_table_entries, number_of_output_table_entries, + move(input_tables), move(clut_values), move(output_tables))); +} + ErrorOr> 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 a6b579ec4d3..a1afb083abb 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -86,6 +86,124 @@ private: Vector m_values; }; +struct EMatrix { + S15Fixed16 e[9]; + + S15Fixed16 const& operator[](int i) const + { + VERIFY(i >= 0 && i < 9); + return e[i]; + } +}; + +// ICC v4, 10.10 lut16Type +class Lut16TagData : public TagData { +public: + static constexpr TagTypeSignature Type { 0x6D667432 }; // 'mft2' + + static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); + + Lut16TagData(u32 offset, u32 size, EMatrix e, + u8 number_of_input_channels, u8 number_of_output_channels, u8 number_of_clut_grid_points, + u16 number_of_input_table_entries, u16 number_of_output_table_entries, + Vector input_tables, Vector clut_values, Vector output_tables) + : TagData(offset, size, Type) + , m_e(e) + , m_number_of_input_channels(number_of_input_channels) + , m_number_of_output_channels(number_of_output_channels) + , m_number_of_clut_grid_points(number_of_clut_grid_points) + , m_number_of_input_table_entries(number_of_input_table_entries) + , m_number_of_output_table_entries(number_of_output_table_entries) + , m_input_tables(move(input_tables)) + , m_clut_values(move(clut_values)) + , m_output_tables(move(output_tables)) + { + VERIFY(m_input_tables.size() == number_of_input_channels * number_of_input_table_entries); + VERIFY(m_output_tables.size() == number_of_output_channels * number_of_output_table_entries); + } + + EMatrix const& e_matrix() const { return m_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; } + u8 number_of_clut_grid_points() const { return m_number_of_clut_grid_points; } + + u16 number_of_input_table_entries() const { return m_number_of_input_table_entries; } + u16 number_of_output_table_entries() const { return m_number_of_output_table_entries; } + + Vector const& input_tables() const { return m_input_tables; } + Vector const& clut_values() const { return m_clut_values; } + Vector const& output_tables() const { return m_output_tables; } + +private: + EMatrix m_e; + + u8 m_number_of_input_channels; + u8 m_number_of_output_channels; + u8 m_number_of_clut_grid_points; + + u16 m_number_of_input_table_entries; + u16 m_number_of_output_table_entries; + + Vector m_input_tables; + Vector m_clut_values; + Vector m_output_tables; +}; + +// ICC v4, 10.11 lut8Type +class Lut8TagData : public TagData { +public: + static constexpr TagTypeSignature Type { 0x6D667431 }; // 'mft1' + + static ErrorOr> from_bytes(ReadonlyBytes, u32 offset, u32 size); + + Lut8TagData(u32 offset, u32 size, EMatrix e, + u8 number_of_input_channels, u8 number_of_output_channels, u8 number_of_clut_grid_points, + u16 number_of_input_table_entries, u16 number_of_output_table_entries, + Vector input_tables, Vector clut_values, Vector output_tables) + : TagData(offset, size, Type) + , m_e(e) + , m_number_of_input_channels(number_of_input_channels) + , m_number_of_output_channels(number_of_output_channels) + , m_number_of_clut_grid_points(number_of_clut_grid_points) + , m_number_of_input_table_entries(number_of_input_table_entries) + , m_number_of_output_table_entries(number_of_output_table_entries) + , m_input_tables(move(input_tables)) + , m_clut_values(move(clut_values)) + , m_output_tables(move(output_tables)) + { + VERIFY(m_input_tables.size() == number_of_input_channels * number_of_input_table_entries); + VERIFY(m_output_tables.size() == number_of_output_channels * number_of_output_table_entries); + } + + EMatrix const& e_matrix() const { return m_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; } + u8 number_of_clut_grid_points() const { return m_number_of_clut_grid_points; } + + u16 number_of_input_table_entries() const { return m_number_of_input_table_entries; } + u16 number_of_output_table_entries() const { return m_number_of_output_table_entries; } + + Vector const& input_tables() const { return m_input_tables; } + Vector const& clut_values() const { return m_clut_values; } + Vector const& output_tables() const { return m_output_tables; } + +private: + EMatrix m_e; + + u8 m_number_of_input_channels; + u8 m_number_of_output_channels; + u8 m_number_of_clut_grid_points; + + u16 m_number_of_input_table_entries; + u16 m_number_of_output_table_entries; + + Vector m_input_tables; + Vector m_clut_values; + Vector m_output_tables; +}; + // ICC v4, 10.15 multiLocalizedUnicodeType class MultiLocalizedUnicodeTagData : public TagData { public: diff --git a/Userland/Utilities/icc.cpp b/Userland/Utilities/icc.cpp index 7a98d4d7752..40e4c7d969e 100644 --- a/Userland/Utilities/icc.cpp +++ b/Userland/Utilities/icc.cpp @@ -135,6 +135,26 @@ ErrorOr serenity_main(Main::Arguments arguments) // FIXME: Maybe print the actual points if -v is passed? outln(" curve with {} points", curve.values().size()); } + } else if (tag_data->type() == Gfx::ICC::Lut16TagData::Type) { + auto& lut16 = static_cast(*tag_data); + outln(" input table: {} channels x {} entries", lut16.number_of_input_channels(), lut16.number_of_input_table_entries()); + outln(" output table: {} channels x {} entries", lut16.number_of_output_channels(), lut16.number_of_output_table_entries()); + outln(" color lookup table: {} grid points, {} total entries", lut16.number_of_clut_grid_points(), lut16.clut_values().size()); + + auto const& e = lut16.e_matrix(); + 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::Lut8TagData::Type) { + auto& lut8 = static_cast(*tag_data); + outln(" input table: {} channels x {} entries", lut8.number_of_input_channels(), lut8.number_of_input_table_entries()); + outln(" output table: {} channels x {} entries", lut8.number_of_output_channels(), lut8.number_of_output_table_entries()); + outln(" color lookup table: {} grid points, {} total entries", lut8.number_of_clut_grid_points(), lut8.clut_values().size()); + + auto const& e = lut8.e_matrix(); + 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::MultiLocalizedUnicodeTagData::Type) { auto& multi_localized_unicode = static_cast(*tag_data); for (auto& record : multi_localized_unicode.records()) {