diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index ebef73ded5..1cc07743a6 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1227,7 +1227,7 @@ ErrorOr Profile::to_pcs_a_to_b(TagData const& tag_data, ReadonlyBy if (a_to_b.number_of_output_channels() != number_of_components_in_color_space(connection_space())) return Error::from_string_literal("ICC::Profile::to_pcs_a_to_b: mAB output channel count does not match profile connection space size"); - return a_to_b.evaluate(color); + return a_to_b.evaluate(connection_space(), color); } } VERIFY_NOT_REACHED(); diff --git a/Userland/Libraries/LibGfx/ICC/TagTypes.h b/Userland/Libraries/LibGfx/ICC/TagTypes.h index c601383e42..6a82611047 100644 --- a/Userland/Libraries/LibGfx/ICC/TagTypes.h +++ b/Userland/Libraries/LibGfx/ICC/TagTypes.h @@ -448,7 +448,7 @@ public: Vector const& b_curves() const { return m_b_curves; } // Returns the result of the LUT pipeline for u8 inputs. - ErrorOr evaluate(ReadonlyBytes) const; + ErrorOr evaluate(ColorSpace connection_space, ReadonlyBytes) const; private: u8 m_number_of_input_channels; @@ -1023,8 +1023,9 @@ private: Vector m_xyzs; }; -inline ErrorOr LutAToBTagData::evaluate(ReadonlyBytes color_u8) const +inline ErrorOr LutAToBTagData::evaluate(ColorSpace connection_space, ReadonlyBytes color_u8) const { + VERIFY(connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB); VERIFY(number_of_input_channels() == color_u8.size()); VERIFY(number_of_output_channels() == 3); @@ -1032,6 +1033,15 @@ inline ErrorOr LutAToBTagData::evaluate(ReadonlyBytes color_u8) co // "Data are processed using these elements via the following sequence: // (“A” curves) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (“M” curves) ⇨ (matrix) ⇨ (“B” curves). + // "The domain and range of the A and B curves and CLUT are defined to consist of all real numbers between 0,0 and 1,0 inclusive. + // The first entry is located at 0,0, the last entry at 1,0, and intermediate entries are uniformly spaced using an increment of 1,0/(m-1). + // For the A and B curves, m is the number of entries in the table. For the CLUT, m is the number of grid points along each dimension. + // Since the domain and range of the tables are 0,0 to 1,0 it is necessary to convert all device values and PCSLAB values to this numeric range. + // It shall be assumed that the maximum value in each case is set to 1,0 and the minimum value to 0,0 and all intermediate values are + // linearly scaled accordingly." + // Scaling from the full range to 0..1 before a curve and then back after the curve only to scale to 0..1 again before the next curve is a no-op, + // so we only scale back to the full range at the very end of this function. + auto evaluate_curve = [](LutCurveType const& curve, float f) { VERIFY(curve->type() == CurveTagData::Type || curve->type() == ParametricCurveTagData::Type); if (curve->type() == CurveTagData::Type) @@ -1087,27 +1097,29 @@ inline ErrorOr LutAToBTagData::evaluate(ReadonlyBytes color_u8) co (float)e[3] * color[0] + (float)e[4] * color[1] + (float)e[5] * color[2] + (float)e[10], (float)e[6] * color[0] + (float)e[7] * color[1] + (float)e[8] * color[2] + (float)e[11] }; - - // Mystery conversion factor! - // skcms, littlecms, and argyll all do this somewhere, but I don't understand why! - // skcms has a "TODO: understand" comment as well. - // littlecms and argyll have both comments which don't make sense to me. - // littlecms does this to the matrix profile matrices (i.e. it considers the lut pcs scale canonical). - // skcms does it to the mAB matrix (...which means it'll do something different if the matrix is missing, - // and it'll also do something different if the b curve isn't the identity). - // argyll does it in Lut_Lut2XYZ(), but I'm not clear on when that's called. - // SampleICC does it in IccCmm.cpp, XYZScale() and in IccUtil.cpp, icXyzFromPcs(). - // Without this, colors are too bright. So let's do it too, and maybe I'll understand it one day. - new_color *= 65535 / 32768.f; // ??? - color = new_color.clamped(0.f, 1.f); } - return FloatVector3 { + FloatVector3 output_color { evaluate_curve(m_b_curves[0], color[0]), evaluate_curve(m_b_curves[1], color[1]), evaluate_curve(m_b_curves[2], color[2]) }; + + // ICC v4, 6.3.4.2 General PCS encoding + if (connection_space == ColorSpace::PCSXYZ) { + // Table 11 - PCSXYZ X, Y or Z encoding + output_color *= 65535 / 32768.0f; + } else { + VERIFY(connection_space == ColorSpace::PCSLAB); + // 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; } }