From 3abbe58450053301b27e5e665e4d0b91238d9683 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 29 Jun 2026 19:18:24 -0400 Subject: [PATCH] fix(agent): flatten transparency onto white before RGB (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A naive convert('RGB') on a palette-with-transparency image (common: character PNGs on a clear background) lets PIL guess the transparent pixels — black-ish artifacts that bleed into the crop + the CCIP embedding (and the "should be converted to RGBA" warning). to_rgb() composites over white first for a clean, consistent background; used by both stills and video frames. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Ttrj5P7upUTueSfoJcxEqa --- agent/fc_agent/media.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/agent/fc_agent/media.py b/agent/fc_agent/media.py index e3efac5..3432428 100644 --- a/agent/fc_agent/media.py +++ b/agent/fc_agent/media.py @@ -13,8 +13,23 @@ def is_video(mime: str) -> bool: return bool(mime) and (mime.startswith("video/") or mime in {"image/gif"}) +def to_rgb(img: Image.Image) -> Image.Image: + """RGB, flattening any transparency onto white first. A naive convert('RGB') + on a palette-with-transparency image (common for character PNGs on a clear + background) lets PIL guess the transparent pixels — usually black artifacts + that bleed into the crop + the embedding (and the "should be converted to + RGBA" warning). Compositing over white gives a clean, consistent background.""" + if img.mode in ("RGBA", "LA", "PA") or ( + img.mode == "P" and "transparency" in img.info + ): + img = img.convert("RGBA") + bg = Image.new("RGBA", img.size, (255, 255, 255, 255)) + return Image.alpha_composite(bg, img).convert("RGB") + return img.convert("RGB") + + def load_image(data: bytes) -> Image.Image: - return Image.open(io.BytesIO(data)).convert("RGB") + return to_rgb(Image.open(io.BytesIO(data))) def sample_frames( @@ -44,5 +59,5 @@ def sample_frames( names = sorted(n for n in os.listdir(tmp) if n.startswith("f_")) for i, name in enumerate(names[:cap]): with Image.open(os.path.join(tmp, name)) as im: - out.append((round(i * interval, 2), im.convert("RGB"))) + out.append((round(i * interval, 2), to_rgb(im))) return out