mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-30 22:15:11 +00:00
Color Picker: add Oklab and Oklch color formats (#38052)
* Resolve Resources.resw conflict * Update CIE LCh chroma practical upper bound according to CSS spec * Add review suggestions * Add WIP tests (lch and oklch do not pass yet) * Deduplicate Lab to LCh converter method * Update expect.txt * Fix liberty test color * Reimplement oklab with better precision * Remove CIE LCh * Add tooltip for color param descriptions * Update spell-check expect.txt with new words * Remove 'cielch' and 'lch' from expect.txt --------- Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com> Co-authored-by: Clint Rutkas <clint@rutkas.com> Co-authored-by: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com>
This commit is contained in:
10
.github/actions/spell-check/expect.txt
vendored
10
.github/actions/spell-check/expect.txt
vendored
@@ -1982,4 +1982,12 @@ zoomit
|
||||
ZOOMITX
|
||||
ZXk
|
||||
ZXNs
|
||||
zzz
|
||||
zzz
|
||||
ACIE
|
||||
AOklab
|
||||
BCIE
|
||||
BOklab
|
||||
culori
|
||||
Evercoder
|
||||
LCh
|
||||
CIELCh
|
||||
|
@@ -141,6 +141,40 @@ namespace ManagedCommon
|
||||
return lab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a Oklab color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns>
|
||||
public static (double Lightness, double ChromaticityA, double ChromaticityB) ConvertToOklabColor(Color color)
|
||||
{
|
||||
var linear = ConvertSRGBToLinearRGB(color.R / 255d, color.G / 255d, color.B / 255d);
|
||||
var oklab = GetOklabColorFromLinearRGB(linear.R, linear.G, linear.B);
|
||||
return oklab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a Oklch color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> to convert</param>
|
||||
/// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns>
|
||||
public static (double Lightness, double Chroma, double Hue) ConvertToOklchColor(Color color)
|
||||
{
|
||||
var oklab = ConvertToOklabColor(color);
|
||||
var oklch = GetOklchColorFromOklab(oklab.Lightness, oklab.ChromaticityA, oklab.ChromaticityB);
|
||||
|
||||
return oklch;
|
||||
}
|
||||
|
||||
public static (double R, double G, double B) ConvertSRGBToLinearRGB(double r, double g, double b)
|
||||
{
|
||||
// inverse companding, gamma correction must be undone
|
||||
double rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
||||
double gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
||||
double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
||||
return (rLinear, gLinear, bLinear);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a CIE XYZ color (XYZ)
|
||||
/// The constants of the formula matches this Wikipedia page, but at a higher precision:
|
||||
@@ -156,10 +190,7 @@ namespace ManagedCommon
|
||||
double g = color.G / 255d;
|
||||
double b = color.B / 255d;
|
||||
|
||||
// inverse companding, gamma correction must be undone
|
||||
double rLinear = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : (r / 12.92);
|
||||
double gLinear = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : (g / 12.92);
|
||||
double bLinear = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : (b / 12.92);
|
||||
(double rLinear, double gLinear, double bLinear) = ConvertSRGBToLinearRGB(r, g, b);
|
||||
|
||||
return (
|
||||
(rLinear * 0.41239079926595948) + (gLinear * 0.35758433938387796) + (bLinear * 0.18048078840183429),
|
||||
@@ -210,6 +241,63 @@ namespace ManagedCommon
|
||||
return (l, a, b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a linear RGB color <see cref="double"/> to an Oklab color.
|
||||
/// The constants of this formula come from https://github.com/Evercoder/culori/blob/2bedb8f0507116e75f844a705d0b45cf279b15d0/src/oklab/convertLrgbToOklab.js
|
||||
/// and the implementation is based on https://bottosson.github.io/posts/oklab/
|
||||
/// </summary>
|
||||
/// <param name="r">Linear R value</param>
|
||||
/// <param name="g">Linear G value</param>
|
||||
/// <param name="b">Linear B value</param>
|
||||
/// <returns>The perceptual lightness [0..1] and two chromaticities [-0.5..0.5]</returns>
|
||||
private static (double Lightness, double ChromaticityA, double ChromaticityB)
|
||||
GetOklabColorFromLinearRGB(double r, double g, double b)
|
||||
{
|
||||
double l = (0.41222147079999993 * r) + (0.5363325363 * g) + (0.0514459929 * b);
|
||||
double m = (0.2119034981999999 * r) + (0.6806995450999999 * g) + (0.1073969566 * b);
|
||||
double s = (0.08830246189999998 * r) + (0.2817188376 * g) + (0.6299787005000002 * b);
|
||||
|
||||
double l_ = Math.Cbrt(l);
|
||||
double m_ = Math.Cbrt(m);
|
||||
double s_ = Math.Cbrt(s);
|
||||
|
||||
return (
|
||||
(0.2104542553 * l_) + (0.793617785 * m_) - (0.0040720468 * s_),
|
||||
(1.9779984951 * l_) - (2.428592205 * m_) + (0.4505937099 * s_),
|
||||
(0.0259040371 * l_) + (0.7827717662 * m_) - (0.808675766 * s_)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert an Oklab color <see cref="double"/> from Cartesian form to its polar form Oklch
|
||||
/// https://bottosson.github.io/posts/oklab/#the-oklab-color-space
|
||||
/// </summary>
|
||||
/// <param name="lightness">The <see cref="lightness"/></param>
|
||||
/// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param>
|
||||
/// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param>
|
||||
/// <returns>The perceptual lightness [0..1], the chroma [0..0.5], and the hue angle [0°..360°]</returns>
|
||||
private static (double Lightness, double Chroma, double Hue)
|
||||
GetOklchColorFromOklab(double lightness, double chromaticity_a, double chromaticity_b)
|
||||
{
|
||||
return GetLCHColorFromLAB(lightness, chromaticity_a, chromaticity_b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a color in Cartesian form (Lab) to its polar form (LCh)
|
||||
/// </summary>
|
||||
/// <param name="lightness">The <see cref="lightness"/></param>
|
||||
/// <param name="chromaticity_a">The <see cref="chromaticity_a"/></param>
|
||||
/// <param name="chromaticity_b">The <see cref="chromaticity_b"/></param>
|
||||
/// <returns>The lightness, chroma, and hue angle</returns>
|
||||
private static (double Lightness, double Chroma, double Hue)
|
||||
GetLCHColorFromLAB(double lightness, double chromaticity_a, double chromaticity_b)
|
||||
{
|
||||
// Lab to LCh transformation
|
||||
double chroma = Math.Sqrt(Math.Pow(chromaticity_a, 2) + Math.Pow(chromaticity_b, 2));
|
||||
double hue = Math.Round(chroma, 3) == 0 ? 0.0 : ((Math.Atan2(chromaticity_b, chromaticity_a) * 180d / Math.PI) + 360d) % 360d;
|
||||
return (lightness, chroma, hue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a given <see cref="Color"/> to a natural color (hue, whiteness, blackness)
|
||||
/// </summary>
|
||||
@@ -276,12 +364,17 @@ namespace ManagedCommon
|
||||
{ "Br", 'p' }, // brightness percent
|
||||
{ "In", 'p' }, // intensity percent
|
||||
{ "Ll", 'p' }, // lightness (HSL) percent
|
||||
{ "Lc", 'p' }, // lightness(CIELAB)percent
|
||||
{ "Va", 'p' }, // value percent
|
||||
{ "Wh", 'p' }, // whiteness percent
|
||||
{ "Bn", 'p' }, // blackness percent
|
||||
{ "Ca", 'p' }, // chromaticityA percent
|
||||
{ "Cb", 'p' }, // chromaticityB percent
|
||||
{ "Lc", 'p' }, // lightness (CIE) percent
|
||||
{ "Ca", 'p' }, // chromaticityA (CIELAB) percent
|
||||
{ "Cb", 'p' }, // chromaticityB (CIELAB) percent
|
||||
{ "Lo", 'p' }, // lightness (Oklab/Oklch) percent
|
||||
{ "Oa", 'p' }, // chromaticityA (Oklab) percent
|
||||
{ "Ob", 'p' }, // chromaticityB (Oklab) percent
|
||||
{ "Oc", 'p' }, // chroma (Oklch) percent
|
||||
{ "Oh", 'p' }, // hue angle (Oklch) percent
|
||||
{ "Xv", 'i' }, // X value int
|
||||
{ "Yv", 'i' }, // Y value int
|
||||
{ "Zv", 'i' }, // Z value int
|
||||
@@ -424,6 +517,10 @@ namespace ManagedCommon
|
||||
var (lightnessC, _, _) = ConvertToCIELABColor(color);
|
||||
lightnessC = Math.Round(lightnessC, 2);
|
||||
return lightnessC.ToString(CultureInfo.InvariantCulture);
|
||||
case "Lo":
|
||||
var (lightnessO, _, _) = ConvertToOklabColor(color);
|
||||
lightnessO = Math.Round(lightnessO, 2);
|
||||
return lightnessO.ToString(CultureInfo.InvariantCulture);
|
||||
case "Wh":
|
||||
var (_, whiteness, _) = ConvertToHWBColor(color);
|
||||
whiteness = Math.Round(whiteness * 100);
|
||||
@@ -440,6 +537,22 @@ namespace ManagedCommon
|
||||
var (_, _, chromaticityB) = ConvertToCIELABColor(color);
|
||||
chromaticityB = Math.Round(chromaticityB, 2);
|
||||
return chromaticityB.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oa":
|
||||
var (_, chromaticityAOklab, _) = ConvertToOklabColor(color);
|
||||
chromaticityAOklab = Math.Round(chromaticityAOklab, 2);
|
||||
return chromaticityAOklab.ToString(CultureInfo.InvariantCulture);
|
||||
case "Ob":
|
||||
var (_, _, chromaticityBOklab) = ConvertToOklabColor(color);
|
||||
chromaticityBOklab = Math.Round(chromaticityBOklab, 2);
|
||||
return chromaticityBOklab.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oc":
|
||||
var (_, chromaOklch, _) = ConvertToOklchColor(color);
|
||||
chromaOklch = Math.Round(chromaOklch, 2);
|
||||
return chromaOklch.ToString(CultureInfo.InvariantCulture);
|
||||
case "Oh":
|
||||
var (_, _, hueOklch) = ConvertToOklchColor(color);
|
||||
hueOklch = Math.Round(hueOklch, 2);
|
||||
return hueOklch.ToString(CultureInfo.InvariantCulture);
|
||||
case "Xv":
|
||||
var (x, _, _) = ConvertToCIEXYZColor(color);
|
||||
x = Math.Round(x * 100, 4);
|
||||
@@ -495,8 +608,10 @@ namespace ManagedCommon
|
||||
case "HSI": return "hsi(%Hu, %Si%, %In%)";
|
||||
case "HWB": return "hwb(%Hu, %Wh%, %Bn%)";
|
||||
case "NCol": return "%Hn, %Wh%, %Bn%";
|
||||
case "CIELAB": return "CIELab(%Lc, %Ca, %Cb)";
|
||||
case "CIEXYZ": return "XYZ(%Xv, %Yv, %Zv)";
|
||||
case "CIELAB": return "CIELab(%Lc, %Ca, %Cb)";
|
||||
case "Oklab": return "oklab(%Lo, %Oa, %Ob)";
|
||||
case "Oklch": return "oklch(%Lo, %Oc, %Oh)";
|
||||
case "VEC4": return "(%Reff, %Grff, %Blff, 1f)";
|
||||
case "Decimal": return "%Dv";
|
||||
case "HEX Int": return "0xFF%ReX%GrX%BlX";
|
||||
|
@@ -243,6 +243,40 @@ namespace ColorPicker.Helpers
|
||||
$", {chromaticityB.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a Oklab color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the Oklab color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a Oklab color</returns>
|
||||
private static string ColorToOklab(Color color)
|
||||
{
|
||||
var (lightness, chromaticityA, chromaticityB) = ColorFormatHelper.ConvertToOklabColor(color);
|
||||
lightness = Math.Round(lightness, 2);
|
||||
chromaticityA = Math.Round(chromaticityA, 2);
|
||||
chromaticityB = Math.Round(chromaticityB, 2);
|
||||
|
||||
return $"oklab({lightness.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityA.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chromaticityB.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a CIE LCh color
|
||||
/// </summary>
|
||||
/// <param name="color">The <see cref="Color"/> for the CIE LCh color presentation</param>
|
||||
/// <returns>A <see cref="string"/> representation of a CIE LCh color</returns>
|
||||
private static string ColorToOklch(Color color)
|
||||
{
|
||||
var (lightness, chroma, hue) = ColorFormatHelper.ConvertToOklchColor(color);
|
||||
lightness = Math.Round(lightness, 2);
|
||||
chroma = Math.Round(chroma, 2);
|
||||
hue = Math.Round(hue, 2);
|
||||
|
||||
return $"oklch({lightness.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {chroma.ToString(CultureInfo.InvariantCulture)}" +
|
||||
$", {hue.ToString(CultureInfo.InvariantCulture)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string"/> representation of a CIE XYZ color
|
||||
/// </summary>
|
||||
|
@@ -301,6 +301,12 @@ namespace ColorPicker.ViewModels
|
||||
FormatName = ColorRepresentationType.NCol.ToString(),
|
||||
Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.NCol.ToString()),
|
||||
});
|
||||
_allColorRepresentations.Add(
|
||||
new ColorFormatModel()
|
||||
{
|
||||
FormatName = ColorRepresentationType.CIEXYZ.ToString(),
|
||||
Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ.ToString()),
|
||||
});
|
||||
_allColorRepresentations.Add(
|
||||
new ColorFormatModel()
|
||||
{
|
||||
@@ -310,8 +316,14 @@ namespace ColorPicker.ViewModels
|
||||
_allColorRepresentations.Add(
|
||||
new ColorFormatModel()
|
||||
{
|
||||
FormatName = ColorRepresentationType.CIEXYZ.ToString(),
|
||||
Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ.ToString()),
|
||||
FormatName = ColorRepresentationType.Oklab.ToString(),
|
||||
Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.Oklab.ToString()),
|
||||
});
|
||||
_allColorRepresentations.Add(
|
||||
new ColorFormatModel()
|
||||
{
|
||||
FormatName = ColorRepresentationType.Oklch.ToString(),
|
||||
Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.Oklch.ToString()),
|
||||
});
|
||||
_allColorRepresentations.Add(
|
||||
new ColorFormatModel()
|
||||
|
@@ -364,9 +364,6 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
[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.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.20, 88.97)] // arctic lime
|
||||
@@ -401,13 +398,121 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
var result = ColorFormatHelper.ConvertToCIELABColor(color);
|
||||
|
||||
// lightness[0..100]
|
||||
Assert.AreEqual(Math.Round(result.Lightness, 2), lightness);
|
||||
Assert.AreEqual(lightness, Math.Round(result.Lightness, 2));
|
||||
|
||||
// chromaticityA[-128..127]
|
||||
Assert.AreEqual(Math.Round(result.ChromaticityA, 2), chromaticityA);
|
||||
Assert.AreEqual(chromaticityA, Math.Round(result.ChromaticityA, 2));
|
||||
|
||||
// chromaticityB[-128..127]
|
||||
Assert.AreEqual(Math.Round(result.ChromaticityB, 2), chromaticityB);
|
||||
Assert.AreEqual(chromaticityB, Math.Round(result.ChromaticityB, 2));
|
||||
}
|
||||
|
||||
// Test data calculated using https://oklch.com (which uses https://github.com/Evercoder/culori)
|
||||
[TestMethod]
|
||||
[DataRow("FFFFFF", 1.00, 0.00, 0.00)] // white
|
||||
[DataRow("808080", 0.6, 0.00, 0.00)] // gray
|
||||
[DataRow("000000", 0.00, 0.00, 0.00)] // black
|
||||
[DataRow("FF0000", 0.628, 0.22, 0.13)] // red
|
||||
[DataRow("008000", 0.52, -0.14, 0.11)] // green
|
||||
[DataRow("80FFFF", 0.928, -0.11, -0.03)] // cyan
|
||||
[DataRow("8080FF", 0.661, 0.03, -0.18)] // blue
|
||||
[DataRow("BF40BF", 0.598, 0.18, -0.11)] // magenta
|
||||
[DataRow("BFBF00", 0.779, -0.06, 0.16)] // yellow
|
||||
[DataRow("0048BA", 0.444, -0.03, -0.19)] // absolute zero
|
||||
[DataRow("B0BF1A", 0.767, -0.07, 0.15)] // acid green
|
||||
[DataRow("D0FF14", 0.934, -0.12, 0.19)] // arctic lime
|
||||
[DataRow("1B4D3E", 0.382, -0.06, 0.01)] // brunswick green
|
||||
[DataRow("FFEF00", 0.935, -0.05, 0.19)] // canary yellow
|
||||
[DataRow("FFA600", 0.794, 0.06, 0.16)] // cheese
|
||||
[DataRow("1A2421", 0.25, -0.02, 0)] // dark jungle green
|
||||
[DataRow("003399", 0.371, -0.02, -0.17)] // dark powder blue
|
||||
[DataRow("D70A53", 0.563, 0.22, 0.04)] // debian red
|
||||
[DataRow("80FFD5", 0.916, -0.13, 0.02)] // fathom secret green
|
||||
[DataRow("EFDFBB", 0.907, 0, 0.05)] // dutch white
|
||||
[DataRow("5218FA", 0.489, 0.05, -0.28)] // han purple
|
||||
[DataRow("FF496C", 0.675, 0.21, 0.05)] // infra red
|
||||
[DataRow("545AA7", 0.5, 0.02, -0.12)] // liberty
|
||||
[DataRow("E6A8D7", 0.804, 0.09, -0.04)] // light orchid
|
||||
[DataRow("ADDFAD", 0.856, -0.07, 0.05)] // light moss green
|
||||
[DataRow("E3F988", 0.942, -0.07, 0.12)] // mindaro
|
||||
public void ColorRGBtoOklabTest(string hexValue, double lightness, double chromaticityA, double chromaticityB)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hexValue))
|
||||
{
|
||||
Assert.IsNotNull(hexValue);
|
||||
}
|
||||
|
||||
Assert.IsTrue(hexValue.Length >= 6);
|
||||
|
||||
var red = int.Parse(hexValue.AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var green = int.Parse(hexValue.AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var blue = int.Parse(hexValue.AsSpan(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
|
||||
var color = Color.FromArgb(255, red, green, blue);
|
||||
var result = ColorFormatHelper.ConvertToOklabColor(color);
|
||||
|
||||
// lightness[0..1]
|
||||
Assert.AreEqual(lightness, Math.Round(result.Lightness, 3));
|
||||
|
||||
// chromaticityA[-0.5..0.5]
|
||||
Assert.AreEqual(chromaticityA, Math.Round(result.ChromaticityA, 2));
|
||||
|
||||
// chromaticityB[-0.5..0.5]
|
||||
Assert.AreEqual(chromaticityB, Math.Round(result.ChromaticityB, 2));
|
||||
}
|
||||
|
||||
// Test data calculated using https://oklch.com (which uses https://github.com/Evercoder/culori)
|
||||
[TestMethod]
|
||||
[DataRow("FFFFFF", 1.00, 0.00, 0.00)] // white
|
||||
[DataRow("808080", 0.6, 0.00, 0.00)] // gray
|
||||
[DataRow("000000", 0.00, 0.00, 0.00)] // black
|
||||
[DataRow("FF0000", 0.628, 0.258, 29.23)] // red
|
||||
[DataRow("008000", 0.52, 0.177, 142.5)] // green
|
||||
[DataRow("80FFFF", 0.928, 0.113, 195.38)] // cyan
|
||||
[DataRow("8080FF", 0.661, 0.184, 280.13)] // blue
|
||||
[DataRow("BF40BF", 0.598, 0.216, 327.86)] // magenta
|
||||
[DataRow("BFBF00", 0.779, 0.17, 109.77)] // yellow
|
||||
[DataRow("0048BA", 0.444, 0.19, 260.86)] // absolute zero
|
||||
[DataRow("B0BF1A", 0.767, 0.169, 115.4)] // acid green
|
||||
[DataRow("D0FF14", 0.934, 0.224, 122.28)] // arctic lime
|
||||
[DataRow("1B4D3E", 0.382, 0.06, 170.28)] // brunswick green
|
||||
[DataRow("FFEF00", 0.935, 0.198, 104.67)] // canary yellow
|
||||
[DataRow("FFA600", 0.794, 0.171, 71.19)] // cheese
|
||||
[DataRow("1A2421", 0.25, 0.015, 174.74)] // dark jungle green
|
||||
[DataRow("003399", 0.371, 0.173, 262.12)] // dark powder blue
|
||||
[DataRow("D70A53", 0.563, 0.222, 11.5)] // debian red
|
||||
[DataRow("80FFD5", 0.916, 0.129, 169.38)] // fathom secret green
|
||||
[DataRow("EFDFBB", 0.907, 0.05, 86.89)] // dutch white
|
||||
[DataRow("5218FA", 0.489, 0.286, 279.13)] // han purple
|
||||
[DataRow("FF496C", 0.675, 0.217, 14.37)] // infra red
|
||||
[DataRow("545AA7", 0.5, 0.121, 277.7)] // liberty
|
||||
[DataRow("E6A8D7", 0.804, 0.095, 335.4)] // light orchid
|
||||
[DataRow("ADDFAD", 0.856, 0.086, 144.78)] // light moss green
|
||||
[DataRow("E3F988", 0.942, 0.141, 118.24)] // mindaro
|
||||
public void ColorRGBtoOklchTest(string hexValue, double lightness, double chroma, double hue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hexValue))
|
||||
{
|
||||
Assert.IsNotNull(hexValue);
|
||||
}
|
||||
|
||||
Assert.IsTrue(hexValue.Length >= 6);
|
||||
|
||||
var red = int.Parse(hexValue.AsSpan(0, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var green = int.Parse(hexValue.AsSpan(2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var blue = int.Parse(hexValue.AsSpan(4, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
|
||||
var color = Color.FromArgb(255, red, green, blue);
|
||||
var result = ColorFormatHelper.ConvertToOklchColor(color);
|
||||
|
||||
// lightness[0..1]
|
||||
Assert.AreEqual(lightness, Math.Round(result.Lightness, 3));
|
||||
|
||||
// chroma[0..0.5]
|
||||
Assert.AreEqual(chroma, Math.Round(result.Chroma, 3));
|
||||
|
||||
// hue[0°..360°]
|
||||
Assert.AreEqual(hue, Math.Round(result.Hue, 2));
|
||||
}
|
||||
|
||||
// The following results are computed using LittleCMS2, an open-source color management engine,
|
||||
@@ -428,9 +533,6 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
[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
|
||||
|
@@ -23,8 +23,10 @@ namespace Microsoft.ColorPicker.UnitTests
|
||||
[DataRow("HSV", "hsv(0, 0%, 0%)")]
|
||||
[DataRow("HWB", "hwb(0, 0%, 100%)")]
|
||||
[DataRow("RGB", "rgb(0, 0, 0)")]
|
||||
[DataRow("CIELAB", "CIELab(0, 0, 0)")]
|
||||
[DataRow("CIEXYZ", "XYZ(0, 0, 0)")]
|
||||
[DataRow("CIELAB", "CIELab(0, 0, 0)")]
|
||||
[DataRow("Oklab", "oklab(0, 0, 0)")]
|
||||
[DataRow("Oklch", "oklch(0, 0, 0)")]
|
||||
[DataRow("VEC4", "(0f, 0f, 0f, 1f)")]
|
||||
[DataRow("Decimal", "0")]
|
||||
[DataRow("HEX Int", "0xFF000000")]
|
||||
|
@@ -32,8 +32,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
VisibleColorFormats.Add("HSI", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("HSI")));
|
||||
VisibleColorFormats.Add("HWB", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("HWB")));
|
||||
VisibleColorFormats.Add("NCol", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("NCol")));
|
||||
VisibleColorFormats.Add("CIELAB", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("CIELAB")));
|
||||
VisibleColorFormats.Add("CIEXYZ", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("CIEXYZ")));
|
||||
VisibleColorFormats.Add("CIELAB", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("CIELAB")));
|
||||
VisibleColorFormats.Add("Oklab", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("Oklab")));
|
||||
VisibleColorFormats.Add("Oklch", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("Oklch")));
|
||||
VisibleColorFormats.Add("VEC4", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("VEC4")));
|
||||
VisibleColorFormats.Add("Decimal", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("Decimal")));
|
||||
VisibleColorFormats.Add("HEX Int", new KeyValuePair<bool, string>(false, ColorFormatHelper.GetDefaultFormat("HEX Int")));
|
||||
|
@@ -80,5 +80,20 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Enumerations
|
||||
/// Color presentation as an 8-digit hexadecimal integer (0xFFFFFFFF)
|
||||
/// </summary>
|
||||
HexInteger = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Color representation as CIELCh color space (L[0..100], C[0..230], h[0°..360°])
|
||||
/// </summary>
|
||||
CIELCh = 14,
|
||||
|
||||
/// <summary>
|
||||
/// Color representation as Oklab color space (L[0..1], a[-0.5..0.5], b[-0.5..0.5])
|
||||
/// </summary>
|
||||
Oklab = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Color representation as Oklch color space (L[0..1], C[0..0.5], h[0°..360°])
|
||||
/// </summary>
|
||||
Oklch = 16,
|
||||
}
|
||||
}
|
||||
|
@@ -32,7 +32,8 @@
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Description}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="NoWrap" />
|
||||
TextWrapping="NoWrap"
|
||||
ToolTipService.ToolTip="{x:Bind Description}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
|
@@ -47,12 +47,17 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
|
||||
new ColorFormatParameter() { Parameter = "%In", Description = resourceLoader.GetString("Help_intensity") },
|
||||
new ColorFormatParameter() { Parameter = "%Hn", Description = resourceLoader.GetString("Help_hueNat") },
|
||||
new ColorFormatParameter() { Parameter = "%Ll", Description = resourceLoader.GetString("Help_lightnessNat") },
|
||||
new ColorFormatParameter() { Parameter = "%Lc", Description = resourceLoader.GetString("Help_lightnessCIE") },
|
||||
new ColorFormatParameter() { Parameter = "%Va", Description = resourceLoader.GetString("Help_value") },
|
||||
new ColorFormatParameter() { Parameter = "%Wh", Description = resourceLoader.GetString("Help_whiteness") },
|
||||
new ColorFormatParameter() { Parameter = "%Bn", Description = resourceLoader.GetString("Help_blackness") },
|
||||
new ColorFormatParameter() { Parameter = "%Ca", Description = resourceLoader.GetString("Help_chromaticityA") },
|
||||
new ColorFormatParameter() { Parameter = "%Cb", Description = resourceLoader.GetString("Help_chromaticityB") },
|
||||
new ColorFormatParameter() { Parameter = "%Lc", Description = resourceLoader.GetString("Help_lightnessCIE") },
|
||||
new ColorFormatParameter() { Parameter = "%Ca", Description = resourceLoader.GetString("Help_chromaticityACIE") },
|
||||
new ColorFormatParameter() { Parameter = "%Cb", Description = resourceLoader.GetString("Help_chromaticityBCIE") },
|
||||
new ColorFormatParameter() { Parameter = "%Lo", Description = resourceLoader.GetString("Help_lightnessOklab") },
|
||||
new ColorFormatParameter() { Parameter = "%Oa", Description = resourceLoader.GetString("Help_chromaticityAOklab") },
|
||||
new ColorFormatParameter() { Parameter = "%Ob", Description = resourceLoader.GetString("Help_chromaticityBOklab") },
|
||||
new ColorFormatParameter() { Parameter = "%Oc", Description = resourceLoader.GetString("Help_chromaOklch") },
|
||||
new ColorFormatParameter() { Parameter = "%Oh", Description = resourceLoader.GetString("Help_hueOklch") },
|
||||
new ColorFormatParameter() { Parameter = "%Xv", Description = resourceLoader.GetString("Help_X_value") },
|
||||
new ColorFormatParameter() { Parameter = "%Yv", Description = resourceLoader.GetString("Help_Y_value") },
|
||||
new ColorFormatParameter() { Parameter = "%Zv", Description = resourceLoader.GetString("Help_Z_value") },
|
||||
|
@@ -1660,11 +1660,11 @@ Made with 💗 by Microsoft and the PowerToys community.</value>
|
||||
<data name="Help_blackness" xml:space="preserve">
|
||||
<value>blackness</value>
|
||||
</data>
|
||||
<data name="Help_chromaticityA" xml:space="preserve">
|
||||
<value>chromaticityA</value>
|
||||
<data name="Help_chromaticityACIE" xml:space="preserve">
|
||||
<value>chromaticity A (CIE Lab)</value>
|
||||
</data>
|
||||
<data name="Help_chromaticityB" xml:space="preserve">
|
||||
<value>chromaticityB</value>
|
||||
<data name="Help_chromaticityBCIE" xml:space="preserve">
|
||||
<value>chromaticity B (CIE Lab)</value>
|
||||
</data>
|
||||
<data name="Help_X_value" xml:space="preserve">
|
||||
<value>X value</value>
|
||||
@@ -4460,7 +4460,7 @@ Activate by holding the key for the character you want to add an accent to, then
|
||||
<data name="NewPlus_Behaviour_Replace_Variables_Info_Card_Title.Text" xml:space="preserve">
|
||||
<value>Commonly used variables</value>
|
||||
<comment>New+ commonly used variables header in the flyout info card</comment>
|
||||
</data>
|
||||
</data>
|
||||
<data name="NewPlus_Year_YYYY_Variable_Description.Text" xml:space="preserve">
|
||||
<value>Year, represented by a full four or five digits, depending on the calendar used.</value>
|
||||
<comment>New+ description of the year $YYYY variable - casing of $YYYY is important</comment>
|
||||
@@ -4999,4 +4999,25 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
||||
<data name="CmdPal_ActivationShortcut.Description" xml:space="preserve">
|
||||
<value>Go to Command Palette settings to customize the activation shortcut.</value>
|
||||
</data>
|
||||
<data name="Help_chromaCIE" xml:space="preserve">
|
||||
<value>chroma (CIE LCh)</value>
|
||||
</data>
|
||||
<data name="Help_hueCIE" xml:space="preserve">
|
||||
<value>hue (CIE LCh)</value>
|
||||
</data>
|
||||
<data name="Help_lightnessOklab" xml:space="preserve">
|
||||
<value>lightness (Oklab/Oklch)</value>
|
||||
</data>
|
||||
<data name="Help_chromaticityAOklab" xml:space="preserve">
|
||||
<value>chromaticity A (Oklab)</value>
|
||||
</data>
|
||||
<data name="Help_chromaticityBOklab" xml:space="preserve">
|
||||
<value>chromaticity B (Oklab)</value>
|
||||
</data>
|
||||
<data name="Help_chromaOklch" xml:space="preserve">
|
||||
<value>chroma (Oklch)</value>
|
||||
</data>
|
||||
<data name="Help_hueOklch" xml:space="preserve">
|
||||
<value>hue (Oklch)</value>
|
||||
</data>
|
||||
</root>
|
Reference in New Issue
Block a user