Skip to content

✨ animate GIFs in image side preview via Skia codec#4410

Merged
guiyanakuang merged 3 commits into
mainfrom
feat/issue-4409-animate-gif-side-preview
May 16, 2026
Merged

✨ animate GIFs in image side preview via Skia codec#4410
guiyanakuang merged 3 commits into
mainfrom
feat/issue-4409-animate-gif-side-preview

Conversation

@guiyanakuang
Copy link
Copy Markdown
Member

Closes #4409

Summary

  • Add SkiaAnimatedImageView, a Skia-Codec-backed animated image renderer (no new dependency — Skiko already ships it). It decodes frames into a single reused Bitmap and advances on each frame's declared duration; Codec/Bitmap native handles are released via awaitDispose/DisposableEffect.
  • The frame loop is gated on UserAttentionService.attentionOn(MAIN_WINDOW, SEARCH_WINDOW) — animation pauses when neither host window is visible, resumes instantly when either becomes visible.
  • New animated formats (animated WebP, APNG, HEIC sequences) can be enabled by adding one entry to animatedImageExtensions: Set<String> in SkiaAnimatedImageView.kt. The renderer auto-degrades to a static frame when frameCount <= 1, so static decoders for these formats keep working too.
  • Refactored ImageSidePreviewView into a thin orchestrator + AnimatedImageSidePreview (Skia branch with visibility wiring) + StaticImageSidePreview (Coil branch unchanged). Static image rendering for PNG/JPEG/WebP/HEIC/SVG is untouched.

Test plan

  • Copy a .gif (e.g. drag one from a chat client and Cmd+C); open the main window; select the entry — side preview should animate continuously.
  • Hide the main window for several seconds, then reopen — animation should have paused (frame index visibly jumps forward when re-shown).
  • Open the search window (global shortcut) on a .gif entry — animation should play in the search window's side preview as well.
  • Hide both main and search windows — verify no GIF-decode CPU activity in profiler.
  • Copy a static PNG/JPEG/WebP — renders via Coil exactly as before (no regression).
  • Copy a single-frame GIF — renders the one frame, no infinite loop.
  • Copy a corrupt or truncated GIF — falls back to the broken-image icon, no crash.

Coil 3 has no desktop GIF decoder, so the side preview only ever showed the
first frame. Add SkiaAnimatedImageView, which decodes frames with
org.jetbrains.skia.Codec (already shipped with Skiko, no new dependency) and
plays them on a per-frame-duration loop. The loop is gated on
UserAttentionService.attentionOn(MAIN_WINDOW, SEARCH_WINDOW) so animation
pauses whenever neither host window is visible.

Animated extensions live in a single Set in SkiaAnimatedImageView.kt so future
formats (animated WebP, APNG, HEIC sequences) can be enabled by appending one
entry. Static image rendering is split into StaticImageSidePreview and the
animated branch into AnimatedImageSidePreview, leaving ImageSidePreviewView as
a thin orchestrator.
The side preview is mounted exclusively inside SideSearchWindowContent, so
also watching MAIN_WINDOW kept the frame loop running whenever the main
window was visible — even though the GIF was never on screen. Drop the
attentionOn(MAIN, SEARCH) combine and read isSearchWindowVisible directly.
@guiyanakuang guiyanakuang merged commit 4d0c67a into main May 16, 2026
5 checks passed
@guiyanakuang guiyanakuang deleted the feat/issue-4409-animate-gif-side-preview branch May 16, 2026 04:56
@guiyanakuang guiyanakuang mentioned this pull request May 20, 2026
3 tasks
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.

Animate GIFs in image side preview

1 participant