From dd5dbb2a40bd8d102cbf4db855037839d7b4d2f3 Mon Sep 17 00:00:00 2001 From: PankajBhojwani Date: Thu, 7 Oct 2021 15:43:17 -0700 Subject: [PATCH] Implement the Delta E algorithm to improve color perception (#11095) - Implements the Delta E algorithm - Uses the Delta E algorithm to precalculate adjusted foreground values based on possible foreground/background color pairs in the color table - Adds a setting to use the adjusted foreground values when applicable ## PR Checklist * [x] Closes #2638 * [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA * [ ] Tests added/passed * [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx * [ ] Schema updated. * [x] I work here ## Validation Steps Performed Before: color before After (note dark blue): color after --- .github/actions/spelling/allow/math.txt | 7 + .github/actions/spelling/expect/web.txt | 2 + NOTICE.md | 37 +++ doc/cascadia/profiles.schema.json | 8 + src/cascadia/TerminalCore/ColorFix.cpp | 228 ++++++++++++++++++ src/cascadia/TerminalCore/ColorFix.hpp | 49 ++++ src/cascadia/TerminalCore/ICoreAppearance.idl | 1 + src/cascadia/TerminalCore/Terminal.cpp | 8 +- src/cascadia/TerminalCore/Terminal.hpp | 4 + .../TerminalCore/terminalcore-common.vcxitems | 2 + .../TerminalCore/terminalrenderdata.cpp | 83 ++++++- .../TerminalSettingsEditor/Appearances.h | 3 +- .../TerminalSettingsEditor/Appearances.idl | 1 + .../TerminalSettingsEditor/Appearances.xaml | 8 + .../Resources/en-US/Resources.resw | 8 + .../AppearanceConfig.cpp | 4 + .../TerminalSettingsModel/AppearanceConfig.h | 2 + .../CascadiaSettings.cpp | 1 + .../IAppearanceConfig.idl | 1 + .../TerminalSettings.cpp | 1 + .../TerminalSettingsModel/TerminalSettings.h | 2 + .../UnitTests_Control/MockControlSettings.h | 1 + .../UnitTests_TerminalCore/MockTermSettings.h | 1 + 23 files changed, 453 insertions(+), 9 deletions(-) create mode 100644 src/cascadia/TerminalCore/ColorFix.cpp create mode 100644 src/cascadia/TerminalCore/ColorFix.hpp diff --git a/.github/actions/spelling/allow/math.txt b/.github/actions/spelling/allow/math.txt index 1482aedaba0..096fc197473 100644 --- a/.github/actions/spelling/allow/math.txt +++ b/.github/actions/spelling/allow/math.txt @@ -1,3 +1,10 @@ +atan +CPrime +HBar +HPrime isnan +LPrime +LStep powf +RSub sqrtf diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index 3072b0075b4..826edf1af8e 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,5 +1,7 @@ http www +easyrgb +php ecma rapidtables WCAG diff --git a/NOTICE.md b/NOTICE.md index 44e0f66629c..8fc966f8aee 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -252,6 +252,43 @@ DEALINGS IN THE SOFTWARE. ``` +## ConEmu +**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu) + +### License + +``` +BSD 3-Clause License + +Copyright (c) 2009-2017, Maximus5 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + # Microsoft Open Source This product also incorporates source code from other Microsoft open source projects, all licensed under the MIT license. diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 4f55d576b27..f61e74f4e41 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -188,6 +188,10 @@ ], "type": "string" }, + "adjustIndistinguishableColors": { + "description": "When set to true, we will (when necessary) adjust the foreground color to make it more visible, based on the background color.", + "type": "boolean" + }, "experimental.retroTerminalEffect": { "description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.", "type": "boolean" @@ -1973,6 +1977,10 @@ } ] }, + "adjustIndistinguishableColors": { + "description": "When set to true, we will (when necessary) adjust the foreground color to make it more visible, based on the background color.", + "type": "boolean" + }, "scrollbarState": { "default": "visible", "description": "Defines the visibility of the scrollbar.", diff --git a/src/cascadia/TerminalCore/ColorFix.cpp b/src/cascadia/TerminalCore/ColorFix.cpp new file mode 100644 index 00000000000..577cfe41871 --- /dev/null +++ b/src/cascadia/TerminalCore/ColorFix.cpp @@ -0,0 +1,228 @@ +// 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" + +#include +#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) +{ + 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; +} diff --git a/src/cascadia/TerminalCore/ColorFix.hpp b/src/cascadia/TerminalCore/ColorFix.hpp new file mode 100644 index 00000000000..aa32ecebd6b --- /dev/null +++ b/src/cascadia/TerminalCore/ColorFix.hpp @@ -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(); +}; diff --git a/src/cascadia/TerminalCore/ICoreAppearance.idl b/src/cascadia/TerminalCore/ICoreAppearance.idl index 5300ec6748f..ddeb5b83825 100644 --- a/src/cascadia/TerminalCore/ICoreAppearance.idl +++ b/src/cascadia/TerminalCore/ICoreAppearance.idl @@ -67,5 +67,6 @@ namespace Microsoft.Terminal.Core CursorStyle CursorShape; UInt32 CursorHeight; Boolean IntenseIsBright; + Boolean AdjustIndistinguishableColors; }; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 1ecf6158ccc..3433b60d969 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -53,7 +53,8 @@ Terminal::Terminal() : _taskbarState{ 0 }, _taskbarProgress{ 0 }, _trimBlockSelection{ false }, - _intenseIsBright{ true } + _intenseIsBright{ true }, + _adjustIndistinguishableColors{ true } { auto dispatch = std::make_unique(*this); auto engine = std::make_unique(std::move(dispatch)); @@ -175,11 +176,16 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) _defaultBg = newBackgroundColor.with_alpha(0); _defaultFg = appearance.DefaultForeground(); _intenseIsBright = appearance.IntenseIsBright(); + _adjustIndistinguishableColors = appearance.AdjustIndistinguishableColors(); for (int i = 0; i < 16; i++) { _colorTable.at(i) = til::color{ appearance.GetColorTableEntry(i) }; } + if (_adjustIndistinguishableColors) + { + _MakeAdjustedColorArray(); + } CursorType cursorShape = CursorType::VerticalBar; switch (appearance.CursorShape()) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 0e105b1e19d..3a2f3dff175 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -301,6 +301,7 @@ class Microsoft::Terminal::Core::Terminal final : bool _bracketedPasteMode; bool _trimBlockSelection; bool _intenseIsBright; + bool _adjustIndistinguishableColors; size_t _taskbarState; size_t _taskbarProgress; @@ -401,6 +402,9 @@ class Microsoft::Terminal::Core::Terminal final : Microsoft::Console::VirtualTerminal::SgrStack _sgrStack; + void _MakeAdjustedColorArray(); + std::array, 18> _adjustedForegroundColors; + #ifdef UNIT_TESTING friend class TerminalCoreUnitTests::TerminalBufferTests; friend class TerminalCoreUnitTests::TerminalApiTest; diff --git a/src/cascadia/TerminalCore/terminalcore-common.vcxitems b/src/cascadia/TerminalCore/terminalcore-common.vcxitems index 5b777af15c0..f74187ac696 100644 --- a/src/cascadia/TerminalCore/terminalcore-common.vcxitems +++ b/src/cascadia/TerminalCore/terminalcore-common.vcxitems @@ -8,6 +8,7 @@ + Create @@ -19,6 +20,7 @@ + diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index fe1fee627dc..df3483ab0f3 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -3,11 +3,16 @@ #include "pch.h" #include "Terminal.hpp" +#include "ColorFix.hpp" #include + using namespace Microsoft::Terminal::Core; using namespace Microsoft::Console::Types; using namespace Microsoft::Console::Render; +static constexpr size_t DefaultBgIndex{ 16 }; +static constexpr size_t DefaultFgIndex{ 17 }; + Viewport Terminal::GetViewport() noexcept { return _GetVisibleViewport(); @@ -44,14 +49,47 @@ const TextAttribute Terminal::GetDefaultBrushColors() noexcept std::pair Terminal::GetAttributeColors(const TextAttribute& attr) const noexcept { + std::pair colors; _blinkingState.RecordBlinkingUsage(attr); - auto colors = attr.CalculateRgbColors( - _colorTable, - _defaultFg, - _defaultBg, - _screenReversed, - _blinkingState.IsBlinkingFaint(), - _intenseIsBright); + const auto fgTextColor = attr.GetForeground(); + const auto bgTextColor = attr.GetBackground(); + + // We want to nudge the foreground color to make it more perceivable only for the + // default color pairs within the color table + if (_adjustIndistinguishableColors && + !(attr.IsFaint() || (attr.IsBlinking() && _blinkingState.IsBlinkingFaint())) && + (fgTextColor.IsDefault() || fgTextColor.IsLegacy()) && + (bgTextColor.IsDefault() || bgTextColor.IsLegacy())) + { + const auto bgIndex = bgTextColor.IsDefault() ? DefaultBgIndex : bgTextColor.GetIndex(); + auto fgIndex = fgTextColor.IsDefault() ? DefaultFgIndex : fgTextColor.GetIndex(); + + if (fgTextColor.IsIndex16() && (fgIndex < 8) && attr.IsBold() && _intenseIsBright) + { + // There is a special case for bold here - we need to get the bright version of the foreground color + fgIndex += 8; + } + + if (attr.IsReverseVideo() ^ _screenReversed) + { + colors.first = _adjustedForegroundColors[fgIndex][bgIndex]; + colors.second = fgTextColor.GetColor(_colorTable, _defaultFg); + } + else + { + colors.first = _adjustedForegroundColors[bgIndex][fgIndex]; + colors.second = bgTextColor.GetColor(_colorTable, _defaultBg); + } + } + else + { + colors = attr.CalculateRgbColors(_colorTable, + _defaultFg, + _defaultBg, + _screenReversed, + _blinkingState.IsBlinkingFaint(), + _intenseIsBright); + } colors.first |= 0xff000000; // We only care about alpha for the default BG (which enables acrylic) // If the bg isn't the default bg color, or reverse video is enabled, make it fully opaque. @@ -262,3 +300,34 @@ const bool Terminal::IsUiaDataInitialized() const noexcept // UiaData are not yet initialized. return !!_buffer; } + +// Method Description: +// - Creates the adjusted color array, which contains the possible foreground colors, +// adjusted for perceivability +// - The adjusted color array is 2-d, and effectively maps a background and foreground +// color pair to the adjusted foreground for that color pair +void Terminal::_MakeAdjustedColorArray() +{ + // The color table has 16 colors, but the adjusted color table needs to be 18 + // to include the default background and default foreground colors + std::array colorTableWithDefaults; + std::copy_n(std::begin(_colorTable), 16, std::begin(colorTableWithDefaults)); + colorTableWithDefaults[DefaultBgIndex] = _defaultBg; + colorTableWithDefaults[DefaultFgIndex] = _defaultFg; + for (auto fgIndex = 0; fgIndex < 18; ++fgIndex) + { + const auto fg = til::at(colorTableWithDefaults, fgIndex); + for (auto bgIndex = 0; bgIndex < 18; ++bgIndex) + { + if (fgIndex == bgIndex) + { + _adjustedForegroundColors[bgIndex][fgIndex] = fg; + } + else + { + const auto bg = til::at(colorTableWithDefaults, bgIndex); + _adjustedForegroundColors[bgIndex][fgIndex] = ColorFix::GetPerceivableColor(fg, bg); + } + } + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index f6fa4b3d644..980d16d167a 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -1,4 +1,4 @@ -/*++ +/*++ Copyright (c) Microsoft Corporation Licensed under the MIT license. @@ -93,6 +93,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance, BackgroundImageStretchMode); OBSERVABLE_PROJECTED_SETTING(_appearance, BackgroundImageAlignment); OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle); + OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors); private: Model::AppearanceConfig _appearance; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index fe2450b6d3c..33e4567fda2 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -46,6 +46,7 @@ namespace Microsoft.Terminal.Settings.Editor OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Xaml.Media.Stretch, BackgroundImageStretchMode); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.ConvergedAlignment, BackgroundImageAlignment); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Microsoft.Terminal.Settings.Model.IntenseStyle, IntenseTextStyle); + OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Boolean, AdjustIndistinguishableColors); } [default_interface] runtimeclass Appearances : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index c364f68921b..c1f3253766c 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -153,6 +153,14 @@ SettingOverrideSource="{x:Bind Appearance.RetroTerminalEffectOverrideSource, Mode=OneWay}"> + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index a912895ff02..ae939a52a98 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -799,6 +799,14 @@ When enabled, enables retro terminal effects such as glowing text and scan lines. A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect". + + Automatically adjust lightness of indistinguishable text + Header for a control to toggle if we should adjust the foreground color's lightness to make it more visible when necessary, based on the background color. + + + When enabled, enables automatic adjustment of indistinguishable colors, which will, only when necessary, adjust the foreground color's lightness to make it more visible (based on the background color). + A description for what the "adjust indistinguishable colors" setting does. Presented near "Profile_AdjustIndistinguishableColors". + Scrollbar visibility Header for a control to select the visibility of the scrollbar in a session. diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp index 2384de2ff48..dcc1677e6ca 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.cpp @@ -26,6 +26,7 @@ static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageA static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" }; static constexpr std::string_view PixelShaderPathKey{ "experimental.pixelShaderPath" }; static constexpr std::string_view IntenseTextStyleKey{ "intenseTextStyle" }; +static constexpr std::string_view AdjustIndistinguishableColorsKey{ "adjustIndistinguishableColors" }; static constexpr std::string_view LegacyAcrylicTransparencyKey{ "acrylicOpacity" }; static constexpr std::string_view OpacityKey{ "opacity" }; @@ -52,6 +53,7 @@ winrt::com_ptr AppearanceConfig::CopyAppearance(const Appearan appearance->_PixelShaderPath = source->_PixelShaderPath; appearance->_IntenseTextStyle = source->_IntenseTextStyle; appearance->_Opacity = source->_Opacity; + appearance->_AdjustIndistinguishableColors = source->_AdjustIndistinguishableColors; return appearance; } @@ -73,6 +75,7 @@ Json::Value AppearanceConfig::ToJson() const JsonUtils::SetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::SetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); JsonUtils::SetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle); + JsonUtils::SetValueForKey(json, AdjustIndistinguishableColorsKey, _AdjustIndistinguishableColors); JsonUtils::SetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter{}); return json; @@ -105,6 +108,7 @@ void AppearanceConfig::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::GetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); JsonUtils::GetValueForKey(json, IntenseTextStyleKey, _IntenseTextStyle); + JsonUtils::GetValueForKey(json, AdjustIndistinguishableColorsKey, _AdjustIndistinguishableColors); JsonUtils::GetValueForKey(json, LegacyAcrylicTransparencyKey, _Opacity); JsonUtils::GetValueForKey(json, OpacityKey, _Opacity, JsonUtils::OptionalConverter{}); } diff --git a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h index e15f5ef8287..6cf9a5f2d36 100644 --- a/src/cascadia/TerminalSettingsModel/AppearanceConfig.h +++ b/src/cascadia/TerminalSettingsModel/AppearanceConfig.h @@ -54,6 +54,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::IAppearanceConfig, Model::IntenseStyle, IntenseTextStyle, Model::IntenseStyle::Bright); INHERITABLE_SETTING(Model::IAppearanceConfig, double, Opacity, 1.0); + INHERITABLE_SETTING(Model::IAppearanceConfig, bool, AdjustIndistinguishableColors, true); + private: winrt::weak_ref _sourceProfile; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 0feafcdb743..ae3b7853cf9 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -318,6 +318,7 @@ Model::Profile CascadiaSettings::DuplicateProfile(const Model::Profile& source) DUPLICATE_SETTING_MACRO_SUB(appearance, target, RetroTerminalEffect); DUPLICATE_SETTING_MACRO_SUB(appearance, target, CursorShape); DUPLICATE_SETTING_MACRO_SUB(appearance, target, CursorHeight); + DUPLICATE_SETTING_MACRO_SUB(appearance, target, AdjustIndistinguishableColors); DUPLICATE_SETTING_MACRO_SUB(appearance, target, Opacity); } diff --git a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl index af89f55e8a6..01df05fedd2 100644 --- a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl +++ b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl @@ -51,6 +51,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_APPEARANCE_SETTING(Boolean, RetroTerminalEffect); INHERITABLE_APPEARANCE_SETTING(String, PixelShaderPath); INHERITABLE_APPEARANCE_SETTING(IntenseStyle, IntenseTextStyle); + INHERITABLE_APPEARANCE_SETTING(Boolean, AdjustIndistinguishableColors); INHERITABLE_APPEARANCE_SETTING(Double, Opacity); }; } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 187b3cedb3e..5e4d1eeea8a 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -202,6 +202,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold); _IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright); + _AdjustIndistinguishableColors = appearance.AdjustIndistinguishableColors(); _Opacity = appearance.Opacity(); } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 1c27f689ea8..90b7b60fef7 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -114,6 +114,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, IntenseIsBright); + INHERITABLE_SETTING(Model::TerminalSettings, bool, AdjustIndistinguishableColors); + // ------------------------ End of Core Settings ----------------------- INHERITABLE_SETTING(Model::TerminalSettings, hstring, ProfileName); diff --git a/src/cascadia/UnitTests_Control/MockControlSettings.h b/src/cascadia/UnitTests_Control/MockControlSettings.h index c2e6db9ae84..485f9b146b8 100644 --- a/src/cascadia/UnitTests_Control/MockControlSettings.h +++ b/src/cascadia/UnitTests_Control/MockControlSettings.h @@ -47,6 +47,7 @@ namespace ControlUnitTests WINRT_PROPERTY(bool, TrimBlockSelection, false); WINRT_PROPERTY(bool, DetectURLs, true); WINRT_PROPERTY(bool, IntenseIsBright, true); + WINRT_PROPERTY(bool, AdjustIndistinguishableColors, true); // ------------------------ End of Core Settings ----------------------- WINRT_PROPERTY(winrt::hstring, ProfileName); diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h index 2c23d221712..d3ce6a403de 100644 --- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -74,6 +74,7 @@ namespace TerminalCoreUnitTests void DetectURLs(bool) {} WINRT_PROPERTY(bool, IntenseIsBright, true); + WINRT_PROPERTY(bool, AdjustIndistinguishableColors, true); private: int32_t _historySize;