mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 12:37:44 +00:00
LibGfx/ICC: Implement forward transform for mft1 and mft2 tags
mft1 and mft2 tags are very similar. The only difference is that mft1 uses an u8 lookup table, while mft2 uses a u16 lookup table. This means their PCS lookup encodings are different, and mft2 uses a PCSLAB encoding that's different from other places in the v4 spec.
This commit is contained in:
parent
b138bc0004
commit
72f5461af4
2 changed files with 179 additions and 6 deletions
|
@ -1213,12 +1213,14 @@ ErrorOr<FloatVector3> Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBy
|
||||||
VERIFY(number_of_components_in_color_space(connection_space()) == 3);
|
VERIFY(number_of_components_in_color_space(connection_space()) == 3);
|
||||||
|
|
||||||
switch (tag_data.type()) {
|
switch (tag_data.type()) {
|
||||||
case Lut16TagData::Type:
|
case Lut16TagData::Type: {
|
||||||
// FIXME
|
auto const& a_to_b = static_cast<Lut16TagData const&>(tag_data);
|
||||||
return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mft2 tags not yet implemented");
|
return a_to_b.evaluate(data_color_space(), connection_space(), color);
|
||||||
case Lut8TagData::Type:
|
}
|
||||||
// FIXME
|
case Lut8TagData::Type: {
|
||||||
return Error::from_string_literal("ICC::Profile::to_pcs: AToB*Tag handling for mft1 tags not yet implemented");
|
auto const& a_to_b = static_cast<Lut8TagData const&>(tag_data);
|
||||||
|
return a_to_b.evaluate(data_color_space(), connection_space(), color);
|
||||||
|
}
|
||||||
case LutAToBTagData::Type: {
|
case LutAToBTagData::Type: {
|
||||||
auto const& a_to_b = static_cast<LutAToBTagData const&>(tag_data);
|
auto const& a_to_b = static_cast<LutAToBTagData const&>(tag_data);
|
||||||
if (a_to_b.number_of_input_channels() != number_of_components_in_color_space(data_color_space()))
|
if (a_to_b.number_of_input_channels() != number_of_components_in_color_space(data_color_space()))
|
||||||
|
|
|
@ -319,6 +319,8 @@ public:
|
||||||
Vector<u16> const& clut_values() const { return m_clut_values; }
|
Vector<u16> const& clut_values() const { return m_clut_values; }
|
||||||
Vector<u16> const& output_tables() const { return m_output_tables; }
|
Vector<u16> const& output_tables() const { return m_output_tables; }
|
||||||
|
|
||||||
|
ErrorOr<FloatVector3> evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EMatrix3x3 m_e;
|
EMatrix3x3 m_e;
|
||||||
|
|
||||||
|
@ -376,6 +378,8 @@ public:
|
||||||
Vector<u8> const& clut_values() const { return m_clut_values; }
|
Vector<u8> const& clut_values() const { return m_clut_values; }
|
||||||
Vector<u8> const& output_tables() const { return m_output_tables; }
|
Vector<u8> const& output_tables() const { return m_output_tables; }
|
||||||
|
|
||||||
|
ErrorOr<FloatVector3> evaluate(ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EMatrix3x3 m_e;
|
EMatrix3x3 m_e;
|
||||||
|
|
||||||
|
@ -1030,6 +1034,173 @@ private:
|
||||||
Vector<XYZ, 1> m_xyzs;
|
Vector<XYZ, 1> m_xyzs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline ErrorOr<FloatVector3> 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<float, 4> 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, 4> {
|
||||||
|
(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<unsigned> 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<FloatVector3> 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<float, 4> 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, 4> {
|
||||||
|
(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<unsigned> 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<FloatVector3> LutAToBTagData::evaluate(ColorSpace connection_space, ReadonlyBytes color_u8) const
|
inline ErrorOr<FloatVector3> LutAToBTagData::evaluate(ColorSpace connection_space, ReadonlyBytes color_u8) const
|
||||||
{
|
{
|
||||||
VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);
|
VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue