From e5d2f40095d41186a8140ce5b98e7308954dc5de Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 16 Jul 2026 17:47:36 +0200 Subject: [PATCH 1/6] Allow Glide v4 next to v3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d84c9a1e0b2..12eb67448de 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "laravel/prompts": "^0.3.0", "league/commonmark": "^2.2", "league/csv": "^9.1", - "league/glide": "^3.0", + "league/glide": "^3.0 || ^4.0", "maennchen/zipstream-php": "^3.1", "michelf/php-smartypants": "^1.8.1", "nesbot/carbon": "^3.0", From fe1525450343fb3aab37a43e0bd67bf5a0ee4f67 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 16 Jul 2026 17:48:34 +0200 Subject: [PATCH 2/6] Add a v3/v4 compatibility layer --- src/Imaging/Intervention.php | 85 ++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/Imaging/Intervention.php diff --git a/src/Imaging/Intervention.php b/src/Imaging/Intervention.php new file mode 100644 index 00000000000..fc2a6f622fb --- /dev/null +++ b/src/Imaging/Intervention.php @@ -0,0 +1,85 @@ + GdDriver::class, + 'imagick' => ImagickDriver::class, + default => $driver, + }; + + return new $class; + } + + public static function manager(?string $driver = null): ImageManager + { + return new ImageManager(static::driver($driver)); + } + + public static function decode(ImageManager $manager, string $contents): ImageInterface + { + return static::isV4() + ? $manager->decodeBinary($contents) + : $manager->read($contents); + } + + public static function create(ImageManager $manager, int $width, int $height): ImageInterface + { + return static::isV4() + ? $manager->createImage($width, $height) + : $manager->create($width, $height); + } + + public static function colorAt(ImageInterface $image, int $x, int $y): ColorInterface + { + return static::isV4() + ? $image->colorAt($x, $y) + : $image->pickColor($x, $y); + } + + public static function fillTransparentAreas(ImageInterface $image, string $color): ImageInterface + { + return static::isV4() + ? $image->fillTransparentAreas($color) + : $image->blendTransparency($color); + } + + public static function encodeByExtension(ImageInterface $image, string $extension, ?int $quality = null): EncodedImageInterface + { + // v4's encoders type the quality as an int, so leave it out entirely rather + // than passing null through and letting the format's default apply. + $options = $quality === null ? [] : ['quality' => $quality]; + + return static::isV4() + ? $image->encodeUsingFileExtension($extension, ...$options) + : $image->encodeByExtension($extension, ...$options); + } +} From 33ab746d52c004a1e15cd8d1cddb738552322e4c Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 16 Jul 2026 17:49:00 +0200 Subject: [PATCH 3/6] Build the image validator's driver directly --- src/Providers/GlideServiceProvider.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Providers/GlideServiceProvider.php b/src/Providers/GlideServiceProvider.php index 87be5dab6f3..e7d341aca63 100644 --- a/src/Providers/GlideServiceProvider.php +++ b/src/Providers/GlideServiceProvider.php @@ -3,7 +3,6 @@ namespace Statamic\Providers; use Illuminate\Support\ServiceProvider; -use Intervention\Image\ImageManager; use League\Glide\Server; use Statamic\Contracts\Imaging\ImageManipulator; use Statamic\Contracts\Imaging\UrlBuilder; @@ -13,6 +12,7 @@ use Statamic\Imaging\GlideUrlBuilder; use Statamic\Imaging\ImageGenerator; use Statamic\Imaging\ImageValidator; +use Statamic\Imaging\Intervention; use Statamic\Imaging\PresetGenerator; use Statamic\Imaging\StaticUrlBuilder; @@ -42,16 +42,8 @@ public function register() ); }); - $this->app->bind(ImageValidator::class, function ($app) { - $driver = config('statamic.assets.image_manipulation.driver', 'gd'); - - $imageManager = match ($driver) { - 'gd' => ImageManager::gd(), - 'imagick' => ImageManager::imagick(), - default => ImageManager::withDriver($driver), - }; - - return new ImageValidator($imageManager->driver()); + $this->app->bind(ImageValidator::class, function () { + return new ImageValidator(Intervention::driver()); }); } From 34509755267a503ed85ef5cb526741473a92c902 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 16 Jul 2026 17:49:52 +0200 Subject: [PATCH 4/6] Use the compatibility layer for the crop processor --- src/CP/Assets/CropProcessor.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/CP/Assets/CropProcessor.php b/src/CP/Assets/CropProcessor.php index b304aea696f..67f3a14b8fe 100644 --- a/src/CP/Assets/CropProcessor.php +++ b/src/CP/Assets/CropProcessor.php @@ -2,7 +2,7 @@ namespace Statamic\CP\Assets; -use Intervention\Image\ImageManager; +use Statamic\Imaging\Intervention; /** * @internal @@ -13,7 +13,7 @@ public function crop(string $contents, string $extension, int $x, int $y, int $w { // Bake in EXIF orientation so the crop coordinates, which come from the // auto-oriented image the user saw in the browser, line up with the source. - $image = $this->manager()->read($contents)->orient(); + $image = Intervention::decode(Intervention::manager(), $contents)->orient(); $x = max(0, min($x, $image->width() - 1)); $y = max(0, min($y, $image->height() - 1)); @@ -25,10 +25,10 @@ public function crop(string $contents, string $extension, int $x, int $y, int $w // JPEG has no alpha channel, so flatten any transparency onto a // background colour rather than letting it default to black. if (in_array(strtolower($extension), ['jpg', 'jpeg'])) { - $image->blendTransparency($background ?? 'ffffff'); + Intervention::fillTransparentAreas($image, $background ?? 'ffffff'); } - return (string) $image->encodeByExtension($extension, quality: $quality ?? self::defaultQuality()); + return (string) Intervention::encodeByExtension($image, $extension, $quality ?? self::defaultQuality()); } public static function defaultQuality(): int @@ -37,15 +37,4 @@ public static function defaultQuality(): int ?? config('statamic.assets.image_manipulation.defaults.quality') ?? 90; } - - protected function manager(): ImageManager - { - $driver = config('statamic.assets.image_manipulation.driver', 'gd'); - - return match ($driver) { - 'gd' => ImageManager::gd(), - 'imagick' => ImageManager::imagick(), - default => ImageManager::withDriver($driver), - }; - } } From b91d9b1f72877b34ff796773b3762c902c7f0c75 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 16 Jul 2026 17:50:05 +0200 Subject: [PATCH 5/6] Tweak tests --- tests/Feature/Assets/CropAssetTest.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/Feature/Assets/CropAssetTest.php b/tests/Feature/Assets/CropAssetTest.php index c369cd8ed54..fc9e526b8bd 100644 --- a/tests/Feature/Assets/CropAssetTest.php +++ b/tests/Feature/Assets/CropAssetTest.php @@ -5,12 +5,12 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Storage; -use Intervention\Image\ImageManager; use PHPUnit\Framework\Attributes\Test; use Statamic\Assets\AssetContainer; use Statamic\CP\Assets\CropProcessor; use Statamic\Events\AssetUploaded; use Statamic\Facades; +use Statamic\Imaging\Intervention; use Tests\FakesRoles; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -308,17 +308,23 @@ private function cropRoute($path) private function makeImage($width, $height) { - return (string) ImageManager::gd()->create($width, $height)->fill('ff0000')->encodeByExtension('jpg'); + $image = Intervention::create(Intervention::manager('gd'), $width, $height)->fill('ff0000'); + + return (string) Intervention::encodeByExtension($image, 'jpg'); } private function makeTransparentImage($width, $height) { - return (string) ImageManager::gd()->create($width, $height)->encodeByExtension('png'); + $image = Intervention::create(Intervention::manager('gd'), $width, $height); + + return (string) Intervention::encodeByExtension($image, 'png'); } private function redChannel($path) { - $hex = ImageManager::gd()->read(Storage::disk('test')->get($path))->pickColor(5, 5)->toHex(); + $image = Intervention::decode(Intervention::manager('gd'), Storage::disk('test')->get($path)); + + $hex = Intervention::colorAt($image, 5, 5)->toHex(); return hexdec(substr($hex, 0, 2)); } From 429f0b8705ab5e045908d4334cc979af55cc90d8 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 16 Jul 2026 12:53:08 -0400 Subject: [PATCH 6/6] Explicitly require intervention/image --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 12eb67448de..d4c50ba3c00 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "composer/semver": "^3.4", "guzzlehttp/guzzle": "^6.3 || ^7.0", "inertiajs/inertia-laravel": "^2.0", + "intervention/image": "^3.9.1 || ^4.0", "james-heinrich/getid3": "^1.9.21", "laravel/framework": "^12.40.0 || ^13.0", "laravel/prompts": "^0.3.0",