-
Notifications
You must be signed in to change notification settings - Fork 8.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for querying the DECCTR color table report #17708
Changes from 3 commits
6a7f9a2
4004569
38330d1
dd8be4e
738186c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -374,6 +374,32 @@ til::color Utils::ColorFromRGB100(const int r, const int g, const int b) noexcep | |
return { red, green, blue }; | ||
} | ||
|
||
// Function Description: | ||
// - Returns the RGB percentage components of a given til::color value. | ||
// Arguments: | ||
// - color: the color being queried | ||
// Return Value: | ||
// - a tuple containing the three components | ||
std::tuple<int, int, int> Utils::ColorToRGB100(const til::color color) noexcept | ||
{ | ||
// The color class components are in the range 0 to 255, so we | ||
// need to scale them by 100/255 to obtain percentage values. We | ||
// can optimise this conversion with a pre-created lookup table. | ||
static constexpr auto scale255To100 = [] { | ||
std::array<int8_t, 256> lut{}; | ||
for (size_t i = 0; i < std::size(lut); i++) | ||
{ | ||
lut.at(i) = gsl::narrow_cast<uint8_t>((i * 100 + 128) / 255); | ||
} | ||
return lut; | ||
}(); | ||
|
||
const auto red = til::at(scale255To100, color.r); | ||
const auto green = til::at(scale255To100, color.g); | ||
const auto blue = til::at(scale255To100, color.b); | ||
return { red, green, blue }; | ||
} | ||
|
||
// Routine Description: | ||
// - Constructs a til::color value from HLS components. | ||
// Arguments: | ||
|
@@ -424,6 +450,62 @@ til::color Utils::ColorFromHLS(const int h, const int l, const int s) noexcept | |
return { comp3, comp2, comp1 }; // cyan to blue | ||
} | ||
|
||
// Function Description: | ||
// - Returns the HLS components of a given til::color value. | ||
// Arguments: | ||
// - color: the color being queried | ||
// Return Value: | ||
// - a tuple containing the three components | ||
std::tuple<int, int, int> Utils::ColorToHLS(const til::color color) noexcept | ||
{ | ||
const auto red = color.r / 255.f; | ||
const auto green = color.g / 255.f; | ||
const auto blue = color.b / 255.f; | ||
|
||
// This calculation is based on the RGB to HSL algorithm described in | ||
// Wikipedia: https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB | ||
// We start by calculating the maximum and minimum component values. | ||
const auto maxComp = std::max(std::max(red, green), blue); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. huh, TIL that there's an initializer list version of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (no request to change) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool. I didn't know you could do that. I'm happy to make that change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: In areas where performance is crucial, I recommend against using that overload as it depends on the quality of the STL implementation and compiler, which in this particular case is not great. Microsoft's STL chooses to use vectorized operations for this which cannot be inlined. This forces MSVC to allocate all arguments on the stack. Disabling the vectorization fixes that issue, but still causes MSVC to allocate the array on the stack anyway, as it has always struggled with optimizing function arguments from stack- to register-passing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, I did actually check the generated assembly for the initializer list version before making this change, and it looked identical to me. |
||
const auto minComp = std::min(std::min(red, green), blue); | ||
|
||
// The chroma value is the range of those components. | ||
const auto chroma = maxComp - minComp; | ||
|
||
// And the luma is the middle of the range. But we're actually calculating | ||
// double that value here to save on a division. | ||
const auto luma2 = (maxComp + minComp); | ||
|
||
// The saturation is half the chroma value divided by min(luma, 1-luma), | ||
// but since the luma is already doubled, we can use the chroma as is. | ||
const auto divisor = std::min(luma2, 2.f - luma2); | ||
const auto sat = divisor > 0 ? chroma / divisor : 0.f; | ||
|
||
// Finally we calculate the hue, which is represented by the angle of a | ||
// vector to a point in a color hexagon with blue, magenta, red, yellow, | ||
// green, and cyan at its corners. As noted above, the DEC standard has | ||
// blue at 0°, red at 120°, and green at 240°, which is slightly different | ||
// from the way that hue is typically mapped in modern color models. | ||
auto hue = 0.f; | ||
if (chroma != 0) | ||
{ | ||
if (maxComp == red) | ||
hue = (green - blue) / chroma + 2.f; // magenta to yellow | ||
else if (maxComp == green) | ||
hue = (blue - red) / chroma + 4.f; // yellow to cyan | ||
else if (maxComp == blue) | ||
hue = (red - green) / chroma + 6.f; // cyan to magenta | ||
} | ||
|
||
// The hue value calculated above is essentially a fractional offset from the | ||
// six hexagon corners, so it has to be scaled by 60 to get the angle value. | ||
// Luma and saturation are percentages so must be scaled by 100, but our luma | ||
// value is already doubled, so only needs to be scaled by 50. | ||
const auto h = static_cast<int>(hue * 60.f + 0.5f) % 360; | ||
const auto l = static_cast<int>(luma2 * 50.f + 0.5f); | ||
const auto s = static_cast<int>(sat * 100.f + 0.5f); | ||
return { h, l, s }; | ||
} | ||
|
||
// Routine Description: | ||
// - Converts a hex character to its equivalent integer value. | ||
// Arguments: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for
TABLE_SIZE
: is it correct that we will emit theFRAME_BACKGROUND
et al? Are those indices stable across terminal emulators?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if they are set to
INVALID_COLOR
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of the terminals that support this, there is one which is a genuine terminal emulator, which only returns 16 colors (since that is what you would get on the original hardware terminals). The others that I know of return 256 colors, which includes the color cube and gray scale entries that are common to most modern terminals.
I wanted to include our entire color table, even though the indices aren't standard, because you shouldn't really need to care about that - it just provides a way to save and restore the entire set. However, I think the
INVALID_COLOR
entries are going to be a problem, because they're not going to restore correctly, and that could break things.But maybe we can just drop any colors from the report that are
INVALID_COLOR
, and that way there's no risk of corrupting them with a simple save and restore that doesn't change anything. We won't then be able to restore anyINVALID_COLOR
entries that have been changed, but that's no worse than if we only reported the first 256.