Processing Images
April 29, 2026 · View on GitHub
use PhpCollective\Infrastructure\Storage\FileFactory;
use PhpCollective\Infrastructure\Storage\FileStorage;
use PhpCollective\Infrastructure\Storage\PathBuilder\PathBuilder;
use PhpCollective\Infrastructure\Storage\Processor\Image\Driver;
use PhpCollective\Infrastructure\Storage\Processor\Image\ImageProcessor;
use PhpCollective\Infrastructure\Storage\Processor\Image\ImageVariantCollection;
use PhpCollective\Infrastructure\Storage\StorageAdapterFactory;
use PhpCollective\Infrastructure\Storage\StorageService;
/*******************************************************************************
* Configuring the stores - Your DI container or bootstrapping should do this
******************************************************************************/
$storageService = new StorageService(
new StorageAdapterFactory()
);
$pathBuilder = new PathBuilder();
$fileStorage = new FileStorage(
$storageService,
$pathBuilder,
);
// Driver::Auto picks Imagick when the extension is loaded and falls
// back to GD; use Driver::Gd or Driver::Imagick to choose explicitly.
$imageProcessor = ImageProcessor::create(Driver::Auto, $fileStorage, $pathBuilder);
/*******************************************************************************
* Save the original first
******************************************************************************/
$file = FileFactory::fromDisk('./tests/Fixtures/titus.jpg', 'local')
->withUuid('914e1512-9153-4253-a81e-7ee2edc1d973')
->addToCollection('avatar')
->belongsToModel('User', '1');
$file = $fileStorage->store($file);
/*******************************************************************************
* Creating manipulated versions of the file
******************************************************************************/
$collection = ImageVariantCollection::create();
// Resize with aspect ratio preservation (recommended for most cases)
$collection->addNew('thumbnail')
->scale(300, 300)
->optimize();
// Resize to exact dimensions (stretches image)
$collection->addNew('resizeAndFlip')
->flipHorizontal()
->resize(300, 300)
->optimize();
// Crop to exact dimensions
$collection->addNew('crop')
->crop(100, 100);
// Repeating the same operation type keeps both steps in order
$collection->addNew('effects')
->blur(1)
->blur(6);
$file = $file->withVariants($collection->toArray());
// Process ALL variants (default)
$file = $imageProcessor->process($file);
// Or: process only specific variants (per-call filter)
// $file = $imageProcessor->process($file, ['thumbnail', 'crop']);
Tuning the encoder
ImageProcessor exposes a few setters for output tuning:
$imageProcessor
->setQuality(90) // single value, all formats
->setQuality(['webp' => 80, 'jpg' => 90, 'avif' => 70]) // per-format map
->setStripExif(true) // privacy + smaller files (default)
->setPreserveProfile(true) // wide-gamut color (default)
->setPreserveAnimation(true); // animated GIF/WebP keep all frames (default)
setQuality() accepts either an int (1–100, applied to every quality-aware encoder) or an array keyed by extension. setStripExif(true) is the default and only affects encoders that support the strip argument (jpg / jpeg / pjpg / webp / avif / heic / tiff / jp2). setPreserveProfile(true) (also the default) captures the source ICC profile after decode and re-applies it after operations run, so wide-gamut sources keep rendering correctly even if a callback strips the profile mid-pipeline.
Animated images
Animated GIF and WebP sources are preserved through the pipeline by default — every frame runs through the operations chain (resize / crop / cover / etc. apply per-frame) and the encoder emits animated output. Call setPreserveAnimation(false) to flatten to a single static frame:
// Keep all frames (default)
$imageProcessor->process($file);
// Flatten to a single frame — useful for static thumbnails or when
// converting to a non-animated format like JPEG via `convert()`.
$imageProcessor
->setPreserveAnimation(false)
->process($file);
Selecting a subset of variants
Pass an explicit list as the second argument to process() to scope a single call:
// Only the named variants are written
$file = $imageProcessor->process($file, ['thumbnail']);
// Default (no second arg) processes every variant on the file
$file = $imageProcessor->process($file);
The filter is per-call — there's no leakage between invocations, so it's safe to share an ImageProcessor instance across requests / queue workers.
Variant serialization
ImageVariantCollection::toArray() preserves operation order, including repeated operations of the same name. Single operations keep the legacy shape:
'resize' => ['width' => 300, 'height' => 300]
If the same operation is added more than once, that entry becomes a list:
'blur' => [
['level' => 1],
['level' => 6],
]
ImageVariantCollection::fromArray() accepts both shapes and rebuilds the variant chain in the same order.