Texture Edge Bleed

Push colour outwards under transparent texels so mipmaps and downscaling stop showing a dark rim.

A dark (or white) outline around a transparent sprite, leaf or decal means the texture's fully transparent pixels still hold black or white RGB, and bilinear filtering or smaller mipmap levels average that hidden colour into the visible edge. The fix is to fill those pixels with the edge's own colour — edge bleed, also called dilation or alpha padding — or to let the engine do it on import (Godot's Fix Alpha Border, Unity's Alpha is Transparency). Nerulio's Edge bleed pushes RGB 2, 4, 8 or 16 px into texels with alpha 0 and never writes alpha.

Drop image files hereProcess many files at once
No upload · processed on this device

Where the halo comes from

A PNG stores colour and alpha separately (straight alpha), so a texel with alpha 0 still has an RGB value. Most programs write (0, 0, 0) or (255, 255, 255) there. You never see it at 1:1, because alpha 0 hides it.

Filtering and mipmaps mix neighbours. A bilinear sample halfway between an opaque edge texel and a transparent one averages both colours and both alphas; a mip level averages each 2 × 2 block of the level above. The result has the right, half-way alpha but a colour pulled towards the hidden black or white, so the edge darkens or glows — more at every smaller mip level, which is why the outline often appears only when the object is far away or scaled down.

Edge bleed replaces the hidden colour. Nerulio grows the colour one ring per round: every transparent texel next to one that already has colour takes the average RGB of those neighbours (all 8 directions), so after N rounds everything within N px of the sprite carries its edge colour. Texels with alpha above 0 are never touched and no alpha byte is written. Premultiplied-alpha textures avoid the problem in another way: their colour is already multiplied by alpha, so a transparent texel adds nothing.

How far to bleed. In a box-filtered mip chain a texel of level k covers 2ᵏ × 2ᵏ source texels, so a transparent texel mixed into an edge block is at most 2ᵏ − 1 px from the sprite. A 4 px bleed therefore keeps the levels down to 1/4 clean, 8 px down to 1/8 and 16 px down to 1/16 — the three levels the Lab's mipmap preview shows.

Straight alpha
Colour and alpha stored independently; RGB exists even where alpha is 0.
Edge bleed (dilation)
Copying edge colour outwards into transparent texels without changing their alpha.
Mip level
A pre-shrunk copy (1/2, 1/4, 1/8 …) the GPU uses when the texture is drawn small.
Premultiplied alpha
Colour already multiplied by alpha; hidden colour cannot leak.

Example: one edge block, before and after

A red sprite texel (210, 40, 30, 255) sits next to a transparent texel that holds black. Mip level 1 averages the 2 × 2 block (two opaque, two transparent):

Without bleed   (210+210+0+0)/4, (40+40+0+0)/4, (30+30+0+0)/4, alpha (255+255+0+0)/4
              = (105, 20, 15, 128)    half transparent and half as bright: the dark rim
With bleed      transparent texels now hold (210, 40, 30, 0)
              = (210, 40, 30, 128)    half transparent, same red

Texels filled around a 24 × 24 opaque square (bleed N px: (24 + 2N)² − 24²):
  2 px → 208    4 px → 448    8 px → 1024    16 px → 2560

The 4 px figure is the one the Lab measured on its test sprite: 448 texels changed, all with alpha 0, and not a single alpha byte changed.

Check the result

  • In Fix › Mipmaps, the edge at 1/4 … 1/16 should keep the sprite's colour after bleeding instead of turning darker.
  • The result note says how many transparent texels were filled; open the saved PNG in the Channels stage: the A channel is unchanged and RGB under alpha 0 now carries the edge colour.
  • In the engine, view the object small or far away with mipmaps on: the outline should be gone.

Troubleshooting

SymptomLikely causeHow to checkFix
Dark or white outline only when the object is small or far awayMip levels average the hidden RGB into the edgeFix › Mipmaps: compare 1/4, 1/8, 1/16 before and after bleedingBleed at least as far as the smallest level you see (4 px → 1/4, 8 px → 1/8, 16 px → 1/16), or turn on the engine's import option
A thin fringe at 1:1 when the sprite moves, rotates or is scaledBilinear filtering samples between the edge and the transparent neighbourZoom the engine view: the fringe follows the silhouetteBleed 2 px or more; for pixel art use Nearest filtering instead (blurry pixel art in Godot)
The bleed disappeared after resizing the textureThe resize went through a canvas, which discards colour under alpha 0Open the resized file in the Channels stage: RGB under alpha 0 is 0 againResize first and bleed last — the Lab's Export runs resize → bleed → encode in that order
The bleed is gone after saving as WebP or JPEGThose formats are encoded through the browser canvas; JPEG has no alpha at allCompare the PNG and the WebP in the Channels stageKeep bled textures as PNG
Colour of the neighbouring sprite shows at the edge in an atlasSprites are packed without gaps, so filtering reaches the next spriteLook at the atlas: are sprites touching?That needs padding or extrusion between sprites, not bleed: see atlas padding and the sprite sheet packer
Semi-transparent edge pixels themselves are darkThe art was exported against a black matte, so the colour is baked into pixels with alpha 1–254The dark pixels have alpha above 0Nerulio cannot fix this (bleed never touches alpha > 0): re-export from the source without a matte

Other ways to do it

OptionWhen it is the better choice
Godot 4: Process › Fix Alpha Border (import option)Textures imported into Godot: it fills transition pixels with the surrounding colour on import and is on by default, so a bled PNG is usually not needed there.
Unity: Alpha is Transparency (import option)Textures imported into Unity: it dilates the colour channels to avoid filtering artifacts at alpha edges.
Premultiplied alphaYour renderer blends premultiplied textures: hidden colour cannot leak at all.

Versions and sources

Measured on the Texture Lab (docs/TEXTURE-LAB.md): edge bleed changed 448 texels around a 24 × 24 square (exactly four rings), none with alpha above 0; the alpha plane had 0 differing bytes; the first ring read (210, 40, 30, 0). The mipmap preview is the same per-channel box average as mipChain in src/game/texture-fix.js. Engine import options are quoted from the Godot 4.7 and Unity 6 documentation.

Official documentation used

Texture Edge Bleed

Push colour outwards under transparent texels so mipmaps and downscaling stop showing a dark rim.

How it works

  1. Add images
  2. Adjust settings and review the result
  3. Download a new file

Supported formats

PNG / JPG / WebP / AVIF / HEIC → PNG — Input decoding depends on browser support and optional engines.

What this tool does

Push colour outwards under transparent texels so mipmaps and downscaling stop showing a dark rim.

Implementation and verified quality

advanced · iterative nearest-opaque RGB dilation (alpha untouched). Quality qualification is in progress. Pro/Flagship quality has not been established.

Before you export

Alpha is never changed. It cannot repair an alpha channel that was already cut or compressed badly.

Frequently asked questions

What are this tool’s limitations?

Alpha is never changed. It cannot repair an alpha channel that was already cut or compressed badly.

Are my files uploaded?

This tool processes the files you open on your device without uploading them. Images you explicitly attach to community posts are uploaded separately. Optional engines and models are downloaded from third parties; those requests expose connection information such as your IP address.

Will my work be saved automatically?

No. Images and editing state stay in this tab; media output can use local temporary storage. Changing language preserves your work; reloading, closing the tab, or following a link to another page can clear it.