File Storage Image Processing

April 29, 2026 · View on GitHub

CI Coverage Latest Stable Version Total Downloads PHPStan PHP Version Software License

Image Processing for the File Storage Library.

Built on top of Intervention Image v4.

Features

  • Generates and stores variants of an uploaded image (thumbnails, avatars, hero crops, …)
  • 24+ first-class image operations: resize, scale, cover, crop, rotate, flip, sharpen, orient (EXIF auto-rotate), brightness, contrast, grayscale, colorize, blur, pixelate, trim, resizeCanvas, padding, place (watermark), convert (format swap), …
  • Per-format encoder quality, EXIF/metadata strip toggle, ICC color-profile preservation
  • Pluggable operation registry — add custom operations without forking
  • Optional file-size optimization via spatie/image-optimizer
  • Works with League Flysystem for flexible storage backends
  • Fluent API for chaining operations, including repeating the same operation type multiple times

Requirements

Installation

composer require php-collective/file-storage-image-processor

Quick Example

use PhpCollective\Infrastructure\Storage\Processor\Image\Driver;
use PhpCollective\Infrastructure\Storage\Processor\Image\Format;
use PhpCollective\Infrastructure\Storage\Processor\Image\ImageProcessor;
use PhpCollective\Infrastructure\Storage\Processor\Image\ImageVariantCollection;
use PhpCollective\Infrastructure\Storage\Processor\Image\Position;

// 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);

$collection = ImageVariantCollection::create();

// Create a thumbnail with aspect ratio preserved
$collection->addNew('thumbnail')
    ->scale(300, 300)
    ->optimize();

// Create an avatar that fills exact dimensions
$collection->addNew('avatar')
    ->cover(150, 150, Position::TopCenter)
    ->optimize();

// Re-encode a JPEG source as WebP — Format enum or string both work
$collection->addNew('webp')
    ->scale(800, 600)
    ->convert(Format::Webp);

// Repeat the same operation type without losing earlier steps
$collection->addNew('effects')
    ->blur(1)
    ->blur(6);

$file = $file->withVariants($collection->toArray());
$file = $imageProcessor->process($file);

Tuning the encoder

$imageProcessor
    ->setQuality(['webp' => 80, 'jpg' => 90, 'avif' => 70]) // per-format
    ->setStripExif(true)            // privacy + smaller files (default)
    ->setPreserveProfile(true)      // keep wide-gamut color rendering (default)
    ->setPreserveAnimation(true);   // animated GIF/WebP keep all frames (default)

setPreserveAnimation(false) flattens animated sources to a single frame — useful for static thumbnail variants or when converting to a non-animated format like JPEG.

Processing a subset of variants

// Per-call filter — does not leak into subsequent process() calls.
$file = $imageProcessor->process($file, ['thumbnail']);

Custom operations

use PhpCollective\Infrastructure\Storage\Processor\Image\Operation\Operation;
use PhpCollective\Infrastructure\Storage\Processor\Image\Operation\OperationRegistry;

$registry = OperationRegistry::default()
    ->register('myFilter', static fn (array $args): Operation => new MyFilter(...$args));

$processor = new ImageProcessor($storage, $pathBuilder, $imageManager, urlBuilder: null, operationRegistry: $registry);

Repeated operations of the same name are preserved in order when you serialize variants with toArray() and rebuild them later with ImageVariantCollection::fromArray().

Documentation

Please start by reading the documentation in the docs/ directory:

Upgrading from 1.x to 2.x

This major release migrates to Intervention Image v4 and replaces the stringly-typed dispatcher with a typed Operation class hierarchy. See the CHANGELOG for the full list; the headline changes:

  • PHP 8.3+ required (was 8.1)
  • Intervention Image v4 required (was v3)
  • processOnlyTheseVariants() / processAll() removed — use the new process($file, ['thumbnail']) per-call filter instead
  • Operations::POSITION_* string constants replaced by the Position enum (Position::Center, Position::TopCenter, …)
  • ImageVariant::FLIP_HORIZONTAL/FLIP_VERTICAL constants replaced by the FlipDirection enum
  • Operations class is gone — operations are now individual classes under src/Operation/ resolved via OperationRegistry
  • flip() now accepts FlipDirection|string (string form still works for config-driven setups)
  • cover() no longer takes a (always-ignored) $callback parameter