corrected srgb blending
I've experimented with using corrected srgb alpha blending, like what egui has done in their latest release https://github.com/emilk/egui/releases/tag/0.32.0.
To me, the text at small sizes looks significantly better and more readable, especially light text on a dark background.
However, I'm not sold on the implementation or the API, and it's probably not the best way to do it. I mostly just wanted to showcase what I've found in hopes it will improve glyphon.
Also, this breaks rendering when using "web colors", so that will need to be fixed before merging.
Before (srgb)
Before (web colors)
After (srgb)
Done some more experimenting, and I found let corrected_coverage = 1.0 - pow(1.0 - coverage, 0.7); makes light text one dark backgrounds even more readable.
Here's what light text on a dark background looks like with let corrected_coverage = 1.0 - pow(1.0 - coverage, 0.7);:
And here's what dark text on a light background looks like with a subtle tweak of: let corrected_coverage = 1.0 - pow(1.0 - coverage, 2.1);. It's subtle, but I think it looks a bit sharper.
I've also been thinking of how to better integrate this.
For one, we can probably have the renderer automatically detect if it should use DarkOnLight mode or LightOnDark mode by measuring the value of the color. If the value is greater than 50%, then use LightOnDark mode. If it is less than 50%, use DarkOnLight mode.
And secondly, using the pow function in the fragment shader is less than ideal. It is probably possible to do this on the CPU first before uploading the texture. Though we would need to add an extra flag to the glyph cache key to differentiate between DarkOnLight mode and LightOnDark glyphs.
Here's what light text on a dark background looks like with
let corrected_coverage = 1.0 - pow(1.0 - coverage, 0.7);:
Actually looking back on this it looks a little too bold. let corrected_coverage = 1.0 - sqrt(1.0 - coverage) was fine.
I noticed that let corrected_coverage = 1.0 - sqrt(1.0 - coverage); actually makes light text on dark backgrounds look a bit dimmer than its intended color, so scaling it up a little bit with let corrected_coverage = min(1.0, (1.0 - sqrt(1.0 - coverage)) * 1.11); makes the text look more like its intended color.
And playing around more with dark text on light backgrounds, I think the equation let corrected_coverage = min(1.0, sqrt(coverage) * 1.11); gives better results.
Oh actually, I just made a new discovery.
I discovered that the let corrected_coverage = 2.0 * coverage - coverage * coverage; for dark text on a light background was actually an approximation for the srgb gamma function let corrected_coverage = pow(coverage, 1.0 / 2.2);.
So changed dark on light to let corrected_coverage = pow(coverage, 1.0 / 2.2); and light on dark to let corrected_coverage = 1.0 - pow(1.0 - coverage, 1.0 / 2.2); gives these results, which definitely looks the smoothest. So these are probably the actually mathematical correct functions to use.
Using let corrected_coverage = pow(coverage, 1.4); for light text on a dark background also works quite well, and seems better at preserving the intended color.
Or wait, perhaps using sqrt would be better than pow for performance reasons.
Apparently sqrt is actually quite cheap on GPUs.
I've found that let corrected_coverage = 1.0 - sqrt(min(1.0, max(0.0, 1.0 - (coverage * 1.2)))); seems to work the best for preserving the intended color for light text on a dark background.
This piecewise approximation seems to work very well for light text on a dark background too:
let part_1 = 0.67 * coverage;
let part_2 = coverage - 0.14;
let part_3 = min(1.0, 2.0 * (coverage - 0.42));
let corrected_coverage = select(select(part_1, part_2, coverage > 0.424242), part_3, coverage > 0.7);