Why build this?

I started this project because I wanted something better than existing GIF animation tools. The concept was great, but I ran into performance issues and wanted more control over how it worked and looked. So I rebuilt it from scratch in Rust with a focus on stability through multi-process architecture and keeping the codebase open for anyone to contribute to or learn from.

Multi-Process Architecture

The core insight: a bad animation shouldn't crash your entire session. Gif-Engine runs each animation in its own isolated process, managed by a central "manager" process that handles the UI and configuration.

Process Isolation Benefits

The tradeoff is higher memory usage per animation, but for desktop use with 2-5 concurrent animations, the stability gain is worth it.

Process Model: Manager (UI + library) spawns Player processes (one per animation). IPC via stdin/stdout commands and shared JSON state files.

Transparent Window Rendering

The real challenge was getting true per-pixel alpha transparency working on Windows. Most "transparent" windows are actually keying out a single color, which produces harsh edges and doesn't blend with the desktop.

WS_EX_LAYERED Implementation

// Window creation flags for true alpha blending
WS_EX_LAYERED       // Enable per-pixel alpha
WS_EX_TOOLWINDOW    // Hide from taskbar
WS_EX_TRANSPARENT   // Click-through by default

The rendering pipeline: decode frames via the image crate, composite to handle GIF disposal methods correctly (restore to background, do not dispose, etc.), then present via UpdateLayeredWindow with a 32-bit DIB section containing premultiplied alpha.

Frame Composition Challenges

GIF disposal methods are notoriously tricky. Each frame can specify:

Getting this right required maintaining a full framebuffer and tracking the "previous" state for each frame, not just the current one.

Window Anchoring ("Pet Mode")

The most requested feature: attach animations to specific windows so they follow movement and stay positioned relative to the window they're attached to.

Implementation Details

// Anchor tracking loop
target_hwnd = FindWindowEx(...);
GetWindowRect(target_hwnd, &rect);
SetWindowPos(
    anim_hwnd,
    HWND_TOPMOST,
    rect.left + offset_x,
    rect.top + offset_y,
    width, height,
    SWP_NOACTIVATE
);

The manager enumerates running processes, finds their main window handles, and polls window position at 60Hz. When the target window moves, the animation repositions to maintain the relative offset. Edge cases handled:

Click-Through and Interaction

By default, animations are "click-through" so you can work behind them. But users need to drag and reposition sometimes. The solution: modifier keys.

// Hit testing based on modifier key state
if (GetAsyncKeyState(VK_CONTROL) & 0x8000) {
    // Enable interaction mode
    SetWindowLong(hwnd, GWL_EXSTYLE,
        ex_style & ~WS_EX_TRANSPARENT);
} else {
    // Click-through mode
    SetWindowLong(hwnd, GWL_EXSTYLE,
        ex_style | WS_EX_TRANSPARENT);
}

State Persistence

Everything lives in %APPDATA%\gif-engine\:

The app copies imported animations to its own directory, so reorganizing your original files doesn't break the library. serde handles JSON serialization with pretty-printing for human-readable diffs.

Lessons Learned

Process Management Is Harder Than It Looks

On Windows, cleanly terminating a child process requires care. Initially I used TerminateProcess which leaves resources dangling. Switched to a graceful shutdown protocol: send "quit" command, wait for exit with timeout, then hard-terminate if needed.

Window Z-Order Is Surprisingly Complex

Getting animations to stay above specific windows but not steal focus required deep dives into WS_EX_NOACTIVATE, SetWindowPos flags, and the nuances of HWND_TOPMOST vs relative positioning.

Rust's Windows API Bindings Are Good

The windows crate provides raw FFI bindings. For higher-level abstractions, winit handles window creation but I needed raw Win32 for the layered window flags. Mixing raw and managed code worked fine with proper unsafe blocks.

← Previous: Flux Messaging Next: ArkVisor →