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

Imagine Filters Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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

  1. Look at the repo before Composer: 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.
  2. Try 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.
  3. If you want the code, vendor it: the 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.

Implementation Patterns

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.

Gotchas and Tips

  • It is dead code. No PHP 8 support, no Imagine 1.x support, no security patches, no bug fixes. Composer will likely force you onto an old imagine/imagine that conflicts with everything else in a modern app. Do not add it to require in a greenfield project.
  • Driver divergence. Some 2014-era filters reach into GD/Imagick resources directly (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.
  • Alpha and transparency. PNG/WebP uploads with alpha will get flattened by any "tint", "background", or "fill" filter that isn't mask-aware. Do alpha-sensitive operations first, and verify with a transparent PNG fixture — this is the single most common surprise.
  • EXIF orientation. Imagine 0.5 does nothing about it; your "sharpen then resize" output can come out sideways. Rotate from EXIF before filtering (Intervention: ->orientate()), then strip metadata so you don't leak GPS coordinates from user uploads.
  • Memory. GD decodes the whole image into memory. A 12MP phone photo is ~48MB as a resource and large uploads will blow memory_limit in a worker. Resize before applying expensive effects, raise the limit only on the queue worker, and reject oversized uploads at validation.
  • Caching. Never run filters per request. Key the cache on 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.
  • Autoload quirks. 2014 packages frequently use PSR-0. If you vendor the code, re-namespace it (App\Support\Image\Filters\...) and let Composer's PSR-4 handle it, rather than registering an extra autoload prefix.
  • Extension point worth keeping. The genuinely reusable idea is "filter = a class implementing a one-method interface, registered by name." Recreate that interface in your own codebase and the package becomes irrelevant — that's the correct outcome.
  • Licensing is your friend. MIT means vendoring, copying, and modifying is fine. Given 0 dependents and an archive flag, copying the parts you need into your own repo is better engineering than declaring a dependency on an abandoned package.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin