Skip to content

fix(sponsorkit): bake avatar shape masks into the bitmaps for transparent corners#5745

Merged
leaanthony merged 1 commit into
masterfrom
fix/sponsorkit-baked-avatar-masks
Jul 4, 2026
Merged

fix(sponsorkit): bake avatar shape masks into the bitmaps for transparent corners#5745
leaanthony merged 1 commit into
masterfrom
fix/sponsorkit-baked-avatar-masks

Conversation

@leaanthony

@leaanthony leaanthony commented Jul 4, 2026

Copy link
Copy Markdown
Member

Fixes the square avatar corners visible outside the circle/squircle rings on the credits pages.

Why corners showed

Avatars were embedded as square JPEGs and shaped only by a runtime clip-path. Browsers don't clip SVG <image> elements reliably: headless WebKit and Chromium's software rasterizer consistently paint the raw square bitmap (and GPU Chrome does so intermittently during paint), so the JPEG corners poked out past the rings.

Before (WebKit) After (WebKit)
square black corners outside the sponsor ring clean circle

The fix

Cut the shape into the pixels at generation time, so no renderer clipping is needed anywhere (pages, READMEs, any engine):

  • maskShape applies the circle (sponsors) or the same n=4 superellipse squircle used by squirclePathD (contributors) to the alpha channel, with a ~1px anti-aliased edge.
  • Photos embed as JPEG with the corners flattened onto the card background colour (JPEG has no alpha; the card is always behind the avatars, so the corners are invisible).
  • Flat-colour avatars (identicons/logos) embed as lossless palette PNGs with genuinely transparent corners.
  • Smaller encoding wins per avatar; the runtime clip-path stays as best-effort for the rare undecodable webp passthrough.

Sizes

  • sponsors.svg: 233 KiB → 253 KiB
  • contributors.svg: 1.05 MiB → 1.35 MiB (alpha PNGs cost more than the previously kept-original square PNGs; palette quantization + BestCompression claws back most of it)

Verification

Reproduced the bug and verified the fix with headless WebKit + Chromium (Playwright) rendering the actual generated files: old files show square corners in both engines, new files render clean circles/squircles, squircle edge checked at 3× zoom. Both images in this PR were regenerated with the new tool using the maintainer token (tier data correct: Champion/Gold/Silver/Bronze groups match the live card).

Summary by CodeRabbit

  • New Features

    • Avatar images are now shaped more consistently, with circular styling for sponsors and rounded squircle styling for contributors.
    • Avatar handling now better preserves image quality and automatically chooses the most efficient compatible format.
  • Bug Fixes

    • Improved support for avatars with transparency and uncommon image formats, reducing unexpected rendering issues.

…rent corners

Avatars were embedded as square JPEGs shaped only by a runtime SVG
clip-path. Browsers do not clip <image> elements reliably: WebKit and
Chromium's software rasterizer (and occasionally GPU Chrome during
paint) draw the raw square bitmap, so avatar corners showed outside the
circle/squircle rings on the credits pages.

Cut the shape into the pixels at generation time instead: the circle
(sponsors) or superellipse squircle (contributors, matching
squirclePathD) is applied to the alpha channel with an anti-aliased
edge. Photographic avatars embed as JPEG with corners flattened onto
the card background colour (JPEG has no alpha; the card is always
behind them); flat-colour avatars embed as lossless palette PNGs with
genuinely transparent corners. The smaller encoding wins per avatar.
The runtime clip-path attributes stay as a best-effort for the rare
undecodable (webp) passthrough avatars.

Verified in headless WebKit and Chromium, which reproduced the unclipped
corners with the old files and render clean shapes with the new ones.

Both images regenerated with the new tool.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 401a12a2-e271-4717-acbd-4e5db3bf582d

📥 Commits

Reviewing files that changed from the base of the PR and between cb525e7 and 2a93352.

⛔ Files ignored due to path filters (2)
  • website/static/img/contributors.svg is excluded by !**/*.svg
  • website/static/img/sponsors.svg is excluded by !**/*.svg
📒 Files selected for processing (2)
  • tools/sponsorkit/avatar.go
  • tools/sponsorkit/main.go

Walkthrough

Avatar generation in tools/sponsorkit is changed to apply superellipse alpha masks (circle or squircle) instead of flattening transparency directly. FetchAvatars/fetchAvatar gain a maskExp parameter, new helpers toPaletted and maskShape are added, and encoding adaptively chooses PNG or JPEG. main.go wires circle masks for sponsors and squircle masks for contributors.

Changes

Masked Avatar Generation

Layer / File(s) Summary
Mask constants and function signatures
tools/sponsorkit/avatar.go
Adds maskCircle/maskSquircle constants and updates FetchAvatars/fetchAvatar signatures to accept maskExp float64.
Masking, palettization, and adaptive encoding
tools/sponsorkit/avatar.go
Applies maskShape to alpha-mask decoded images, re-flattens corners for JPEG compatibility, adds toPaletted for lossless indexed PNG conversion, and picks the smaller of PNG/JPEG encodings; unknown formats pass through as data URIs.
Wire masks into sponsor/contributor generation
tools/sponsorkit/main.go
generateSponsors passes maskCircle and generateContributors passes maskSquircle to FetchAvatars.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Main as generateSponsors/generateContributors
  participant FetchAvatars
  participant fetchAvatar
  participant maskShape

  Main->>FetchAvatars: FetchAvatars(..., maskExp)
  FetchAvatars->>fetchAvatar: fetchAvatar(url, size, quality, maskExp)
  fetchAvatar->>maskShape: compute superellipse alpha mask
  maskShape-->>fetchAvatar: anti-aliased mask
  fetchAvatar-->>FetchAvatars: PNG or JPEG data URI
Loading

Possibly related PRs

  • wailsapp/wails#5724: Adds squircle-based contributors.svg mosaic generation, directly connected to this PR's squircle/circle avatar masking.

Poem

A rabbit clips each avatar round,
Circles and squircles, masked and found,
PNG or JPEG, whichever's small,
Corners flattened, fitting all,
Hop, hop — the sponsors shine tonight! 🐇✂️

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sponsorkit-baked-avatar-masks

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@leaanthony leaanthony marked this pull request as ready for review July 4, 2026 12:05
Copilot AI review requested due to automatic review settings July 4, 2026 12:05
@leaanthony leaanthony merged commit daf83df into master Jul 4, 2026
17 checks passed
@leaanthony leaanthony deleted the fix/sponsorkit-baked-avatar-masks branch July 4, 2026 12:05
Copilot stopped reviewing on behalf of leaanthony due to an error July 4, 2026 12:05
leaanthony pushed a commit that referenced this pull request Jul 4, 2026
… avatar shape masks into the bitmaps for transparent corners

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR updates the avatar pipeline in tools/sponsorkit to bake circle/squircle masking into downloaded avatar bitmaps and choose an optimal embedded image encoding (PNG vs JPEG) to avoid unreliable SVG <image> clipping behavior.

Changes:

  • Extend FetchAvatars / fetchAvatar to apply a superellipse-based mask (circle for sponsors, squircle for contributors).
  • Re-encode avatars as either PNG (optionally paletted) or JPEG depending on which yields a smaller data URI.
  • Add masking, palettization, and PNG encoding helpers.

Reviewed changes

Copilot reviewed 2 out of 4 changed files in this pull request and generated 4 comments.

File Description
tools/sponsorkit/main.go Updates call sites to pass circle/squircle mask exponents into avatar fetching.
tools/sponsorkit/avatar.go Implements bitmap masking and size-based PNG/JPEG re-encoding (including optional paletted PNG).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +166 to +174
rx, ry := float64(w)/2, float64(h)/2
for y := 0; y < h; y++ {
fy := math.Abs((float64(y) + 0.5 - ry) / ry)
for x := 0; x < w; x++ {
fx := math.Abs((float64(x) + 0.5 - rx) / rx)
// Normalised superellipse "radius" of this pixel; the shape edge
// sits at d=1 and d scales roughly linearly with distance near it.
d := math.Pow(math.Pow(fx, exp)+math.Pow(fy, exp), 1/exp)
cov := (1-d)*rx + 0.5
Comment on lines +167 to +173
for y := 0; y < h; y++ {
fy := math.Abs((float64(y) + 0.5 - ry) / ry)
for x := 0; x < w; x++ {
fx := math.Abs((float64(x) + 0.5 - rx) / rx)
// Normalised superellipse "radius" of this pixel; the shape edge
// sits at d=1 and d scales roughly linearly with distance near it.
d := math.Pow(math.Pow(fx, exp)+math.Pow(fy, exp), 1/exp)
Comment on lines +115 to +117
var pbuf bytes.Buffer
enc := png.Encoder{CompressionLevel: png.BestCompression}
if pal := toPaletted(masked); pal != nil {
Comment on lines +29 to +35
// scaled for hi-dpi), bakes the shape mask into the bitmap and returns data
// URIs keyed by login. The mask is applied to the pixels rather than left to
// a runtime clip-path because browsers do not clip <image> elements reliably
// (WebKit, and Chromium's software rasterizer, intermittently paint the raw
// square bitmap). Failures are reported but non-fatal: missing entries fall
// back to a placeholder at render time.
func FetchAvatars(client *http.Client, sponsors []Sponsor, sizes map[string]int, quality int, maskExp float64) map[string]string {
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.

2 participants