"Compress image before uploading" turns transparent WebP/GIF backgrounds black and animated GIF/WebP into a still JPEG #331

Closed
opened 2026-10-05 23:20:22 -04:00 by jared · 0 comments
Owner

The per-file Compress image before uploading switch on the upload card (UploadCardRenderer.tsx; src/app/utils/imageCompression.ts) is offered for every raster type except SVG. It re-encodes through a canvas to JPEG. It skips PNG to protect transparency, but nothing else, and the code itself says "GIF animation is lost on canvas".

Measured (the real compressImage in the browser):

transparent 64×64 WebP with a red square → image/jpeg; transparent corner became rgba(0,0,0,255) (black), centre red
image/png                                → skipped (✔)
isCompressible('image/gif')              → true

So turning the switch on for a transparent WebP sticker, logo or screenshot gives a black background. For an animated GIF or animated WebP, the result is a single still frame. The card only shows the new size, so the user finds out after sending.

Fix:

  • Hide the switch, or make compressImage return null, for image/gif, image/apng and animated WebP (check the ANIM chunk).
  • For WebP (and any type) with alpha, either skip it or composite onto white before toBlob('image/jpeg'). Better still, encode to WebP, which keeps alpha, where canvas.toBlob('image/webp') is supported.
  • Add unit cases: transparent WebP keeps alpha or is skipped; GIF isn't compressible.

Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

The per-file **Compress image before uploading** switch on the upload card (`UploadCardRenderer.tsx`; `src/app/utils/imageCompression.ts`) is offered for **every raster type except SVG**. It re-encodes through a canvas to JPEG. It skips PNG to protect transparency, but nothing else, and the code itself says *"GIF animation is lost on canvas"*. **Measured** (the real `compressImage` in the browser): ``` transparent 64×64 WebP with a red square → image/jpeg; transparent corner became rgba(0,0,0,255) (black), centre red image/png → skipped (✔) isCompressible('image/gif') → true ``` So turning the switch on for a transparent WebP sticker, logo or screenshot gives a **black background**. For an **animated GIF or animated WebP**, the result is a single still frame. The card only shows the new size, so the user finds out after sending. **Fix:** - Hide the switch, or make `compressImage` return null, for `image/gif`, `image/apng` and animated WebP (check the `ANIM` chunk). - For WebP (and any type) with alpha, either skip it or composite onto white before `toBlob('image/jpeg')`. Better still, encode to WebP, which keeps alpha, where `canvas.toBlob('image/webp')` is supported. - Add unit cases: transparent WebP keeps alpha or is skipped; GIF isn't compressible. --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the bugpriority: lowarea: media labels 2026-10-05 23:20:22 -04:00
jared closed this issue 2026-10-18 07:23:10 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#331