Repository navigation
Keep text color and alpha in create_text_sprite textures - #2923
Merged
Merged
Conversation
pyglet draws text with glBlendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA) for color and alpha alike. Over a transparent texture that squares the alpha and multiplies the color by it, so the sprite's alpha was applied twice when drawn: edges came out darker and thinner, text with partial alpha was nearly invisible, and edges were partly transparent even with an opaque background_color. Draw the text over black and over white instead. Over black each pixel is the color times the alpha, and over white it's lighter by (1 - alpha), which gives the exact alpha and color for any number of layers. The background color is composited under the result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 6, 2026
pvcraven
pushed a commit
that referenced
this pull request
Oct 8, 2026
- Python 3.9 -> 3.10 in the install, SBC and contributing guides, and a release checklist note listing everywhere the minimum appears (#1302). - ShapeElementList: link pyglet.shapes, "in a back" -> "in a batch" (#2608). - gui/1_layouts example: use the bundled Liberation Sans instead of a CSS-style list of fonts most systems don't have (#2743). - create_text_sprite: drop the stale warning that the sprite draws as a black box with another texture atlas; text sprites keep their image since #2923 (#1883). - performance tips: "due to since" typo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pvcraven
pushed a commit
that referenced
this pull request
Oct 8, 2026
- Python 3.9 -> 3.10 in the install, SBC and contributing guides, and a release checklist note listing everywhere the minimum appears (#1302). - ShapeElementList: link pyglet.shapes, "in a back" -> "in a batch" (#2608). - gui/1_layouts example: use the bundled Liberation Sans instead of a CSS-style list of fonts most systems don't have (#2743). - create_text_sprite: drop the stale warning that the sprite draws as a black box with another texture atlas; text sprites keep their image since #2923 (#1883). - performance tips: "due to since" typo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pvcraven
pushed a commit
that referenced
this pull request
Oct 8, 2026
- Python 3.9 -> 3.10 in the install, SBC and contributing guides, and a release checklist note listing everywhere the minimum appears (#1302). - ShapeElementList: link pyglet.shapes, "in a back" -> "in a batch" (#2608). - gui/1_layouts example: use the bundled Liberation Sans instead of a CSS-style list of fonts most systems don't have (#2743). - create_text_sprite: drop the stale warning that the sprite draws as a black box with another texture atlas; text sprites keep their image since #2923 (#1883). - performance tips: "due to since" typo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pvcraven
pushed a commit
that referenced
this pull request
Oct 8, 2026
- Python 3.9 -> 3.10 in the install, SBC and contributing guides, and a release checklist note listing everywhere the minimum appears (#1302). - ShapeElementList: link pyglet.shapes, "in a back" -> "in a batch" (#2608). - gui/1_layouts example: use the bundled Liberation Sans instead of a CSS-style list of fonts most systems don't have (#2743). - create_text_sprite: drop the stale warning that the sprite draws as a black box with another texture atlas; text sprites keep their image since #2923 (#1883). - performance tips: "due to since" typo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pvcraven
added a commit
that referenced
this pull request
Oct 8, 2026
- Python 3.9 -> 3.10 in the install, SBC and contributing guides, and a release checklist note listing everywhere the minimum appears (#1302). - ShapeElementList: link pyglet.shapes, "in a back" -> "in a batch" (#2608). - gui/1_layouts example: use the bundled Liberation Sans instead of a CSS-style list of fonts most systems don't have (#2743). - create_text_sprite: drop the stale warning that the sprite draws as a black box with another texture atlas; text sprites keep their image since #2923 (#1883). - performance tips: "due to since" typo. Co-authored-by: Paul V Craven <paul.craven@optimizley.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 6 of the text improvements:
create_text_spritesprites were drawn fainter than the same text drawn directly.The problem
pyglet draws text with
glBlendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA), which uses the same factors for color and alpha. Drawn into a transparent texture, that gives each pixel:The sprite is then blended by that alpha again when drawn. Measured on the texture of a 30pt "Hello":
background_color: edge pixelsDrawn over a background, the sprite version of opaque white text had about 22% less "ink" than the same text drawn directly (how far its pixels move from the background). Text with alpha 128 had about 85% less, so it was nearly invisible.
pyglet's text groups set their own blend function on every draw, so arcade can't set a separate alpha blend from outside.
The fix
The text is drawn twice, over opaque black and over opaque white:
This is exact for any number of layers (strokes, shadows, gradients, decorations), because the composite is linear in the background. The math uses Pillow: subtract, then an
RGBa→RGBAconversion, which divides out the alpha. Abackground_coloris composited under the result withImage.alpha_composite, so partly transparent backgrounds work too. The result is stored as the texture's image and written to the atlas, so it still survives atlas rebuilds.Directly drawn text (
Text.draw(),draw_text()) isn't affected; it was already correct.Cost
create_text_spriteis called once per sprite, not every frame. Per call, alternating old and new:"Score: 100", 16ptMost of the extra time is the second GPU readback (about 1.1 ms). Drawing both passes into one temporary framebuffer and reading it once could save about 1 ms, but it adds complexity for a one-time cost, so I left it out.
Tests
New tests in
tests/unit/text/test_text_sprite.py:test_texture_matches_text_drawn_directly: the texture composited over a background matches the text drawn directly over the same background within 2. It runs for white, red, alpha 128, alpha 60, and a gradient ending at alpha 128.test_opaque_text_is_opaque: alpha reaches 255, and edge pixels keep the text's own color (within 2 from 8-bit rounding).test_alpha_text_keeps_its_alpha: alpha-128 text tops out at 128, not 64.test_partly_transparent_backgroundandtest_opaque_background.On
development, 8 of them fail, including the opaque background one. The tests compare texture contents rather than drawn sprites. My display runs at 125% scaling, and I found that there sprites are drawn scaled by the pixel ratio even in test mode, while text isn't. That's a separate HiDPI issue, and it's probably why the same 3 render tests fail only on my machine.Full suite on pyglet 3.0.dev11: 1566 passed, no warnings, plus those 3 local-only failures. Ruff is clean, and mypy has no new errors.
The changelog has an entry with the other
create_text_spritefixes under Unreleased → Fixes.🤖 Generated with Claude Code