emilk / egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
https://www.egui.rs/
Apache License 2.0
20.85k stars 1.5k forks source link

Tracking issue: Improve text antialiasing #2639

Open parasyte opened 1 year ago

parasyte commented 1 year ago

This is a follow up to #2490.

Related to:

Background and rationale

To improve text legibility, font rasterization currently aligns each glyph to the pixel grid on the display. This is done in two places: First as part of text layout, the glyph cursor itself is rounded to the pixel grid (providing uniform kerning). And second during tessellation (reducing any linear filtering artifacts). Rounding in the tessellator is also important for font caching within the texture atlas, but only when the texture atlas is intended to be rasterized 1:1 with the display (more on that below).

The net effect is that glyphs are rasterized crisply (very little blurring apart from basic grayscale antialiasing performed by ab_glyph).

There are a few drawbacks to the current design:

  1. The standard antialiasing is decent, but legibility suffers with small fonts where there isn't enough physical pixel resolution to show intricate glyph details.
  2. Monospace fonts no longer have a true fixed width, since the glyph cursor position (not just the glyph position) is rounded during text layout.
    • An opt-in feature flag introduced in #2490 is available to maintain text bounding box sizes at any display DPI, but it introduces kerning issues.

Proposed solutions

More than one solution exists. There isn't one that will fix all of the issues, but a combination can have great results. In all cases, the goal is to remove the pixel grid rounding in both layout and tessellation.

1. Subpixel rendering

First, we should consider subpixel rendering. This effectively triples the horizontal resolution on displays with horizontal subpixels, and nearly doubles both dimensions of resolution for displays with pentile or similar subpixel layouts. It can have exceptional results (see FreeType, ClearType, et al.)

The primary advantage of subpixel rendering is that it is not limited to text. It can easily be used on the entire GUI where everything is simply rendered at a higher resolution than the native display. This also means that subpixel rendering can be done in egui itself without deferring to a font rasterization library.

The biggest disadvantage is that it requires knowledge of subpixel layout on the physical display. And that each physical subpixel layout needs its own rendering model (though models can be shared when subpixel ordering is the only difference. E.g., RGB vs BGR).

2. Supersampling

SSAA is another technique for antialiasing where the rasterization is performed at a much higher resolution than the display, and then scaled down to fit.

While supersampling can be done on the entire frame, like subpixel rendering, it is not ideal because it can cause a loss of contrast at pixel edges. Using this technique only for the fonts also has the advantage that it limits the performance impact required by multi-sampling and also lessens the need for more texture memory. It also shares the advantage that it can be done without deferring to a font rasterization library, since it is just a matter of using the library to rasterize larger glyphs that are then rescaled to the appropriate size for display. A consequence of this is that rounding glyph positions to the pixel grid is no longer useful for font caching, since the font cache has a much higher resolution than the display anyway.

The disadvantage is that it is still not free, since it requires more texture memory for the higher resolution rasterization and more GPU bandwidth for sampling the high-resolution texture multiple times for each pixel ultimately put on the display. And without hinting, the "most important" pixels of the glyphs are not guaranteed to align with the pixel grid, resulting in some unnecessary blurring of text.

3. Font hinting

Some fonts contain metadata for the rasterizer to help sharpen the edges of glyphs in relation to the pixel grid. Hinting can also be done in an automated way with vector fonts. FreeType supports both.

An ideal implementation will improve legibility of small fonts by increasing contrast on the pixel grid without artificially distorting either the glyph shapes or the spacing between them. Hinting can be combined with subpixel rendering to improve the shapes and spacing.

The biggest disadvantage of hinting is that it acts a lot like the pixel grid rounding done per-glyph in egui. It can change the shape of glyphs, especially when rasterizing small fonts. Another disadvantage is that it must be done entirely within the font rasterization library. Hinting is not supported by ab_glyph: https://github.com/alexheretic/ab-glyph/issues/60#issuecomment-1066882303 The feature would either have to be contributed upstream or ab_glyph replaced with another rasterizer that supports hinting.

4. Signed Distance Field rasterization

SDFs have some interesting and unique advantages over other antialiasing methods. This is kind of reimagining a vector font as a grid (field) of gaussian functions representing the distance from a pixel on the display to the edge of the glyph. As a result, it has properties of both the original vector font and the raster image.

The biggest disadvantage is that it has to be done in the font rasterization library.

Some example crates that use this technique are sdf_glyph_renderer and fontsdf. Outside of Rust, there is GLyphy written in C++ and GLES2, and sdf-glyph-foundry written in C++ for mapbox.

5. Alternative font rasterizing libraries

There are a few to choose from. egui currently uses ab_glyph.

  1. swash supports subpixel rendering and various hinting strategies. Also supports color emoji (see #2551)
    • My only experience with swash is that it is used in the lapce editor, and it has excellent font rendering.
  2. FreeType is available, but requires precompiled libraries.
  3. Pango is in a similar situation to FreeType.
  4. pathfinder has been a work in progress for a very long time and supports subpixel rendering and "slight hinting".

I also evaluated fontdue, which appears to have some of the necessary pieces for subpixel rendering but not hinting (that I could find, anyway).

emilk commented 1 year ago

I'd be keen to try using https://github.com/pop-os/cosmic-text in epaint. which uses swash underneath.

ctrlcctrlv commented 1 year ago

Hello from downstream MFEKglif project.

Our goal is to solve this, along with #1016, #2179 and other issues related by using the Skia backend we commissioned from @lucasmerlin.

We will then rasterize/shape once and store whole strings/words in the atlas, not letters.

ctrlcctrlv commented 1 year ago

I will just コピペ what I wrote to Discord here as an update, now that I've read all the text layout code in egui, and all of egui_skia, and all of egui_sdl2_event…

image image

Links: #2179, https://www.unicode.org/L2/L2002/02279-muller.htm .

atynagano commented 4 months ago

I personally implemented swash driven font rendering with font hinting. https://github.com/atynagano/egui-swash ab_glyph swash The second image is by swash. compare The right image is by swash.

Resonanz commented 1 month ago

This is great, but it is unclear what the images above are demonstrating. To my poor old eyes, the top image and the left image look clearer and more aliased than their alternatives.

atynagano commented 1 month ago

@Resonanz As @parasyte mentioned:

  1. Font hinting Some fonts contain metadata for the rasterizer to help sharpen the edges of glyphs in relation to the pixel grid. Hinting can also be done in an automated way with vector fonts. FreeType supports both. An ideal implementation will improve legibility of small fonts by increasing contrast on the pixel grid without artificially distorting either the glyph shapes or the spacing between them.

In the example in my images, you can see that the “Arthur” string is sharper.

Resonanz commented 1 month ago

Thanks @atynagano for explaining. Thanks also to @parasyte for the detailed explanation of this really important issue.

emilk commented 1 week ago

We've decided to switch to Cosmic Text for text rendering, which uses Swash: