WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@alltheseas
Copy link

@alltheseas alltheseas commented Oct 31, 2025

Warning

Please note this PR may contain hallucinations, and has not been reviewed by a professional developer. Proceed at your own risk!

Overview

  • adds color emojis
image

Feature Integration

  • Cargo now ships an opt-in emoji_color feature; enabling it pulls in swash through the
    workspace (Cargo.toml:129, crates/epaint/Cargo.toml:42, crates/egui/Cargo.toml:51). By
    default nothing changes—Monochrome glyphs remain the fallback.

Color Glyph Rasterization Path

  • When the feature is active, every FontImpl keeps the old ab_glyph::FontArc for outlines
    and an optional ColorFont that wraps a swash::scale::ScaleContext plus the raw font
    bytes (crates/epaint/src/text/font.rs:188-299).
  • During glyph allocation we still try the cache first (crates/epaint/src/text/
    font.rs:459-477). On a cache miss, the code now:
    1. Asks ColorFont::render_color_image to rasterize the glyph through swash::Render,
      preferring COLR outlines and color bitmaps (crates/epaint/src/text/font.rs:217-
      247).
    2. If swash returns an RGBA image, upload it straight into the atlas with
      Color32::from_rgba_unmultiplied and build a UvRect based on the placement offsets
      swash gives (crates/epaint/src/text/font.rs:559-591).
    3. Mark the GlyphAllocation as precolored: true; cache the result just like grayscale
      glyphs.
  • If the font lacks color layers or the feature is off, we fall back to the existing
    monochrome outline path untouched.

Layout and Rendering Adjustments

Glyph structs (crates/epaint/src/text/text_layout.rs:229-533, crates/epaint/src/text/
text_layout_types.rs:652-703).
  • Tessellation records which vertex ranges already represent fully-colored quads, forcing
    their vertex color to Color32::WHITE so the texture colors pass through (crates/epaint/
    src/text/text_layout.rs:768-915).
  • Downstream consumers—text shape transforms, global tints, and the mesh-to-gpu path—
    skip recoloring those vertex ranges, so text overrides or dark/light theme adjustments
    don’t wash out emoji (crates/epaint/src/shape_transform.rs:90-105, crates/epaint/src/
    tessellator.rs:2056-2077).

Validation Demo

  • crates/eframe/examples/color_emoji_demo.rs wires it all together: it installs a color
    font (e.g. NotoColorEmoji.ttf), enables the feature at runtime, and renders a label
    showing tinted text plus multi-colored emoji, demonstrating that color quads render
    correctly while surrounding text keeps its tint.

Net effect: with --features egui/emoji_color, any font that carries COLR/CBDT data (like
Noto Color Emoji) now renders in full color through the standard atlas/mesh pipeline

Running the demo app

  1. Grab a color font (Noto Color Emoji works well):
    wget -O NotoColorEmoji.ttf https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf
  2.  cargo run -p eframe --example color_emoji_demo --features egui/emoji_color
    
  3. Observe & validate color emojis

@github-actions
Copy link

Preview is being built...

Preview will be available at https://egui-pr-preview.github.io/pr/7677-my-color-emoji

View snapshot changes at kitdiff

This was referenced Oct 31, 2025
@jb55
Copy link

jb55 commented Nov 1, 2025

this is what is see when I run:

cargo run --release --features emoji_color --example color_emoji_demo

@alltheseas
Copy link
Author

@jb55 did you run this prior to cargo

wget -O NotoColorEmoji.ttf https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf

@jb55
Copy link

jb55 commented Nov 1, 2025

that one works, I tried the one from https://fonts.google.com/noto/specimen/Noto+Color+Emoji and it didn't work

@jb55
Copy link

jb55 commented Nov 1, 2025

this implementation looks pretty clean at first glance, but I'm not expert on this part of the code. what do you think @emilk ? prays to the egui gods he is not too busy to review

@alltheseas
Copy link
Author

A few more font options:

- Twemoji Mozilla (Twitter/Firefox build, COLR/CPAL, CC‑BY 4.0)
 - OpenMoji Color (COLRv0, CC‑BY‑SA 4.0; also variant TweMoji, both open)
 - Blobmoji (fork of Google’s set, COLR/CBDT, CC‑BY 4.0)

Font requirements:

Any font you add must ship a COLR/CPAL, COLRv1, CBDT/CBLC, or SBIX table; you
  can inspect with ttx -l font.ttf. Once verified, drop the file into assets/
  fonts, add it in font_data.insert(...), and list its family in the emoji
  vector in crates/notedeck/src/fonts.rs:191 (ordering lets you prefer one art
  style over another).

@valadaptive
Copy link
Contributor

