Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Gif Creator Laravel Package

sybio/gif-creator

GifCreator is a lightweight PHP library for building animated GIFs from a sequence of images. Add frames, set per-frame durations and loop counts, and generate GIFs with a simple API—ideal for dynamic badges, previews, and lightweight animations.

View on GitHub
Deep Wiki
Context7

Getting Started

sybio/gif-creator is a single-class, framework-agnostic PHP library — no service provider, no facade, no config, no artisan command. Everything you need to know fits in one short file.

Install:

composer require sybio/gif-creator

Where to look first: open src/GifCreator/GifCreator.php. The entire public API is two methods:

  • create(array $frames, array $durations, int $loop = 0, int $dis = 2) — pass it frame sources (file paths, URLs, or raw binary blobs) and a parallel array of durations in centiseconds.
  • getGif() — returns the assembled animated GIF as a binary string (not a file, not a resource).

Reading that file (~200 lines) tells you everything about how frames are parsed and how the animation metadata is written. There is nothing hidden.

First use case — stitch pre-rendered frames into a GIF and drop it on a Laravel disk:

use GifCreator\GifCreator;
use Illuminate\Support\Facades\Storage;

$frames = [
    storage_path('app/frames/frame-01.png'),
    storage_path('app/frames/frame-02.png'),
    storage_path('app/frames/frame-03.png'),
];

// Durations are in 1/100ths of a second: 20 = 0.2s per frame
$durations = [20, 20, 20];

$gc = new GifCreator();
$gc->create($frames, $durations, 0); // 0 = loop forever

Storage::disk('public')->put('spinner.gif', $gc->getGif());

That is the happy path. Everything else in a Laravel app is (a) preparing frames that are correctly sized and encoded and (b) deciding where and when to run this — never inline in the request thread for anything user-triggered.

Implementation Patterns

Wrap the package behind a service. Don't call new GifCreator from controllers or models. A thin adapter keeps it swappable for Imagick/FFmpeg later and gives you a single place to add limits, logging, and validation.

namespace App\Services;

use GifCreator\GifCreator;

class GifBuilder
{
    public function build(array $frameBinaries, int $frameDurationCs = 12, int $loop = 0): string
    {
        $gc = new GifCreator();
        $gc->create($frameBinaries, array_fill(0, count($frameBinaries), $frameDurationCs), $loop);

        return $gc->getGif();
    }
}

Bind it in a service provider ($this->app->bind(GifBuilder::class)) so tests can fake it.

Normalize frames upstream — with Intervention Image or Imagick. This is the single most important pattern. GifCreator does not resize, crop, or pad. It assumes every frame is already the same pixel dimensions; mismatched frames produce corrupt output. Use Intervention Image (or raw Imagick/GD) to enforce a fixed canvas:

use Intervention\Image\Facades\Image;

$canvas = ['w' => 640, 'h' => 360];
$frameBinaries = [];

foreach ($uploadedFiles as $path) {
    $img = Image::make($path)
        ->fit($canvas['w'], $canvas['h'])           // resize + crop to fit
        ->encode('gif');                            // or 'png' — both work as frames

    $frameBinaries[] = (string) $img;
}

Passing binary strings into create() is noticeably faster than passing file paths (no repeated disk I/O) and cleaner in a queued job.

Run generation in a queued job, never in the request cycle. A 20-frame 800×800 sequence routinely takes several hundred MB and multiple seconds. A controller that calls GifBuilder::build() inline will eventually 502 under load.

class BuildGif implements ShouldQueue
{
    public $timeout = 120;
    public $tries   = 2;

    public function __construct(public array $framePaths, public string $outputKey, public int $delayCs = 12) {}

    public function handle(GifBuilder $builder): void
    {
        $binaries = array_map(fn ($p) => Storage::get($p), $this->framePaths);

        $gif = $builder->build($binaries, $this->delayCs);

        Storage::disk('public')->put($this->outputKey, $gif);
    }
}

Dispatch to a dedicated media queue so GIF jobs don't starve interactive work, and cap concurrency per worker host (memory × concurrency must stay under memory_limit).

