Skip to content

Saved icons have RGB 0,0,0 in every fully transparent pixel, undoing the flat colour fill, so straight-alpha filtering gives dark fringes #161

Open

Description

@matt-edmondson

What's wrong

The processing step paints every pixel's RGB with the target colour, "transparent pixels included", so that filtering never blends a different colour into the edges (IconHelper.cs:392-400). CLAUDE.md and README.md:221 promise the same. However, the shared encoder throws that work away at save time:

internal static PngEncoder Encoder { get; } = new()
{
BitDepth = PngBitDepth.Bit8,
ColorType = PngColorType.RgbWithAlpha,
TransparentColorMode = PngTransparentColorMode.Clear,
};

TransparentColorMode = PngTransparentColorMode.Clear,

Clear rewrites every alpha-0 pixel to transparent black. In addition, CropSquareAndResize pads the canvas with Rgba32.ParseHex("00000000") (L416), so the padding is black as well.

Reproduction

Input: a 300×200 anti-aliased white ellipse on a transparent background. Run iconhelper -i in -o out --color "#3080FF" --size 64.

Output pixel census:

  • alpha 255, rgb (48,128,255): 1,977 pixels
  • partial alpha, rgb (48,128,255): 203 pixels
  • alpha 0, rgb (0,0,0): 1,916 pixels

Expected: rgb (48,128,255) in every pixel.

Upscaling the output 4× with bilinear filtering and no alpha premultiplication, which is how a straight-alpha GPU texture is sampled, made 2,899 of 6,190 edge pixels darker than the target colour.

The in-memory pipeline is fine: the resize premultiplies, and alpha values survive exactly. The loss happens only in the encoder and in the padding colour.

Why it matters

These icons are meant for apps such as ImGui and game engines, which often sample textures in straight alpha with bilinear filtering or mipmaps. A black RGB in the transparent area bleeds into the edges as a dark halo, which is exactly the artefact the flat colour field was added to prevent.

Suggested fix

  • Set TransparentColorMode = PngTransparentColorMode.Preserve.
  • Pad with the target colour at alpha 0 (new Rgba32(colorR, colorG, colorB, 0)) instead of 00000000.
  • Regenerate the gold masters. Files may grow slightly, because the colour field no longer compresses as runs of zeros.

Acceptance: every pixel of an output PNG, including alpha-0 and padding pixels, has the target RGB. Add a test that asserts this on a sample with transparent margins and non-zero --padding.

Activity

  1. matt-edmondson commented on Oct 30, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions