terminal: render: defer conversion of TextColor into COLORREF until actual rendering time (avoid ConPTY narrowing bugs)
Right now, every time Renderer needs to switch the brush colors, it does the following:
- get the
TextColor
from the attribute run - get the RGB value of the color from the color table
- pass that to
Engine::UpdateDrawingBrushes
Step 2 is lossy, and is the source of a class of bugs I’ll call “ConPTY narrowing” bugs. In addition, there’s a step where VtEngine
needs to know the color table to do a translation from RGB back into indexed color.
“narrowing” bugs
application output | conpty sends | why |
---|---|---|
\e[38;2;22;198;12m |
\e[92m |
RGB(22, 198, 12) matches color index 10 in Campbell |
\e[38;5;127m |
\e[38;2;175;0;175m |
the xterm-256color color palette gets translated into RGB because Campbell only covers 0-16 |
\e[38;2;12;12;12m |
\e[30m |
RGB(12, 12, 12) matches index 0 in Campbell and index 0 is the default color (#293) |
If Renderer passed the TextColor directly to the Engine, Xterm256Engine could pass through all indexed colors unchanged, and XtermEngine and WinTelnetEngine could flatten to the 16-color space.
- #3076 is caused by us doing early unpacking of colors and boldness; deferring until later will yield an improvement in preservation of underline status.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 4
- Comments: 47 (18 by maintainers)
Commits related to this issue
- Add support for the DECSCNM screen mode (#3817) ## Summary of the Pull Request This adds support for the [`DECSCNM`](https://vt100.net/docs/vt510-rm/DECSCNM.html) private mode escape sequence, whi... — committed to microsoft/terminal by j4james 4 years ago
- Make sure background color is opaque when attrs are reversed (#5509) In order to support a transparent background for the acrylic effect, the renderer sets the alpha value to zero for the default ba... — committed to microsoft/terminal by j4james 4 years ago
- Make sure background color is opaque when attrs are reversed (#5509) In order to support a transparent background for the acrylic effect, the renderer sets the alpha value to zero for the default ba... — committed to microsoft/terminal by j4james 4 years ago
- Refactor the SGR implementation in AdaptDispatch (#5758) This is an attempt to simplify the SGR (Select Graphic Rendition) implementation in conhost, to cut down on the number of methods required i... — committed to microsoft/terminal by j4james 4 years ago
- Fix SGR indexed colors to distinguish Indexed256 color (and more) (#5834) This PR introduces a new `ColorType` to allow us to distinguish between `SGR` indexed colors from the 16 color table, the lo... — committed to microsoft/terminal by j4james 4 years ago
- Refactor the SGR implementation in AdaptDispatch (#5758) This is an attempt to simplify the SGR (Select Graphic Rendition) implementation in conhost, to cut down on the number of methods required i... — committed to jelster/terminal by j4james 4 years ago
- Fix SGR indexed colors to distinguish Indexed256 color (and more) (#5834) This PR introduces a new `ColorType` to allow us to distinguish between `SGR` indexed colors from the 16 color table, the lo... — committed to jelster/terminal by j4james 4 years ago
- Remove the WinTelnetEngine (#6526) Nobody was using it. Discussed in #2661. — committed to microsoft/terminal by DHowett 4 years ago
- Improve the propagation of color attributes over ConPTY (#6506) This PR reimplements the VT rendering engines to do a better job of preserving the original color types when propagating attributes ov... — committed to microsoft/terminal by j4james 4 years ago
- Update Fork (#1) * Draw the cursor underneath text, and above the background (#6337) ## Summary of the Pull Request Are you sure that you won’t ever need it, and that they look exactly the same on every terminal?
For the second eight colors (SGR 90 … 97 vs. 38:5:8 … 38:5:15, and the same with hundreds and forties for background) this might easily be correct, probably it is correct, although I’m not 100% sure. And no one can guarantee that xterm or some other popular terminal will never introduce some difference, e.g. SGR 2 faint getting applied to the first format only.
For the first eight colors (SGR 30 … 37 vs. 38:5:0 … 38:5:7, and the same with forties for the background) you know it’s not true, we’ve just recently talked about it in #5384. Unless you want to hardcode the xterm 331 … 347 behavior forever.
Which means you can perhaps squash the two representation of the second 8 colors, but not the first 8. Where does it take you? A more complex, harder to understand and harder to maintain codebase, with some squeezing logic where it’s not required at all. Different codepath for the two possible representations of the first 8 colors versus the two possible representations of the second 8 colors, not just where they need to differ, but elsewhere too. Harder to debug, as you’ll run into unexpected inconsistencies wherever you peek into the data. Saving 8 possible values out of the little-bit-more-than-2^24 values, still resulting in little-bit-more-than-2^24, that is, not solving anything here. And in some presumably very rare cases, where the two representations of the same color reside next to each other, you might save a few bytes getting transferred. I find it absolutely pointless.
The experience of having already fixed quite a few color handling bugs in VTE tells me that @Undercat is on the right track here. Treat 95 and 38:5:13 differently all the way through, it’s pointless to “optimize” solely for the sake of 8 colors, based on the current knowledge / future assumption that they’ll end up being the same RGB. Such “optimization” is counterproductive, you lose a lot by having more complex architecture and more complex code, while practically win nothing.
IMO squeezing them only made sense if the two different notations of the 16 colors would be synonyms, and would be guaranteed that they’ll always remain so (as in, let’s say, it’s okay to forget whether it was
38:5:13
with colons, or38;5;13
with semicolons). This is clearly not the case here.Well, this is a skeleton of what I propose morphing
TextColor
into (I prototyped it in GNU using a much more compact style because it’s more comfortable for me to design and test things that way, but just imagine it expanded out into Microsoft syntax for the moment).Basically:
COLORREF
for easy interconversion (simple masking).It would be necessary to re-plumb
TerminalApi.cpp
andTerminalDispatchGraphics.cpp
in order to record the source type accurately enough (right now, there is a complete reliance on constructor type overloading in order to determine the color regime used by the source, but that is inappropriate and dangerous when you start getting multiple types that are built on single octet data words, in addition to being confusing…better to make it explicit).Before I even start to shoe-horn this monster into the existing code (which will obviously take more than a day, not including validation), I’d be interested in some feedback. Comments? Suggestions? Flames?
IIRC, I had a super old branch where I tried to get
TextColor
to store a 256-color index, not just a 16-color index. I’m not sure that this is the right branch, but dev/migrie/b/1223-change-256-table looks like it might be the one I’m thinking of.That might be able to be combined with the “plumb
TextColor
through” to get all of 4bit, 8bit, and 24bit colors in the same format they were originally.I think the solution we had in mind was plumbing our
TextColor
orTextAttribute
structures all the way to the renderer, instead of just theCOLORREF
, then letting the renderer decide what it wants. Most of the render engines will just want the RGB color, but the VT render engine will actually be able to use theTextColor
to differentiate between someone setting a color from the 16-color palette and someone coincidentally using the same RGB value as one of the 16-color values.But that’s still going to convert an application’s request for
31
into a 24-bit value and make it impossible for a connected terminal to determine that the application emitted31
. It’s just flipping this issue over so that a different set of data is lost, but this time the lost data is the entire 16-color palette, isn’t it?Getting rid of the 16-color SGR will make it impossible for PTY hosts to redefine the 16-color palette entirely, as the pty driver will translate
31
into, say,38;2;355;0;0
. There’s no way for a pty host to push its view of the palette into the pty driver.