зеркало из https://github.com/microsoft/terminal.git
Use Oklab for text and cursor contrast adjustments (#15283)
Oklab by Björn Ottosson is a color space that has less irregularities than the CIELAB color space used for ΔE2000. The distance metric for Oklab (ΔEOK) is defined by CSS4 as the simple euclidian distance. This allows us to drastically simplify the code needed to determine a color that has enough contrast. The new implementation still lacks proper gamut mapping, but that's another and less important issue. I also made it so that text with the dim attribute gets adjusted just like regular text, since this is an accessibility feature after all. The new code is so much faster than the old code (12-125x) that I dropped any caching code we had. While this increases the CPU overhead when printing lots of indexed colors, the code is way less complex now. "Increases" in this case however means something in the order of 15-60ns per color change (as measured on my CPU). It's possible to further improve the performance using explicit SIMD instructions, but I've left that as a future improvement, since that will make the code quite a bit more verbose and I didn't want to hinder the initial review. Finally, these new routines are also used for ensuring that the AtlasEngine cursors remains visible at all times. Closes #9610 ## Validation Steps Performed * When `adjustIndistinguishableColors` is enabled colors are distinguishable ✅ * An inverted cursor on top of a `#7f7f7f` foreground & background is still visible ✅ * A colored cursor on top of a background with identical color is still visible ✅ * Cursors on a transparent background are visible ✅
This commit is contained in:
Родитель
cc89787c34
Коммит
4dd9493135
|
@ -1,6 +1,6 @@
|
|||
Anup
|
||||
austdi
|
||||
arkthur
|
||||
austdi
|
||||
Ballmer
|
||||
bhoj
|
||||
Bhojwani
|
||||
|
@ -31,8 +31,8 @@ jerrysh
|
|||
Kaiyu
|
||||
kimwalisch
|
||||
KMehrain
|
||||
KODELIFE
|
||||
Kodelife
|
||||
KODELIFE
|
||||
Kourosh
|
||||
kowalczyk
|
||||
leonardder
|
||||
|
@ -61,6 +61,7 @@ oising
|
|||
oldnewthing
|
||||
opengl
|
||||
osgwiki
|
||||
Ottosson
|
||||
pabhojwa
|
||||
panos
|
||||
paulcam
|
||||
|
@ -88,8 +89,8 @@ Wirt
|
|||
Wojciech
|
||||
zadjii
|
||||
Zamor
|
||||
Zamora
|
||||
zamora
|
||||
Zamora
|
||||
zljubisic
|
||||
Zoey
|
||||
zorio
|
||||
|
|
|
@ -112,6 +112,7 @@
|
|||
^src/tools/RenderingTests/main.cpp$
|
||||
^src/tools/texttests/fira\.txt$
|
||||
^src/tools/U8U16Test/(?:fr|ru|zh)\.txt$
|
||||
^src/types/ColorFix.cpp
|
||||
^src/types/ut_types/UtilsTests.cpp$
|
||||
^tools/ReleaseEngineering/ServicingPipeline.ps1$
|
||||
ignore$
|
||||
|
|
|
@ -43,6 +43,7 @@ antialiasing
|
|||
ANull
|
||||
anycpu
|
||||
APARTMENTTHREADED
|
||||
APCA
|
||||
APCs
|
||||
APIENTRY
|
||||
apiset
|
||||
|
@ -194,6 +195,8 @@ chh
|
|||
chk
|
||||
CHT
|
||||
Cic
|
||||
cielab
|
||||
Cielab
|
||||
Clcompile
|
||||
CLE
|
||||
cleartype
|
||||
|
@ -584,6 +587,7 @@ ENU
|
|||
ENUMLOGFONT
|
||||
ENUMLOGFONTEX
|
||||
enumranges
|
||||
EOK
|
||||
eplace
|
||||
EPres
|
||||
EQU
|
||||
|
@ -1290,6 +1294,8 @@ OEMFONT
|
|||
OEMFORMAT
|
||||
OEMs
|
||||
offboarded
|
||||
oklab
|
||||
Oklab
|
||||
OLEAUT
|
||||
OLECHAR
|
||||
onecore
|
||||
|
@ -1770,6 +1776,8 @@ srcsrv
|
|||
SRCSRVTRG
|
||||
srctool
|
||||
srect
|
||||
srgb
|
||||
Srgb
|
||||
srv
|
||||
srvinit
|
||||
srvpipe
|
||||
|
|
49
NOTICE.md
49
NOTICE.md
|
@ -301,41 +301,28 @@ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
```
|
||||
|
||||
## ConEmu
|
||||
**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu)
|
||||
## Oklab
|
||||
**Source**: [https://bottosson.github.io/posts/oklab/](https://bottosson.github.io/posts/oklab/)
|
||||
|
||||
### License
|
||||
|
||||
```
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2009-2017, Maximus5 <ConEmu.Maximus5@gmail.com>
|
||||
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.
|
||||
Copyright (c) 2020 Björn Ottosson
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
# Microsoft Open Source
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Perceptual Color Nudging</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
html {
|
||||
background-color: #0c0c0c;
|
||||
color: #cccccc;
|
||||
font-family: "Cascadia Code", "Cascadia Mono", monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body>div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
form,
|
||||
h2 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="flex: 2; align-items: flex-start; background-color: #0c0c0c">
|
||||
<form>
|
||||
<input id="background-color" name="background-color" type="color" value="#0c0c0c" />
|
||||
<label for="background-color">background color</label>
|
||||
</form>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Input</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-input"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ΔE2000<br>(ConEmu)</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-cielab"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ΔEOK</td>
|
||||
<td>WCAG21:<br>APCA:</td>
|
||||
<td id="stats-oklab"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="input" style="flex: 1">
|
||||
<h2>Input</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<div id="cielab" style="flex: 1">
|
||||
<h2>ΔE2000 (ConEmu)</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<div id="oklab" style="flex: 1">
|
||||
<h2>ΔEOK</h2>
|
||||
<pre style="font-size: 8pt">猫</pre>
|
||||
<pre style="font-size: 10pt">猫</pre>
|
||||
<pre style="font-size: 12pt">猫</pre>
|
||||
<pre style="font-size: 14pt">猫</pre>
|
||||
<pre style="font-size: 16pt">猫</pre>
|
||||
<pre style="font-size: 32pt">猫</pre>
|
||||
<pre style="font-size: 64pt">猫</pre>
|
||||
</div>
|
||||
<script type="module">
|
||||
import Color from "https://cdn.jsdelivr.net/npm/colorjs.io@0.4.3/+esm";
|
||||
|
||||
window.Color = Color;
|
||||
|
||||
const input = document.getElementById("input");
|
||||
const cielab = document.getElementById("cielab");
|
||||
const oklab = document.getElementById("oklab");
|
||||
|
||||
const statsInput = document.getElementById("stats-input");
|
||||
const statsCielab = document.getElementById("stats-cielab");
|
||||
const statsOklab = document.getElementById("stats-oklab");
|
||||
|
||||
let backgroundColor = new Color("#0c0c0c");
|
||||
let foregroundColor = new Color("#0c0c0c");
|
||||
let foregroundColorRange = null;
|
||||
let previousSecsIntegral = -1;
|
||||
|
||||
function saturate(val) {
|
||||
return val < 0 ? 0 : val > 1 ? 1 : val;
|
||||
}
|
||||
|
||||
function clipToSrgb(color) {
|
||||
return color.to("srgb").toGamut({ method: "clip" });
|
||||
}
|
||||
|
||||
function nudgeCielab(backgroundColor, foregroundColor) {
|
||||
const backgroundCielab = backgroundColor.to("lab-d65");
|
||||
const foregroundCielab = foregroundColor.to("lab-d65");
|
||||
|
||||
const de1 = Color.deltaE(foregroundColor, backgroundCielab, "2000");
|
||||
if (de1 >= 12.0) {
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 1; i++) {
|
||||
const step = (i == 0) ? 5.0 : -5.0;
|
||||
foregroundCielab.l += step;
|
||||
|
||||
while (((i == 0) && foregroundCielab.l <= 100) || (i == 1 && foregroundCielab.l >= 0)) {
|
||||
const de2 = Color.deltaE(foregroundCielab, backgroundCielab, "2000");
|
||||
if (de2 >= 20.0) {
|
||||
return clipToSrgb(foregroundCielab);
|
||||
}
|
||||
foregroundCielab.l += step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nudgeOklab(backgroundColor, foregroundColor) {
|
||||
const backgroundOklab = backgroundColor.to("oklab");
|
||||
const foregroundOklab = foregroundColor.to("oklab");
|
||||
const deltaSquared = {
|
||||
l: (backgroundOklab.l - foregroundOklab.l) ** 2,
|
||||
a: (backgroundOklab.a - foregroundOklab.a) ** 2,
|
||||
b: (backgroundOklab.b - foregroundOklab.b) ** 2,
|
||||
};
|
||||
const distance = deltaSquared.l + deltaSquared.a + deltaSquared.b;
|
||||
|
||||
if (distance >= 0.25) {
|
||||
return foregroundColor;
|
||||
}
|
||||
|
||||
let deltaL = Math.sqrt(0.25 - deltaSquared.a - deltaSquared.b);
|
||||
if (foregroundOklab.l < backgroundOklab.l)
|
||||
{
|
||||
deltaL = -deltaL;
|
||||
}
|
||||
|
||||
foregroundOklab.l = backgroundOklab.l + deltaL;
|
||||
if (foregroundOklab.l < 0 || foregroundOklab.l > 1)
|
||||
{
|
||||
foregroundOklab.l = backgroundOklab.l - deltaL;
|
||||
}
|
||||
|
||||
return clipToSrgb(foregroundOklab);
|
||||
}
|
||||
|
||||
function contrastStringLevels(num, level0, level1) {
|
||||
const str = num.toFixed(1);
|
||||
if (num < level0) {
|
||||
return `<span style="color:crimson">${str}</span>`;
|
||||
}
|
||||
if (num < level1) {
|
||||
return `<span style="color:coral">${str}</span>`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function contrastString(foregroundColor) {
|
||||
const contrastWCAG21 = contrastStringLevels(foregroundColor.contrast(backgroundColor, "WCAG21"), 3, 4.5);
|
||||
const contrastAPCA = contrastStringLevels(Math.abs(foregroundColor.contrast(backgroundColor, "APCA")), 45, 60);
|
||||
return `${contrastWCAG21}<br/>${contrastAPCA}`;
|
||||
}
|
||||
|
||||
function animate(time) {
|
||||
const timeScale = time / 1000;
|
||||
const secsIntegral = Math.trunc(timeScale);
|
||||
const secsFractional = timeScale % 1;
|
||||
|
||||
if (previousSecsIntegral != secsIntegral) {
|
||||
const foregroundColorTarget = new Color("srgb", backgroundColor.coords.map(c => saturate(c + Math.random() - 0.5)));
|
||||
foregroundColorRange = foregroundColor.range(foregroundColorTarget, { space: "srgb" });
|
||||
previousSecsIntegral = secsIntegral;
|
||||
}
|
||||
|
||||
foregroundColor = foregroundColorRange(secsFractional);
|
||||
input.style.color = foregroundColor.toString({ inGamut: false });
|
||||
|
||||
const foregroundCielabNudged = nudgeCielab(backgroundColor, foregroundColor);
|
||||
const foregroundOklabNudged = nudgeOklab(backgroundColor, foregroundColor);
|
||||
|
||||
cielab.style.color = foregroundCielabNudged;
|
||||
oklab.style.color = foregroundOklabNudged;
|
||||
|
||||
statsInput.innerHTML = contrastString(foregroundColor);
|
||||
statsCielab.innerHTML = contrastString(foregroundCielabNudged);
|
||||
statsOklab.innerHTML = contrastString(foregroundOklabNudged);
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
document.getElementById("background-color").addEventListener("input", event => {
|
||||
backgroundColor = new Color(event.target.value);
|
||||
document.documentElement.style.backgroundColor = backgroundColor;
|
||||
}, false);
|
||||
|
||||
document.documentElement.style.backgroundColor = backgroundColor;
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -64,7 +64,7 @@ bool TextColor::CanBeBrightened() const noexcept
|
|||
|
||||
bool TextColor::IsLegacy() const noexcept
|
||||
{
|
||||
return IsIndex16() || (IsIndex256() && _index < 16);
|
||||
return (IsIndex16() || IsIndex256()) && _index < 16;
|
||||
}
|
||||
|
||||
bool TextColor::IsIndex16() const noexcept
|
||||
|
@ -82,6 +82,11 @@ bool TextColor::IsDefault() const noexcept
|
|||
return _meta == ColorType::IsDefault;
|
||||
}
|
||||
|
||||
bool TextColor::IsDefaultOrLegacy() const noexcept
|
||||
{
|
||||
return _meta != ColorType::IsRgb && _index < 16;
|
||||
}
|
||||
|
||||
bool TextColor::IsRgb() const noexcept
|
||||
{
|
||||
return _meta == ColorType::IsRgb;
|
||||
|
|
|
@ -37,12 +37,14 @@ Revision History:
|
|||
#include "WexTestClass.h"
|
||||
#endif
|
||||
|
||||
// The enum values being in this particular order allows the compiler to do some useful optimizations,
|
||||
// like simplifying `IsIndex16() || IsIndex256()` into a simple range check without branching.
|
||||
enum class ColorType : BYTE
|
||||
{
|
||||
IsIndex256 = 0x0,
|
||||
IsIndex16 = 0x1,
|
||||
IsDefault = 0x2,
|
||||
IsRgb = 0x3
|
||||
IsDefault,
|
||||
IsIndex16,
|
||||
IsIndex256,
|
||||
IsRgb
|
||||
};
|
||||
|
||||
enum class ColorAlias : size_t
|
||||
|
@ -121,6 +123,7 @@ public:
|
|||
bool IsIndex16() const noexcept;
|
||||
bool IsIndex256() const noexcept;
|
||||
bool IsDefault() const noexcept;
|
||||
bool IsDefaultOrLegacy() const noexcept;
|
||||
bool IsRgb() const noexcept;
|
||||
|
||||
void SetColor(const COLORREF rgbColor) noexcept;
|
||||
|
|
|
@ -180,7 +180,6 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance)
|
|||
{
|
||||
_renderSettings.SetColorTableEntry(i, til::color{ appearance.GetColorTableEntry(i) });
|
||||
}
|
||||
_renderSettings.MakeAdjustedColorArray();
|
||||
|
||||
auto cursorShape = CursorType::VerticalBar;
|
||||
switch (appearance.CursorShape())
|
||||
|
@ -1325,8 +1324,6 @@ void Terminal::ApplyScheme(const Scheme& colorScheme)
|
|||
|
||||
_renderSettings.SetColorTableEntry(TextColor::CURSOR_COLOR, til::color{ colorScheme.CursorColor });
|
||||
|
||||
_renderSettings.MakeAdjustedColorArray();
|
||||
|
||||
// Tell the control that the scrollbar has somehow changed. Used as a
|
||||
// workaround to force the control to redraw any scrollbar marks whose color
|
||||
// may have changed.
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <shader_vs.h>
|
||||
|
||||
#include "dwrite.h"
|
||||
#include "../../types/inc/ColorFix.hpp"
|
||||
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_COLORIZE_GLYPH_ATLAS
|
||||
#include "colorbrewer.h"
|
||||
|
@ -826,7 +827,7 @@ void BackendD3D::_flushQuads(const RenderingPayload& p)
|
|||
|
||||
if (!_cursorRects.empty())
|
||||
{
|
||||
_drawCursorForeground(p);
|
||||
_drawCursorForeground();
|
||||
}
|
||||
|
||||
// TODO: Shrink instances buffer
|
||||
|
@ -1627,9 +1628,19 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p)
|
|||
static_cast<u16>(p.s->font->cellSize.x * (x1 - x0)),
|
||||
p.s->font->cellSize.y,
|
||||
};
|
||||
const auto isInverted = cursorColor == 0xffffffff;
|
||||
const auto background = isInverted ? bg ^ 0xc0c0c0 : cursorColor;
|
||||
const auto foreground = isInverted ? 0 : bg;
|
||||
auto background = cursorColor;
|
||||
auto foreground = bg;
|
||||
|
||||
if (cursorColor == 0xffffffff)
|
||||
{
|
||||
background = bg ^ 0xffffff;
|
||||
foreground = 0xffffffff;
|
||||
}
|
||||
|
||||
// The legacy console used to invert colors by just doing `bg ^ 0xc0c0c0`. This resulted
|
||||
// in a minimum squared distance of just 0.029195 across all possible color combinations.
|
||||
background = ColorFix::GetPerceivableColor(background, bg, 0.25f * 0.25f);
|
||||
|
||||
auto& c0 = _cursorRects.emplace_back(position, size, background, foreground);
|
||||
|
||||
switch (static_cast<CursorType>(p.s->cursor->cursorType))
|
||||
|
@ -1702,7 +1713,7 @@ void BackendD3D::_drawCursorBackground(const RenderingPayload& p)
|
|||
}
|
||||
}
|
||||
|
||||
void BackendD3D::_drawCursorForeground(const RenderingPayload& p)
|
||||
void BackendD3D::_drawCursorForeground()
|
||||
{
|
||||
// NOTE: _appendQuad() may reallocate the _instances vector. It's important to iterate
|
||||
// by index, because pointers (or iterators) would get invalidated. It's also important
|
||||
|
@ -1779,7 +1790,7 @@ void BackendD3D::_drawCursorForeground(const RenderingPayload& p)
|
|||
{
|
||||
// The _instances vector is _huge_ (easily up to 50k items) whereas only 1-2 items will actually overlap
|
||||
// with the cursor. --> Make this loop more compact by putting as much as possible into a function call.
|
||||
const auto added = _drawCursorForegroundSlowPath(p, c, i);
|
||||
const auto added = _drawCursorForegroundSlowPath(c, i);
|
||||
i += added;
|
||||
instancesCount += added;
|
||||
}
|
||||
|
@ -1787,7 +1798,7 @@ void BackendD3D::_drawCursorForeground(const RenderingPayload& p)
|
|||
}
|
||||
}
|
||||
|
||||
size_t BackendD3D::_drawCursorForegroundSlowPath(const RenderingPayload& p, const CursorRect& c, size_t offset)
|
||||
size_t BackendD3D::_drawCursorForegroundSlowPath(const CursorRect& c, size_t offset)
|
||||
{
|
||||
// We won't die from copying 24 bytes. It simplifies the code below especially in
|
||||
// respect to when/if we overwrite the _instances[offset] slot with a cutout.
|
||||
|
@ -1886,7 +1897,9 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const RenderingPayload& p, cons
|
|||
target.color = it.color;
|
||||
}
|
||||
|
||||
const auto cursorColor = p.s->cursor->cursorColor;
|
||||
auto color = c.foreground == 0xffffffff ? it.color ^ 0xffffff : c.foreground;
|
||||
color = ColorFix::GetPerceivableColor(color, c.background, 0.5f * 0.5f);
|
||||
|
||||
// If the cursor covers the entire glyph (like, let's say, a full-box cursor with an ASCII character),
|
||||
// we don't append a new quad, but rather reuse the one that already exists (cutoutCount == 0).
|
||||
auto& target = cutoutCount ? _appendQuad() : _instances[offset];
|
||||
|
@ -1898,7 +1911,7 @@ size_t BackendD3D::_drawCursorForegroundSlowPath(const RenderingPayload& p, cons
|
|||
target.size.y = static_cast<u16>(intersectionB - intersectionT);
|
||||
target.texcoord.x = static_cast<u16>(it.texcoord.x + intersectionL - instanceL);
|
||||
target.texcoord.y = static_cast<u16>(it.texcoord.y + intersectionT - instanceT);
|
||||
target.color = cursorColor == 0xffffffff ? it.color ^ 0xc0c0c0 : c.foreground;
|
||||
target.color = color;
|
||||
|
||||
return addedInstances;
|
||||
}
|
||||
|
|
|
@ -220,8 +220,8 @@ namespace Microsoft::Console::Render::Atlas
|
|||
void _splitDoubleHeightGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
|
||||
void _drawGridlines(const RenderingPayload& p, u16 y);
|
||||
void _drawCursorBackground(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _drawCursorForeground(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const RenderingPayload& p, const CursorRect& c, size_t offset);
|
||||
ATLAS_ATTR_COLD void _drawCursorForeground();
|
||||
ATLAS_ATTR_COLD size_t _drawCursorForegroundSlowPath(const CursorRect& c, size_t offset);
|
||||
void _drawSelection(const RenderingPayload& p);
|
||||
void _executeCustomShader(RenderingPayload& p);
|
||||
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
using namespace Microsoft::Console::Render;
|
||||
using Microsoft::Console::Utils::InitializeColorTable;
|
||||
|
||||
static constexpr size_t AdjustedFgIndex{ 16 };
|
||||
static constexpr size_t AdjustedBgIndex{ 17 };
|
||||
static constexpr size_t AdjustedBrightFgIndex{ 18 };
|
||||
|
||||
RenderSettings::RenderSettings() noexcept
|
||||
{
|
||||
InitializeColorTable(_colorTable);
|
||||
|
@ -71,42 +67,6 @@ void RenderSettings::ResetColorTable() noexcept
|
|||
InitializeColorTable({ _colorTable.data(), 16 });
|
||||
}
|
||||
|
||||
// Routine 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 RenderSettings::MakeAdjustedColorArray() noexcept
|
||||
{
|
||||
// The color table has 16 colors, but the adjusted color table needs to be 19
|
||||
// to include the default background, default foreground and bright default foreground colors
|
||||
std::array<COLORREF, 19> colorTableWithDefaults;
|
||||
std::copy_n(std::begin(_colorTable), 16, std::begin(colorTableWithDefaults));
|
||||
colorTableWithDefaults[AdjustedFgIndex] = GetColorAlias(ColorAlias::DefaultForeground);
|
||||
colorTableWithDefaults[AdjustedBgIndex] = GetColorAlias(ColorAlias::DefaultBackground);
|
||||
|
||||
// We need to use TextColor to calculate the bright default fg
|
||||
TextColor defaultFg;
|
||||
colorTableWithDefaults[AdjustedBrightFgIndex] = defaultFg.GetColor(_colorTable, GetColorAliasIndex(ColorAlias::DefaultForeground), true);
|
||||
|
||||
for (auto fgIndex = 0; fgIndex < 19; ++fgIndex)
|
||||
{
|
||||
const auto fg = til::at(colorTableWithDefaults, fgIndex);
|
||||
for (auto bgIndex = 0; bgIndex < 19; ++bgIndex)
|
||||
{
|
||||
if (fgIndex == bgIndex)
|
||||
{
|
||||
_adjustedForegroundColors[bgIndex][fgIndex] = fg;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto bg = til::at(colorTableWithDefaults, bgIndex);
|
||||
_adjustedForegroundColors[bgIndex][fgIndex] = ColorFix::GetPerceivableColor(fg, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Updates the given index in the color table to a new value.
|
||||
// Arguments:
|
||||
|
@ -196,74 +156,37 @@ std::pair<COLORREF, COLORREF> RenderSettings::GetAttributeColors(const TextAttri
|
|||
const auto dimFg = attr.IsFaint() || (_blinkShouldBeFaint && attr.IsBlinking());
|
||||
const auto swapFgAndBg = attr.IsReverseVideo() ^ GetRenderMode(Mode::ScreenReversed);
|
||||
|
||||
// We want to nudge the foreground color to make it more perceivable only for the
|
||||
// default color pairs within the color table
|
||||
if (Feature_AdjustIndistinguishableText::IsEnabled() &&
|
||||
GetRenderMode(Mode::IndexedDistinguishableColors) &&
|
||||
!dimFg &&
|
||||
!attr.IsInvisible() &&
|
||||
(fgTextColor.IsDefault() || fgTextColor.IsLegacy()) &&
|
||||
(bgTextColor.IsDefault() || bgTextColor.IsLegacy()))
|
||||
auto fg = fgTextColor.GetColor(_colorTable, defaultFgIndex, brightenFg);
|
||||
auto bg = bgTextColor.GetColor(_colorTable, defaultBgIndex);
|
||||
|
||||
if (dimFg)
|
||||
{
|
||||
const auto bgIndex = bgTextColor.IsDefault() ? AdjustedBgIndex : bgTextColor.GetIndex();
|
||||
auto fgIndex = fgTextColor.IsDefault() ? AdjustedFgIndex : fgTextColor.GetIndex();
|
||||
|
||||
if (brightenFg)
|
||||
{
|
||||
// There is a special case for intense here - we need to get the bright version of the foreground color
|
||||
if (fgTextColor.IsIndex16() && (fgIndex < 8))
|
||||
{
|
||||
fgIndex += 8;
|
||||
}
|
||||
else if (fgTextColor.IsDefault())
|
||||
{
|
||||
fgIndex = AdjustedBrightFgIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (swapFgAndBg)
|
||||
{
|
||||
const auto fg = _adjustedForegroundColors[fgIndex][bgIndex];
|
||||
const auto bg = fgTextColor.GetColor(_colorTable, defaultFgIndex, brightenFg);
|
||||
return { fg, bg };
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto fg = _adjustedForegroundColors[bgIndex][fgIndex];
|
||||
const auto bg = bgTextColor.GetColor(_colorTable, defaultBgIndex);
|
||||
return { fg, bg };
|
||||
}
|
||||
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
||||
}
|
||||
else
|
||||
if (swapFgAndBg)
|
||||
{
|
||||
auto fg = fgTextColor.GetColor(_colorTable, defaultFgIndex, brightenFg);
|
||||
auto bg = bgTextColor.GetColor(_colorTable, defaultBgIndex);
|
||||
std::swap(fg, bg);
|
||||
}
|
||||
if (attr.IsInvisible())
|
||||
{
|
||||
fg = bg;
|
||||
}
|
||||
|
||||
if (dimFg)
|
||||
{
|
||||
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
||||
}
|
||||
if (swapFgAndBg)
|
||||
{
|
||||
std::swap(fg, bg);
|
||||
}
|
||||
if (attr.IsInvisible())
|
||||
{
|
||||
fg = bg;
|
||||
}
|
||||
|
||||
// We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to
|
||||
// catch the cases where the fg was intentionally set to be the same as the bg. In either case,
|
||||
// don't adjust the foreground.
|
||||
if (Feature_AdjustIndistinguishableText::IsEnabled() &&
|
||||
// We intentionally aren't _only_ checking for attr.IsInvisible here, because we also want to
|
||||
// catch the cases where the fg was intentionally set to be the same as the bg. In either case,
|
||||
// don't adjust the foreground.
|
||||
if constexpr (Feature_AdjustIndistinguishableText::IsEnabled())
|
||||
{
|
||||
if (
|
||||
_renderMode.any(Mode::IndexedDistinguishableColors, Mode::AlwaysDistinguishableColors) &&
|
||||
fg != bg &&
|
||||
GetRenderMode(Mode::AlwaysDistinguishableColors))
|
||||
(_renderMode.test(Mode::AlwaysDistinguishableColors) || (fgTextColor.IsDefaultOrLegacy() && bgTextColor.IsDefaultOrLegacy())))
|
||||
{
|
||||
fg = ColorFix::GetPerceivableColor(fg, bg);
|
||||
fg = ColorFix::GetPerceivableColor(fg, bg, 0.5f * 0.5f);
|
||||
}
|
||||
|
||||
return { fg, bg };
|
||||
}
|
||||
|
||||
return { fg, bg };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -33,7 +33,6 @@ namespace Microsoft::Console::Render
|
|||
bool GetRenderMode(const Mode mode) const noexcept;
|
||||
const std::array<COLORREF, TextColor::TABLE_SIZE>& GetColorTable() const noexcept;
|
||||
void ResetColorTable() noexcept;
|
||||
void MakeAdjustedColorArray() noexcept;
|
||||
void SetColorTableEntry(const size_t tableIndex, const COLORREF color);
|
||||
COLORREF GetColorTableEntry(const size_t tableIndex) const;
|
||||
void SetColorAlias(const ColorAlias alias, const size_t tableIndex, const COLORREF color);
|
||||
|
@ -48,7 +47,6 @@ namespace Microsoft::Console::Render
|
|||
til::enumset<Mode> _renderMode{ Mode::BlinkAllowed, Mode::IntenseIsBright };
|
||||
std::array<COLORREF, TextColor::TABLE_SIZE> _colorTable;
|
||||
std::array<size_t, static_cast<size_t>(ColorAlias::ENUM_COUNT)> _colorAliasIndices;
|
||||
std::array<std::array<COLORREF, 19>, 19> _adjustedForegroundColors;
|
||||
size_t _blinkCycle = 0;
|
||||
mutable bool _blinkIsInUse = false;
|
||||
bool _blinkShouldBeFaint = false;
|
||||
|
|
|
@ -2940,11 +2940,6 @@ bool AdaptDispatch::SetClipboard(const std::wstring_view content)
|
|||
bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwColor)
|
||||
{
|
||||
_renderSettings.SetColorTableEntry(tableIndex, dwColor);
|
||||
if (_renderSettings.GetRenderMode(RenderSettings::Mode::IndexedDistinguishableColors))
|
||||
{
|
||||
// Re-calculate the adjusted colors now that one of the entries has been changed
|
||||
_renderSettings.MakeAdjustedColorArray();
|
||||
}
|
||||
|
||||
// If we're a conpty, always return false, so that we send the updated color
|
||||
// value to the terminal. Still handle the sequence so apps that use
|
||||
|
@ -3010,11 +3005,6 @@ bool AdaptDispatch::AssignColor(const DispatchTypes::ColorItem item, const VTInt
|
|||
case DispatchTypes::ColorItem::NormalText:
|
||||
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultForeground, fgIndex);
|
||||
_renderSettings.SetColorAliasIndex(ColorAlias::DefaultBackground, bgIndex);
|
||||
if (_renderSettings.GetRenderMode(RenderSettings::Mode::IndexedDistinguishableColors))
|
||||
{
|
||||
// Re-calculate the adjusted colors now that these aliases have been changed
|
||||
_renderSettings.MakeAdjustedColorArray();
|
||||
}
|
||||
break;
|
||||
case DispatchTypes::ColorItem::WindowFrame:
|
||||
_renderSettings.SetColorAliasIndex(ColorAlias::FrameForeground, fgIndex);
|
||||
|
|
|
@ -1,231 +1,212 @@
|
|||
// 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 "precomp.h"
|
||||
|
||||
#include "inc/ColorFix.hpp"
|
||||
|
||||
// This table contains a direct mapping from 8-bit sRGB to linear RGB.
|
||||
// It was generated using the following code:
|
||||
// #include <charconv>
|
||||
//
|
||||
// int main() {
|
||||
// char buffer[32];
|
||||
//
|
||||
// for (int i = 0; i < 256; ++i) {
|
||||
// auto v = i / 255.0f;
|
||||
// v = v <= 0.04045f ? v / 12.92f : powf((v + 0.055f) / 1.055f, 2.4f);
|
||||
//
|
||||
// auto p = std::to_chars(&buffer[0], &buffer[32], v, std::chars_format::hex, -1).ptr;
|
||||
// strcpy_s(p, &buffer[32] - p, "f,");
|
||||
//
|
||||
// if (i % 16 == 0) printf("\n ");
|
||||
// printf("0x%-15s", &buffer[0]);
|
||||
// }
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// clang-format off
|
||||
static constexpr float srgbToRgbLUT[256]{
|
||||
0x0.000000p+0f, 0x1.3e4568p-12f, 0x1.3e4568p-11f, 0x1.dd681cp-11f, 0x1.3e4568p-10f, 0x1.8dd6c2p-10f, 0x1.dd681cp-10f, 0x1.167cbap-9f, 0x1.3e4568p-9f, 0x1.660e16p-9f, 0x1.8dd6c2p-9f, 0x1.b6a31ap-9f, 0x1.e1e31ap-9f, 0x1.07c38cp-8f, 0x1.1fcc2cp-8f, 0x1.390ffap-8f,
|
||||
0x1.53936ep-8f, 0x1.6f5adep-8f, 0x1.8c6a92p-8f, 0x1.aac6c2p-8f, 0x1.ca7382p-8f, 0x1.eb74e0p-8f, 0x1.06e76ap-7f, 0x1.18c2a4p-7f, 0x1.2b4e06p-7f, 0x1.3e8b7cp-7f, 0x1.527cd6p-7f, 0x1.6723eep-7f, 0x1.7c8292p-7f, 0x1.929a86p-7f, 0x1.a96d8ep-7f, 0x1.c0fd62p-7f,
|
||||
0x1.d94bbep-7f, 0x1.f25a44p-7f, 0x1.061550p-6f, 0x1.135f3ep-6f, 0x1.210bb6p-6f, 0x1.2f1b8ap-6f, 0x1.3d8f84p-6f, 0x1.4c6866p-6f, 0x1.5ba6fap-6f, 0x1.6b4c02p-6f, 0x1.7b5840p-6f, 0x1.8bcc72p-6f, 0x1.9ca956p-6f, 0x1.adefaap-6f, 0x1.bfa020p-6f, 0x1.d1bb72p-6f,
|
||||
0x1.e44258p-6f, 0x1.f73582p-6f, 0x1.054ad2p-5f, 0x1.0f31b8p-5f, 0x1.194fccp-5f, 0x1.23a55ep-5f, 0x1.2e32c6p-5f, 0x1.38f85cp-5f, 0x1.43f678p-5f, 0x1.4f2d64p-5f, 0x1.5a9d76p-5f, 0x1.664700p-5f, 0x1.722a56p-5f, 0x1.7e47c6p-5f, 0x1.8a9fa2p-5f, 0x1.973238p-5f,
|
||||
0x1.a3ffdep-5f, 0x1.b108d4p-5f, 0x1.be4d6ep-5f, 0x1.cbcdfcp-5f, 0x1.d98ac8p-5f, 0x1.e7841ep-5f, 0x1.f5ba52p-5f, 0x1.0216cep-4f, 0x1.096f2ap-4f, 0x1.10e660p-4f, 0x1.187c94p-4f, 0x1.2031ecp-4f, 0x1.28068ap-4f, 0x1.2ffa94p-4f, 0x1.380e2cp-4f, 0x1.404176p-4f,
|
||||
0x1.489496p-4f, 0x1.5107aep-4f, 0x1.599ae0p-4f, 0x1.624e50p-4f, 0x1.6b2224p-4f, 0x1.741676p-4f, 0x1.7d2b6ap-4f, 0x1.866124p-4f, 0x1.8fb7c4p-4f, 0x1.992f6cp-4f, 0x1.a2c83ep-4f, 0x1.ac8258p-4f, 0x1.b65ddep-4f, 0x1.c05aeep-4f, 0x1.ca79aap-4f, 0x1.d4ba32p-4f,
|
||||
0x1.df1ca4p-4f, 0x1.e9a122p-4f, 0x1.f447d0p-4f, 0x1.ff10c2p-4f, 0x1.04fe0ep-3f, 0x1.0a8500p-3f, 0x1.101d46p-3f, 0x1.15c6f0p-3f, 0x1.1b820ap-3f, 0x1.214ea8p-3f, 0x1.272cd6p-3f, 0x1.2d1ca4p-3f, 0x1.331e20p-3f, 0x1.39315ap-3f, 0x1.3f5660p-3f, 0x1.458d46p-3f,
|
||||
0x1.4bd612p-3f, 0x1.5230d8p-3f, 0x1.589da0p-3f, 0x1.5f1c82p-3f, 0x1.65ad88p-3f, 0x1.6c50c2p-3f, 0x1.73063cp-3f, 0x1.79ce06p-3f, 0x1.80a82cp-3f, 0x1.8794bep-3f, 0x1.8e93c8p-3f, 0x1.95a55cp-3f, 0x1.9cc984p-3f, 0x1.a4004ep-3f, 0x1.ab49ccp-3f, 0x1.b2a606p-3f,
|
||||
0x1.ba1516p-3f, 0x1.c196f8p-3f, 0x1.c92bc0p-3f, 0x1.d0d37ep-3f, 0x1.d88e40p-3f, 0x1.e05c18p-3f, 0x1.e83d08p-3f, 0x1.f03120p-3f, 0x1.f83870p-3f, 0x1.002984p-2f, 0x1.044078p-2f, 0x1.08611ap-2f, 0x1.0c8b74p-2f, 0x1.10bf8ap-2f, 0x1.14fd64p-2f, 0x1.194506p-2f,
|
||||
0x1.1d967ap-2f, 0x1.21f1c2p-2f, 0x1.2656e8p-2f, 0x1.2ac5f0p-2f, 0x1.2f3ee2p-2f, 0x1.33c1c4p-2f, 0x1.384e9cp-2f, 0x1.3ce56ep-2f, 0x1.418644p-2f, 0x1.463124p-2f, 0x1.4ae610p-2f, 0x1.4fa512p-2f, 0x1.546e30p-2f, 0x1.59416ep-2f, 0x1.5e1ed2p-2f, 0x1.630666p-2f,
|
||||
0x1.67f830p-2f, 0x1.6cf430p-2f, 0x1.71fa6ep-2f, 0x1.770af2p-2f, 0x1.7c25c2p-2f, 0x1.814ae2p-2f, 0x1.867a5ap-2f, 0x1.8bb42ep-2f, 0x1.90f866p-2f, 0x1.964706p-2f, 0x1.9ba016p-2f, 0x1.a1039ap-2f, 0x1.a67198p-2f, 0x1.abea16p-2f, 0x1.b16d1ap-2f, 0x1.b6faa8p-2f,
|
||||
0x1.bc92cap-2f, 0x1.c23582p-2f, 0x1.c7e2d6p-2f, 0x1.cd9acep-2f, 0x1.d35d6cp-2f, 0x1.d92abap-2f, 0x1.df02bap-2f, 0x1.e4e574p-2f, 0x1.ead2ecp-2f, 0x1.f0cb28p-2f, 0x1.f6ce2ep-2f, 0x1.fcdc04p-2f, 0x1.017a5ap-1f, 0x1.048c1cp-1f, 0x1.07a34ep-1f, 0x1.0abff2p-1f,
|
||||
0x1.0de20cp-1f, 0x1.11099ep-1f, 0x1.1436acp-1f, 0x1.176936p-1f, 0x1.1aa140p-1f, 0x1.1ddecep-1f, 0x1.2121e2p-1f, 0x1.246a7cp-1f, 0x1.27b8a2p-1f, 0x1.2b0c56p-1f, 0x1.2e659ap-1f, 0x1.31c472p-1f, 0x1.3528dep-1f, 0x1.3892e2p-1f, 0x1.3c0282p-1f, 0x1.3f77bep-1f,
|
||||
0x1.42f29cp-1f, 0x1.46731cp-1f, 0x1.49f940p-1f, 0x1.4d850cp-1f, 0x1.511682p-1f, 0x1.54ada6p-1f, 0x1.584a7ap-1f, 0x1.5bed00p-1f, 0x1.5f953cp-1f, 0x1.63432ep-1f, 0x1.66f6d8p-1f, 0x1.6ab040p-1f, 0x1.6e6f66p-1f, 0x1.72344cp-1f, 0x1.75fef8p-1f, 0x1.79cf6ap-1f,
|
||||
0x1.7da5a4p-1f, 0x1.8181a8p-1f, 0x1.85637cp-1f, 0x1.894b20p-1f, 0x1.8d3896p-1f, 0x1.912be2p-1f, 0x1.952504p-1f, 0x1.992402p-1f, 0x1.9d28dcp-1f, 0x1.a13396p-1f, 0x1.a54430p-1f, 0x1.a95aaep-1f, 0x1.ad7714p-1f, 0x1.b19960p-1f, 0x1.b5c19ap-1f, 0x1.b9efc0p-1f,
|
||||
0x1.be23d8p-1f, 0x1.c25ddcp-1f, 0x1.c69ddep-1f, 0x1.cae3d2p-1f, 0x1.cf2fc6p-1f, 0x1.d381acp-1f, 0x1.d7d99ap-1f, 0x1.dc377ep-1f, 0x1.e09b70p-1f, 0x1.e5055ep-1f, 0x1.e9755cp-1f, 0x1.edeb5cp-1f, 0x1.f26772p-1f, 0x1.f6e98cp-1f, 0x1.fb71c0p-1f, 0x1.000000p+0f,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
TIL_FAST_MATH_BEGIN
|
||||
|
||||
static constexpr float gMinThreshold = 12.0f;
|
||||
static constexpr float gExpThreshold = 20.0f;
|
||||
static constexpr float gLStep = 5.0f;
|
||||
|
||||
static constexpr float rad006 = 0.104719755119659774f;
|
||||
static constexpr float rad025 = 0.436332312998582394f;
|
||||
static constexpr float rad030 = 0.523598775598298873f;
|
||||
static constexpr float rad060 = 1.047197551196597746f;
|
||||
static constexpr float rad063 = 1.099557428756427633f;
|
||||
static constexpr float rad180 = 3.141592653589793238f;
|
||||
static constexpr float rad275 = 4.799655442984406336f;
|
||||
static constexpr float rad360 = 6.283185307179586476f;
|
||||
|
||||
ColorFix::ColorFix(COLORREF color) noexcept
|
||||
constexpr float saturate(float f) noexcept
|
||||
{
|
||||
rgb = color;
|
||||
_ToLab();
|
||||
return f < 0 ? 0 : (f > 1 ? 1 : f);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper function to calculate HPrime
|
||||
float ColorFix::_GetHPrimeFn(float x, float y) noexcept
|
||||
// FP32 is the very epitome of defined behavior.
|
||||
#pragma warning(suppress : 26497) // You can attempt to make 'cbrtf_est' constexpr unless it contains any undefined behavior (f.4).)
|
||||
__forceinline float cbrtf_est(float a) noexcept
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
// We can't use std::bit_cast here, even though it exists exactly for this purpose.
|
||||
// MSVC is dumb and introduces function calls to std::bit_cast<uint32_t, float>
|
||||
// even at the highest optimization and inlining levels. What.
|
||||
union FP32
|
||||
{
|
||||
return 0;
|
||||
uint32_t u;
|
||||
float f;
|
||||
};
|
||||
|
||||
// http://metamerist.com/cbrt/cbrt.htm showed a great estimator for the cube root:
|
||||
// float_as_uint32_t / 3 + 709921077
|
||||
// It's similar to the well known "fast inverse square root" trick. Lots of numbers around 709921077 perform
|
||||
// at least equally well to 709921077, and it is unknown how and why 709921077 was chosen specifically.
|
||||
FP32 fp{ .f = a }; // evil floating point bit level hacking
|
||||
fp.u = fp.u / 3 + 709921077; // what the fuck?
|
||||
const auto x = fp.f;
|
||||
|
||||
// One round of Newton's method. It follows the Wikipedia article at
|
||||
// https://en.wikipedia.org/wiki/Cube_root#Numerical_methods
|
||||
// For `a`s in the range between 0 and 1, this results in a maximum error of
|
||||
// less than 6.7e-4f, which is not good, but good enough for us, because
|
||||
// we're not an image editor. The benefit is that it's really fast.
|
||||
return (1.0f / 3.0f) * (a / (x * x) + (x + x)); // 1st iteration
|
||||
}
|
||||
|
||||
// This namespace contains functions and structures as defined by: https://bottosson.github.io/posts/oklab/
|
||||
// It has been released into the public domain and is also available under an MIT license.
|
||||
// The only change I made is to replace cbrtf() with cbrtf_est() to cut the CPU cost by a third.
|
||||
namespace oklab
|
||||
{
|
||||
struct Lab
|
||||
{
|
||||
float l;
|
||||
float a;
|
||||
float b;
|
||||
};
|
||||
|
||||
struct RGB
|
||||
{
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
};
|
||||
|
||||
__forceinline Lab linear_srgb_to_oklab(const RGB& c) noexcept
|
||||
{
|
||||
const auto l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
||||
const auto m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
|
||||
const auto s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
|
||||
|
||||
const auto l_ = cbrtf_est(l);
|
||||
const auto m_ = cbrtf_est(m);
|
||||
const auto s_ = cbrtf_est(s);
|
||||
|
||||
return {
|
||||
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
|
||||
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
float ColorFix::_GetDeltaE(ColorFix x1, ColorFix x2) noexcept
|
||||
{
|
||||
constexpr float kSubL = 1;
|
||||
constexpr float kSubC = 1;
|
||||
constexpr float kSubH = 1;
|
||||
|
||||
// Delta L Prime
|
||||
const auto deltaLPrime = x2.L - x1.L;
|
||||
|
||||
// L Bar
|
||||
const auto lBar = (x1.L + x2.L) / 2;
|
||||
|
||||
// C1 & C2
|
||||
const auto c1 = sqrtf(powf(x1.A, 2) + powf(x1.B, 2));
|
||||
const auto c2 = sqrtf(powf(x2.A, 2) + powf(x2.B, 2));
|
||||
|
||||
// C Bar
|
||||
const auto cBar = (c1 + c2) / 2;
|
||||
|
||||
// A Prime 1
|
||||
const auto aPrime1 = x1.A + (x1.A / 2) * (1 - sqrtf(powf(cBar, 7) / (powf(cBar, 7) + powf(25.0f, 7))));
|
||||
|
||||
// A Prime 2
|
||||
const auto aPrime2 = x2.A + (x2.A / 2) * (1 - sqrtf(powf(cBar, 7) / (powf(cBar, 7) + powf(25.0f, 7))));
|
||||
|
||||
// C Prime 1
|
||||
const auto cPrime1 = sqrtf(powf(aPrime1, 2) + powf(x1.B, 2));
|
||||
|
||||
// C Prime 2
|
||||
const auto cPrime2 = sqrtf(powf(aPrime2, 2) + powf(x2.B, 2));
|
||||
|
||||
// C Bar Prime
|
||||
const auto cBarPrime = (cPrime1 + cPrime2) / 2;
|
||||
|
||||
// Delta C Prime
|
||||
const auto deltaCPrime = cPrime2 - cPrime1;
|
||||
|
||||
// S sub L
|
||||
const auto sSubL = 1 + ((0.015f * powf(lBar - 50, 2)) / sqrtf(20 + powf(lBar - 50, 2)));
|
||||
|
||||
// S sub C
|
||||
const auto sSubC = 1 + 0.045f * cBarPrime;
|
||||
|
||||
// h Prime 1
|
||||
const auto hPrime1 = _GetHPrimeFn(x1.B, aPrime1);
|
||||
|
||||
// h Prime 2
|
||||
const auto hPrime2 = _GetHPrimeFn(x2.B, aPrime2);
|
||||
|
||||
// Delta H Prime
|
||||
const auto deltaHPrime = 0 == c1 || 0 == c2 ? 0 : 2 * sqrtf(cPrime1 * cPrime2) * sin(abs(hPrime1 - hPrime2) <= rad180 ? hPrime2 - hPrime1 : (hPrime2 <= hPrime1 ? hPrime2 - hPrime1 + rad360 : hPrime2 - hPrime1 - rad360) / 2);
|
||||
|
||||
// H Bar Prime
|
||||
const auto hBarPrime = (abs(hPrime1 - hPrime2) > rad180) ? (hPrime1 + hPrime2 + rad360) / 2 : (hPrime1 + hPrime2) / 2;
|
||||
|
||||
// T
|
||||
const auto t = 1 - 0.17f * cosf(hBarPrime - rad030) + 0.24f * cosf(2 * hBarPrime) + 0.32f * cosf(3 * hBarPrime + rad006) - 0.20f * cosf(4 * hBarPrime - rad063);
|
||||
|
||||
// S sub H
|
||||
const auto sSubH = 1 + 0.015f * cBarPrime * t;
|
||||
|
||||
// R sub T
|
||||
const auto rSubT = -2 * sqrtf(powf(cBarPrime, 7) / (powf(cBarPrime, 7) + powf(25.0f, 7))) * sin(rad060 * exp(-powf((hBarPrime - rad275) / rad025, 2)));
|
||||
|
||||
// Put it all together!
|
||||
const auto lightness = deltaLPrime / (kSubL * sSubL);
|
||||
const auto chroma = deltaCPrime / (kSubC * sSubC);
|
||||
const auto hue = deltaHPrime / (kSubH * sSubH);
|
||||
|
||||
return sqrtf(powf(lightness, 2) + powf(chroma, 2) + powf(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() noexcept
|
||||
{
|
||||
auto var_R = r / 255.0f;
|
||||
auto var_G = g / 255.0f;
|
||||
auto var_B = b / 255.0f;
|
||||
|
||||
var_R = var_R > 0.04045f ? powf(((var_R + 0.055f) / 1.055f), 2.4f) : var_R / 12.92f;
|
||||
var_G = var_G > 0.04045f ? powf(((var_G + 0.055f) / 1.055f), 2.4f) : var_G / 12.92f;
|
||||
var_B = var_B > 0.04045f ? powf(((var_B + 0.055f) / 1.055f), 2.4f) : var_B / 12.92f;
|
||||
|
||||
var_R = var_R * 100.0f;
|
||||
var_G = var_G * 100.0f;
|
||||
var_B = var_B * 100.0f;
|
||||
|
||||
//Observer. = 2 degrees, Illuminant = D65
|
||||
const auto X = var_R * 0.4124f + var_G * 0.3576f + var_B * 0.1805f;
|
||||
const auto Y = var_R * 0.2126f + var_G * 0.7152f + var_B * 0.0722f;
|
||||
const auto Z = var_R * 0.0193f + var_G * 0.1192f + var_B * 0.9505f;
|
||||
|
||||
auto var_X = X / 95.047f; //ref_X = 95.047 (Observer= 2 degrees, Illuminant= D65)
|
||||
auto var_Y = Y / 100.000f; //ref_Y = 100.000
|
||||
auto var_Z = Z / 108.883f; //ref_Z = 108.883
|
||||
|
||||
var_X = var_X > 0.008856f ? powf(var_X, (1.0f / 3.0f)) : (7.787f * var_X) + (16.0f / 116.0f);
|
||||
var_Y = var_Y > 0.008856f ? powf(var_Y, (1.0f / 3.0f)) : (7.787f * var_Y) + (16.0f / 116.0f);
|
||||
var_Z = var_Z > 0.008856f ? powf(var_Z, (1.0f / 3.0f)) : (7.787f * var_Z) + (16.0f / 116.0f);
|
||||
|
||||
L = (116.0f * var_Y) - 16.0f;
|
||||
A = 500.0f * (var_X - var_Y);
|
||||
B = 200.0f * (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()
|
||||
{
|
||||
auto var_Y = (L + 16.0f) / 116.0f;
|
||||
auto var_X = A / 500.0f + var_Y;
|
||||
auto var_Z = var_Y - B / 200.0f;
|
||||
|
||||
var_Y = (powf(var_Y, 3) > 0.008856f) ? powf(var_Y, 3) : (var_Y - 16.0f / 116.0f) / 7.787f;
|
||||
var_X = (powf(var_X, 3) > 0.008856f) ? powf(var_X, 3) : (var_X - 16.0f / 116.0f) / 7.787f;
|
||||
var_Z = (powf(var_Z, 3) > 0.008856f) ? powf(var_Z, 3) : (var_Z - 16.0f / 116.0f) / 7.787f;
|
||||
|
||||
const auto X = 95.047f * var_X; //ref_X = 95.047 (Observer= 2 degrees, Illuminant= D65)
|
||||
const auto Y = 100.000f * var_Y; //ref_Y = 100.000
|
||||
const auto Z = 108.883f * var_Z; //ref_Z = 108.883
|
||||
|
||||
var_X = X / 100.0f; //X from 0 to 95.047 (Observer = 2 degrees, Illuminant = D65)
|
||||
var_Y = Y / 100.0f; //Y from 0 to 100.000
|
||||
var_Z = Z / 100.0f; //Z from 0 to 108.883
|
||||
|
||||
auto var_R = var_X * 3.2406f + var_Y * -1.5372f + var_Z * -0.4986f;
|
||||
auto var_G = var_X * -0.9689f + var_Y * 1.8758f + var_Z * 0.0415f;
|
||||
auto var_B = var_X * 0.0557f + var_Y * -0.2040f + var_Z * 1.0570f;
|
||||
|
||||
var_R = var_R > 0.0031308f ? 1.055f * powf(var_R, (1 / 2.4f)) - 0.055f : var_R = 12.92f * var_R;
|
||||
var_G = var_G > 0.0031308f ? 1.055f * powf(var_G, (1 / 2.4f)) - 0.055f : var_G = 12.92f * var_G;
|
||||
var_B = var_B > 0.0031308f ? 1.055f * powf(var_B, (1 / 2.4f)) - 0.055f : var_B = 12.92f * var_B;
|
||||
|
||||
r = (BYTE)std::clamp(var_R * 255.0f, 0.0f, 255.0f);
|
||||
g = (BYTE)std::clamp(var_G * 255.0f, 0.0f, 255.0f);
|
||||
b = (BYTE)std::clamp(var_B * 255.0f, 0.0f, 255.0f);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
const ColorFix backLab(bg);
|
||||
ColorFix frontLab(fg);
|
||||
const auto de1 = _GetDeltaE(frontLab, backLab);
|
||||
if (de1 < gMinThreshold)
|
||||
__forceinline RGB oklab_to_linear_srgb(const Lab& c) noexcept
|
||||
{
|
||||
for (auto i = 0; i <= 1; i++)
|
||||
{
|
||||
const auto step = (i == 0) ? gLStep : -gLStep;
|
||||
frontLab.L += step;
|
||||
const auto l_ = c.l + 0.3963377774f * c.a + 0.2158037573f * c.b;
|
||||
const auto m_ = c.l - 0.1055613458f * c.a - 0.0638541728f * c.b;
|
||||
const auto s_ = c.l - 0.0894841775f * c.a - 1.2914855480f * c.b;
|
||||
|
||||
while (((i == 0) && (frontLab.L <= 100)) || ((i == 1) && (frontLab.L >= 0)))
|
||||
{
|
||||
const auto de2 = _GetDeltaE(frontLab, backLab);
|
||||
if (de2 >= gExpThreshold)
|
||||
{
|
||||
frontLab._ToRGB();
|
||||
return frontLab.rgb;
|
||||
}
|
||||
frontLab.L += step;
|
||||
}
|
||||
}
|
||||
const auto l = l_ * l_ * l_;
|
||||
const auto m = m_ * m_ * m_;
|
||||
const auto s = s_ * s_ * s_;
|
||||
|
||||
return {
|
||||
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
|
||||
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
|
||||
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
|
||||
};
|
||||
}
|
||||
return frontLab.rgb;
|
||||
}
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4).
|
||||
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
|
||||
|
||||
__forceinline oklab::RGB colorrefToLinear(COLORREF c) noexcept
|
||||
{
|
||||
const auto r = srgbToRgbLUT[(c >> 0) & 0xff];
|
||||
const auto g = srgbToRgbLUT[(c >> 8) & 0xff];
|
||||
const auto b = srgbToRgbLUT[(c >> 16) & 0xff];
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
__forceinline COLORREF linearToColorref(const oklab::RGB& c) noexcept
|
||||
{
|
||||
auto r = saturate(c.r);
|
||||
auto g = saturate(c.g);
|
||||
auto b = saturate(c.b);
|
||||
r = (r > 0.0031308f ? (1.055f * powf(r, 1.0f / 2.4f) - 0.055f) : 12.92f * r) * 255.0f;
|
||||
g = (g > 0.0031308f ? (1.055f * powf(g, 1.0f / 2.4f) - 0.055f) : 12.92f * g) * 255.0f;
|
||||
b = (b > 0.0031308f ? (1.055f * powf(b, 1.0f / 2.4f) - 0.055f) : 12.92f * b) * 255.0f;
|
||||
return lrintf(r) | (lrintf(g) << 8) | (lrintf(b) << 16);
|
||||
}
|
||||
|
||||
// This function changes `color` so that it is visually different
|
||||
// enough from `reference` that it's (much more easily) readable.
|
||||
// See /doc/color_nudging.html
|
||||
COLORREF ColorFix::GetPerceivableColor(COLORREF color, COLORREF reference, float minSquaredDistance) noexcept
|
||||
{
|
||||
const auto referenceOklab = oklab::linear_srgb_to_oklab(colorrefToLinear(reference));
|
||||
auto colorOklab = oklab::linear_srgb_to_oklab(colorrefToLinear(color));
|
||||
|
||||
// To determine whether the two colors are too close to each other we use the ΔEOK metric
|
||||
// based on the Oklab color space. It's defined as the simple euclidean distance between.
|
||||
auto dl = referenceOklab.l - colorOklab.l;
|
||||
auto da = referenceOklab.a - colorOklab.a;
|
||||
auto db = referenceOklab.b - colorOklab.b;
|
||||
dl *= dl;
|
||||
da *= da;
|
||||
db *= db;
|
||||
|
||||
const auto distance = dl + da + db;
|
||||
if (distance >= minSquaredDistance)
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
// Thanks to ΔEOK being the euclidean distance we can immediately compute the
|
||||
// minimum .l distance that's required for `distance` to be >= `minSquaredDistance`.
|
||||
auto deltaL = sqrtf(minSquaredDistance - da - db);
|
||||
|
||||
// Try to retain the brightness relationship between `reference` and `color`.
|
||||
// If `color` is darker than `reference`, we should first try to make it even darker.
|
||||
if (colorOklab.l < referenceOklab.l)
|
||||
{
|
||||
deltaL = -deltaL;
|
||||
}
|
||||
|
||||
// This primitive way of adjusting the lightness will result in gamut clipping. That's really not good
|
||||
// (it reduces the contrast significantly for some colors), but on the other hand, doing proper gamut
|
||||
// mapping is annoying and expensive. I was unable to find an easy and cheap (!) algorithm that would
|
||||
// change the chroma so that we're (mostly) back inside the gamut, but found none. I'm sure it's
|
||||
// possible to come up with something, but I left that as a future improvement.
|
||||
colorOklab.l = referenceOklab.l + deltaL;
|
||||
if (colorOklab.l < 0 || colorOklab.l > 1)
|
||||
{
|
||||
colorOklab.l = referenceOklab.l - deltaL;
|
||||
}
|
||||
|
||||
return linearToColorref(oklab::oklab_to_linear_srgb(colorOklab)) | (color & 0xff000000);
|
||||
}
|
||||
|
||||
TIL_FAST_MATH_END
|
||||
|
|
|
@ -1,53 +1,13 @@
|
|||
/*++
|
||||
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
|
||||
|
||||
--*/
|
||||
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Licensed under the MIT license.
|
||||
//
|
||||
// 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).
|
||||
#pragma once
|
||||
|
||||
struct ColorFix
|
||||
namespace ColorFix
|
||||
{
|
||||
public:
|
||||
ColorFix(COLORREF color) noexcept;
|
||||
|
||||
static COLORREF GetPerceivableColor(COLORREF fg, COLORREF bg);
|
||||
|
||||
#pragma warning(push)
|
||||
// CL will complain about the both nameless and anonymous struct.
|
||||
#pragma warning(disable : 4201)
|
||||
// RGB
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
BYTE r, g, b, dummy;
|
||||
};
|
||||
COLORREF rgb;
|
||||
};
|
||||
|
||||
// Lab
|
||||
struct
|
||||
{
|
||||
float L, A, B;
|
||||
};
|
||||
#pragma warning(pop)
|
||||
|
||||
private:
|
||||
static float _GetHPrimeFn(float x, float y) noexcept;
|
||||
static float _GetDeltaE(ColorFix x1, ColorFix x2) noexcept;
|
||||
void _ToLab() noexcept;
|
||||
void _ToRGB();
|
||||
};
|
||||
COLORREF GetPerceivableColor(COLORREF color, COLORREF reference, float minSquaredDistance) noexcept;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче