Add support for restoring a DECCTR color table report (#13139)

This PR introduces the framework for the `DECRSTS` sequence which is
used to restore terminal state reports. But to start with, I've just
implemented the `DECCTR` color table report, which provides a way for
applications to alter the terminal's color scheme.

## PR Checklist
* [x] Closes #13132
* [x] CLA signed.
* [x] Tests added/passed
* [ ] Documentation updated.
* [ ] Schema updated.
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

I've added the functions for parsing DEC RGB and HLS color formats into
the `Utils` class, where we've got all our other color parsing routines,
since this functionality will eventually be needed in other VT protocols
like Sixel and ReGIS.

Since `DECRSTS` is a `DCS` sequence, this only works in conhost for now,
or when using the experimental passthrough mode in Windows Terminal.

## Validation Steps Performed

I've added a number of unit tests to check that the `DECCTR` report is
being interpreted as expected. This includes various edge cases (e.g.
omitted and out-of-range parameters), which I have confirmed to match
the color parsing on a real VT240 terminal.
This commit is contained in:
James Holderness 2022-06-03 02:52:00 +01:00 committed by GitHub
parent 9dca6c27ee
commit c157f6346a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 414 additions and 1 deletions

View File

@ -520,6 +520,7 @@ DECAWM
DECCKM
DECCOLM
DECCRA
DECCTR
DECDHL
decdld
DECDLD
@ -545,7 +546,9 @@ DECREQTPARM
DECRLM
DECRQM
DECRQSS
DECRQTSR
DECRST
DECRSTS
DECSASD
DECSC
DECSCA
@ -1006,6 +1009,7 @@ hkey
hkl
HKLM
hlocal
HLS
hlsl
HMENU
HMIDIOUT

View File

@ -132,6 +132,7 @@ class ScreenBufferTests
TEST_METHOD(VtNewlineOutsideMargins);
TEST_METHOD(VtSetColorTable);
TEST_METHOD(VtRestoreColorTableReport);
TEST_METHOD(ResizeTraditionalDoesNotDoubleFreeAttrRows);
@ -1738,6 +1739,237 @@ void ScreenBufferTests::VtSetColorTable()
VERIFY_ARE_EQUAL(RGB(9, 9, 9), gci.GetColorTableEntry(5));
}
void ScreenBufferTests::VtRestoreColorTableReport()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
auto& stateMachine = si.GetStateMachine();
// Set everything to white to start with.
for (auto i = 0; i < 16; i++)
{
gci.SetColorTableEntry(i, RGB(255, 255, 255));
}
// The test cases below are copied from the VT340 default color table, but
// note that our HLS conversion algorithm doesn't exactly match the VT340,
// so some of the component values may be off by 1%.
Log::Comment(L"HLS color definitions");
// HLS(0°,0%,0%) -> RGB(0,0,0)
stateMachine.ProcessString(L"\033P2$p0;1;0;0;0\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(0));
// HLS(0°,49%,59%) -> RGB(51,51,199)
stateMachine.ProcessString(L"\033P2$p1;1;0;49;59\033\\");
VERIFY_ARE_EQUAL(RGB(51, 51, 199), gci.GetColorTableEntry(1));
// HLS(120°,46%,71%) -> RGB(201,34,34)
stateMachine.ProcessString(L"\033P2$p2;1;120;46;71\033\\");
VERIFY_ARE_EQUAL(RGB(201, 34, 34), gci.GetColorTableEntry(2));
// HLS(240°,49%,59%) -> RGB(51,199,51)
stateMachine.ProcessString(L"\033P2$p3;1;240;49;59\033\\");
VERIFY_ARE_EQUAL(RGB(51, 199, 51), gci.GetColorTableEntry(3));
// HLS(60°,49%,59%) -> RGB(199,51,199)
stateMachine.ProcessString(L"\033P2$p4;1;60;49;59\033\\");
VERIFY_ARE_EQUAL(RGB(199, 51, 199), gci.GetColorTableEntry(4));
// HLS(300°,49%,59%) -> RGB(51,199,199)
stateMachine.ProcessString(L"\033P2$p5;1;300;49;59\033\\");
VERIFY_ARE_EQUAL(RGB(51, 199, 199), gci.GetColorTableEntry(5));
// HLS(180°,49%,59%) -> RGB(199,199,51)
stateMachine.ProcessString(L"\033P2$p6;1;180;49;59\033\\");
VERIFY_ARE_EQUAL(RGB(199, 199, 51), gci.GetColorTableEntry(6));
// HLS(0°,46%,0%) -> RGB(117,117,117)
stateMachine.ProcessString(L"\033P2$p7;1;0;46;0\033\\");
VERIFY_ARE_EQUAL(RGB(117, 117, 117), gci.GetColorTableEntry(7));
// HLS(0°,26%,0%) -> RGB(66,66,66)
stateMachine.ProcessString(L"\033P2$p8;1;0;26;0\033\\");
VERIFY_ARE_EQUAL(RGB(66, 66, 66), gci.GetColorTableEntry(8));
// HLS(0°,46%,28%) -> RGB(84,84,150)
stateMachine.ProcessString(L"\033P2$p9;1;0;46;28\033\\");
VERIFY_ARE_EQUAL(RGB(84, 84, 150), gci.GetColorTableEntry(9));
// HLS(120°,42%,38%) -> RGB(148,66,66)
stateMachine.ProcessString(L"\033P2$p10;1;120;42;38\033\\");
VERIFY_ARE_EQUAL(RGB(148, 66, 66), gci.GetColorTableEntry(10));
// HLS(240°,46%,28%) -> RGB(84,150,84)
stateMachine.ProcessString(L"\033P2$p11;1;240;46;28\033\\");
VERIFY_ARE_EQUAL(RGB(84, 150, 84), gci.GetColorTableEntry(11));
// HLS(60°,46%,28%) -> RGB(150,84,150)
stateMachine.ProcessString(L"\033P2$p12;1;60;46;28\033\\");
VERIFY_ARE_EQUAL(RGB(150, 84, 150), gci.GetColorTableEntry(12));
// HLS(300°,46%,28%) -> RGB(84,150,150)
stateMachine.ProcessString(L"\033P2$p13;1;300;46;28\033\\");
VERIFY_ARE_EQUAL(RGB(84, 150, 150), gci.GetColorTableEntry(13));
// HLS(180°,46%,28%) -> RGB(150,150,84)
stateMachine.ProcessString(L"\033P2$p14;1;180;46;28\033\\");
VERIFY_ARE_EQUAL(RGB(150, 150, 84), gci.GetColorTableEntry(14));
// HLS(0°,79%,0%) -> RGB(201,201,201)
stateMachine.ProcessString(L"\033P2$p15;1;0;79;0\033\\");
VERIFY_ARE_EQUAL(RGB(201, 201, 201), gci.GetColorTableEntry(15));
// Reset everything to white again.
for (auto i = 0; i < 16; i++)
{
gci.SetColorTableEntry(i, RGB(255, 255, 255));
}
Log::Comment(L"RGB color definitions");
// RGB(0%,0%,0%) -> RGB(0,0,0)
stateMachine.ProcessString(L"\033P2$p0;2;0;0;0\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(0));
// RGB(20%,20%,78%) -> RGB(51,51,199)
stateMachine.ProcessString(L"\033P2$p1;2;20;20;78\033\\");
VERIFY_ARE_EQUAL(RGB(51, 51, 199), gci.GetColorTableEntry(1));
// RGB(79%,13%,13%) -> RGB(201,33,33)
stateMachine.ProcessString(L"\033P2$p2;2;79;13;13\033\\");
VERIFY_ARE_EQUAL(RGB(201, 33, 33), gci.GetColorTableEntry(2));
// RGB(20%,78%,20%) -> RGB(51,199,51)
stateMachine.ProcessString(L"\033P2$p3;2;20;78;20\033\\");
VERIFY_ARE_EQUAL(RGB(51, 199, 51), gci.GetColorTableEntry(3));
// RGB(78%,20%,78%) -> RGB(199,51,199)
stateMachine.ProcessString(L"\033P2$p4;2;78;20;78\033\\");
VERIFY_ARE_EQUAL(RGB(199, 51, 199), gci.GetColorTableEntry(4));
// RGB(20%,78%,78%) -> RGB(51,199,199)
stateMachine.ProcessString(L"\033P2$p5;2;20;78;78\033\\");
VERIFY_ARE_EQUAL(RGB(51, 199, 199), gci.GetColorTableEntry(5));
// RGB(78%,78%,20%) -> RGB(199,199,51)
stateMachine.ProcessString(L"\033P2$p6;2;78;78;20\033\\");
VERIFY_ARE_EQUAL(RGB(199, 199, 51), gci.GetColorTableEntry(6));
// RGB(46%,46%,46%) -> RGB(117,117,117)
stateMachine.ProcessString(L"\033P2$p7;2;46;46;46\033\\");
VERIFY_ARE_EQUAL(RGB(117, 117, 117), gci.GetColorTableEntry(7));
// RGB(26%,26%,26%) -> RGB(66,66,66)
stateMachine.ProcessString(L"\033P2$p8;2;26;26;26\033\\");
VERIFY_ARE_EQUAL(RGB(66, 66, 66), gci.GetColorTableEntry(8));
// RGB(33%,33%,59%) -> RGB(84,84,150)
stateMachine.ProcessString(L"\033P2$p9;2;33;33;59\033\\");
VERIFY_ARE_EQUAL(RGB(84, 84, 150), gci.GetColorTableEntry(9));
// RGB(58%,26%,26%) -> RGB(148,66,66)
stateMachine.ProcessString(L"\033P2$p10;2;58;26;26\033\\");
VERIFY_ARE_EQUAL(RGB(148, 66, 66), gci.GetColorTableEntry(10));
// RGB(33%,59%,33%) -> RGB(84,150,84)
stateMachine.ProcessString(L"\033P2$p11;2;33;59;33\033\\");
VERIFY_ARE_EQUAL(RGB(84, 150, 84), gci.GetColorTableEntry(11));
// RGB(59%,33%,59%) -> RGB(150,84,150)
stateMachine.ProcessString(L"\033P2$p12;2;59;33;59\033\\");
VERIFY_ARE_EQUAL(RGB(150, 84, 150), gci.GetColorTableEntry(12));
// RGB(33%,59%,59%) -> RGB(84,150,150)
stateMachine.ProcessString(L"\033P2$p13;2;33;59;59\033\\");
VERIFY_ARE_EQUAL(RGB(84, 150, 150), gci.GetColorTableEntry(13));
// RGB(59%,59%,33%) -> RGB(150,150,84)
stateMachine.ProcessString(L"\033P2$p14;2;59;59;33\033\\");
VERIFY_ARE_EQUAL(RGB(150, 150, 84), gci.GetColorTableEntry(14));
// RGB(79%,79%,79%) -> RGB(201,201,201)
stateMachine.ProcessString(L"\033P2$p15;2;79;79;79\033\\");
VERIFY_ARE_EQUAL(RGB(201, 201, 201), gci.GetColorTableEntry(15));
// Reset everything to white again.
for (auto i = 0; i < 16; i++)
{
gci.SetColorTableEntry(i, RGB(255, 255, 255));
}
Log::Comment(L"Multiple color definitions");
// Setting colors 0, 2, and 4 to red, green, and blue (HLS).
stateMachine.ProcessString(L"\033P2$p0;1;120;50;100/2;1;240;50;100/4;1;360;50;100\033\\");
VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(0));
VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(2));
VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(4));
// Setting colors 1, 3, and 5 to red, green, and blue (RGB).
stateMachine.ProcessString(L"\033P2$p1;2;100;0;0/3;2;0;100;0/5;2;0;0;100\033\\");
VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(1));
VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(3));
VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(5));
// The interpretation of omitted and out of range parameter values is based
// on the VT240 and VT340 sixel implementations. It is assumed that color
// parsing is handled in the same way for other operations.
Log::Comment(L"Omitted parameter values");
// Omitted hue interpreted as 0° (blue)
stateMachine.ProcessString(L"\033P2$p6;1;;50;100\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(6));
// Omitted luminosity interpreted as 0% (black)
stateMachine.ProcessString(L"\033P2$p7;1;120;;100\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 0), gci.GetColorTableEntry(7));
// Omitted saturation interpreted as 0% (gray)
stateMachine.ProcessString(L"\033P2$p8;1;120;50\033\\");
VERIFY_ARE_EQUAL(RGB(128, 128, 128), gci.GetColorTableEntry(8));
// Omitted red component interpreted as 0%
stateMachine.ProcessString(L"\033P2$p6;2;;50;100\033\\");
VERIFY_ARE_EQUAL(RGB(0, 128, 255), gci.GetColorTableEntry(6));
// Omitted green component interpreted as 0%
stateMachine.ProcessString(L"\033P2$p7;2;50;;100\033\\");
VERIFY_ARE_EQUAL(RGB(128, 0, 255), gci.GetColorTableEntry(7));
// Omitted blue component interpreted as 0%
stateMachine.ProcessString(L"\033P2$p8;2;50;100\033\\");
VERIFY_ARE_EQUAL(RGB(128, 255, 0), gci.GetColorTableEntry(8));
Log::Comment(L"Out of range parameter values");
// Hue wraps at 360°, so 480° interpreted as 120° (red)
stateMachine.ProcessString(L"\033P2$p9;1;480;50;100\033\\");
VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(9));
// Luminosity is clamped at 100%, so 150% interpreted as 100%
stateMachine.ProcessString(L"\033P2$p10;1;240;150;100\033\\");
VERIFY_ARE_EQUAL(RGB(255, 255, 255), gci.GetColorTableEntry(10));
// Saturation is clamped at 100%, so 120% interpreted as 100%
stateMachine.ProcessString(L"\033P2$p11;1;0;50;120\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(11));
// Red component is clamped at 100%, so 150% interpreted as 100%
stateMachine.ProcessString(L"\033P2$p12;2;150;0;0\033\\");
VERIFY_ARE_EQUAL(RGB(255, 0, 0), gci.GetColorTableEntry(12));
// Green component is clamped at 100%, so 150% interpreted as 100%
stateMachine.ProcessString(L"\033P2$p13;2;0;150;0\033\\");
VERIFY_ARE_EQUAL(RGB(0, 255, 0), gci.GetColorTableEntry(13));
// Blue component is clamped at 100%, so 150% interpreted as 100%
stateMachine.ProcessString(L"\033P2$p14;2;0;0;150\033\\");
VERIFY_ARE_EQUAL(RGB(0, 0, 255), gci.GetColorTableEntry(14));
}
void ScreenBufferTests::ResizeTraditionalDoesNotDoubleFreeAttrRows()
{
// there is not much to verify here, this test passes if the console doesn't crash.

View File

@ -245,6 +245,12 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
WindowFrame = 2,
};
enum class ColorModel : VTInt
{
HLS = 1,
RGB = 2,
};
enum class EraseType : VTInt
{
ToEnd = 0,
@ -481,6 +487,12 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
Size96 = 1
};
enum class ReportFormat : VTInt
{
TerminalStateReport = 1,
ColorTableReport = 2
};
constexpr VTInt s_sDECCOLMSetColumns = 132;
constexpr VTInt s_sDECCOLMResetColumns = 80;

View File

@ -144,6 +144,8 @@ public:
const VTParameter cellHeight,
const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD
virtual StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat format) = 0; // DECRSTS
virtual StringHandler RequestSetting() = 0; // DECRQSS
virtual bool PlaySounds(const VTParameters parameters) = 0; // DECPS

