Skip to content

Commit 14384be

Browse files
committed
LibGfx/ICC+icc: Add a built-in identity LAB profile
This profile idenity-maps PCSLAB to CIELAB. This allows converting byte-encoded LAB values to PCS: % echo '255, 0, 255' | \ icc -n LAB --stdin-u8-to-pcs pcslab(100, -128, 127) It can also be written to a file, for other tools to use it: icc -n LAB --reencode-to serenity-LAB.icc (...or you can then run `icc serenity-LAB.icc` instead of `icc -n LAB` if you want to dump it from a file instead of from memory, for some reason.)
1 parent 582fb48 commit 14384be

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

Userland/Libraries/LibGfx/ICC/WellKnownProfiles.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,80 @@ static ErrorOr<NonnullRefPtr<XYZTagData>> XYZ_data(XYZ xyz)
3838
return try_make_ref_counted<XYZTagData>(0, 0, move(xyzs));
3939
}
4040

41+
static EMatrix3x3 identity_matrix()
42+
{
43+
return EMatrix3x3 {
44+
S15Fixed16(1.0),
45+
S15Fixed16(0.0),
46+
S15Fixed16(0.0),
47+
S15Fixed16(0.0),
48+
S15Fixed16(1.0),
49+
S15Fixed16(0.0),
50+
S15Fixed16(0.0),
51+
S15Fixed16(0.0),
52+
S15Fixed16(1.0),
53+
};
54+
}
55+
56+
template<Unsigned T>
57+
Vector<T> make_2x2x2_cube()
58+
{
59+
Vector<T> values;
60+
auto add_entry = [&values](T x, T y, T z) {
61+
values.append(x);
62+
values.append(y);
63+
values.append(z);
64+
};
65+
auto const N = NumericLimits<T>::max();
66+
add_entry(0, 0, 0);
67+
add_entry(0, 0, N);
68+
add_entry(0, N, 0);
69+
add_entry(0, N, N);
70+
add_entry(N, 0, 0);
71+
add_entry(N, 0, N);
72+
add_entry(N, N, 0);
73+
add_entry(N, N, N);
74+
return values;
75+
}
76+
77+
ErrorOr<NonnullRefPtr<Profile>> IdentityLAB()
78+
{
79+
// Identity mapping between CIELAB and PCSLAB.
80+
81+
auto header = profile_header(ColorSpace::CIELAB, ColorSpace::PCSLAB);
82+
header.device_class = DeviceClass::ColorSpace;
83+
84+
OrderedHashMap<TagSignature, NonnullRefPtr<TagData>> tag_table;
85+
86+
TRY(tag_table.try_set(profileDescriptionTag, TRY(en_US("SerenityOS Identity LAB"sv))));
87+
TRY(tag_table.try_set(copyrightTag, TRY(en_US("Public Domain"sv))));
88+
89+
// Use an identity 8-bit mft1 lookup table.
90+
auto e_matrix = identity_matrix();
91+
92+
Vector<u8> input_tables;
93+
for (int c = 0; c < 3; ++c) {
94+
// mft1 requires 256 entries.
95+
for (int i = 0; i < 256; ++i)
96+
input_tables.append(i);
97+
}
98+
99+
auto clut_values = make_2x2x2_cube<u8>();
100+
Vector<u8> output_tables = input_tables;
101+
102+
auto mft1 = TRY(try_make_ref_counted<Lut8TagData>(0, 0, e_matrix,
103+
3, 3, 2,
104+
256, 256,
105+
move(input_tables), move(clut_values), move(output_tables)));
106+
TRY(tag_table.try_set(AToB0Tag, mft1));
107+
TRY(tag_table.try_set(BToA0Tag, mft1));
108+
109+
// White point.
110+
TRY(tag_table.try_set(mediaWhitePointTag, TRY(XYZ_data(header.pcs_illuminant))));
111+
112+
return Profile::create(header, move(tag_table));
113+
}
114+
41115
ErrorOr<NonnullRefPtr<ParametricCurveTagData>> sRGB_curve()
42116
{
43117
// Numbers from https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ

Userland/Libraries/LibGfx/ICC/WellKnownProfiles.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, Nico Weber <[email protected]>
2+
* Copyright (c) 2023-2025, Nico Weber <[email protected]>
33
*
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
@@ -14,6 +14,8 @@ namespace Gfx::ICC {
1414
class Profile;
1515
class ParametricCurveTagData;
1616

17+
ErrorOr<NonnullRefPtr<Profile>> IdentityLAB();
18+
1719
ErrorOr<NonnullRefPtr<Profile>> sRGB();
1820

1921
ErrorOr<NonnullRefPtr<ParametricCurveTagData>> sRGB_curve();

Userland/Utilities/icc.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
333333
args_parser.add_positional_argument(path, "Path to ICC profile or to image containing ICC profile", "FILE", Core::ArgsParser::Required::No);
334334

335335
StringView name;
336-
args_parser.add_option(name, "Name of a built-in profile, such as 'sRGB'", "name", 'n', "NAME");
336+
args_parser.add_option(name, "Name of a built-in profile, such as 'sRGB', 'LAB'", "name", 'n', "NAME");
337337

338338
StringView dump_out_path;
339339
args_parser.add_option(dump_out_path, "Dump unmodified ICC profile bytes to this path", "dump-to", 0, "FILE");
@@ -377,6 +377,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
377377
ReadonlyBytes icc_bytes;
378378
NonnullRefPtr<Gfx::ICC::Profile> profile = TRY([&]() -> ErrorOr<NonnullRefPtr<Gfx::ICC::Profile>> {
379379
if (!name.is_empty()) {
380+
if (name == "LAB")
381+
return Gfx::ICC::IdentityLAB();
380382
if (name == "sRGB")
381383
return Gfx::ICC::sRGB();
382384
return Error::from_string_literal("unknown profile name");

0 commit comments

Comments
 (0)