Skip to content

Commit

Permalink
Implement the DECCTR color table report.
Browse files Browse the repository at this point in the history
  • Loading branch information
j4james committed Aug 11, 2024
1 parent 6a7f9a2 commit 4004569
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
31 changes: 30 additions & 1 deletion src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4211,8 +4211,37 @@ ITermDispatch::StringHandler AdaptDispatch::RestoreTerminalState(const DispatchT
// - colorModel - the color model to use in the report (1 = HLS, 2 = RGB).
// Return Value:
// - None
void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel /*colorModel*/) const
void AdaptDispatch::_ReportColorTable(const DispatchTypes::ColorModel colorModel) const
{
using namespace std::string_view_literals;

// A valid response always starts with DCS 2 $ s.
fmt::basic_memory_buffer<wchar_t, TextColor::TABLE_SIZE * 18> response;
response.append(L"\033P2$s"sv);

const auto modelNumber = static_cast<int>(colorModel);
for (size_t colorNumber = 0; colorNumber < TextColor::TABLE_SIZE; colorNumber++)
{
response.append(colorNumber > 0 ? L"/"sv : L""sv);
const auto color = til::color(_renderSettings.GetColorTableEntry(colorNumber));
auto x = 0, y = 0, z = 0;
switch (colorModel)
{
case DispatchTypes::ColorModel::HLS:
std::tie(x, y, z) = Utils::ColorToHLS(color);
break;
case DispatchTypes::ColorModel::RGB:
std::tie(x, y, z) = Utils::ColorToRGB100(color);
break;
default:
return;
}
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{};{};{};{}"), colorNumber, modelNumber, x, y, z);
}

// An ST ends the sequence.
response.append(L"\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
}

// Method Description:
Expand Down
2 changes: 2 additions & 0 deletions src/types/inc/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ namespace Microsoft::Console::Utils
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;
std::tuple<int, int, int> ColorToHLS(const til::color color) noexcept;
til::color ColorFromRGB100(const int r, const int g, const int b) noexcept;
std::tuple<int, int, int> ColorToRGB100(const til::color color) noexcept;

bool HexToUint(const wchar_t wch, unsigned int& value) noexcept;
bool StringToUint(const std::wstring_view wstr, unsigned int& value);
Expand Down
82 changes: 82 additions & 0 deletions src/types/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
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:
Expand Down

0 comments on commit 4004569

Please sign in to comment.