Overlay text / branding during frame prep, not in GifCreator. Text, watermarks, and effects belong in the Intervention/Imagick step before handing frames to the encoder, so you can see and test them independently.

Image::make($path)
    ->fit(640, 360)
    ->text('before / after', 20, 20, function ($font) {
        $font->file(resource_path('fonts/Inter-Bold.ttf'));
        $font->size(28);
        $font->color('#ffffff');
    })
    ->encode('png');

Storage and delivery. Write the output to an immutable, content-hashed key (gifs/{user}/{sha1}.gif) so a CDN can cache aggressively, and record the path + metadata (bytes, frame count, duration) on the owning model. Delete intermediate frame files in the job's finally block or a scheduled prune.

Bound everything at the API boundary. Before a job is even dispatched, validate:

  • frame count (e.g. ≤ 24)
  • per-frame dimensions (e.g. ≤ 1280×1280 and identical to each other)
  • total duration (e.g. ≤ 15s)
  • accepted MIME types and file sizes

Reject oversize inputs with a 422 rather than letting a worker OOM.

Testing. The package has no Laravel-facing tests you can trust. Write a fixture test around your adapter: build from a handful of known PNG frames, assert the output begins with GIF89a, assert its byte length is within a range, and store a golden file for a visual diff. Test the failure edges too: empty frame array, duration count != frame count, single frame, 1×1 image.

Gotchas and Tips

Durations are centiseconds, not milliseconds. 10 = 100ms. Getting this wrong is the #1 source of "why is my GIF too fast/slow" tickets. Put the conversion in one place ($ms / 10) and never scatter it.

$durations must be parallel to $frames. If the counts differ you'll get corrupt or truncated output rather than a clean exception. Assert equality in your adapter before calling create().

Loop count is 0 = infinite, 1 = play once. Verify against your target clients; some renderers treat 1 as "loop once more" rather than "play once."

All frames must already be identical dimensions. No padding, no scaling, no letterboxing is done for you. Mismatches yield garbled frames or outright breakage. Force a canonical canvas in the prep step.

Memory is the real ceiling. Every frame is held as a string, the encoder walks them all, and the final GIF is also a string. getGif() never streams to disk — plan your worker memory_limit around (frames × per-frame bytes × 2) + gif bytes. A 24-frame 800×800 set will comfortably clear 100MB.

PHP 8 warning. The code is PHP-5 era. Expect deprecation notices (dynamic properties, #[\ReturnTypeWillChange] territory) on 8.1+. Validate it on your exact runtime in CI before you commit — a small smoke test that just builds a 2-frame GIF is enough.

Visual artifacts are silent failures. Palette quantization and dithering are hand-rolled; gradients, transparency, and anti-aliased text banding badly without careful frame prep. An HTTP 200 with an ugly result is the common failure mode. Add a golden-image visual smoke test to your deploy pipeline — not just assertStatus(200).

Treat it as vendored source. 325 stars, zero dependents, no recent release cadence. Pin the exact composer.lock version, add it to a "known unmaintained — do not auto-update" list, and keep the adapter interface so swapping to Imagick's writeImages or an FFmpeg shell-out is a one-class change. Budget a few hours per quarter to re-check it on PHP upgrades.

Config quirks are your config, not theirs. There is no config/gif-creator.php. Put frame caps, canvas size, default delay, and queue name in your own config/media.php and read them from the adapter — you'll thank yourself when limits need to change without a deploy.

Extension points: there aren't many. The class is closed and does not use interfaces. Your extensibility lives entirely in the wrapper — the prep step (frame source: uploads, rendered charts, screenshots), the persistence layer (local/S3/CDN), and the encoder itself (swap GifCreator for Imagick::writeImages behind GifBuilderInterface). Design those three seams well and the dependency stays replaceable.

Debugging tip. When output looks wrong, dump one frame to a PNG and open it — nine times out of ten the frame itself is mis-sized or has a different color profile, and the encoder did exactly what you asked. Isolate the prep step before suspecting the library.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core