jcfrane / laravel-lens
Configuration-driven image manipulation and caching for Laravel, inspired by LiipImagineBundle
Requires
- php: ^8.2
- illuminate/cache: ^12.0
- illuminate/console: ^12.0
- illuminate/filesystem: ^12.0
- illuminate/routing: ^12.0
- illuminate/support: ^12.0
- illuminate/view: ^12.0
- intervention/image: ^3.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.0
README
Configuration-driven image manipulation and caching for Laravel, inspired by Symfony's LiipImagineBundle.
Define named filter sets in your config, and Laravel Lens handles the rest — lazy processing on first request, automatic caching, and simple Blade integration. Built on top of Intervention Image and Laravel's filesystem abstraction, it works with any storage driver out of the box.
Quick Examples
Define filter sets in your config:
// config/lens.php 'filter_sets' => [ 'avatar' => [ 'quality' => 80, 'filters' => [ ['name' => 'auto_orient'], ['name' => 'thumbnail', 'options' => ['size' => [150, 150], 'mode' => 'outbound']], ], ], 'hero_banner' => [ 'quality' => 90, 'format' => 'webp', 'filters' => [ ['name' => 'resize', 'options' => ['size' => [1200, null]]], ['name' => 'blur', 'options' => ['amount' => 5]], ], ], ],
Use them anywhere in your application:
// Blade directive <img src="@lens('photos/profile.jpg', 'avatar')" alt="Profile" /> // Blade component <x-lens-image path="photos/hero.jpg" filter="hero_banner" alt="Hero" class="w-full" /> // Facade $url = Lens::url('photos/profile.jpg', 'avatar'); // API route (auto-registered) // GET /lens/avatar/photos/profile.jpg
Features
- Configuration-driven filter sets — define once, use everywhere
- 14 built-in filters — thumbnail, crop, resize, scale, watermark, grayscale, flip, rotate, blur, brightness, contrast, background, strip, auto_orient
- Lazy processing — images are processed on first request, then served from cache
- Multiple storage drivers — works with local disk, S3, or any Laravel filesystem driver
- Blade integration —
@lens()directive and<x-lens-image />component - Built-in API route —
/lens/{filterSet}/{path}for on-demand processing - Cache management — Artisan commands for clearing and warming up the cache
- Multiple drivers — GD and Imagick support via Intervention Image
- Runtime overrides — pass options at call time without creating new filter sets
- Extensible — register custom filters with a simple interface
Requirements
- PHP 8.2+
- Laravel 12
- GD or Imagick PHP extension
Installation
composer require jcfrane/laravel-lens
Publish the configuration file:
php artisan vendor:publish --tag=lens-config
Configuration
The published config file (config/lens.php) contains all settings:
Driver
'driver' => env('LENS_DRIVER', 'gd'), // 'gd' or 'imagick'
Data Loader
Configure where source images are loaded from. Uses Laravel filesystem disks:
'data_loader' => [ 'default' => 'filesystem', 'loaders' => [ 'filesystem' => [ 'driver' => 'filesystem', 'disk' => env('LENS_SOURCE_DISK', 'public'), 'base_path' => '', ], ], ],
Cache
Configure where processed images are stored:
'cache' => [ 'default' => 'filesystem', 'resolvers' => [ 'filesystem' => [ 'driver' => 'filesystem', 'disk' => env('LENS_CACHE_DISK', 'public'), 'base_path' => 'lens-cache', ], ], ],
Filter Sets
Each filter set defines an ordered pipeline of filters:
'filter_sets' => [ 'thumbnail_small' => [ 'quality' => 80, // encoding quality (1-100) 'format' => null, // null = keep original, or 'webp', 'jpeg', 'png' 'cache' => null, // null = use default cache resolver 'data_loader' => null, // null = use default data loader 'filters' => [ ['name' => 'auto_orient'], ['name' => 'thumbnail', 'options' => [ 'size' => [150, 150], 'mode' => 'outbound', // 'outbound' (cover) or 'inset' (contain) ]], ], ], ],
Routes
'route' => [ 'enabled' => true, 'prefix' => 'lens', 'middleware' => ['web'], 'signed' => false, ],
Usage
Blade Directive
<img src="@lens('photos/landscape.jpg', 'thumbnail_small')" alt="Landscape" />
Blade Component
<x-lens-image path="photos/landscape.jpg" filter="thumbnail_small" alt="Landscape" class="rounded shadow" />
The component renders an <img> tag with the filtered image URL and passes through any additional HTML attributes.
Facade
use JCFrane\LaravelLens\Facades\Lens; // Get the URL for a filtered image $url = Lens::url('photos/landscape.jpg', 'thumbnail_small'); // Process and cache an image immediately $binary = Lens::resolve('photos/landscape.jpg', 'thumbnail_small'); // Check if a filtered image is cached $cached = Lens::isCached('photos/landscape.jpg', 'thumbnail_small'); // Invalidate cached images Lens::invalidate('photos/landscape.jpg'); Lens::invalidate('photos/landscape.jpg', ['thumbnail_small']);
Runtime Filter Overrides
Override filter options at call time without creating a new filter set:
$url = Lens::url('photo.jpg', 'thumbnail_small', [ 'thumbnail' => ['size' => [200, 200]], ]);
API Route
The package auto-registers a route for on-demand image processing:
GET /lens/{filterSet}/{path}
On the first request, the image is processed through the filter pipeline and cached. Subsequent requests are served from cache with a redirect.
Example: /lens/thumbnail_small/photos/landscape.jpg
Available Filters
| Filter | Options | Description |
|---|---|---|
auto_orient |
— | Rotate based on EXIF orientation data |
thumbnail |
size, mode, upscale |
Scale and/or crop to thumbnail dimensions |
crop |
size, start |
Crop a rectangular region |
resize |
size |
Resize to exact dimensions |
scale |
size |
Scale proportionally |
watermark |
image, position, opacity, size |
Overlay a watermark image |
grayscale |
— | Convert to grayscale |
flip |
direction |
Mirror horizontally or vertically |
rotate |
angle, background |
Rotate by degrees |
blur |
amount |
Apply Gaussian blur |
brightness |
level |
Adjust brightness (-100 to 100) |
contrast |
level |
Adjust contrast (-100 to 100) |
background |
color, size, position |
Set or extend canvas with background color |
strip |
— | Remove animation frames |
Custom Filters
Create a class implementing FilterInterface:
use Intervention\Image\Interfaces\ImageInterface; use JCFrane\LaravelLens\Contracts\FilterInterface; class SepiaFilter implements FilterInterface { public function apply(ImageInterface $image, array $options = []): ImageInterface { return $image->greyscale()->brightness(-10)->contrast(10); } public function name(): string { return 'sepia'; } }
Register it in a service provider:
use JCFrane\LaravelLens\Facades\Lens; Lens::filterManager()->register('sepia', new SepiaFilter());
Then use it in your filter sets:
'filter_sets' => [ 'vintage' => [ 'filters' => [ ['name' => 'sepia'], ['name' => 'blur', 'options' => ['amount' => 2]], ], ], ],
Artisan Commands
# Clear all cached images php artisan lens:cache:clear # Clear cache for a specific filter set php artisan lens:cache:clear --filter=thumbnail_small # Clear cache for a specific image php artisan lens:cache:clear --path=photos/old-image.jpg # Pre-generate cached images php artisan lens:cache:warmup --path=photos/hero.jpg --path=photos/logo.png # Warm up specific filter sets only php artisan lens:cache:warmup --path=photos/hero.jpg --filter=thumbnail_small --filter=thumbnail_medium
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security-related issues, please use the issue tracker.
Credits
Acknowledgments
- LiipImagineBundle — the Symfony bundle that inspired this package
- Intervention Image — the underlying image processing library
License
The MIT License (MIT). Please see License File for more information.