@@ -406,8 +406,9 @@ class Lut8TagData : public TagData {
406406 Vector<u8 > const & output_tables () const { return m_output_tables; }
407407
408408 // FIXME: If we add DeviceLink support, this can become an arbitrary nD -> nD transform.
409- // For now, we only have nD -> 3D.
409+ // For now, 3D -> nD and nD -> 3D is sufficient .
410410 ErrorOr<FloatVector3> evaluate_to_pcs (ColorSpace input_space, ColorSpace connection_space, ReadonlyBytes) const ;
411+ ErrorOr<void > evaluate_from_pcs (ColorSpace connection_space, ColorSpace output_space, FloatVector3, Bytes) const ;
411412
412413private:
413414 EMatrix3x3 m_e;
@@ -1161,8 +1162,6 @@ inline ErrorOr<FloatVector3> Lut8TagData::evaluate_to_pcs(ColorSpace input_space
11611162 // See comment at start of LutAToBTagData::evaluate() for the clipping flow.
11621163 VERIFY (connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);
11631164 VERIFY (number_of_input_channels () == color_u8.size ());
1164-
1165- // FIXME: This will be wrong once Profile::from_pcs_b_to_a() calls this function too.
11661165 VERIFY (number_of_output_channels () == 3 );
11671166
11681167 // ICC v4, 10.11 lut8Type
@@ -1235,6 +1234,96 @@ inline ErrorOr<FloatVector3> Lut8TagData::evaluate_to_pcs(ColorSpace input_space
12351234 return output_color;
12361235}
12371236
1237+ inline ErrorOr<void > Lut8TagData::evaluate_from_pcs (ColorSpace connection_space, ColorSpace output_space, FloatVector3 pcs, Bytes color_u8) const
1238+ {
1239+ // This is very similar to Lut8TagData::evaluate_from_pcs(), but instead of converting from device space to PCS,
1240+ // it converts from PCS to device space.
1241+ VERIFY (connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);
1242+ VERIFY (number_of_input_channels () == 3 );
1243+ VERIFY (number_of_output_channels () == color_u8.size ());
1244+
1245+ // ICC v4, 10.11 lut8Type
1246+ // "Data is processed using these elements via the following sequence:
1247+ // (matrix) ⇨ (1d input tables) ⇨ (multi-dimensional lookup table, CLUT) ⇨ (1d output tables)"
1248+
1249+ if (connection_space == ColorSpace::PCSXYZ) {
1250+ // "An 8-bit PCSXYZ encoding has not been defined, so the interpretation of a lut8Type in a profile that uses PCSXYZ is implementation specific."
1251+ } else {
1252+ VERIFY (connection_space == ColorSpace::PCSLAB);
1253+
1254+ // ICC v4, 6.3.4.2 General PCS encoding
1255+ // Table 12 — PCSLAB L* encoding
1256+ pcs[0 ] = clamp (pcs[0 ] / 100 .0f , 0 .0f , 1 .0f );
1257+
1258+ // Table 13 — PCSLAB a* or PCSLAB b* encoding
1259+ pcs[1 ] = clamp ((pcs[1 ] + 128 .0f ) / 255 .0f , 0 .0f , 1 .0f );
1260+ pcs[2 ] = clamp ((pcs[2 ] + 128 .0f ) / 255 .0f , 0 .0f , 1 .0f );
1261+ }
1262+
1263+ // "3 x 3 matrix (which shall be the identity matrix unless the input colour space is PCSXYZ)"
1264+ // Since "An 8-bit PCSXYZ encoding has not been defined", this should never happen in practice.
1265+ if (connection_space == ColorSpace::PCSXYZ) {
1266+ EMatrix3x3 const & e = m_e;
1267+ pcs = FloatVector3 {
1268+ (float )e[0 ] * pcs[0 ] + (float )e[1 ] * pcs[1 ] + (float )e[2 ] * pcs[2 ],
1269+ (float )e[3 ] * pcs[0 ] + (float )e[4 ] * pcs[1 ] + (float )e[5 ] * pcs[2 ],
1270+ (float )e[6 ] * pcs[0 ] + (float )e[7 ] * pcs[1 ] + (float )e[8 ] * pcs[2 ],
1271+ };
1272+ }
1273+
1274+ // "The input tables are arrays of uInt8Number values. Each input table consists of 256 uInt8Number integers.
1275+ // Each input table entry is appropriately normalized to the range 0 to 255.
1276+ // The inputTable is of size (InputChannels x 256) bytes.
1277+ // When stored in this tag, the one-dimensional lookup tables are packed one after another"
1278+ for (size_t c = 0 ; c < 3 ; ++c)
1279+ pcs[c] = lerp_1d (m_input_tables.span ().slice (c * 256 , 256 ), pcs[c]) / 255 .0f ;
1280+
1281+ // "The CLUT is organized as an i-dimensional array with a given number of grid points in each dimension,
1282+ // where i is the number of input channels (input tables) in the transform.
1283+ // The dimension corresponding to the first input channel varies least rapidly and
1284+ // the dimension corresponding to the last input channel varies most rapidly.
1285+ // Each grid point value is an o-byte array, where o is the number of output channels.
1286+ // The first sequential byte of the entry contains the function value for the first output function,
1287+ // the second sequential byte of the entry contains the function value for the second output function,
1288+ // and so on until all the output functions have been supplied."
1289+ auto sample = [this ](IntVector3 const & coordinates, Span<float > out) {
1290+ size_t stride = out.size ();
1291+ size_t offset = 0 ;
1292+ for (int i = 3 - 1 ; i >= 0 ; --i) {
1293+ offset += coordinates[i] * stride;
1294+ stride *= m_number_of_clut_grid_points;
1295+ }
1296+ for (size_t c = 0 ; c < out.size (); ++c)
1297+ out[c] = (float )m_clut_values[offset + c];
1298+ };
1299+
1300+ Vector<float , 4 > scratch;
1301+ Vector<float , 4 > color;
1302+ scratch.resize (number_of_output_channels ());
1303+ color.resize (number_of_output_channels ());
1304+
1305+ lerp_nd ({ m_number_of_clut_grid_points, m_number_of_clut_grid_points, m_number_of_clut_grid_points }, move (sample), pcs, scratch.span (), color.span ());
1306+
1307+ // "The output tables are arrays of uInt8Number values. Each output table consists of 256 uInt8Number integers.
1308+ // Each output table entry is appropriately normalized to the range 0 to 255.
1309+ // The outputTable is of size (OutputChannels x 256) bytes.
1310+ // When stored in this tag, the one-dimensional lookup tables are packed one after another"
1311+ for (u8 c = 0 ; c < color.size (); ++c)
1312+ color[c] = lerp_1d (m_output_tables.span ().slice (c * 256 , 256 ), color[c] / 255 .0f ) / 255 .0f ;
1313+
1314+ // Since the LUTs assume that everything's in 0..1 and we assume that's mapped linearly to bytes,
1315+ // we don't need to look at output_space.
1316+ // 6.5 Device encoding
1317+ // "The specification of device value encoding is determined by the device. Normally, device values in the range of
1318+ // 0,0 to 1,0 are encoded using a 0 to 255 (FFh) range when using 8 bits"
1319+ (void )output_space;
1320+
1321+ for (u8 c = 0 ; c < color_u8.size (); ++c)
1322+ color_u8[c] = round_to<u8 >(clamp (color[c] * 255 .0f , 0 .0f , 255 .0f ));
1323+
1324+ return {};
1325+ }
1326+
12381327inline ErrorOr<FloatVector3> LutAToBTagData::evaluate (ColorSpace connection_space, ReadonlyBytes color_u8) const
12391328{
12401329 VERIFY (connection_space == ColorSpace::PCSXYZ || connection_space == ColorSpace::PCSLAB);
0 commit comments