diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index b4e04b4331..8028c8b61f 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1213,12 +1213,14 @@ ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBy VERIFY(number_of_components_in_color_space(connection_space()) == 3); switch (tag_data.type()) { - case Lut16TagData::Type: - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mft2 tags not yet implemented"); - case Lut8TagData::Type: - // FIXME - return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mft1 tags not yet implemented"); + case Lut16TagData::Type: { + auto const& a_to_b = static_cast(tag_data); + return a_to_b.evaluate(data_color_space(), connection_space(), color); + } + case Lut8TagData::Type: { + auto const& a_to_b = static_cast(tag_data); + return a_to_b.evaluate(data_color_space(), connection_space(), color); + } case LutAToBTagData::Type: { auto const& a_to_b = static_cast(tag_data); if (a_to_b.number_of_input_channels() != number_of_components_in_color_space(data_color_space())) diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index f57b2b9fab..2e9cbf7e78 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -319,6 +319,8 @@ public: Vector const& clut_values() const { return m_clut_values; } Vector const& output_tables() const { return m_output_tables; } + ErrorOr evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const; + private: EMatrix3x3 m_e; @@ -376,6 +378,8 @@ public: Vector const& clut_values() const { return m_clut_values; } Vector const& output_tables() const { return m_output_tables; } + ErrorOr evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const; + private: EMatrix3x3 m_e; @@ -1030,6 +1034,173 @@ private: Vector m_xyzs; }; +inline ErrorOr Lut16TagData::evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes color_u8) const +{ + // See comment at start of LutAToBTagData::evaluate() for the clipping flow. + VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); + VERIFY(number_of_input_channels() == color_u8.size()); + + // FIXME: This will be wrong once Profile::from_pcs_b_to_a() calls this function too. + VERIFY(number_of_output_channels() == 3); + + // ICC v4, 10.11 lut8Type + // "Data is processed using these elements via the following sequence: + // (matrix) ⇨ (1d input tables) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (1d output tables)" + + Vector color; + for (u8 c : color_u8) + color.append(c / 255.0f); + + // "3 x 3 matrix (which shall be the identity matrix unless the input colour space is PCSXYZ)" + // In practice, it's usually RGB or CMYK. + if (input_space == ColorSpace::PCSXYZ) { + EMatrix3x3 const& e = m_e; + color = Vector { + (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2], + (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2], + (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2], + }; + } + + // "The input tables are arrays of 16-bit unsigned values. Each input table consists of a minimum of two and a maximum of 4096 uInt16Number integers. + // Each input table entry is appropriately normalized to the range 0 to 65535. + // The inputTable is of size (InputChannels x inputTableEntries x 2) bytes. + // When stored in this tag, the one-dimensional lookup tables are packed one after another" + for (size_t c = 0; c < color.size(); ++c) + color[c] = lerp_1d(m_input_tables.span().slice(c * m_number_of_input_table_entries, m_number_of_input_table_entries), color[c]) / 65535.0f; + + // "The CLUT is organized as an i-dimensional array with a given number of grid points in each dimension, + // where i is the number of input channels (input tables) in the transform. + // The dimension corresponding to the first input channel varies least rapidly and + // the dimension corresponding to the last input channel varies most rapidly. + // Each grid point value is an o-byte array, where o is the number of output channels. + // The first sequential byte of the entry contains the function value for the first output function, + // the second sequential byte of the entry contains the function value for the second output function, + // and so on until all the output functions have been supplied." + auto sample = [this](Vector const& coordinates) { + size_t stride = 3; + size_t offset = 0; + for (int i = coordinates.size() - 1; i >= 0; --i) { + offset += coordinates[i] * stride; + stride *= m_number_of_clut_grid_points; + } + return FloatVector3 { (float)m_clut_values[offset], (float)m_clut_values[offset + 1], (float)m_clut_values[offset + 2] }; + }; + auto size = [this](size_t) { return m_number_of_clut_grid_points; }; + FloatVector3 output_color = lerp_nd(move(size), move(sample), color) / 65535.0f; + + // "The output tables are arrays of 16-bit unsigned values. Each output table consists of a minimum of two and a maximum of 4096 uInt16Number integers. + // Each output table entry is appropriately normalized to the range 0 to 65535. + // The outputTable is of size (OutputChannels x outputTableEntries x 2) bytes. + // When stored in this tag, the one-dimensional lookup tables are packed one after another" + for (u8 c = 0; c < output_color.length(); ++c) + output_color[c] = lerp_1d(m_output_tables.span().slice(c * m_number_of_output_table_entries, m_number_of_output_table_entries), output_color[c]) / 65535.0f; + + if (connection_space == ColorSpace::PCSXYZ) { + // Table 11 - PCSXYZ X, Y or Z encoding + output_color *= 65535 / 32768.0f; + } else { + VERIFY(connection_space == ColorSpace::PCSLAB); + + // ICC v4, 10.10 lut16Type + // Note: lut16Type does _not_ use the encoding in 6.3.4.2 General PCS encoding! + + // "To convert colour values from this tag's legacy 16-bit PCSLAB encoding to the 16-bit PCSLAB encoding defined in 6.3.4.2 (Tables 12 and 13), + // multiply all values with 65 535/65 280 (i.e. FFFFh/FF00h). + // Any colour values that are in the value range of legacy 16-bit PCSLAB encoding, but not in the more recent 16-bit PCSLAB encoding, + // shall be clipped on a per-component basis." + output_color *= 65535.0f / 65280.0f; + + // Table 42 — Legacy PCSLAB L* encoding + output_color[0] = clamp(output_color[0] * 100.0f, 0.0f, 100.0f); + + // Table 43 — Legacy PCSLAB a* or PCSLAB b* encoding + output_color[1] = clamp(output_color[1] * 255.0f - 128.0f, -128.0f, 127.0f); + output_color[2] = clamp(output_color[2] * 255.0f - 128.0f, -128.0f, 127.0f); + } + + return output_color; +} + +inline ErrorOr Lut8TagData::evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes color_u8) const +{ + // See comment at start of LutAToBTagData::evaluate() for the clipping flow. + VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); + VERIFY(number_of_input_channels() == color_u8.size()); + + // FIXME: This will be wrong once Profile::from_pcs_b_to_a() calls this function too. + VERIFY(number_of_output_channels() == 3); + + // ICC v4, 10.11 lut8Type + // "Data is processed using these elements via the following sequence: + // (matrix) ⇨ (1d input tables) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (1d output tables)" + + Vector color; + for (u8 c : color_u8) + color.append(c / 255.0f); + + // "3 x 3 matrix (which shall be the identity matrix unless the input colour space is PCSXYZ)" + // In practice, it's usually RGB or CMYK. + if (input_space == ColorSpace::PCSXYZ) { + EMatrix3x3 const& e = m_e; + color = Vector { + (float)e[0] * color[0] + (float)e[1] * color[1] + (float)e[2] * color[2], + (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2], + (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2], + }; + } + + // "The input tables are arrays of uInt8Number values. Each input table consists of 256 uInt8Number integers. + // Each input table entry is appropriately normalized to the range 0 to 255. + // The inputTable is of size (InputChannels x 256) bytes. + // When stored in this tag, the one-dimensional lookup tables are packed one after another" + for (size_t c = 0; c < color.size(); ++c) + color[c] = lerp_1d(m_input_tables.span().slice(c * 256, 256), color[c]) / 255.0f; + + // "The CLUT is organized as an i-dimensional array with a given number of grid points in each dimension, + // where i is the number of input channels (input tables) in the transform. + // The dimension corresponding to the first input channel varies least rapidly and + // the dimension corresponding to the last input channel varies most rapidly. + // Each grid point value is an o-byte array, where o is the number of output channels. + // The first sequential byte of the entry contains the function value for the first output function, + // the second sequential byte of the entry contains the function value for the second output function, + // and so on until all the output functions have been supplied." + auto sample = [this](Vector const& coordinates) { + size_t stride = 3; + size_t offset = 0; + for (int i = coordinates.size() - 1; i >= 0; --i) { + offset += coordinates[i] * stride; + stride *= m_number_of_clut_grid_points; + } + return FloatVector3 { (float)m_clut_values[offset], (float)m_clut_values[offset + 1], (float)m_clut_values[offset + 2] }; + }; + auto size = [this](size_t) { return m_number_of_clut_grid_points; }; + FloatVector3 output_color = lerp_nd(move(size), move(sample), color) / 255.0f; + + // "The output tables are arrays of uInt8Number values. Each output table consists of 256 uInt8Number integers. + // Each output table entry is appropriately normalized to the range 0 to 255. + // The outputTable is of size (OutputChannels x 256) bytes. + // When stored in this tag, the one-dimensional lookup tables are packed one after another" + for (u8 c = 0; c < output_color.length(); ++c) + output_color[c] = lerp_1d(m_output_tables.span().slice(c * 256, 256), output_color[c]) / 255.0f; + + if (connection_space == ColorSpace::PCSXYZ) { + // "An 8-bit PCSXYZ encoding has not been defined, so the interpretation of a lut8Type in a profile that uses PCSXYZ is implementation specific." + } else { + VERIFY(connection_space == ColorSpace::PCSLAB); + + // ICC v4, 6.3.4.2 General PCS encoding + // Table 12 — PCSLAB L* encoding + output_color[0] *= 100.0f; + + // Table 13 — PCSLAB a* or PCSLAB b* encoding + output_color[1] = output_color[1] * 255.0f - 128.0f; + output_color[2] = output_color[2] * 255.0f - 128.0f; + } + + return output_color; +} + inline ErrorOr LutAToBTagData::evaluate(ColorSpace connection_space, ReadonlyBytes color_u8) const { VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);