mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibGfx: Add DeltaE() function
This commit is contained in:
parent
adec1abf81
commit
923027b1df
Notes:
sideshowbarker
2024-07-17 06:51:48 +09:00
Author: https://github.com/nico Commit: https://github.com/SerenityOS/serenity/commit/923027b1df Pull-request: https://github.com/SerenityOS/serenity/pull/18582
5 changed files with 173 additions and 0 deletions
|
@ -1,6 +1,7 @@
|
|||
set(TEST_SOURCES
|
||||
BenchmarkGfxPainter.cpp
|
||||
BenchmarkJPEGLoader.cpp
|
||||
TestDeltaE.cpp
|
||||
TestFontHandling.cpp
|
||||
TestICCProfile.cpp
|
||||
TestImageDecoder.cpp
|
||||
|
|
61
Tests/LibGfx/TestDeltaE.cpp
Normal file
61
Tests/LibGfx/TestDeltaE.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibGfx/DeltaE.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
TEST_CASE(delta_e)
|
||||
{
|
||||
// Test data is from https://hajim.rochester.edu/ece/sites/gsharma/ciede2000/dataNprograms/ciede2000testdata.txt,
|
||||
// linked to from https://hajim.rochester.edu/ece/sites/gsharma/ciede2000/, which is source [5] in
|
||||
// https://www.hajim.rochester.edu/ece/sites/gsharma/ciede2000/ciede2000noteCRNA.pdf
|
||||
struct {
|
||||
Gfx::CIELAB a;
|
||||
Gfx::CIELAB b;
|
||||
float expected_delta;
|
||||
} test_cases[] = {
|
||||
{ { 50.0000, 2.6772, -79.7751 }, { 50.0000, 0.0000, -82.7485 }, 2.0425 },
|
||||
{ { 50.0000, 3.1571, -77.2803 }, { 50.0000, 0.0000, -82.7485 }, 2.8615 },
|
||||
{ { 50.0000, 2.8361, -74.0200 }, { 50.0000, 0.0000, -82.7485 }, 3.4412 },
|
||||
{ { 50.0000, -1.3802, -84.2814 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 },
|
||||
{ { 50.0000, -1.1848, -84.8006 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 },
|
||||
{ { 50.0000, -0.9009, -85.5211 }, { 50.0000, 0.0000, -82.7485 }, 1.0000 },
|
||||
{ { 50.0000, 0.0000, 0.0000 }, { 50.0000, -1.0000, 2.0000 }, 2.3669 },
|
||||
{ { 50.0000, -1.0000, 2.0000 }, { 50.0000, 0.0000, 0.0000 }, 2.3669 },
|
||||
{ { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0009 }, 7.1792 },
|
||||
{ { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0010 }, 7.1792 },
|
||||
{ { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0011 }, 7.2195 },
|
||||
{ { 50.0000, 2.4900, -0.0010 }, { 50.0000, -2.4900, 0.0012 }, 7.2195 },
|
||||
{ { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0009, -2.4900 }, 4.8045 },
|
||||
{ { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0010, -2.4900 }, 4.8045 },
|
||||
{ { 50.0000, -0.0010, 2.4900 }, { 50.0000, 0.0011, -2.4900 }, 4.7461 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 50.0000, 0.0000, -2.5000 }, 4.3065 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 73.0000, 25.0000, -18.0000 }, 27.1492 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 61.0000, -5.0000, 29.0000 }, 22.8977 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 56.0000, -27.0000, -3.0000 }, 31.9030 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 58.0000, 24.0000, 15.0000 }, 19.4535 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.1736, 0.5854 }, 1.0000 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.2972, 0.0000 }, 1.0000 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 50.0000, 1.8634, 0.5757 }, 1.0000 },
|
||||
{ { 50.0000, 2.5000, 0.0000 }, { 50.0000, 3.2592, 0.3350 }, 1.0000 },
|
||||
{ { 60.2574, -34.0099, 36.2677 }, { 60.4626, -34.1751, 39.4387 }, 1.2644 },
|
||||
{ { 63.0109, -31.0961, -5.8663 }, { 62.8187, -29.7946, -4.0864 }, 1.2630 },
|
||||
{ { 61.2901, 3.7196, -5.3901 }, { 61.4292, 2.2480, -4.9620 }, 1.8731 },
|
||||
{ { 35.0831, -44.1164, 3.7933 }, { 35.0232, -40.0716, 1.5901 }, 1.8645 },
|
||||
{ { 22.7233, 20.0904, -46.6940 }, { 23.0331, 14.9730, -42.5619 }, 2.0373 },
|
||||
{ { 36.4612, 47.8580, 18.3852 }, { 36.2715, 50.5065, 21.2231 }, 1.4146 },
|
||||
{ { 90.8027, -2.0831, 1.4410 }, { 91.1528, -1.6435, 0.0447 }, 1.4441 },
|
||||
{ { 90.9257, -0.5406, -0.9208 }, { 88.6381, -0.8985, -0.7239 }, 1.5381 },
|
||||
{ { 6.7747, -0.2908, -2.4247 }, { 5.8714, -0.0985, -2.2286 }, 0.6377 },
|
||||
{ { 2.0776, 0.0795, -1.1350 }, { 0.9033, -0.0636, -0.5514 }, 0.9082 },
|
||||
};
|
||||
|
||||
for (auto const& test_case : test_cases) {
|
||||
// The inputs are given with 4 decimal digits, so we can be up to 0.00005 away just to rounding to 4 decimal digits.
|
||||
EXPECT_APPROXIMATE_WITH_ERROR(Gfx::DeltaE(test_case.a, test_case.b), test_case.expected_delta, 0.00005);
|
||||
EXPECT_APPROXIMATE_WITH_ERROR(Gfx::DeltaE(test_case.b, test_case.a), test_case.expected_delta, 0.00005);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ set(SOURCES
|
|||
ClassicWindowTheme.cpp
|
||||
Color.cpp
|
||||
CursorParams.cpp
|
||||
DeltaE.cpp
|
||||
FillPathImplementation.cpp
|
||||
Filters/ColorBlindnessFilter.cpp
|
||||
Filters/FastBoxBlurFilter.cpp
|
||||
|
|
89
Userland/Libraries/LibGfx/DeltaE.cpp
Normal file
89
Userland/Libraries/LibGfx/DeltaE.cpp
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Format.h>
|
||||
#include <LibGfx/DeltaE.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
float DeltaE(CIELAB const& c1, CIELAB const& c2)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
|
||||
// http://zschuessler.github.io/DeltaE/learn/
|
||||
// https://www.hajim.rochester.edu/ece/sites/gsharma/ciede2000/ciede2000noteCRNA.pdf
|
||||
|
||||
float delta_L_prime = c2.L - c1.L;
|
||||
float L_bar = (c1.L + c2.L) / 2;
|
||||
|
||||
float C1 = hypotf(c1.a, c1.b);
|
||||
float C2 = hypotf(c2.a, c2.b);
|
||||
float C_bar = (C1 + C2) / 2;
|
||||
|
||||
float G = 0.5f * (1 - sqrtf(powf(C_bar, 7) / (powf(C_bar, 7) + powf(25, 7))));
|
||||
float a1_prime = (1 + G) * c1.a;
|
||||
float a2_prime = (1 + G) * c2.a;
|
||||
|
||||
float C1_prime = hypotf(a1_prime, c1.b);
|
||||
float C2_prime = hypotf(a2_prime, c2.b);
|
||||
|
||||
float C_prime_bar = (C1_prime + C2_prime) / 2;
|
||||
float delta_C_prime = C2_prime - C1_prime;
|
||||
|
||||
auto h_prime = [](float b, float a_prime) {
|
||||
if (b == 0 && a_prime == 0)
|
||||
return 0.f;
|
||||
float h_prime = atan2(b, a_prime);
|
||||
if (h_prime < 0)
|
||||
h_prime += 2 * static_cast<float>(M_PI);
|
||||
return h_prime * 180 / static_cast<float>(M_PI);
|
||||
};
|
||||
float h1_prime = h_prime(c1.b, a1_prime);
|
||||
float h2_prime = h_prime(c2.b, a2_prime);
|
||||
|
||||
float delta_h_prime;
|
||||
if (C1_prime == 0 || C2_prime == 0)
|
||||
delta_h_prime = 0;
|
||||
else if (fabsf(h1_prime - h2_prime) <= 180.f)
|
||||
delta_h_prime = h2_prime - h1_prime;
|
||||
else if (h2_prime <= h1_prime)
|
||||
delta_h_prime = h2_prime - h1_prime + 360;
|
||||
else
|
||||
delta_h_prime = h2_prime - h1_prime - 360;
|
||||
|
||||
auto sin_degrees = [](float x) { return sinf(x * static_cast<float>(M_PI) / 180); };
|
||||
auto cos_degrees = [](float x) { return cosf(x * static_cast<float>(M_PI) / 180); };
|
||||
|
||||
float delta_H_prime = 2 * sqrtf(C1_prime * C2_prime) * sin_degrees(delta_h_prime / 2);
|
||||
|
||||
float h_prime_bar;
|
||||
if (C1_prime == 0 || C2_prime == 0)
|
||||
h_prime_bar = h1_prime + h2_prime;
|
||||
else if (fabsf(h1_prime - h2_prime) <= 180.f)
|
||||
h_prime_bar = (h1_prime + h2_prime) / 2;
|
||||
else if (h1_prime + h2_prime < 360)
|
||||
h_prime_bar = (h1_prime + h2_prime + 360) / 2;
|
||||
else
|
||||
h_prime_bar = (h1_prime + h2_prime - 360) / 2;
|
||||
|
||||
float T = 1 - 0.17f * cos_degrees(h_prime_bar - 30) + 0.24f * cos_degrees(2 * h_prime_bar) + 0.32f * cos_degrees(3 * h_prime_bar + 6) - 0.2f * cos_degrees(4 * h_prime_bar - 63);
|
||||
|
||||
float S_L = 1 + 0.015f * powf(L_bar - 50, 2) / sqrtf(20 + powf(L_bar - 50, 2));
|
||||
float S_C = 1 + 0.045f * C_prime_bar;
|
||||
float S_H = 1 + 0.015f * C_prime_bar * T;
|
||||
|
||||
float R_T = -2 * sqrtf(powf(C_prime_bar, 7) / (powf(C_prime_bar, 7) + powf(25, 7))) * sin_degrees(60 * exp(-powf((h_prime_bar - 275) / 25, 2)));
|
||||
|
||||
// "kL, kC, and kH are usually unity."
|
||||
float k_L = 1, k_C = 1, k_H = 1;
|
||||
|
||||
float L = delta_L_prime / (k_L * S_L);
|
||||
float C = delta_C_prime / (k_C * S_C);
|
||||
float H = delta_H_prime / (k_H * S_H);
|
||||
return sqrtf(powf(L, 2) + powf(C, 2) + powf(H, 2) + R_T * C * H);
|
||||
}
|
||||
|
||||
}
|
21
Userland/Libraries/LibGfx/DeltaE.h
Normal file
21
Userland/Libraries/LibGfx/DeltaE.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Nico Weber <thakis@chromium.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/CIELAB.h>
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
// Returns a number between 0 and 100 that describes how far apart two colors are in human perception.
|
||||
// A return value < 1 means that the two colors are not noticeably different.
|
||||
// The larger the return value, the easier it is to tell the two colors apart.
|
||||
// Works better for colors that are somewhat "close".
|
||||
//
|
||||
// You can use ICC::sRGB()->to_lab() to convert sRGB colors to CIELAB.
|
||||
float DeltaE(CIELAB const&, CIELAB const&);
|
||||
|
||||
}
|
Loading…
Reference in a new issue