View File

@ -2552,6 +2552,79 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber,
};
}
// Method Description:
// - DECRSTS - Restores the terminal state from a stream of data previously
// saved with a DECRQTSR query.
// Arguments:
// - format - the format of the state report being restored.
// Return Value:
// - a function to receive the data or nullptr if the format is unsupported.
ITermDispatch::StringHandler AdaptDispatch::RestoreTerminalState(const DispatchTypes::ReportFormat format)
{
switch (format)
{
case DispatchTypes::ReportFormat::ColorTableReport:
return _RestoreColorTable();
default:
return nullptr;
}
}
// Method Description:
// - DECCTR - This is a parser for the Color Table Report received via DECRSTS.
// The report contains a list of color definitions separated with a slash
// character. Each definition consists of 5 parameters: Pc;Pu;Px;Py;Pz
// - Pc is the color number.
// - Pu is the color model (1 = HLS, 2 = RGB).
// - Px, Py, and Pz are component values in the color model.
// Arguments:
// - <none>
// Return Value:
// - a function to parse the report data.
ITermDispatch::StringHandler AdaptDispatch::_RestoreColorTable()
{
return [this, parameter = VTInt{}, parameters = std::vector<VTParameter>{}](const auto ch) mutable {
if (ch >= L'0' && ch <= L'9')
{
parameter *= 10;
parameter += (ch - L'0');
parameter = std::min(parameter, MAX_PARAMETER_VALUE);
}
else if (ch == L';')
{
if (parameters.size() < 5)
{
parameters.push_back(parameter);
}
parameter = 0;
}
else if (ch == L'/' || ch == AsciiChars::ESC)
{
parameters.push_back(parameter);
const auto colorParameters = VTParameters{ parameters.data(), parameters.size() };
const auto colorNumber = colorParameters.at(0).value_or(0);
if (colorNumber < TextColor::TABLE_SIZE)
{
const auto colorModel = DispatchTypes::ColorModel{ colorParameters.at(1) };
const auto x = colorParameters.at(2).value_or(0);
const auto y = colorParameters.at(3).value_or(0);
const auto z = colorParameters.at(4).value_or(0);
if (colorModel == DispatchTypes::ColorModel::HLS)
{
SetColorTableEntry(colorNumber, Utils::ColorFromHLS(x, y, z));
}
else if (colorModel == DispatchTypes::ColorModel::RGB)
{
SetColorTableEntry(colorNumber, Utils::ColorFromRGB100(x, y, z));
}
}
parameters.clear();
parameter = 0;
}
return (ch != AsciiChars::ESC);
};
}
// Method Description:
// - DECRQSS - Requests the state of a VT setting. The value being queried is
// identified by the intermediate and final characters of its control

View File

@ -139,6 +139,8 @@ namespace Microsoft::Console::VirtualTerminal
const VTParameter cellHeight,
const DispatchTypes::DrcsCharsetSize charsetSize) override; // DECDLD
StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat format) override; // DECRSTS
StringHandler RequestSetting() override; // DECRQSS
bool PlaySounds(const VTParameters parameters) override; // DECPS
@ -198,6 +200,8 @@ namespace Microsoft::Console::VirtualTerminal
void _ResetTabStops() noexcept;
void _InitTabStopsForWidth(const VTInt width);
StringHandler _RestoreColorTable();
void _ReportSGRSetting() const;
void _ReportDECSTBMSetting();

View File

@ -135,7 +135,9 @@ public:
const DispatchTypes::DrcsFontSet /*fontSet*/,
const DispatchTypes::DrcsFontUsage /*fontUsage*/,
const VTParameter /*cellHeight*/,
const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; }
const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; } // DECDLD
StringHandler RestoreTerminalState(const DispatchTypes::ReportFormat /*format*/) override { return nullptr; }; // DECRSTS
StringHandler RequestSetting() override { return nullptr; }; // DECRQSS

View File

@ -667,6 +667,9 @@ IStateMachineEngine::StringHandler OutputStateMachineEngine::ActionDcsDispatch(c
parameters.at(6),
parameters.at(7));
break;
case DcsActionCodes::DECRSTS_RestoreTerminalState:
handler = _dispatch->RestoreTerminalState(parameters.at(0));
break;
case DcsActionCodes::DECRQSS_RequestSetting:
handler = _dispatch->RequestSetting();
break;

