sandstorm / laravel-imagor
Laravel integration for Imagor
Fund package maintenance!
Imam Susanto
Requires
- php: ^8.2
- illuminate/contracts: ^10.0|^11.0|^12.0
- spatie/laravel-package-tools: ^1.19
Requires (Dev)
- filament/forms: *
- larastan/larastan: ^2.9
- laravel/pint: ^v1.21
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^9.0.0||^8.22.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-arch: ^2.8
- pestphp/pest-plugin-laravel: ^2.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
README
A comprehensive Laravel package for Imagor integration. Generate optimized, signed image URLs with fluent API including resizing, quality control, visual effects, and advanced processing options. Originally forked from https://github.com/imsus/laravel-imgproxy, which deserves most credit :)
- Laravel integration for Imagor
- Basic Usage
- Laravel Integration
- API Reference
- Image Processing Recipes
- Development Setup
- Troubleshooting
- File Sizes
- Changelog
- Credits
- License
Features
- ๐ Fluent API - Clean, chainable method syntax
- ๐ Secure URLs - HMAC signed URLs with configurable keys
- ๐จ Visual Effects - Blur, sharpen, brightness, contrast, saturation adjustments
- โก Quality Control - Fine-tune compression and output formats
- ๐ง Flexible Resizing - Multiple resize modes with smart cropping
- ๐งฉ Laravel Integration - Service provider, facade, and helper function
- โ Type Safe - PHP 8.2+ with comprehensive validation
- ๐งช Well Tested - Comprehensive test suite with workbench integration
Installation
You can install the package via composer:
composer require sandstorm/laravel-imagor
You can publish the config file with:
php artisan vendor:publish --tag="imagor-config"
To set up Imagor via docker-compose.yaml
, use the following snippet:
services: laravel: # ... # we assume the laravel application lives in "/app" in the Docker container volumes: - laravel-storage:/app/storage environment: IMAGOR_BASE_URL: http://imagor:8091 IMAGOR_SECRET: UNSAFE_DEV_SECRET IMAGOR_SIGNER_TYPE: sha256 IMAGOR_SIGNER_TRUNCATE: 40 # use the mozjpeg version from docker-hub.sandstorm.de/docker-infrastructure/imagor:v1.5.16-mozjpeg # See https://gitlab.sandstorm.de/docker-infrastructure/imagor/container_registry for the currently built versions. # # to get bigger JPEG files - alternatively you can also use the official shumc/imagor image from docker hub imagor: image: docker-hub.sandstorm.de/docker-infrastructure/imagor:v1.5.16-mozjpeg ports: - ${IMAGOR_PORT:-8091:8091} environment: # if things do not work, enable debugging: # DEBUG: 1 PORT: 8091 IMAGOR_SECRET: UNSAFE_DEV_SECRET IMAGOR_SIGNER_TYPE: sha256 IMAGOR_SIGNER_TRUNCATE: 40 IMAGOR_CACHE_HEADER_TTL: 8760h IMAGOR_PROCESS_CONCURRENCY: 25 IMAGOR_PROCESS_QUEUE_SIZE: 200 SERVER_ADDRESS: imagor SERVER_CORS: true # crucial if files contain special characters, e.g. for Laravel temporary uploads # -> disables mangling of filenames during loading FILE_SAFE_CHARS: '--' FILE_LOADER_BASE_DIR: '/app' FILE_RESULT_STORAGE_BASE_DIR: '/mnt/imagor_cache/results' FILE_STORAGE_BASE_DIR: '/mnt/imagor_cache/input' VIPS_MAX_ANIMATION_FRAMES: 1 VIPS_MAX_FILTER_OPS: 20 VIPS_MAX_WIDTH: 10000 VIPS_MAX_HEIGHT: 10000 VIPS_MAX_RESOLUTION: 100000000 # bigger images (optional) VIPS_MOZJPEG: 1 volumes: - laravel-storage:/app/storage
Finally, can call the URL /__imagor-configtest to check if Imagor is configured correctly. This example copies some pre-defined images to the storage folder and then loads it through Imagor to check if everything is wired correctly.
if you want to build the image yourself, see see the ./laravel-imagor folder which contains the sources of this docker image.
Mount the ./storage
folder of your Laravel application to the same folder in the Imagor container;
so if the storage folder is located at /app/storage
, you should mount it to /app/storage
in the Imagor container
as well.
Then, you can use FILE_LOADER_BASE_DIR='/app'
to load images from the mounted storage folder.
Adjust the path mapping if needed via the path_map
config option:
'path_map' => [
// the key is the original (Laravel) path prefix, the value is the corresponding Imagor path prefix AFTER the FILE_LOADER_BASE_DIR.
// so /storage on the right side resolves to /app/storage on the file system of the Imagor container
storage_path() => '/storage',
]
Basic Usage
// Generate URL using helper function $url = imagor() ->resize(width: 400, height: 300) ->uriFor('https://example.com/image.jpg');
NOTE: the image URL to be processed is passed at the END of the chain, to be able to use the same instance of the
Imagor
class for multiple image processing operations:
$resizeOp = imagor() ->resize(width: 400, height: 300); $url1 = $resizeOp->uriFor('https://example.com/image.jpg'); $url2 = $resizeOp->uriFor('https://example.com/foo.jpg');
NOTE: the Imagor
class is immutable, so you always need to assign the result of a method call to a variable:
$imagor = imagor(); // โ WILL NOT WORK โ because a new object is returned $imagor->resize(width: 400, height: 300); $imagor->uriFor('https://example.com/image.jpg'); // โ Instead, do the following: $imagor = $imagor->resize(width: 400, height: 300); $imagor->uriFor('https://example.com/image.jpg');
Accessing the Imagor object
The following methods exist for accessing the Imagor object:
- inject
Sandstorm\LaravelImagor\ImagorFactory
, and call->new()
to get a new instance of theImagor
class - inject
Sandstorm\LaravelImagor\Imagor
- you'll get a new instance of theImagor
class every time - use the
imagor()
helper function
If in doubt, use one of the injections.
Resizing & Cropping
// Basic resizing $url = imagor() ->resize(width: 400, height: 300) ->uriFor($imageUrl); // Fit image within dimensions (preserves aspect ratio) $url = imagor() ->resize(width: 400, height: 300) ->fitIn() ->uriFor($imageUrl); // Force stretch to exact dimensions (does NOT preserve aspect ratio) $url = imagor() ->resize(width: 400, height: 300) ->stretch() ->uriFor($imageUrl); // Smart cropping with focal point detection $url = imagor() ->resize(width: 400, height: 300) ->smart() ->uriFor($imageUrl); // Manual cropping (left, top, right, bottom) $url = imagor() ->crop(10, 10, 300, 200) ->uriFor($imageUrl);
Quality & Format Control
// Set JPEG quality $url = imagor()->resize(width: 400)->quality(85)->uriFor($imageUrl); // Convert to different formats $webpUrl = imagor()->resize(width: 400)->format('webp')->uriFor($imageUrl); $avifUrl = imagor()->resize(width: 400)->format('avif')->uriFor($imageUrl); $pngUrl = imagor()->resize(width: 400)->format('png')->uriFor($imageUrl);
Visual Effects
$url = imagor() ->resize(width: 500, height: 300) ->blur(2.0) // Blur effect ->sharpen(1.5) // Sharpen details ->brightness(20) // Increase brightness ->contrast(110) // Enhance contrast (percentage) ->saturation(120) // Boost saturation (percentage) ->uriFor($imageUrl);
Image Transformations
// Flip images $url = imagor() ->resize(width: 400, height: 300) ->flipHorizontally() ->flipVertically() ->uriFor($imageUrl); // Add padding (left, top, right, bottom) $url = imagor() ->resize(width: 400, height: 300) ->padding(10, 10, 10, 10) ->uriFor($imageUrl); // Set alignment for cropping $url = imagor() ->resize(width: 400, height: 300) ->hAlign('left') // 'left', 'right', 'center' ->vAlign('top') // 'top', 'bottom', 'middle' ->uriFor($imageUrl);
Laravel Integration
Livewire: Display temporary uploaded files
before:
<img width="300" src="{{ $mediaFile->temporaryUrl() }}" />
after:
<img width="300" src="{{ imagor()->resize(300)->uriFor($mediaFile->getPathname()) }}" />
Display images from the public storage
before:
<img src="{{ asset('storage/' . $post->media_files[0]) }}" />
after:
<img src="{{ imagor()->resize(300)->uriFor(Storage::disk('public')->path($post->media_files[0])) }}">
TODO: see if we can make this work a bit simpler :)
Upload images in Filament Forms
before:
FileUpload::make('media_files') ->acceptedFileTypes(['image/*']) ->rules(['image', 'max:10240']) // max 10MB per file ->disk('public') ->visibility('public')
after:
use Sandstorm\LaravelImagor\Filament\Components\ImagorFileUpload; ImagorFileUpload::make('media_files') ->acceptedFileTypes(['image/*']) ->rules(['image', 'max:10240']) // max 10MB per file // not required anymore, imagor also works with private files ->disk('public') // not required anymore, imagor also works with private files ->visibility('public') // specify which size is needed ->imageProcessor(imagor()->resize(100)), // same logic, different syntax (without global function) ->imageProcessor(fn(ImagorPathBuilder $imagor) => $imagor->resize(100)),
Blade Directives
Create custom Blade directives for common use cases:
// In AppServiceProvider::boot() use Illuminate\Support\Facades\Blade; Blade::directive('imagorWide', function ($expression) { return "<?php echo imagor()->resize(width: 400)->uriFor($expression); ?>"; }); Blade::directive('avatar', function ($expression) { return "<?php echo imagor()->resize(width: 150, height: 150)->smart()->uriFor($expression); ?>"; });
{{-- Usage in Blade templates --}} <img src="@imagorWide($product->image)" alt="Product"> <img src="@avatar($user->avatar)" alt="User Avatar">
API Reference
Available Methods
Method | Parameters | Description |
---|---|---|
resize(int $width, int $height) |
Width, height in pixels | Set image dimensions |
crop(int $a, int $b, int $c, int $d) |
Coordinates | Manual crop (left, top, right, bottom) |
fitIn() |
- | Fit image within dimensions |
stretch() |
- | Force resize without aspect ratio |
smart() |
- | Smart focal point detection |
trim() |
- | Remove surrounding whitespace |
flipHorizontally() |
- | Flip image horizontally |
flipVertically() |
- | Flip image vertically |
padding(int $left, int $top, int $right, int $bottom) |
Padding values | Add padding around image |
hAlign(string $align) |
'left', 'right', 'center' | Horizontal alignment |
vAlign(string $align) |
'top', 'bottom', 'middle' | Vertical alignment |
quality(int $quality) |
0-100 | Set JPEG quality |
format(string $format) |
Format string | Set output format |
blur(float $sigma) |
โฅ0.0 | Apply blur effect |
sharpen(float $sigma) |
โฅ0.0 | Apply sharpen effect |
brightness(int $amount) |
-255 to 255 | Adjust brightness |
contrast(int $amount) |
Percentage | Adjust contrast |
saturation(int $amount) |
Percentage | Adjust saturation |
addFilter(string $name, ...$args) |
Filter name and args | Add custom filter |
uriFor(string $sourceImage) |
Image URL | Generate final URL |
Supported Formats
jpeg
- JPEG formatpng
- PNG formatgif
- GIF formatwebp
- WebP formatavif
- AVIF formatjxl
- JPEG XL formattiff
- TIFF formatjp2
- JPEG 2000 format
Image Processing Recipes
Photo Enhancement
// Portrait enhancement $enhancedPortrait = imagor() ->resize(width: 600, height: 800) ->smart() ->brightness(8) // Slightly brighter ->contrast(110) // Enhanced contrast ->saturation(105) // Subtle saturation boost ->sharpen(0.8) // Gentle sharpening ->quality(92) ->uriFor($portrait); // Vintage effect $vintageEffect = imagor() ->resize(width: 600, height: 400) ->saturation(70) // Reduced saturation ->contrast(90) // Lower contrast ->brightness(-10) // Slightly darker ->quality(85) ->uriFor($image);
E-commerce Optimization
// Clean product photos $productClean = imagor() ->resize(width: 800, height: 800) ->fitIn() ->trim() // Remove whitespace ->brightness(15) // Bright and clean ->contrast(110) // Good contrast ->sharpen(1.5) // Sharp product details ->quality(95) // High quality for products ->format('webp') ->uriFor($product);
Development Setup
Unit & Integration Tests
# Run all tests composer test # Run with coverage composer test-coverage
Interactive Testing with Workbench
The package includes a comprehensive workbench environment for interactive testing:
# Start the workbench server composer start # Or build separately and serve composer build php vendor/bin/testbench serve
Now access the Visual Test Suite at http://localhost:8000/imgproxy-visual-test
Troubleshooting
Problem: Getting "unsafe" URLs instead of signed URLs
http://localhost:8000/unsafe/400x300/...
Solution: Ensure IMAGOR_SECRET
is set in your .env
file.
Problem: Images not loading/404 errors Solutions:
- Verify Imagor server is running at the configured base URL
- Check source image URLs are accessible
- Ensure Imagor server can reach source URLs (firewall/network issues)
Problem: Poor image quality
Solutions:
- Increase quality setting:
->quality(90)
- Use appropriate output format:
->format('webp')
- Avoid excessive sharpening:
->sharpen(1.0)
instead of higher values
File Sizes
imagor_docker, with VIPS_MOZJPEG: 0; or shumc/imagor:latest
(no mozjpeg)
docker compose down -v docker compose up -d +---------------------+--------+------------+----------+ | version | format | dimensions | size | +---------------------+--------+------------+----------+ | original | jpeg | orig | 6.3 MB | | optimized_no_change | jpeg | orig | 3.7 MB | | optimized_quality95 | jpeg | orig | 13.8 MB | | optimized_quality80 | jpeg | orig | 4.3 MB | | optimized_quality50 | jpeg | orig | 2.5 MB | | optimized_no_change | jpeg | 600x600 | 44.3 KB | | optimized_quality95 | jpeg | 600x600 | 161.9 KB | | optimized_quality80 | jpeg | 600x600 | 50.8 KB | | optimized_quality50 | jpeg | 600x600 | 29.7 KB | | optimized_no_change | webp | orig | 1.5 MB | | optimized_quality95 | webp | orig | 6.2 MB | | optimized_quality80 | webp | orig | 1.9 MB | | optimized_quality50 | webp | orig | 1.1 MB | | optimized_no_change | webp | 600x600 | 28.6 KB | | optimized_quality95 | webp | 600x600 | 97.4 KB | | optimized_quality80 | webp | 600x600 | 35.6 KB | | optimized_quality50 | webp | 600x600 | 20.5 KB | +---------------------+--------+------------+----------+
imagor_docker, with VIPS_MOZJPEG: 1
f.e. the image ghcr.io/cshum/imagor-mozjpeg:docker-variants from cshum/imagor#456 (comment)
docker compose down -v docker compose up -d vendor/bin/testbench imagor:benchmark-image-sizes +---------------------+--------+------------+----------+ | version | format | dimensions | size | without mozjpeg (from above) +---------------------+--------+------------+----------+ | original | jpeg | orig | 6.3 MB | | optimized_no_change | jpeg | orig | 2.5 MB | 3.7 MB (32.4% bigger) | optimized_quality95 | jpeg | orig | 10.6 MB | 13.8 MB (23.2% bigger) | optimized_quality80 | jpeg | orig | 3.1 MB | 4.3 MB (27.9% bigger) | optimized_quality50 | jpeg | orig | 1.5 MB | 2.5 MB (40.0% bigger) | optimized_no_change | jpeg | 600x600 | 35.9 KB | 44.3 KB (19.0% bigger) | optimized_quality95 | jpeg | 600x600 | 152.2 KB | 161.9 KB (6.0% bigger) | optimized_quality80 | jpeg | 600x600 | 42.8 KB | 50.8 KB (15.7% bigger) | optimized_quality50 | jpeg | 600x600 | 22.1 KB | 29.7 KB (25.6% bigger) | optimized_no_change | webp | orig | 1.5 MB | | optimized_quality95 | webp | orig | 6.2 MB | | optimized_quality80 | webp | orig | 1.9 MB | | optimized_quality50 | webp | orig | 1.1 MB | | optimized_no_change | webp | 600x600 | 28.6 KB | | optimized_quality95 | webp | 600x600 | 97.4 KB | | optimized_quality80 | webp | 600x600 | 35.6 KB | | optimized_quality50 | webp | 600x600 | 20.5 KB | +---------------------+--------+------------+----------+
Changelog
Please see CHANGELOG for more information on what has changed recently.
Credits
- Imam Susanto for the original package
- Sandstorm Media for the Imagor fork
- All Contributors
License
The MIT License (MIT). Please see License File for more information.