Oof. This PR adds a second, entirely separate font rendering stack. There's ab_glyph, which uses ttf-parser and its own rasterizer; and now there's swash, which depends on skrifa, read-fonts, and zeno for rasterization. Two completely different sets of crates to do the exact same thing.

I haven't looked too much at the added code, but it really does seem like the second font rendering stack is entirely bolted on. I don't think Swash is under active development anymore.

@alltheseas
Copy link
Author

alltheseas commented Nov 10, 2025

Thanks for the feedback.

-Swash only works with emoji_color enabled. The value is off by default. Single ab_glyph is still compiled. Second set of emoji crates are explicitly opt-in.
-Ab_glyph is in charge of monochrome, while forwards color glyphs through ColorFont::try_allocate_color_glyph (crates/epaint/src/text/font.rs:187-318, 467-585)
-Color emoji requires COLR/CPAL/CBDT/CBLC parsing plus multi-layer painting. ab_glyph/ttf-parser currently expose only monochrome outlines.
-The above means that we would need to implement those tables, blending rules, and bitmap strike selection ourselves.
-Duplication would be limited to the last-stage rasterizer that ab_glyph doesn’t provide.

If the only acceptable approach is to add color emoji support to ab_glyph, there is a complex way forward or two that can be explored. Uncertain if these will work. I was not able to make progress.

## Bitmap Emoji Plan (ab-glyph only)
1. Detect bitmap-capable fonts when instantiating `FontImpl` by probing `Font::glyph_raster_image2`.
2. In `try_allocate_color_glyph`, request the closest strike size via `glyph_raster_image2` and upload the returned bitmap into the atlas:
   - `BitmapPremulBgra32`: copy RGBA directly.
   - `BitmapGray*` / `BitmapMono*`: expand to alpha channel and tint.
   - `Png`: decode once (e.g., `png` crate) to RGBA before upload.
3. Apply offsets from `GlyphImage.origin` to populate `UvRect`, and cache allocations the same way Swash glyphs are cached.
4. Keep the whole path behind `emoji_color` and add tests/examples that exercise bitmap emoji rendering.

### Bitmap Tradeoffs
- **Pros**: stays within the existing ab-glyph stack, avoids another dependency, and works for CBDT/CBLC or sbix fonts (e.g., Noto Color Emoji).
- **Cons**: strikes exist only at discrete sizes, so scaling can blur; uploads transfer full RGBA bitmaps (more VRAM/bandwidth than outline rasterization); PNG/SVG decoding costs CPU. This approach does **not** cover COLR/CPAL fonts, so Swash (or equivalent) is still required for them unless the limitation is acceptable.

## COLR Support Roadmap (High Effort)
1. Extend `owned_ttf_parser` usage inside ab-glyph to expose COLR/CPAL table data. Add APIs such as `Font::colr_layers(glyph_id)` that return structured layer/paint information plus palette entries.
2. Design a public representation (e.g., `ColorGlyph` enum) so renderers can consume COLR data without immediate rasterization. Feature-gate this under something like `color-fonts`.
3. Implement a layered color renderer:
   - For COLRv0: rasterize each outline via `ab_glyph_rasterizer`, fill with palette colors, and composite into an RGBA buffer sized for the requested pixels-per-EM.
   - For COLRv1: incrementally support paints (solid fills first, then gradients/transforms). Unsupported paints should fall back to Swash or monochrome rendering.
4. Integrate the renderer into epaint’s `try_allocate_color_glyph`, using the new path when COLR data is available and Swash only as a fallback.
5. Add fixtures/tests for COLRv0 (e.g., Noto Color Emoji) and COLRv1 (Segoe UI Emoji) plus benchmarks to track rasterization cost.

### COLR Tradeoffs
- Significant engineering effort across ab-glyph, owned_ttf_parser, and epaint.
- Duplicates some of Swash’s functionality; maintenance burden moves in-house.
- Once parity is reached, the Swash dependency could be removed, but until then it remains a safety net for unsupported paints or SVG glyphs.

@lucasmerlin
Copy link
Collaborator

lucasmerlin commented Nov 11, 2025

This looks really cool, but I agree with @valadaptive that adding an entirely separate font rendering stack is definitely not the way to go.

Note that I also previously experimented with color emoji support: #7333
That one is way more lightweight, it basically just adds an api to register some texture as a glyph in the atlas.
I think we could expand on that and maybe add some callback api so you could load the textures only when needed.

If you wanted, you could then use that api to do the emoji rendering via swash in your application (or release it as a 3rd party egui plugin crate). Although the more reasonable approach would likely be to just include the emojis as png files and load those via that api.

For egui-built-in emoji font support we should wait for #7694, which should allow us to implement this eventually

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Color Emoji support

4 participants