-
Notifications
You must be signed in to change notification settings - Fork 19
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
Consider replacing anstyle-lossy with prettypretty #200
Comments
At the moment,
I had considered It seems your focus is on RGB to 16-colors conversion. Seeing as we have no use case for that atm and your description sounds heavier weight than |
I believe that prettypretty addresses the quality concern quite nicely. As to performance: I am not convinced that the performance of color conversions matters, especially when it comes to interactive use. Some OS terminal implementations are notoriously slow to begin with. If humans are on the critical path, we tend to be operating at much slower speeds as well. More importantly, converting styles on the critical output path seems like the wrong usage model anyways. I cover this topic in the documentation, see the progress bar deep dive. I strongly believe that the right way forward is to define terminal application styles separately from the output routines and to adjust them to terminal capabilities, runtime context, and user preferences at application startup. If an application does that, the performance overhead won't matter. Finally, my subjective impression, now that I ported all critical color routines to Rust, is that native prettypretty is blazing fast. But it probably is worth doing some benchmarking just so I have some idea what the cost of floating point operations is in Rust. (It's been quite a ride, in part because Rust's support for floating point math is really uneven. Currently, we can't do floating point operations in const functions. But we can do them in const expressions. So the universal duct tape of Rust, macros, can save the day yet again....)
Actually, the focus is on conversion any which way, between the terminal color representations, to high-resolution color, and back again to 24-bit, 8-bit, and ANSI colors. They are all implemented and work well enough. But the conversion from any of the other colors to ANSI colors probably is the hardest because there are so few candidates and those candidates have rather unusual semantics, i.e., they don't have intrinsic color values and hence are abstract colors.
That would be fantastic. If you get started on that, please do keep notes on what still are rough edges and let me know, so that I can improve the developer experience. Two more things: First, when you say "lossy conversion from 16-colors to RGB," that's not quite lossy. It's perfectly accurate and color-preserving if the conversion uses the current color theme, which prettypretty encourages applications to do. Though, the result is very much context-sensitive and depends on the current terminal and color theme. Second, I noticed that you kind of duct-taped the two default foreground and background colors into anstyle's output routines. I started out in a similar way, then added an explicit sentinel for the default color (prettypretty was still Python-only at the time, so using |
While its an improvement, I think there is still enough loss in differentiation that someone should probably theme for 16-color independent of truecolor.
Its not just the cost of color conversions but the parsing of ANSI escape codes.
That is the model I used for a while and found to be pretty broken. For TUIs and some CLIs, I think it can still work. In this model, you can do all of the work to pick color palettes but in the end, you are still left with two palettes, stdout and stderr. The code formatting output needs to know which of those two it's writing to. For TUIs, everything just goes to the screen and this distinction is moot, so that works.
In response to something you said, I went digging into the docs and I think it would be helpful for them to be polished up a bit first. I opened https://docs.rs/prettypretty/latest/prettypretty/ and had no idea where something you mentioned was or how to use it. Some thoughts
Its a weird middle ground. You can strictly convert back losslessly if you have the same theme as generated it.
What do you mean by "current color theme". Are you detecting what color theme the terminal is set to? btw in the case where I use this, there is no terminal. I also don't really see much point to this when there is a terminal unless you are manipulating the colors.
Frankly, I don't even know what you are talking about. I don't see how a |
I'm writing this in the cab to the airport, so my apologies for the lack of quotes for context. The GitHub iPhone app doesn't have good support for commenting. Thanks for the feedback. I did make some documentation changes, including restoring the overview of main types in the API docs, adding text in the item summaries for helpers, and adding a light dusting of links. Also great point about docs.rs. The version displayed there now is Rust-only. (I actually have color badges for Python- and Rust-only items but docs.rs doesn't pick up my CSS.) I'll add more links and example over the coming few weeks but am traveling in Europe for 10 days, so won't have much bandwidth. Still, the latest release is out and much improved thanks to your feedback. As to your comments about needing to parse before output, that seems to be a result of your decision to use strings as the only representation. I can see that that necessitates parsing, but that also is a strong reason to use a different in-memory representation. However, I don't see how can get away with not parsing before output and also have a model where apps don't need to adjust their terminal styles. If you don't dynamically adjust styles to terminal capabilities and user preferences at some point before output, then you may just end up with ANSI escapes in the output that weren't consumed by the terminal or colors where none are wanted. How do you plan on achieving both? Prettypretty does indeed query the terminal for its current color theme. It's part of adjusting to the runtime context. The code currently lives in Python only but I plan on porting to Rust soon. Though I'm a bit weary of its interaction with async. Since I need non-blocking single character reads (well, with a short timeout), I currently use select on Linux + macOS (and punt on Windows). That works because there's only one file descriptor that needs selecting. Since I don't want to hardcode that into the Rust version, I may just try the sans-IO approach and provide the good ol' select implementation as the lightweight default. Finally, the two default colors for foreground and background correspond to SGR parameters 39 and 49, respectively. Many terminals make them configurable as well and they may not be the same as any of the 16 ANSI colors. While their use is limited by them only affecting foreground and background, they actually are a great solution to restoring a terminal to a known good state. By modeling them, prettypretty can automatically compute the style that undoes another or that is the difference from another style for incremental updates. |
This is making it sounds like its theoretical. I am actively using this strategy today. EDIT: there are also cases involved here that involve JSON APIs (rust to cargo) where it adds even more complexity to design a bespoke styling "API" (json schema). The only time we parse is to strip ANSI escape codes and that is a diferent level of parsing than needing to translate truecolor escape codes to 16-color escape codes.
Calculating undoing or incremental updates is out of scope of |
Hi,
nice work! I happen to agree on much of the premise of this project and am also building a library, called prettypretty, to improve color output of terminal applications. But where this project seems to have focused on integration and interoperability with the ecosystem, I focused on the color science. I believe that both projects have complementary strengths and would benefit from each other. Concretely, I am suggesting that you replace the anstyle-lossy crate ("Lossy") with prettypretty because you'll get much better color conversions. Before I share results and on a more personal level, it was really cool discovering that we independently came up with the same color conversion algorithm based on a brute force search for the closest color.
I spent some time earlier today trying to understand the source for Lossy's color math and couldn't quite discern whether the author is confused about color science or just using language and symbols in ways that are unfamiliar to me. I do know of at least one expert who similarly abuses the term "perceptually uniform," so I can't decide. But in either case, basing one's color matching experiments on an RGB cube without defining either base color space or the new primaries and without accounting for test subjects' differences in color vision is a recipe for meaningless results. Assuming that the color space really is sRGB, those 64 colors are not even remotely close to being distributed in a perceptually uniform manner—as you can see in the plots of chroma/hue pairs and lightness values after conversion to Oklab, a color space that comes closer to being perceptually uniform than most. The rainbow line is the boundary of the sRGB gamut and the chart is labelled 60+4 because 4 colors are grays and they all sit on the origin.
Suffice to say, that this is not a space where Euclidian distance is an accurate reflection of color distance. But maybe the weights make all the difference. I performed some experiments:
* Lossy's brute force search for the closest match in sRGB with Lossy's distance metric
* Prettypretty's independent implementation of the same algorithm but using prettypretty's high-resolution colors with floating point coordinates, the Oklrab color space (a slight improvement on the above mentioned Oklab), and an unweighted Euclidian distance metric
* My own conversion algorithm: I developed it after noticing some pathological results from closest match search. It exploits the semantics of ANSI colors, which come in pairs of regular and bright colors, and uses hue to select the closest pair and then lightness to select the closest matching color, all in Oklrch, the cylindrical version of Oklrab.
Before I share the three color grids for the three algorithms, here is the color grid without downsampling but showing the input colors:
Here are the results of Lossy's algorithm:
The color ramps on the right show that the algorithm is sensitive to luminance but its coordinate space and metrics push it towards the grays.
Here are the results for the Oklrab version with high-resolution colors:
The ramps are more fractured and there are fewer grays, both improvements in my mind.
Here is my own algorithm:
Since it matches colors and grays to colors and grays only, the grays are limited to single cells on the diagonal from upper-left to lower-right corner, and the colors dominate. There also are more graduations. Arguably, it's a bit heavy on the bright reds and might benefit from a weight to bias red selection towards the darker tone.
Still, prettypretty clearly outperforms Lossy and also has more options for other conversions as well as color manipulation in general. So please do consider replacing anstyle-lossy with prettypretty. Or, since you are really good at interfacing with the ecosystem, do offer an option that enables prettypretty as the color engine.
Cheers!
Robert
The text was updated successfully, but these errors were encountered: