zenstruck/imagine-filters
Adds a set of reusable image filters on top of the Imagine library, making common manipulations like resizing, cropping, thumbnails, and watermarking easier to configure and chain in PHP applications.
What it actually is. zenstruck/imagine-filters is a plain PHP library — no service provider, no facade, no config file — that adds a handful of Imagine\Filter\FilterInterface implementations on top of imagine/imagine. Each filter is a small class whose only job is to mutate an Imagine\Image\ImageInterface. The "package" value is the collection of transformations plus a name→filter registry so you can refer to filters by string.
Reality check before you install. Last release was December 2014, it is archived, has 2 stars and 0 dependents. It predates PHP 7, Imagine 1.x, and every modern Laravel convention. Treat it as reference source code, not a dependency.
Minimal steps
composer.json (to see the imagine/imagine constraint — expect ^0.5/^0.6), then src/ for the filter classes and the manager/registry, then tests/ for the real constructor signatures.composer require zenstruck/imagine-filters only if you are already pinned to an old Imagine; otherwise composer why-not zenstruck/imagine-filters will tell you immediately it conflicts.src/ tree is tiny and MIT-licensed, so copying the filter classes into app/Support/Image/Filters/ (namespaced to your app) is the pragmatic path and sidesteps the abandoned-dependency problem entirely.First use case. Post-process an uploaded image inside a queued job before your normal thumbnail step:
use Imagine\Gd\Imagine;
use Imagine\Image\Box;
// Class/registry names below are illustrative — check src/ for the real ones.
$imagine = new Imagine();
$image = $imagine->open(Storage::disk('public')->path('uploads/photo.jpg'));
$filters = new FilterCollection(); // name from src/
$filters->get('sharpen')->apply($image); // signature varies; check tests/
$image->resize(new Box(800, 600))
->save(Storage::disk('public')->path('uploads/photo-thumb.jpg'));
The placement is the point: this belongs in a job or a dedicated ImagePipeline service, never in a controller or Blade template.
1. Wrap the registry in a container binding. Don't let Imagine objects or the package's manager leak into your app code. Bind a single FilterRegistry singleton in a service provider, register each filter by name, and inject it where needed. When you later migrate to intervention/image, only that binding changes.
2. Config-driven variant definitions. The most useful thing to copy from this package's era (LiipImagineBundle) is the idea of declaring variants in config:
// config/images.php
return [
'thumb' => ['filters' => ['sharpen' => [], 'resize' => ['w' => 200, 'h' => 200]]],
'hero' => ['filters' => ['tint' => ['color' => '#0a0a0a'], 'resize' => ['w' => 1600]]],
];
Then a single ProcessVariant job reads the definition and applies filters in order. This keeps transformation logic out of models and controllers and makes variants reviewable in a diff.
3. One job per variant, idempotent. Dispatch ProcessImageVariant($mediaId, 'thumb'), key the output path by sha1(source) . '-' . $variant, and bail early if the file already exists. Uploads should never block a request.
4. Compose with Imagine's built-ins, not instead of them. These extra filters are meant to be chained with Resize, Crop, Thumbnail, Watermark. Keep the whole chain in one testable method rather than scattering ->apply() calls. Order matters more than you think — see the alpha note below.
5. Filesystem integration. Imagine needs a real path. For local disks use Storage::path(). For S3, pull to a tmpfile(), process, then Storage::put() and delete the temp file — or use $imagine->load($bytes) and $image->get('jpeg', ['quality' => 85]) to avoid the round trip.
6. Porting to a maintained stack. In a modern Laravel codebase, the equivalents live in intervention/image v3 or league/glide:
| Package filter | Modern equivalent |
|---|---|
| sharpen / blur / gamma | intervention/image: ->sharpen(), ->blur(), ->gamma(), or Glide sharp / blur / filt |
| tint / colorize | ->colorize(), ->fill() with blend, or Glide filt |
| background / canvas | ->canvas(), ->resizeCanvas() |
| watermark | ->place() / Glide watermark |
| grayscale | ->greyscale() / Glide filt=greyscale |
Because each filter here is ~20–40 lines implementing apply(ImageInterface $image), porting one is a 15-minute exercise: reimplement it as a small class against Intervention\Image\Image, or just call the equivalent fluent method inline.
7. Testing shape. Write a FilterPipelineTest that asserts output dimensions, mime type, and that the file exists. Keep one small "golden image" test per filter if you port them, and pin the driver version in CI.
imagine/imagine that conflicts with everything else in a modern app. Do not add it to require in a greenfield project.getGdResource(), getImagick()). Those calls break across Imagine versions and produce visibly different results on GD vs Imagick vs Gmagick. If you port a filter, either restrict yourself to the public ImageInterface API or pin the driver explicitly.->orientate()), then strip metadata so you don't leak GPS coordinates from user uploads.memory_limit in a worker. Resize before applying expensive effects, raise the limit only on the queue worker, and reject oversized uploads at validation.source hash + filter names + filter options; changing a tint colour must invalidate the variant, so include the options in the key, not just the filter name.App\Support\Image\Filters\...) and let Composer's PSR-4 handle it, rather than registering an extra autoload prefix.How can I help you explore Laravel packages today?