View File

@ -149,6 +149,7 @@ namespace Microsoft::Console::VirtualTerminal
enum DcsActionCodes : uint64_t
{
DECDLD_DownloadDRCS = VTID("{"),
DECRSTS_RestoreTerminalState = VTID("$p"),
DECRQSS_RequestSetting = VTID("$q")
};

View File

@ -47,6 +47,8 @@ namespace Microsoft::Console::Utils
til::color ColorFromHexString(const std::string_view wstr);
std::optional<til::color> ColorFromXTermColor(const std::wstring_view wstr) noexcept;
std::optional<til::color> ColorFromXParseColorSpec(const std::wstring_view wstr) noexcept;
til::color ColorFromHLS(const int h, const int l, const int s) noexcept;
til::color ColorFromRGB100(const int r, const int g, const int b) noexcept;
bool HexToUint(const wchar_t wch, unsigned int& value) noexcept;
bool StringToUint(const std::wstring_view wstr, unsigned int& value);

View File

@ -318,6 +318,84 @@ catch (...)
return std::nullopt;
}
// Routine Description:
// - Constructs a til::color value from RGB percentage components.
// Arguments:
// - r - The red component of the color (0-100%).
// - g - The green component of the color (0-100%).
// - b - The blue component of the color (0-100%).
// Return Value:
// - The color defined by the given components.
til::color Utils::ColorFromRGB100(const int r, const int g, const int b) noexcept
{
// The color class is expecting components in the range 0 to 255,
// so we need to scale our percentage values by 255/100. We can
// optimise this conversion with a pre-created lookup table.
static constexpr auto scale100To255 = [] {
std::array<uint8_t, 101> lut{};
for (size_t i = 0; i < std::size(lut); i++)
{
lut.at(i) = gsl::narrow_cast<uint8_t>((i * 255 + 50) / 100);
}
return lut;
}();
const auto red = til::at(scale100To255, std::min<unsigned>(r, 100u));
const auto green = til::at(scale100To255, std::min<unsigned>(g, 100u));
const auto blue = til::at(scale100To255, std::min<unsigned>(b, 100u));
return { red, green, blue };
}
// Routine Description:
// - Constructs a til::color value from HLS components.
// Arguments:
// - h - The hue component of the color (0-360°).
// - l - The luminosity component of the color (0-100%).
// - s - The saturation component of the color (0-100%).
// Return Value:
// - The color defined by the given components.
til::color Utils::ColorFromHLS(const int h, const int l, const int s) noexcept
{
const auto hue = h % 360;
const auto lum = gsl::narrow_cast<float>(std::min(l, 100));
const auto sat = gsl::narrow_cast<float>(std::min(s, 100));
// This calculation is based on the HSL to RGB algorithm described in
// Wikipedia: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
// We start by calculating the chroma value, and the point along the bottom
// faces of the RGB cube with the same hue and chroma as our color (x).
const auto chroma = (50.f - abs(lum - 50.f)) * sat / 50.f;
const auto x = chroma * (60 - abs(hue % 120 - 60)) / 60.f;
// We'll also need an offset added to each component to match lightness.
const auto lightness = lum - chroma / 2.0f;
// We use the chroma value for the brightest component, x for the second
// brightest, and 0 for the last. The values are scaled by 255/100 to get
// them in the range 0 to 255, as required by the color class.
constexpr auto scale = 255.f / 100.f;
const auto comp1 = gsl::narrow_cast<uint8_t>((chroma + lightness) * scale + 0.5f);
const auto comp2 = gsl::narrow_cast<uint8_t>((x + lightness) * scale + 0.5f);
const auto comp3 = gsl::narrow_cast<uint8_t>((0 + lightness) * scale + 0.5f);
// Finally we order the components based on the given hue. But note that the
// DEC terminals used a different mapping for hue than is typical for modern
// color models. Blue is at 0°, red is at 120°, and green is at 240°.
// See DEC STD 070, ReGIS Graphics Extension, § 8.6.2.2.2, Color by Value.
if (hue < 60)
return { comp2, comp3, comp1 }; // blue to magenta
else if (hue < 120)
return { comp1, comp3, comp2 }; // magenta to red
else if (hue < 180)
return { comp1, comp2, comp3 }; // red to yellow
else if (hue < 240)
return { comp2, comp1, comp3 }; // yellow to green
else if (hue < 300)
return { comp3, comp1, comp2 }; // green to cyan
else
return { comp3, comp2, comp1 }; // cyan to blue
}
// Routine Description:
// - Converts a hex character to its equivalent integer value.
// Arguments: