Skip to content

feat: add GIF decoder using Rust gif crate#104

Open
xfalcox wants to merge 5 commits intojamsinclair:mainfrom
xfalcox:claude/gif-rust-decoder
Open

feat: add GIF decoder using Rust gif crate#104
xfalcox wants to merge 5 commits intojamsinclair:mainfrom
xfalcox:claude/gif-rust-decoder

Conversation

@xfalcox
Copy link
Copy Markdown

@xfalcox xfalcox commented Mar 31, 2026

Summary

  • Adds @jsquash/gif decoder package using the Rust gif crate (v0.13) compiled to WASM via wasm-pack
  • Replaces the C/Emscripten giflib approach from GIF support #102 with a Rust implementation that follows the same patterns as the existing png and oxipng packages
  • Supports three methods: decode (first frame → ImageData), decodeAnimated (all frames with timing/disposal), and isAnimated (quick multi-frame check)
  • WASM binary is only 38KB (release + LTO + size-optimized)

Changes

  • codec/Cargo.toml — Rust crate config with gif, wasm-bindgen, js-sys, web-sys deps
  • codec/src/lib.rs — GIF decoder with full animation support (frame disposal methods, transparency, delay handling)
  • codec/package.json + codec/pre.js — Build scripts and Node/CloudFlare Workers polyfills (matches png/oxipng pattern)
  • decode.ts — TypeScript wrapper with lazy WASM init, GIF header validation
  • index.ts, meta.ts, package.json, tsconfig.json — Package scaffolding

xfalcox and others added 5 commits March 31, 2026 11:05
Replace C/Emscripten giflib approach with Rust gif crate compiled via
wasm-pack, matching the existing pattern used by png and oxipng packages.
Supports decode (single frame), decodeAnimated (all frames with timing),
and isAnimated methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Interactive HTML test page that demonstrates decode, decodeAnimated,
and isAnimated methods with file input, animation playback controls,
and timing info.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Import directly from the local wasm-pack output. Call init() for WASM
initialization and pass Uint8Array to the raw wasm-bindgen functions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Redesigned example page with card layout, checkerboard transparency
background, drag-and-drop file input, and a filmstrip showing all
decoded frames as clickable thumbnails below the animation player.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All three exported functions (decode, decodeAnimated, isAnimated) now
check for valid GIF87a/GIF89a headers before passing data to the gif
crate, giving a clear error instead of a cryptic panic on non-GIF input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xfalcox
Copy link
Copy Markdown
Author

xfalcox commented Mar 31, 2026

Created some test pages I can commit when all this is merged, like

image

xfalcox added a commit to discourse/jSquash that referenced this pull request May 7, 2026
xfalcox added a commit to discourse/discourse that referenced this pull request May 11, 2026
## Summary

- Extends client-side image optimization to convert **JXL → JPEG**,
**HEIC → JPEG**, and **animated GIF → animated WEBP** using jSquash WASM
packages before upload
- Transparent JXL/HEIC images are converted to **WEBP** instead of JPEG,
so transparency is preserved (WEBP compresses much better than PNG for
this case)
- Gated as an **upcoming change** via
`composer_media_optimization_image_convert_enabled` (experimental,
`feature,all_members`, opt-in per group through the upcoming changes
admin UI)
- Sends raw file bytes to Web Worker for formats browsers can't decode
natively (JXL, HEIC), with new `"convert"` and `"convertAnimated"`
worker message types
- Adds JXL to `authorized_extensions` and `supported_images`; adds
HEIC/HEIF to `supported_images` for consistency
- Skips GIF→WEBP when output is larger than input
- Falls back to filename when MIME type is missing (browsers may not
recognize JXL/HEIC)

### Note on jSquash packages

This depends on Discourse-scoped forks of the jSquash packages
(`@discourse/jxl`, `@discourse/heic`, `@discourse/webp`,
`@discourse/gif`, `@discourse/jpeg`, `@discourse/resize`). The following
upstream PRs would let us move back to the canonical `@jsquash/*`
packages, but the upstream maintainer is currently unresponsive:

- jamsinclair/jSquash#101 (`@jsquash/heic`)
- jamsinclair/jSquash#103 (`@jsquash/webp`
animated support)
- jamsinclair/jSquash#104 (`@jsquash/gif`)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant