diff options
-rw-r--r-- | Tests/LibGfx/TestICCProfile.cpp | 50 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICC/Profile.cpp | 53 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ICC/Profile.h | 7 |
3 files changed, 110 insertions, 0 deletions
diff --git a/Tests/LibGfx/TestICCProfile.cpp b/Tests/LibGfx/TestICCProfile.cpp index d1361a8acd..5c5386eb1b 100644 --- a/Tests/LibGfx/TestICCProfile.cpp +++ b/Tests/LibGfx/TestICCProfile.cpp @@ -153,3 +153,53 @@ TEST_CASE(to_pcs) float f192 = sRGB_curve.evaluate(192 / 255.f); EXPECT_APPROXIMATE_VECTOR3(xyz_from_sRGB(64, 128, 192), r_xyz * f64 + g_xyz * f128 + b_xyz * f192); } + +TEST_CASE(to_lab) +{ + auto sRGB = MUST(Gfx::ICC::sRGB()); + auto lab_from_sRGB = [sRGB](u8 r, u8 g, u8 b) { + u8 rgb[3] = { r, g, b }; + return MUST(sRGB->to_lab(rgb)); + }; + + // The `expected` numbers are from https://colorjs.io/notebook/ for this snippet of code: + // new Color("srgb", [0, 0, 0]).lab.toString(); + // + // new Color("srgb", [1, 0, 0]).lab.toString(); + // new Color("srgb", [0, 1, 0]).lab.toString(); + // new Color("srgb", [0, 0, 1]).lab.toString(); + // + // new Color("srgb", [1, 1, 0]).lab.toString(); + // new Color("srgb", [1, 0, 1]).lab.toString(); + // new Color("srgb", [0, 1, 1]).lab.toString(); + // + // new Color("srgb", [1, 1, 1]).lab.toString(); + + Gfx::ICC::Profile::CIELAB expected[] = { + { 0, 0, 0 }, + { 54.29054294696968, 80.80492033462421, 69.89098825896275 }, + { 87.81853633115202, -79.27108223854806, 80.99459785152247 }, + { 29.56829715344471, 68.28740665215547, -112.02971798617645 }, + { 97.60701009682253, -15.749846639252663, 93.39361164266089 }, + { 60.16894098715946, 93.53959546199253, -60.50080231921204 }, + { 90.66601315791455, -50.65651077286893, -14.961666625736525 }, + { 100.00000139649632, -0.000007807961277528364, 0.000006766250648659877 }, + }; + + // We're off by more than the default EXPECT_APPROXIMATE() error, so use EXPECT_APPROXIMATE_WITH_ERROR(). + // The difference is not too bad: ranges for L*, a*, b* are [0, 100], [-125, 125], [-125, 125], + // so this is an error of considerably less than 0.1 for u8 channels. +#define EXPECT_APPROXIMATE_LAB(l1, l2) \ + EXPECT_APPROXIMATE_WITH_ERROR((l1).L, (l2).L, 0.01); \ + EXPECT_APPROXIMATE_WITH_ERROR((l1).a, (l2).a, 0.03); \ + EXPECT_APPROXIMATE_WITH_ERROR((l1).b, (l2).b, 0.02); + + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 0, 0), expected[0]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 0, 0), expected[1]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 255, 0), expected[2]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 0, 255), expected[3]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 255, 0), expected[4]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 0, 255), expected[5]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(0, 255, 255), expected[6]); + EXPECT_APPROXIMATE_LAB(lab_from_sRGB(255, 255, 255), expected[7]); +} diff --git a/Userland/Libraries/LibGfx/ICC/Profile.cpp b/Userland/Libraries/LibGfx/ICC/Profile.cpp index 91d15a0f12..e0b4c5b44d 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.cpp +++ b/Userland/Libraries/LibGfx/ICC/Profile.cpp @@ -1470,6 +1470,59 @@ ErrorOr<FloatVector3> Profile::to_pcs(ReadonlyBytes color) VERIFY_NOT_REACHED(); } +ErrorOr<Profile::CIELAB> Profile::to_lab(ReadonlyBytes color) +{ + auto pcs = TRY(to_pcs(color)); + if (connection_space() == ColorSpace::PCSLAB) + return CIELAB { pcs[0], pcs[1], pcs[2] }; + + if (connection_space() != ColorSpace::PCSXYZ) { + VERIFY(device_class() == DeviceClass::DeviceLink); + return Error::from_string_literal("ICC::Profile::to_lab: conversion for DeviceLink not implemented"); + } + + // 6.3.2.2 Translation between media-relative colorimetric data and ICC-absolute colorimetric data + // 6.3.2.3 Computation of PCSLAB + // 6.3.4 Colour space encodings for the PCS + // A.3 PCS encodings + + auto f = [](float x) { + if (x > powf(6.f / 29.f, 3)) + return cbrtf(x); + return x / (3 * powf(6.f / 29.f, 2)) + 4.f / 29.f; + }; + + // "X/Xn is replaced by Xr/Xi (or Xa/Xmw)" + + // 6.3.2.2 Translation between media-relative colorimetric data and ICC-absolute colorimetric data + // "The translation from ICC-absolute colorimetric data to media-relative colorimetry data is given by Equations + // Xr = (Xi/Xmw) * Xa + // where + // Xr media-relative colorimetric data (i.e. PCSXYZ); + // Xa ICC-absolute colorimetric data (i.e. nCIEXYZ); + // Xmw nCIEXYZ values of the media white point as specified in the mediaWhitePointTag; + // Xi PCSXYZ values of the PCS white point defined in 6.3.4.3." + // 6.3.4.3 PCS encodings for white and black + // "Table 14 — Encodings of PCS white point: X 0,9642 Y 1,0000 Z 0,8249" + // That's identical to the values in 7.2.16 PCS illuminant field (Bytes 68 to 79). + // 9.2.36 mediaWhitePointTag + // "For displays, the values specified shall be those of the PCS illuminant as defined in 7.2.16." + // ...so for displays, this is all equivalent I think? It's maybe different for OutputDevice profiles? + + float Xn = pcs_illuminant().X; + float Yn = pcs_illuminant().Y; + float Zn = pcs_illuminant().Z; + + float x = pcs[0] / Xn; + float y = pcs[1] / Yn; + float z = pcs[2] / Zn; + + float L = 116 * f(y) - 16; + float a = 500 * (f(x) - f(y)); + float b = 200 * (f(y) - f(z)); + return CIELAB { L, a, b }; +} + XYZ const& Profile::red_matrix_column() const { return xyz_data(redMatrixColumnTag); } XYZ const& Profile::green_matrix_column() const { return xyz_data(greenMatrixColumnTag); } XYZ const& Profile::blue_matrix_column() const { return xyz_data(blueMatrixColumnTag); } diff --git a/Userland/Libraries/LibGfx/ICC/Profile.h b/Userland/Libraries/LibGfx/ICC/Profile.h index f703cc3a2f..ec0c96f17a 100644 --- a/Userland/Libraries/LibGfx/ICC/Profile.h +++ b/Userland/Libraries/LibGfx/ICC/Profile.h @@ -266,6 +266,13 @@ public: // Call connection_space() to find out the space the result is in. ErrorOr<FloatVector3> to_pcs(ReadonlyBytes); + struct CIELAB { + float L; // L* + float a; // a* + float b; // b* + }; + ErrorOr<CIELAB> to_lab(ReadonlyBytes); + // Only call these if you know that this is an RGB matrix-based profile. XYZ const& red_matrix_column() const; XYZ const& green_matrix_column() const; |