intervention/gif
Native PHP GIF encoder/decoder for reading and writing GIFs without image extensions. Decode files or binary streams, and build animated GIFs frame-by-frame with delays, offsets, and loop control. Includes a GD-based Splitter to extract frames as GDImage objects.
Installation:
composer require intervention/gif
Ensure your project uses PHP 8.3+ and has the GD extension (only required for Splitter class).
First Use Case: Decode an existing GIF to inspect its frames:
use Intervention\Gif\Decoder;
$gifData = Decoder::decode(storage_path('app/animation.gif'));
$frames = $gifData->getFrames(); // Array of GDImage objects (if GD is available)
Quick Encoding: Create a simple animated GIF from static frames:
use Intervention\Gif\Builder;
$gif = Builder::canvas(100, 100);
$gif->addFrame(resource://'frame1.png', 0.5); // 0.5s delay
$gif->addFrame(resource://'frame2.png', 0.5);
$gif->setLoops(0); // Infinite loop
$encodedGif = $gif->encode();
file_put_contents('output.gif', $encodedGif);
Key Classes to Explore:
Decoder: Parse GIFs into structured data (frames, metadata).Builder: Construct GIFs programmatically.Splitter: Extract frames as GDImage objects (GD-dependent).GifDataStream: Core data model for decoded GIFs.Where to Look First:
tests/ directory for real-world usage patterns.src/Blocks/ for GIF specification details (e.g., GraphicControlBlock, ImageDescriptorBlock).phpdoc or use IDE autocompletion for Decoder/Builder methods.Use the Builder to create GIFs from user-generated content (e.g., progress trackers, reactions):
use Intervention\Gif\Builder;
use Illuminate\Support\Facades\Storage;
public function generateProgressGif(Request $request)
{
$progress = $request->input('progress'); // 0-100
$gif = Builder::canvas(200, 50);
// Add frames for each progress step
for ($i = 0; $i <= 100; $i += 10) {
$frame = imagecreatetruecolor(200, 50);
$white = imagecolorallocate($frame, 255, 255, 255);
$blue = imagecolorallocate($frame, 0, 0, 255);
imagefilledrectangle($frame, 0, 0, $i * 2, 50, $blue);
$gif->addFrame($frame, 0.2); // 0.2s delay
}
$gif->setLoops(0);
return response($gif->encode(), 200, ['Content-Type' => 'image/gif']);
}
Validate uploaded GIFs for compliance (e.g., loop limits, frame count) using Laravel’s validation:
use Intervention\Gif\Decoder;
use Illuminate\Validation\Rule;
Rule::gifMaxLoops = function ($attribute, $maxLoops, $fail) {
$gif = Decoder::decode($attribute);
if ($gif->getLoops() > $maxLoops) {
$fail('The :attribute may not loop more than '.$maxLoops.' times.');
}
};
// Usage:
$request->validate([
'animation' => ['required', 'file', Rule::gifMaxLoops(5)],
]);
Process GIFs asynchronously using Laravel queues:
use Intervention\Gif\Decoder;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessGif implements ShouldQueue
{
use Queueable;
public function handle()
{
$gif = Decoder::decode(storage_path('queue/animation.gif'));
// Process frames (e.g., resize, optimize)
$optimized = $this->optimizeGif($gif);
$optimized->save(storage_path('processed/animation.gif'));
}
}
Combine intervention/image and intervention/gif for advanced frame manipulation:
use Intervention\Image\Facades\Image;
use Intervention\Gif\Builder;
$gif = Builder::canvas(300, 300);
$frame1 = Image::make('frame1.jpg')->resize(300, 300);
$frame2 = Image::make('frame2.jpg')->resize(300, 300)->rotate(10);
$gif->addFrame($frame1->__toString(), 0.5);
$gif->addFrame($frame2->__toString(), 0.5);
Extract GIF metadata (e.g., frame delays, transparency) for analytics:
$gif = Decoder::decode($path);
$metadata = [
'width' => $gif->getWidth(),
'height' => $gif->getHeight(),
'frameCount' => count($gif->getFrames()),
'loopCount' => $gif->getLoops(),
'globalPalette' => $gif->getGlobalColorTable() ? 'Yes' : 'No',
];
Service Provider Binding:
Bind the Decoder/Builder to the container for dependency injection:
public function register()
{
$this->app->bind(Decoder::class, function () {
return new Decoder();
});
}
File System Integration:
Use Laravel’s Storage facade to handle GIF files:
use Illuminate\Support\Facades\Storage;
$path = Storage::path('uploads/animation.gif');
$gif = Decoder::decode($path);
API Responses: Serve GIFs directly from controllers:
return response($gif->encode(), 200, [
'Content-Type' => 'image/gif',
'Cache-Control' => 'public, max-age=31536000',
]);
Caching: Cache decoded GIFs to avoid reprocessing:
$gif = Cache::remember("gif_{$path}", now()->addHours(1), function () use ($path) {
return Decoder::decode($path);
});
Error Handling: Gracefully handle corrupted GIFs:
try {
$gif = Decoder::decode($path);
} catch (\Intervention\Gif\Exceptions\InvalidGifException $e) {
Log::error("Invalid GIF uploaded: {$path}");
return back()->withError('Invalid GIF file.');
}
Streaming Large GIFs: Stream GIFs to avoid memory issues:
$response = new StreamingResponse(function () use ($gif) {
echo $gif->encode();
});
return $response;
Batch Processing:
Process GIFs in chunks using Laravel’s Chunk helper:
Gif::chunk(100, function ($gifs) {
foreach ($gifs as $gif) {
// Process each GIF
}
});
GD Fallback:
Check for GD extension before using Splitter:
if (extension_loaded('gd')) {
$frames = $gif->split();
} else {
// Fallback: Process frames manually or use a different library
}
GD Dependency for Splitter:
Splitter::split() method requires the GD extension to extract frames as GDImage objects.getFrames() (returns raw binary data) or implement a custom frame parser.if (!extension_loaded('gd')) {
throw new \RuntimeException('GD extension is required for frame splitting.');
}
Memory Limits:
memory_limit.memory_limit or process frames in smaller batches.Corrupted GIF Handling:
InvalidGifException for malformed GHow can I help you explore Laravel packages today?