diff --git a/composer.json b/composer.json index 8ba4092d3..361dd7e48 100644 --- a/composer.json +++ b/composer.json @@ -22,12 +22,14 @@ "ext-fileinfo": "*", "ext-gd": "*", "ext-mbstring": "*", + "ext-ffi": "*", "benjaminhoegh/parsedown-toc": "^1.5", "cecil/resource-watcher": "^4.0", "cocur/slugify": "^4.6", "dflydev/dot-access-data": "^3.0", "erusev/parsedown-extra": "^0.8", "intervention/image": "^3.11", + "intervention/image-driver-vips": "^1.0", "james-heinrich/getid3": "^1.9", "laravel-zero/phar-updater": "^1.4", "matthiasmullie/minify": "^1.3", @@ -79,7 +81,8 @@ "ergebnis/composer-normalize": true }, "platform": { - "php": "8.1.32" + "php": "8.1.32", + "ext-ffi": "3.4.5" }, "sort-packages": true }, diff --git a/composer.lock b/composer.lock index 3b298dbc7..f5e0772da 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "426abc751976db888fb6c16e8581e5cd", + "content-hash": "e89ecd37987479faf7b78f78a70d8577", "packages": [ { "name": "benjaminhoegh/parsedown-toc", @@ -531,6 +531,80 @@ ], "time": "2025-07-30T13:13:19+00:00" }, + { + "name": "intervention/image-driver-vips", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image-driver-vips.git", + "reference": "67f92c78cbe94a303f28d7ed84afd464eefac7aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image-driver-vips/zipball/67f92c78cbe94a303f28d7ed84afd464eefac7aa", + "reference": "67f92c78cbe94a303f28d7ed84afd464eefac7aa", + "shasum": "" + }, + "require": { + "intervention/image": "^3.11.0", + "jcupitt/vips": "^2.4", + "php": "^8.1" + }, + "require-dev": { + "ext-fileinfo": "*", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "slevomat/coding-standard": "~8.0", + "squizlabs/php_codesniffer": "^3.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Intervention\\Image\\Drivers\\Vips\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + }, + { + "name": "Thomas Picquet", + "email": "thomas@sctr.net" + } + ], + "description": "libvips driver for Intervention Image", + "homepage": "https://image.intervention.io/", + "keywords": [ + "image", + "libvips", + "vips" + ], + "support": { + "issues": "https://github.com/Intervention/image-driver-vips/issues", + "source": "https://github.com/Intervention/image-driver-vips/tree/1.0.6" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + }, + { + "url": "https://ko-fi.com/interventionphp", + "type": "ko_fi" + } + ], + "time": "2025-06-10T14:30:21+00:00" + }, { "name": "james-heinrich/getid3", "version": "v1.9.23", @@ -598,6 +672,67 @@ }, "time": "2023-10-19T13:18:49+00:00" }, + { + "name": "jcupitt/vips", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/libvips/php-vips.git", + "reference": "a54c1cceea581b592a199edd61a7c06f44a24c08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/libvips/php-vips/zipball/a54c1cceea581b592a199edd61a7c06f44a24c08", + "reference": "a54c1cceea581b592a199edd61a7c06f44a24c08", + "shasum": "" + }, + "require": { + "ext-ffi": "*", + "php": ">=7.4", + "psr/log": "^1.1.3|^2.0|^3.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpdocumentor/shim": "^3.3", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jcupitt\\Vips\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Cupitt", + "email": "jcupitt@gmail.com", + "homepage": "https://github.com/jcupitt", + "role": "Developer" + } + ], + "description": "A high-level interface to the libvips image processing library.", + "homepage": "https://github.com/libvips/php-vips", + "keywords": [ + "image", + "libvips", + "processing" + ], + "support": { + "issues": "https://github.com/libvips/php-vips/issues", + "source": "https://github.com/libvips/php-vips/tree/v2.5.0" + }, + "time": "2025-04-04T17:10:13+00:00" + }, { "name": "laravel-zero/phar-updater", "version": "v1.4.0", @@ -9019,11 +9154,13 @@ "php": "^8.1||^8.2||^8.3||^8.4", "ext-fileinfo": "*", "ext-gd": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-ffi": "*" }, "platform-dev": {}, "platform-overrides": { - "php": "8.1.32" + "php": "8.1.32", + "ext-ffi": "3.4.5" }, "plugin-api-version": "2.6.0" } diff --git a/src/Asset/Image.php b/src/Asset/Image.php index 7c6c42906..bd08d13bd 100644 --- a/src/Asset/Image.php +++ b/src/Asset/Image.php @@ -17,6 +17,7 @@ use Cecil\Exception\RuntimeException; use Intervention\Image\Drivers\Gd\Driver as GdDriver; use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver; +use Intervention\Image\Drivers\Vips\Driver as VipsDriver; use Intervention\Image\Encoders\AutoEncoder; use Intervention\Image\ImageManager; @@ -27,24 +28,34 @@ * and generating data URLs. * * This class uses the Intervention Image library to handle image processing. - * It supports both GD and Imagick drivers, depending on the available PHP extensions. + * It supports GD, Imagick and Vips drivers, depending on the available PHP extensions. */ class Image { /** - * Create new manager instance with available driver. + * Create new manager instance with available driver, faster first. */ private static function manager(): ImageManager { $driver = null; - // ImageMagick is available? (for a future quality option) - if (\extension_loaded('imagick') && class_exists('Imagick')) { - $driver = ImagickDriver::class; + // use Imagick for better quality + /*if (\extension_loaded('imagick') && class_exists('Imagick')) { + $driver = new ImagickDriver(); + }*/ + // libvips is the fastest driver (if FFI is available) + if (\extension_loaded('ffi')) { + ini_set('ffi.enable', 'true'); + $driver = new VipsDriver(); + try { + $driver->checkHealth(); + } catch (\Intervention\Image\Exceptions\DriverException) { + $driver = null; + } } - // Use GD, because it's the faster driver - if (\extension_loaded('gd') && \function_exists('gd_info')) { - $driver = GdDriver::class; + // GD by default + if ($driver === null && \extension_loaded('gd') && \function_exists('gd_info')) { + $driver = new GdDriver(); } if ($driver) { @@ -59,7 +70,7 @@ private static function manager(): ImageManager ); } - throw new RuntimeException('PHP GD (or Imagick) extension is required.'); + throw new RuntimeException('PHP GD extension is required.'); } /** @@ -213,8 +224,15 @@ public static function getDominantColor(Asset $asset): string $image = self::manager()->read(self::resize($asset, 100, 50)); return $image->reduceColors(1)->pickColor(0, 0)->toString(); - } catch (\Exception $e) { - throw new RuntimeException(\sprintf('Can\'t get dominant color of "%s": %s', $asset['path'], $e->getMessage())); + } catch (\Exception) { + // fallback to GD driver + try { + $image = ImageManager::withDriver(GdDriver::class)->read(self::resize($asset, 100, 50)); + + return $image->reduceColors(1)->pickColor(0, 0)->toString(); + } catch (\Exception $e) { + throw new RuntimeException(\sprintf('Can\'t get dominant color of "%s": %s', $asset['path'], $e->getMessage())); + } } }