intervention/gif
Native PHP GIF encoder/decoder for GIF data streams with no image extensions required. Decode files or binary content, and build animated GIFs via a Builder. Includes optional GD-based Splitter to extract frames into GDImage objects. Supports PHP 8.3+.
Installation:
composer require intervention/gif
Ensure your project uses PHP 8.3+ and has the GD extension (required for Splitter class).
First Use Case:
Decode a GIF:
use Intervention\Gif\Decoder;
$gifData = Decoder::decode(storage_path('app/animation.gif'));
// $gifData is an Intervention\Gif\GifDataStream object
Encode a GIF:
use Intervention\Gif\Builder;
$gif = Builder::canvas(32, 32)
->addFrame(storage_path('app/frame1.gif'), 0.5)
->addFrame(storage_path('app/frame2.gif'), 0.5)
->setLoops(3);
$encodedGif = $gif->encode();
file_put_contents(storage_path('app/output.gif'), $encodedGif);
Where to Look First:
src/Decoder.php for parsing GIFs.src/Builder.php for creating GIFs.src/Blocks/ for GIF metadata (e.g., GlobalColorTable, GraphicControlExtension).tests/Unit/ for usage examples and edge cases.Decoding Workflow:
Decoder::decode() for file paths or binary content.$gifData->getFrames() (returns GifFrame[]).$gifData->getLogicalScreenDescriptor() or $gifData->getGraphicControlExtensions().$gif = Decoder::decode($binaryContent);
foreach ($gif->getFrames() as $frame) {
$delay = $frame->getGraphicControlExtension()->getDelayTime();
// Process frame...
}
Encoding Workflow:
Builder::canvas(width, height) for a blank GIF.addFrame(path, delay, left, top).setLoops(int) (0 = infinite).encode() to get binary data.$gif = Builder::canvas(100, 100)
->addFrame('frame1.png', 1.0, 0, 0)
->addFrame('frame2.png', 1.0, 0, 0)
->setLoops(0); // Infinite loop
$binaryGif = $gif->encode();
Integration with Laravel:
// app/Providers/GifServiceProvider.php
public function register()
{
$this->app->bind(Decoder::class, function () {
return new Decoder();
});
$this->app->bind(Builder::class, function () {
return new Builder();
});
}
use Illuminate\Validation\Rule;
$rules = [
'gif' => ['required', Rule::exists('storage', 'path')->where('extension', 'gif')],
];
// app/Jobs/EncodeGifJob.php
public function handle()
{
$gif = Builder::canvas(200, 200)
->addFrame('frame1.png', 0.5)
->encode();
Storage::put('encoded.gif', $gif);
}
Frame Extraction with GD:
Splitter::split() to break animated GIFs into GDImage objects (for further processing with Intervention\Image):
use Intervention\Gif\Splitter;
$frames = Splitter::split($gifPath);
foreach ($frames as $frame) {
$image = Image::gd($frame);
$image->resize(100, 100);
// Save or process...
}
Dynamic GIF Generation:
$template = Decoder::decode('template.gif');
$userImage = Image::make($userUpload)->resize(50, 50);
$builder = Builder::canvas($template->getWidth(), $template->getHeight())
->addFrame($userImage->toGD(), 1.0)
->setLoops(1);
GIF Validation:
try {
$gif = Decoder::decode($file);
} catch (\Intervention\Gif\Exceptions\GifException $e) {
throw ValidationException::withMessages(['gif' => 'Invalid GIF file.']);
}
Metadata Preservation:
$gif = Decoder::decode($file);
$delay = $gif->getGraphicControlExtensions()[0]->getDelayTime();
$loopCount = $gif->getLogicalScreenDescriptor()->getLoopCount();
Batch Processing:
collect() and queues:
$paths = Storage::disk('uploads')->files('*.gif');
foreach ($paths as $path) {
EncodeGifJob::dispatch($path)->onQueue('gifs');
}
GD Dependency:
Splitter class requires GD. If GD is unavailable, frame extraction will fail.if (!extension_loaded('gd')) {
throw new \RuntimeException('GD extension is required for frame extraction.');
}
Corrupted GIFs:
try {
$gif = Decoder::decode($file);
} catch (\Intervention\Gif\Exceptions\GifException $e) {
Log::error("Invalid GIF: {$e->getMessage()}");
return response()->json(['error' => 'Invalid GIF'], 400);
}
Frame Delays:
100 = 1 second). Convert carefully:
$delay = $frame->getGraphicControlExtension()->getDelayTime() / 100; // Convert to seconds
Memory Limits:
memory_limit:
ini_set('memory_limit', '512M');
Loop Count vs. Repetitions:
setLoops(0) = infinite loop. setLoops(1) = play once.getLoopCount() to check.Inspect GIF Metadata:
GifDataStream object to debug:
$gif = Decoder::decode($file);
dd($gif->getLogicalScreenDescriptor(), $gif->getGraphicControlExtensions());
Validate Binary Data:
$binary = $builder->encode();
if (empty($binary) || strlen($binary) < 100) {
throw new \RuntimeException('Encoding failed: empty output.');
}
Check for GD Errors:
Splitter::split() fails, verify GD is installed and the file is a valid GIF:
if (!function_exists('gd_info')) {
throw new \RuntimeException('GD extension not available.');
}
Leverage Intervention Image:
Intervention\Image to resize frames before encoding:
$frame = Image::make($framePath)->resize(200, 200);
$builder->addFrame($frame->toGD(), 0.5);
Cache Decoded GIFs:
GifDataStream objects to avoid reprocessing:
$gif = Cache::remember("gif_{$path}", now()->addHours(1), function () use ($path) {
return Decoder::decode($path);
});
Use Laravel Events:
How can I help you explore Laravel packages today?