-
Notifications
You must be signed in to change notification settings - Fork 8.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement the Delta E algorithm to improve color perception #11095
Merged
Merged
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
6373bae
transfer
PankajBhojwani da2cad8
works
PankajBhojwani d1c9748
remove
PankajBhojwani 0c8168b
method descriptions
PankajBhojwani 7b1eefe
spell, format
PankajBhojwani f8003d3
order
PankajBhojwani e4fced6
default on setting
PankajBhojwani 041e5ce
remove colorspace
PankajBhojwani 12e8d6c
neater
PankajBhojwani 384bf49
private
PankajBhojwani 9f9af2e
dedcode
PankajBhojwani 326decb
delete a bunch, static functions
PankajBhojwani 5bcbde2
update comments
PankajBhojwani 48f6199
precalculate
PankajBhojwani fe40978
array
PankajBhojwani 7551212
format
PankajBhojwani fdd8017
fix swap
PankajBhojwani 23445c7
move out of text attribute
PankajBhojwani 8b21bab
mtd description
PankajBhojwani 0a7d3a9
conemu license
PankajBhojwani a99a430
conflict
PankajBhojwani 3691791
pr comments
PankajBhojwani 5795247
conflict 2
PankajBhojwani daf815f
spell
PankajBhojwani a95fb42
rename setting, only ignore same index
PankajBhojwani 7277f9d
Constants only in cpp file
PankajBhojwani acb2fb4
move member
PankajBhojwani File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,10 @@ | ||
atan | ||
CPrime | ||
HBar | ||
HPrime | ||
isnan | ||
LPrime | ||
LStep | ||
powf | ||
RSub | ||
sqrtf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
http | ||
www | ||
easyrgb | ||
php | ||
ecma | ||
rapidtables | ||
WCAG | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
// A lot of code was taken from | ||
// https://github.com/Maximus5/ConEmu/blob/master/src/ConEmu/ColorFix.cpp | ||
// and then adjusted to fit our style guidelines | ||
|
||
#include "pch.h" | ||
PankajBhojwani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
PankajBhojwani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#include <Windows.h> | ||
#include "ColorFix.hpp" | ||
|
||
static constexpr double gMinThreshold = 12.0; | ||
static constexpr double gExpThreshold = 20.0; | ||
static constexpr double gLStep = 5.0; | ||
|
||
static constexpr double rad006 = 0.104719755119659774; | ||
static constexpr double rad025 = 0.436332312998582394; | ||
static constexpr double rad030 = 0.523598775598298873; | ||
static constexpr double rad060 = 1.047197551196597746; | ||
static constexpr double rad063 = 1.099557428756427633; | ||
static constexpr double rad180 = 3.141592653589793238; | ||
static constexpr double rad275 = 4.799655442984406336; | ||
static constexpr double rad360 = 6.283185307179586476; | ||
|
||
ColorFix::ColorFix(COLORREF color) | ||
{ | ||
rgb = color; | ||
_ToLab(); | ||
} | ||
|
||
// Method Description: | ||
// - Helper function to calculate HPrime | ||
double ColorFix::_GetHPrimeFn(double x, double y) | ||
{ | ||
if (x == 0 && y == 0) | ||
{ | ||
return 0; | ||
} | ||
|
||
const auto hueAngle = atan2(x, y); | ||
return hueAngle >= 0 ? hueAngle : hueAngle + rad360; | ||
} | ||
|
||
// Method Description: | ||
// - Given 2 colors, computes the DeltaE value between them | ||
// Arguments: | ||
// - x1: the first color | ||
// - x2: the second color | ||
// Return Value: | ||
// - The DeltaE value between x1 and x2 | ||
double ColorFix::_GetDeltaE(ColorFix x1, ColorFix x2) | ||
{ | ||
constexpr double kSubL = 1; | ||
constexpr double kSubC = 1; | ||
constexpr double kSubH = 1; | ||
|
||
// Delta L Prime | ||
const double deltaLPrime = x2.L - x1.L; | ||
|
||
// L Bar | ||
const double lBar = (x1.L + x2.L) / 2; | ||
|
||
// C1 & C2 | ||
const double c1 = sqrt(pow(x1.A, 2) + pow(x1.B, 2)); | ||
const double c2 = sqrt(pow(x2.A, 2) + pow(x2.B, 2)); | ||
|
||
// C Bar | ||
const double cBar = (c1 + c2) / 2; | ||
|
||
// A Prime 1 | ||
const double aPrime1 = x1.A + (x1.A / 2) * (1 - sqrt(pow(cBar, 7) / (pow(cBar, 7) + pow(25.0, 7)))); | ||
|
||
// A Prime 2 | ||
const double aPrime2 = x2.A + (x2.A / 2) * (1 - sqrt(pow(cBar, 7) / (pow(cBar, 7) + pow(25.0, 7)))); | ||
|
||
// C Prime 1 | ||
const double cPrime1 = sqrt(pow(aPrime1, 2) + pow(x1.B, 2)); | ||
|
||
// C Prime 2 | ||
const double cPrime2 = sqrt(pow(aPrime2, 2) + pow(x2.B, 2)); | ||
|
||
// C Bar Prime | ||
const double cBarPrime = (cPrime1 + cPrime2) / 2; | ||
|
||
// Delta C Prime | ||
const double deltaCPrime = cPrime2 - cPrime1; | ||
|
||
// S sub L | ||
const double sSubL = 1 + ((0.015 * pow(lBar - 50, 2)) / sqrt(20 + pow(lBar - 50, 2))); | ||
|
||
// S sub C | ||
const double sSubC = 1 + 0.045 * cBarPrime; | ||
|
||
// h Prime 1 | ||
const double hPrime1 = _GetHPrimeFn(x1.B, aPrime1); | ||
|
||
// h Prime 2 | ||
const double hPrime2 = _GetHPrimeFn(x2.B, aPrime2); | ||
|
||
// Delta H Prime | ||
const double deltaHPrime = 0 == c1 || 0 == c2 ? 0 : 2 * sqrt(cPrime1 * cPrime2) * sin(abs(hPrime1 - hPrime2) <= rad180 ? hPrime2 - hPrime1 : (hPrime2 <= hPrime1 ? hPrime2 - hPrime1 + rad360 : hPrime2 - hPrime1 - rad360) / 2); | ||
|
||
// H Bar Prime | ||
const double hBarPrime = (abs(hPrime1 - hPrime2) > rad180) ? (hPrime1 + hPrime2 + rad360) / 2 : (hPrime1 + hPrime2) / 2; | ||
|
||
// T | ||
const double t = 1 - 0.17 * cos(hBarPrime - rad030) + 0.24 * cos(2 * hBarPrime) + 0.32 * cos(3 * hBarPrime + rad006) - 0.20 * cos(4 * hBarPrime - rad063); | ||
|
||
// S sub H | ||
const double sSubH = 1 + 0.015 * cBarPrime * t; | ||
|
||
// R sub T | ||
const double rSubT = -2 * sqrt(pow(cBarPrime, 7) / (pow(cBarPrime, 7) + pow(25.0, 7))) * sin(rad060 * exp(-pow((hBarPrime - rad275) / rad025, 2))); | ||
|
||
// Put it all together! | ||
const double lightness = deltaLPrime / (kSubL * sSubL); | ||
const double chroma = deltaCPrime / (kSubC * sSubC); | ||
const double hue = deltaHPrime / (kSubH * sSubH); | ||
|
||
return sqrt(pow(lightness, 2) + pow(chroma, 2) + pow(hue, 2) + rSubT * chroma * hue); | ||
} | ||
|
||
// Method Description: | ||
// - Populates our L, A, B values, based on our r, g, b values | ||
// - Converts a color in rgb format to a color in lab format | ||
// - Reference: http://www.easyrgb.com/index.php?X=MATH&H=01#text1 | ||
void ColorFix::_ToLab() | ||
{ | ||
double var_R = r / 255.0; | ||
double var_G = g / 255.0; | ||
double var_B = b / 255.0; | ||
|
||
var_R = var_R > 0.04045 ? pow(((var_R + 0.055) / 1.055), 2.4) : var_R / 12.92; | ||
var_G = var_G > 0.04045 ? pow(((var_G + 0.055) / 1.055), 2.4) : var_G / 12.92; | ||
var_B = var_B > 0.04045 ? pow(((var_B + 0.055) / 1.055), 2.4) : var_B / 12.92; | ||
|
||
var_R = var_R * 100.; | ||
var_G = var_G * 100.; | ||
var_B = var_B * 100.; | ||
|
||
//Observer. = 2 degrees, Illuminant = D65 | ||
const double X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805; | ||
const double Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722; | ||
const double Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505; | ||
|
||
double var_X = X / 95.047; //ref_X = 95.047 (Observer= 2 degrees, Illuminant= D65) | ||
double var_Y = Y / 100.000; //ref_Y = 100.000 | ||
double var_Z = Z / 108.883; //ref_Z = 108.883 | ||
|
||
var_X = var_X > 0.008856 ? pow(var_X, (1. / 3.)) : (7.787 * var_X) + (16. / 116.); | ||
var_Y = var_Y > 0.008856 ? pow(var_Y, (1. / 3.)) : (7.787 * var_Y) + (16. / 116.); | ||
var_Z = var_Z > 0.008856 ? pow(var_Z, (1. / 3.)) : (7.787 * var_Z) + (16. / 116.); | ||
|
||
L = (116. * var_Y) - 16.; | ||
A = 500. * (var_X - var_Y); | ||
B = 200. * (var_Y - var_Z); | ||
} | ||
|
||
// Method Description: | ||
// - Populates our r, g, b values, based on our L, A, B values | ||
// - Converts a color in lab format to a color in rgb format | ||
// - Reference: http://www.easyrgb.com/index.php?X=MATH&H=01#text1 | ||
void ColorFix::_ToRGB() | ||
{ | ||
double var_Y = (L + 16.) / 116.; | ||
double var_X = A / 500. + var_Y; | ||
double var_Z = var_Y - B / 200.; | ||
|
||
var_Y = (pow(var_Y, 3) > 0.008856) ? pow(var_Y, 3) : (var_Y - 16. / 116.) / 7.787; | ||
var_X = (pow(var_X, 3) > 0.008856) ? pow(var_X, 3) : (var_X - 16. / 116.) / 7.787; | ||
var_Z = (pow(var_Z, 3) > 0.008856) ? pow(var_Z, 3) : (var_Z - 16. / 116.) / 7.787; | ||
|
||
double X = 95.047 * var_X; //ref_X = 95.047 (Observer= 2 degrees, Illuminant= D65) | ||
double Y = 100.000 * var_Y; //ref_Y = 100.000 | ||
double Z = 108.883 * var_Z; //ref_Z = 108.883 | ||
|
||
var_X = X / 100.; //X from 0 to 95.047 (Observer = 2 degrees, Illuminant = D65) | ||
var_Y = Y / 100.; //Y from 0 to 100.000 | ||
var_Z = Z / 100.; //Z from 0 to 108.883 | ||
|
||
double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986; | ||
double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415; | ||
double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570; | ||
|
||
var_R = var_R > 0.0031308 ? 1.055 * pow(var_R, (1 / 2.4)) - 0.055 : var_R = 12.92 * var_R; | ||
var_G = var_G > 0.0031308 ? 1.055 * pow(var_G, (1 / 2.4)) - 0.055 : var_G = 12.92 * var_G; | ||
var_B = var_B > 0.0031308 ? 1.055 * pow(var_B, (1 / 2.4)) - 0.055 : var_B = 12.92 * var_B; | ||
|
||
r = (BYTE)std::clamp(var_R * 255., 0., 255.); | ||
g = (BYTE)std::clamp(var_G * 255., 0., 255.); | ||
b = (BYTE)std::clamp(var_B * 255., 0., 255.); | ||
} | ||
|
||
// Method Description: | ||
// - Given foreground and background colors, change the foreground color to | ||
// make it more perceivable if necessary | ||
// - Arguments: | ||
// - fg: the foreground color | ||
// - bg: the background color | ||
// - Return Value: | ||
// - The foreground color after performing any necessary changes to make it more perceivable | ||
COLORREF ColorFix::GetPerceivableColor(COLORREF fg, COLORREF bg) | ||
{ | ||
// If the colors are the same, don't do any adjusting | ||
if (fg == bg) | ||
{ | ||
return fg; | ||
} | ||
ColorFix backLab(bg); | ||
ColorFix frontLab(fg); | ||
const double de1 = _GetDeltaE(frontLab, backLab); | ||
if (de1 < gMinThreshold) | ||
{ | ||
for (int i = 0; i <= 1; i++) | ||
{ | ||
const double step = (i == 0) ? gLStep : -gLStep; | ||
frontLab.L += step; | ||
|
||
while (((i == 0) && (frontLab.L <= 100)) || ((i == 1) && (frontLab.L >= 0))) | ||
{ | ||
const double de2 = _GetDeltaE(frontLab, backLab); | ||
if (de2 >= gExpThreshold) | ||
{ | ||
frontLab._ToRGB(); | ||
return frontLab.rgb; | ||
} | ||
frontLab.L += step; | ||
} | ||
} | ||
} | ||
return frontLab.rgb; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/*++ | ||
Copyright (c) Microsoft Corporation | ||
Licensed under the MIT license. | ||
|
||
Module Name: | ||
- ColorFix | ||
|
||
Abstract: | ||
- Implementation of perceptual color nudging, which allows the Terminal | ||
to slightly shift the foreground color to make it more perceivable on | ||
the current background (for cases where the foreground is very close | ||
to being imperceivable on the background). | ||
|
||
Author(s): | ||
- Pankaj Bhojwani - Sep 2021 | ||
|
||
--*/ | ||
|
||
#pragma once | ||
|
||
struct ColorFix | ||
{ | ||
public: | ||
ColorFix(COLORREF color); | ||
|
||
static COLORREF GetPerceivableColor(COLORREF fg, COLORREF bg); | ||
|
||
// RGB | ||
union | ||
{ | ||
struct | ||
{ | ||
BYTE r, g, b, dummy; | ||
}; | ||
COLORREF rgb; | ||
}; | ||
|
||
// Lab | ||
struct | ||
{ | ||
double L, A, B; | ||
}; | ||
|
||
private: | ||
static double _GetHPrimeFn(double x, double y); | ||
static double _GetDeltaE(ColorFix x1, ColorFix x2); | ||
void _ToLab(); | ||
void _ToRGB(); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"adjust" perhaps instead of "nudge"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"increasedColorContrast" ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm does that sound very similar to high contrast mode? Also I wonder if people will confuse that to mean 'this feature always increases contrast' instead of 'this feature will move the foreground slightly only if the foreground is very similar to the background'