Sprite Halos or Atlas Bleed? Run These Controls

A dark sprite outline and a line sampled from a neighboring atlas cell need different fixes. First draw the sprite as a standalone image using the same alpha interpretation, filter and scale as the atlas version. Then compare both over light and dark backgrounds. A defect that survives isolation points away from neighboring atlas content, although the isolated control must preserve the relevant rendering settings.
Key Takeaways
- Compare isolated and packed images before increasing padding.
- Treat edge color, alpha representation and sampling as separate variables.
- Verify the final mip and scale range; a one-scale screenshot does not validate every sampler footprint.
Save the failing pair and its settings
Keep the original source, packed texture and metadata. Record filter mode, mipmap use, texture compression, alpha mode and the scale at which the defect occurs. A new packing run that changes all of these cannot show which change helped.
Make a diagnostic copy with a saturated green neighbor beside the affected sprite. This artificial color is a tracer. If the artifact becomes green in the atlas version but is absent from the isolated control, neighboring texels are a stronger candidate. The test is intentionally conspicuous and should never replace the production artwork.
TexturePacker's texture settings distinguish spacing between shapes from extrusion of edge pixels. Empty space and repeated edge color are different inputs to the sampler. Their existence is useful evidence, but no fixed padding value is sufficient for every scale, mip level and renderer.
Compare four controls
Use identical output resolution and capture at native size. Avoid evaluating a screenshot after a chat app has resized or recompressed it.
| Control | What stays fixed | What the result can suggest |
|---|---|---|
| Standalone sprite, light background | Source pixels and draw settings | Dark fringe may be in colors or alpha handling |
| Standalone sprite, dark background | Same image and scale | Light fringe may reveal matte contamination |
| Atlas with saturated neighbor | Sprite art and alpha settings | Matching tracer color suggests neighbor sampling |
| Atlas at failing scale or mip | Intended presentation path | A clean 1:1 view alone was insufficient |
These are clues, not an automated diagnosis. Compression can alter edge pixels. A shader can add an outline. Multiple faults can coexist. Disable a custom effect only in a separate controlled run so the original failure remains reproducible.
Check whether alpha was multiplied twice
Straight-alpha colors store RGB independently of opacity. Premultiplied colors already include the alpha factor. The upload path and blending path must agree about that representation. MDN's WebGL guidance discusses alpha handling as a pipeline choice, rather than a generic “make transparent” switch.
Here is an illustrative single-channel calculation in normalized values. A white straight-alpha sample has color 1.0 and alpha 0.5. Over black, source-over gives a color contribution of 0.5. A premultiplied sample already stores 0.5; multiplying that value by alpha again gives 0.25, a darker contribution. This arithmetic illustrates one mismatch. It does not model color-space conversion, quantization or a complete renderer.
Inspect the actual loader, not just an editor checkbox. Moving from an image element to an ImageBitmap can move where relevant upload options belong. The Reference: ImageBitmap upload guide addresses that specific boundary.
Separate matte repair from atlas repair
If the isolated source already has pale or dark fringes, inspect partly transparent edge pixels. Their RGB values can contain a background color from an earlier compositing step. Repacking those same pixels cannot restore the intended edge colors.
Godot's Reference: image import documentation describes alpha-border correction and premultiplied-alpha import options separately. Use the option that matches the diagnosed condition, then recheck the material and blend path. Applying every available alpha option is not a reliable sequence.
If the tracer-neighbor test implicates atlas sampling, inspect frame bounds, extrusion, padding and mip generation together. A wrongly offset UV rectangle can still sample a neighbor despite generous empty margins. A metadata mismatch can even select the wrong rectangle entirely.
Do not make a hard alpha threshold your first general repair. It changes the silhouette of soft effects. See GIMP's threshold-alpha scope before removing partial opacity, and Pillow's paste-mask behavior when the edge changed during atlas assembly.
Calibrate the diagnosis with two known faults
Use two deliberately different fixtures before classifying production art. These are proposed renderer controls with computed expected values, not captured graphics results. Keep color values normalized and use a linear, straight-alpha source-over model for the arithmetic; disable compression and custom effects for these controls.
For case A, the intended edge is red RGB (1,0,0) at alpha 0.5. Contaminate only its RGB to (1,0.5,0.5), keeping alpha unchanged. Over black, the intended result is (0.5,0,0) but the contaminated result is (0.5,0.25,0.25). Over white, they become (1,0.5,0.5) and (1,0.75,0.75). Pack the same source with sampling confined to that texel. The fringe remains in both versions; changing the atlas neighbor cannot remove the source contamination.
For case B, use an opaque red subject texel and an opaque green neighbor. A deliberately chosen 50:50 linear sample across their boundary gives (0.5,0.5,0), while the isolated red control remains (1,0,0). This specifies a sampler input, not a claim that every UV or mip level produces equal weights. Adjust the test UV until the constructed control actually exercises the boundary, then record it.
def over(rgb, alpha, background):
return tuple(c * alpha + b * (1 - alpha)
for c, b in zip(rgb, background))
assert over((1, 0, 0), 0.5, (0, 0, 0)) == (0.5, 0.0, 0.0)
assert over((1, 0.5, 0.5), 0.5, (0, 0, 0)) == (0.5, 0.25, 0.25)
assert over((1, 0, 0), 0.5, (1, 1, 1)) == (1.0, 0.5, 0.5)
assert over((1, 0.5, 0.5), 0.5, (1, 1, 1)) == (1.0, 0.75, 0.75)
assert tuple((r + g) / 2 for r, g in zip((1, 0, 0), (0, 1, 0))) == (0.5, 0.5, 0.0)
| Known input or observed branch | Isolated control | Packed control | Decision to record |
|---|---|---|---|
| A: contaminated source RGB | Pink contamination on both backgrounds | Same contamination when sampling stays within the sprite | Reject a neighbor-only explanation; inspect source colors and alpha handling separately |
| B: clean source, green boundary sample | Clean red | Green component appears at the deliberately sampled boundary | Retain UV/filter/neighbor hypothesis; reject source contamination as the only explanation |
| A plus B in one asset | Contamination survives isolation | Additional tracer contribution appears near the boundary | Retain both faults; repair and rerun each independently |
| Neither fault under the control settings | Matches its specified reference | Matches its specified reference | Mark only these controls clean; do not clear untested production scales |
| Control changed alpha mode, mip, shader or scale | Not comparable | Not comparable | Inconclusive: restore matched settings before eliminating either hypothesis |
Save the input RGBA values, background, UV, filter, mip condition and observed color beside each decision. A real capture may differ from these linear values because of transfer functions or quantization; compare in a defined color space and tolerance. Failure to reproduce the known bad case means the diagnostic control itself is not ready to clear production art.
Define what a passing edge looks like
Keep a reference of the intended source edge over both backgrounds. Require the atlas version to agree with that reference within the project's chosen compression tolerance at every supported scale and mip condition in the test plan. The green tracer must not appear in the subject edge.
Archive the corrected settings and repeat after the real neighbor art is restored. This checks the actual delivery asset. A passing tracer control is evidence about the tested sampling path, not proof that every device or custom shader will render it identically.
Evidence and editorial responsibility
FrameSprite Engineering prepared this AI-assisted article and has a commercial interest in FrameSprite. See the team and editorial policy and contact page. Sources were consulted on 2026-10-30. Controls and numeric values are illustrative; no exporter, browser graphics context or engine was executed for this article.
About the author
- FrameSprite team publication: technical guides for image processing, sprite workflows and game development. Our AI-generated guides identify their sources, executed synthetic fixtures and testing limits. Original guides: https://www.framesprite.com/guides
Comments
Join the discussionNo comments yet. Be the first to comment.



