Skip to content

Commit 5d443c8

Browse files
committed
LibGfx/ICC: Implement coverting from PCS to Lut8TagData
Makes this work: % echo 'pcslab(100, -128, 127)' | \ Build/lagom/bin/icc -n LAB --stdin-u8-from-pcs 255, 0, 255 Also makes it possible to write a roundtrip test for the IdentityLAB() profile, so do so.
1 parent 14384be commit 5d443c8

File tree

3 files changed

+102
-6
lines changed

3 files changed

+102
-6
lines changed

Tests/LibGfx/TestICCProfile.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ static void test_roundtrip(Gfx::ICC::Profile const& profile)
294294
}
295295
}
296296

297+
TEST_CASE(roundtrip_lab_mft1)
298+
{
299+
auto profile = TRY_OR_FAIL(Gfx::ICC::IdentityLAB());
300+
test_roundtrip(*profile);
301+
}
302+
297303
TEST_CASE(roundtrip_sRGB_matrix_profile)
298304
{
299305
auto profile = TRY_OR_FAIL(Gfx::ICC::sRGB());

Userland/Libraries/LibGfx/ICC/Profile.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,9 +1494,10 @@ ErrorOr<void> Profile::from_pcs_b_to_a(TagData const& tag_data, FloatVector3 con
14941494
case Lut16TagData::Type:
14951495
// FIXME
14961496
return Error::from_string_literal("ICC::Profile::to_pcs: BToA*Tag handling for mft2 tags not yet implemented");
1497-
case Lut8TagData::Type:
1498-
// FIXME
1499-
return Error::from_string_literal("ICC::Profile::to_pcs: BToA*Tag handling for mft1 tags not yet implemented");
1497+
case Lut8TagData::Type: {
1498+
auto const& a_to_b = static_cast<Lut8TagData const&>(tag_data);
1499+
return a_to_b.evaluate_from_pcs(connection_space(), data_color_space(), pcs, out_bytes);
1500+
}
15001501
case LutBToATagData::Type: {
15011502
auto const& b_to_a = static_cast<LutBToATagData const&>(tag_data);
15021503
if (b_to_a.number_of_input_channels() != number_of_components_in_color_space(connection_space()))

Userland/Libraries/LibGfx/ICC/TagTypes.h

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

412413
private:
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+
12381327
inline 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

Comments
 (0)