mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-06-07 01:08:18 +08:00
[ColorPicker] Increase precision of CIEXYZ format (#17041)
* Increase precision of CIEXYZ conversion matrix The output has 4 decimal places, so the conversion matrix should be more than 6 digits to avoid round-off errors. * Match unit tests and docs with new CIEXYZ conversion matrix * Remove negative sign from zeros I generated the unit test results from other color-management systems. It seems that they sometimes output negative zeros for very small values. Let's just remove the negative signs for aesthetic. * Fix spelling mistakes in ColorConverterTest.cs * Explain how to obtain CIEXYZ unit test reference values * Explain the CIELAB output is D65 adapted version * Add words related to CIEXYZ conversion to spellcheck bypass list
This commit is contained in:
parent
b7d528b6e8
commit
42ba008323
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@ -178,6 +178,7 @@ bpp
|
||||
bricelam
|
||||
BRIGHTGREEN
|
||||
Browsable
|
||||
brucelindbloom
|
||||
bsd
|
||||
bstr
|
||||
bti
|
||||
@ -523,6 +524,7 @@ enum
|
||||
EOAC
|
||||
eol
|
||||
epicgames
|
||||
Eqn
|
||||
ERASEBKGND
|
||||
EREOF
|
||||
EResize
|
||||
@ -2033,6 +2035,7 @@ towupper
|
||||
tracelogging
|
||||
traies
|
||||
transcoded
|
||||
transicc
|
||||
Transnistria
|
||||
TRAYMOUSEMESSAGE
|
||||
triaging
|
||||
|
@ -162,8 +162,10 @@ namespace ColorPicker.Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a CIE XYZ color (XYZ)
|
||||
/// The constants of the formula used come from this wikipedia page:
|
||||
/// The constants of the formula matches this Wikipedia page, but at a higher precision:
|
||||
/// https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation_(sRGB_to_CIE_XYZ)
|
||||
/// This page provides a method to calculate the constants:
|
||||
/// http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>The X [0..1], Y [0..1] and Z [0..1]</returns>
|
||||
@ -179,14 +181,14 @@ namespace ColorPicker.Helpers
|
||||
double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
||||
|
||||
return (
|
||||
(rLinear * 0.4124) + (gLinear * 0.3576) + (bLinear * 0.1805),
|
||||
(rLinear * 0.2126) + (gLinear * 0.7152) + (bLinear * 0.0722),
|
||||
(rLinear * 0.0193) + (gLinear * 0.1192) + (bLinear * 0.9505)
|
||||
(rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429),
|
||||
(rLinear * 0.21263900587151036) + (gLinear * 0.71516867876775593) + (bLinear * 0.07219231536073372),
|
||||
(rLinear * 0.01933081871559185) + (gLinear * 0.11919477979462599) + (bLinear * 0.95053215224966058)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a CIE XYZ color <see cref="double"/> to a CIE LAB color (LAB)
|
||||
/// Convert a CIE XYZ color <see cref="double"/> to a CIE LAB color (LAB) adapted to sRGB D65 white point
|
||||
/// The constants of the formula used come from this wikipedia page:
|
||||
/// https://en.wikipedia.org/wiki/CIELAB_color_space#Converting_between_CIELAB_and_CIEXYZ_coordinates
|
||||
/// </summary>
|
||||
@ -197,10 +199,19 @@ namespace ColorPicker.Helpers
|
||||
private static (double lightness, double chromaticityA, double chromaticityB)
|
||||
GetCIELABColorFromCIEXYZ(double x, double y, double z)
|
||||
{
|
||||
// These values are based on the D65 Illuminant
|
||||
x = x * 100 / 95.0489;
|
||||
y = y * 100 / 100.0;
|
||||
z = z * 100 / 108.8840;
|
||||
// sRGB reference white (x=0.3127, y=0.3290, Y=1.0), actually CIE Standard Illuminant D65 truncated to 4 decimal places,
|
||||
// then converted to XYZ using the formula:
|
||||
// X = x * (Y / y)
|
||||
// Y = Y
|
||||
// Z = (1 - x - y) * (Y / y)
|
||||
double x_n = 0.9504559270516717;
|
||||
double y_n = 1.0;
|
||||
double z_n = 1.0890577507598784;
|
||||
|
||||
// Scale XYZ values relative to reference white
|
||||
x /= x_n;
|
||||
y /= y_n;
|
||||
z /= z_n;
|
||||
|
||||
// XYZ to CIELab transformation
|
||||
double delta = 6d / 29;
|
||||
|
@ -305,35 +305,35 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("FFFFFF", 100.00, 0.00, -0.01)] // white
|
||||
[DataRow("808080", 53.59, 0.00, -0.01)] // gray
|
||||
[DataRow("FFFFFF", 100.00, 0.00, 0.00)] // white
|
||||
[DataRow("808080", 53.59, 0.00, 0.00)] // gray
|
||||
[DataRow("000000", 0.00, 0.00, 0.00)] // black
|
||||
[DataRow("FF0000", 53.23, 80.11, 67.22)] // red
|
||||
[DataRow("FF0000", 53.24, 80.09, 67.20)] // red
|
||||
[DataRow("008000", 46.23, -51.70, 49.90)] // green
|
||||
[DataRow("80FFFF", 93.16, -35.23, -10.87)] // cyan
|
||||
[DataRow("8080FF", 59.20, 33.1, -63.47)] // blue
|
||||
[DataRow("BF40BF", 50.10, 65.51, -41.49)] // magenta
|
||||
[DataRow("8080FF", 59.20, 33.10, -63.46)] // blue
|
||||
[DataRow("BF40BF", 50.10, 65.50, -41.48)] // magenta
|
||||
[DataRow("BFBF00", 75.04, -17.35, 76.03)] // yellow
|
||||
[DataRow("008000", 46.23, -51.70, 49.90)] // green
|
||||
[DataRow("8080FF", 59.20, 33.1, -63.47)] // blue
|
||||
[DataRow("BF40BF", 50.10, 65.51, -41.49)] // magenta
|
||||
[DataRow("0048BA", 34.35, 27.94, -64.81)] // absolute zero
|
||||
[DataRow("8080FF", 59.20, 33.10, -63.46)] // blue
|
||||
[DataRow("BF40BF", 50.10, 65.50, -41.48)] // magenta
|
||||
[DataRow("0048BA", 34.35, 27.94, -64.80)] // absolute zero
|
||||
[DataRow("B0BF1A", 73.91, -23.39, 71.15)] // acid green
|
||||
[DataRow("D0FF14", 93.87, -40.21, 88.97)] // arctic lime
|
||||
[DataRow("1B4D3E", 29.13, -20.97, 3.95)] // brunswick green
|
||||
[DataRow("D0FF14", 93.87, -40.20, 88.97)] // arctic lime
|
||||
[DataRow("1B4D3E", 29.13, -20.96, 3.95)] // brunswick green
|
||||
[DataRow("FFEF00", 93.01, -13.86, 91.48)] // canary yellow
|
||||
[DataRow("FFA600", 75.16, 23.41, 79.11)] // cheese
|
||||
[DataRow("FFA600", 75.16, 23.41, 79.10)] // cheese
|
||||
[DataRow("1A2421", 13.18, -5.23, 0.56)] // dark jungle green
|
||||
[DataRow("003399", 25.77, 28.89, -59.10)] // dark powder blue
|
||||
[DataRow("D70A53", 46.03, 71.91, 18.02)] // debian red
|
||||
[DataRow("80FFD5", 92.09, -45.08, 9.28)] // fathom secret green
|
||||
[DataRow("EFDFBB", 89.26, -0.13, 19.64)] // dutch white
|
||||
[DataRow("5218FA", 36.65, 75.63, -97.71)] // han purple
|
||||
[DataRow("FF496C", 59.07, 69.90, 21.79)] // infra red
|
||||
[DataRow("545AA7", 41.20, 19.32, -42.35)] // liberty
|
||||
[DataRow("E6A8D7", 75.91, 30.13, -14.80)] // light orchid
|
||||
[DataRow("ADDFAD", 84.32, -25.67, 19.36)] // light moss green
|
||||
[DataRow("E3F988", 94.25, -23.70, 51.57)] // mindaro
|
||||
[DataRow("003399", 25.76, 28.89, -59.09)] // dark powder blue
|
||||
[DataRow("D70A53", 46.03, 71.90, 18.03)] // debian red
|
||||
[DataRow("80FFD5", 92.09, -45.08, 9.29)] // fathom secret green
|
||||
[DataRow("EFDFBB", 89.26, -0.13, 19.65)] // dutch white
|
||||
[DataRow("5218FA", 36.65, 75.63, -97.70)] // han purple
|
||||
[DataRow("FF496C", 59.08, 69.89, 21.80)] // infra red
|
||||
[DataRow("545AA7", 41.20, 19.32, -42.34)] // liberty
|
||||
[DataRow("E6A8D7", 75.91, 30.13, -14.79)] // light orchid
|
||||
[DataRow("ADDFAD", 84.32, -25.67, 19.37)] // light moss green
|
||||
[DataRow("E3F988", 94.25, -23.70, 51.58)] // mindaro
|
||||
public void ColorRGBtoCIELABTest(string hexValue, double lightness, double chromaticityA, double chromaticityB)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hexValue))
|
||||
@ -360,37 +360,44 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
Assert.AreEqual(Math.Round(result.chromaticityB, 2), chromaticityB);
|
||||
}
|
||||
|
||||
// The following results are computed using LittleCMS2, an open-source color management engine,
|
||||
// with the following command-line arguments:
|
||||
// echo 0xFF 0xFF 0xFF | transicc -i "*sRGB" -o "*XYZ" -t 3 -d 0
|
||||
// where "0xFF 0xFF 0xFF" are filled in with the hexadecimal red/green/blue values;
|
||||
// "-t 3" means using absolute colorimetric intent, in other words, disabling white point scaling;
|
||||
// "-d 0" means disabling chromatic adaptation, otherwise it will output CIEXYZ-D50 instead of D65.
|
||||
//
|
||||
// If we have the same results as the reference output listed below, it means our algorithm is accurate.
|
||||
[TestMethod]
|
||||
[DataRow("FFFFFF", 95.0500, 100.0000, 108.9000)] // white
|
||||
[DataRow("808080", 20.5175, 21.5861, 23.5072)] // gray
|
||||
[DataRow("FFFFFF", 95.0456, 100.0000, 108.9058)] // white
|
||||
[DataRow("808080", 20.5166, 21.5861, 23.5085)] // gray
|
||||
[DataRow("000000", 0.0000, 0.0000, 0.0000)] // black
|
||||
[DataRow("FF0000", 41.2400, 21.2600, 1.9300)] // red
|
||||
[DataRow("008000", 7.7192, 15.4383, 2.5731)] // green
|
||||
[DataRow("80FFFF", 62.7121, 83.3292, 107.3866)] // cyan
|
||||
[DataRow("8080FF", 34.6713, 27.2475, 98.0397)] // blue
|
||||
[DataRow("BF40BF", 32.7232, 18.5047, 51.1373)] // magenta
|
||||
[DataRow("BFBF00", 40.1167, 48.3380, 7.2158)] // yellow
|
||||
[DataRow("008000", 7.7192, 15.4383, 2.5731)] // green
|
||||
[DataRow("80FFFF", 62.7121, 83.3292, 107.3866)] // cyan
|
||||
[DataRow("8080FF", 34.6713, 27.2475, 98.0397)] // blue
|
||||
[DataRow("BF40BF", 32.7232, 18.5047, 51.1373)] // magenta
|
||||
[DataRow("0048BA", 11.1803, 8.1799, 47.4440)] // absolute zero
|
||||
[DataRow("B0BF1A", 36.7218, 46.5663, 8.0300)] // acid green
|
||||
[DataRow("D0FF14", 61.8987, 84.9804, 13.8023)] // arctic lime
|
||||
[DataRow("1B4D3E", 3.9754, 5.8886, 5.4845)] // brunswick green
|
||||
[DataRow("FFEF00", 72.1065, 82.9930, 12.2188)] // canary yellow
|
||||
[DataRow("FFA600", 54.8762, 48.5324, 6.4754)] // cheese
|
||||
[DataRow("1A2421", 1.3314, 1.5912, 1.6758)] // dark jungle green
|
||||
[DataRow("003399", 6.9336, 4.6676, 30.6725)] // dark powder blue
|
||||
[DataRow("D70A53", 29.6942, 15.2887, 9.5696)] // debian red
|
||||
[DataRow("80FFD5", 56.6723, 80.9133, 75.5817)] // fathom secret green
|
||||
[DataRow("EFDFBB", 70.9539, 74.7139, 57.6953)] // dutch white
|
||||
[DataRow("5218FA", 21.0616, 9.3492, 91.1370)] // han purple
|
||||
[DataRow("FF496C", 46.3293, 27.1078, 16.9779)] // infra red
|
||||
[DataRow("545AA7", 14.2874, 11.9872, 38.1199)] // liberty
|
||||
[DataRow("E6A8D7", 58.9015, 49.7346, 70.7853)] // light orchid
|
||||
[DataRow("ADDFAD", 51.1641, 64.6767, 49.3224)] // light moss green
|
||||
[DataRow("E3F988", 69.9982, 85.8598, 36.1759)] // mindaro
|
||||
[DataRow("FF0000", 41.2391, 21.2639, 1.9331)] // red
|
||||
[DataRow("008000", 7.7188, 15.4377, 2.5729)] // green
|
||||
[DataRow("80FFFF", 62.7084, 83.3261, 107.3900)] // cyan
|
||||
[DataRow("8080FF", 34.6688, 27.2469, 98.0434)] // blue
|
||||
[DataRow("BF40BF", 32.7217, 18.5062, 51.1405)] // magenta
|
||||
[DataRow("BFBF00", 40.1154, 48.3384, 7.2171)] // yellow
|
||||
[DataRow("008000", 7.7188, 15.4377, 2.5729)] // green
|
||||
[DataRow("8080FF", 34.6688, 27.2469, 98.0434)] // blue
|
||||
[DataRow("BF40BF", 32.7217, 18.5062, 51.1405)] // magenta
|
||||
[DataRow("0048BA", 11.1792, 8.1793, 47.4455)] // absolute zero
|
||||
[DataRow("B0BF1A", 36.7205, 46.5663, 8.0311)] // acid green
|
||||
[DataRow("D0FF14", 61.8965, 84.9797, 13.8037)] // arctic lime
|
||||
[DataRow("1B4D3E", 3.9752, 5.8883, 5.4847)] // brunswick green
|
||||
[DataRow("FFEF00", 72.1042, 82.9942, 12.2215)] // canary yellow
|
||||
[DataRow("FFA600", 54.8747, 48.5351, 6.4783)] // cheese
|
||||
[DataRow("1A2421", 1.3313, 1.5911, 1.6759)] // dark jungle green
|
||||
[DataRow("003399", 6.9329, 4.6672, 30.6735)] // dark powder blue
|
||||
[DataRow("D70A53", 29.6934, 15.2913, 9.5719)] // debian red
|
||||
[DataRow("80FFD5", 56.6693, 80.9105, 75.5840)] // fathom secret green
|
||||
[DataRow("EFDFBB", 70.9510, 74.7146, 57.6991)] // dutch white
|
||||
[DataRow("5218FA", 21.0597, 9.3488, 91.1403)] // han purple
|
||||
[DataRow("FF496C", 46.3280, 27.1114, 16.9814)] // infra red
|
||||
[DataRow("545AA7", 14.2864, 11.9869, 38.1214)] // liberty
|
||||
[DataRow("E6A8D7", 58.8989, 49.7359, 70.7897)] // light orchid
|
||||
[DataRow("ADDFAD", 51.1617, 64.6757, 49.3246)] // light moss green
|
||||
[DataRow("E3F988", 69.9955, 85.8597, 36.1785)] // mindaro
|
||||
public void ColorRGBtoCIEXYZTest(string hexValue, double x, double y, double z)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hexValue))
|
||||
|
Loading…
Reference in New Issue
Block a user