Skip to content

Flatten transparent pixels to reduce palette size#224

Merged
197g merged 3 commits intoimage-rs:masterfrom
Muccarini:flatten_transparent_pixels
Nov 23, 2025
Merged

Flatten transparent pixels to reduce palette size#224
197g merged 3 commits intoimage-rs:masterfrom
Muccarini:flatten_transparent_pixels

Conversation

@Muccarini
Copy link
Contributor

When creating frames from RGBA data with Frame::from_rgba_speed(), all transparent pixels (alpha = 0) are now flattened to use the same RGB values. This ensures transparent pixels count as a single color entry instead of multiple entries in the palette.

Benefit: Reduces palette size and helps avoid unnecessary NeuQuant quantization when the image has ≤256 unique colors.

The BTreeSet<(u8, u8, u8, u8)> now counts: opaque colors + 1 transparent color.

like suggested in #110

One concern I have is around handlng of "transparent". For NeuQuant, it looks to me like if there's multiple colours that have 0 alpha, only one gets selected as transparent, but independently NeuQuant will build an RGBA palette for all the given colours, and of multiple colours with 0 alpha, only one will be mapped to transparent. The others will become visible.
. . . I suggest the pre-pass converts all transparent pixels to explicitly the same RGBA value to avoid this issue.

PS. to avoid .unwrap() i inserted a redundant Some() check. my rust knowledge is limited, any suggestion is appreciated.

@fintelia
Copy link
Contributor

I'd probably suggest either this:

if pix[3] != 0 {
    pix[3] = 0xFF;
} else {
    match transparent {
        ...
    }
}

Or this:

if pix[3] != 0 {
    pix[3] = 0xFF;
    continue;
}

match transparent {
    ...
}

src/common.rs Outdated
Comment on lines +225 to +231
} else if transparent.is_none() {
transparent = Some([pix[0], pix[1], pix[2], pix[3]]);
} else if let Some([r, g, b, a]) = transparent {
pix[0] = r;
pix[1] = g;
pix[2] = b;
pix[3] = a;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use if let instead of match here since we only need contents from the Some-branch but follow #224 (comment) otherwise.

Suggested change
} else if transparent.is_none() {
transparent = Some([pix[0], pix[1], pix[2], pix[3]]);
} else if let Some([r, g, b, a]) = transparent {
pix[0] = r;
pix[1] = g;
pix[2] = b;
pix[3] = a;
continue;
}
if let Some([r, g, b, a]) = transparent {
pix[0] = r;
pix[1] = g;
pix[2] = b;
pix[3] = a;
} else {
transparent = Some([pix[0], pix[1], pix[2], pix[3]]);
}

@197g 197g merged commit 9d292eb into image-rs:master Nov 23, 2025
13 checks passed
@Muccarini Muccarini deleted the flatten_transparent_pixels branch November 23, 2025 21:58
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.

3 participants