Skip to content

Commit

Permalink
DEP Upgrade to intervention/image 3
Browse files Browse the repository at this point in the history
Also add strict typing to Image_Backend and InterventionBackend.
  • Loading branch information
GuySartorelli committed Jul 23, 2024
1 parent e5b927b commit 04e0016
Show file tree
Hide file tree
Showing 17 changed files with 359 additions and 439 deletions.
16 changes: 16 additions & 0 deletions _config/image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,19 @@ SilverStripe\Core\Injector\Injector:
SilverStripe\Assets\Image_Backend:
class: SilverStripe\Assets\InterventionBackend
factory: '%$SilverStripe\Assets\ImageBackendFactory'
InterventionImageDriver:
class: 'Intervention\Image\Drivers\Gd\Driver'
Intervention\Image\ImageManager:
factory: '%$SilverStripe\Assets\InterventionManagerFactory'
constructor:
autoOrientation: true

---
Name: assetsimage-imagick
After: '#assetsimage'
Only:
extensionloaded: imagick
---
SilverStripe\Core\Injector\Injector:
InterventionImageDriver:
class: 'Intervention\Image\Drivers\Imagick\Driver'
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"silverstripe/framework": "^6",
"silverstripe/vendor-plugin": "^2",
"symfony/filesystem": "^6.1",
"intervention/image": "^2.7.2",
"intervention/image": "^3.7",
"league/flysystem": "^3.9.0"
},
"require-dev": {
Expand Down
111 changes: 8 additions & 103 deletions src/Conversion/InterventionImageFileConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

namespace SilverStripe\Assets\Conversion;

use Imagick;
use Intervention\Image\Exception\ImageException;
use Intervention\Image\Exceptions\RuntimeException;
use SilverStripe\Assets\File;
use SilverStripe\Assets\Image_Backend;
use SilverStripe\Assets\InterventionBackend;
Expand Down Expand Up @@ -31,7 +30,9 @@ public function supportsConversion(string $fromExtension, string $toExtension, a
if (!is_a($backend, InterventionBackend::class)) {
return false;
}
return $this->supportedByIntervention($fromExtension, $backend) && $this->supportedByIntervention($toExtension, $backend);
/** @var InterventionBackend $backend */
$driver = $backend->getImageManager()->driver();
return $driver->supports($fromExtension) && $driver->supports($toExtension);
}

public function convert(DBFile|File $from, string $toExtension, array $options = []): DBFile
Expand All @@ -46,7 +47,9 @@ public function convert(DBFile|File $from, string $toExtension, array $options =
$actualClass = $originalBackend ? get_class($originalBackend) : 'null';
throw new FileConverterException("ImageBackend must be an instance of InterventionBackend. Got $actualClass");
}
if (!$this->supportedByIntervention($toExtension, $originalBackend)) {
/** @var InterventionBackend $originalBackend */
$driver = $originalBackend->getImageManager()->driver();
if (!$driver->supports($toExtension)) {
throw new FileConverterException("Convertion to format '$toExtension' is not suported.");
}

Expand All @@ -66,7 +69,7 @@ function (AssetStore $store, string $filename, string $hash, string $variant) us
return [$tuple, $backend];
}
);
} catch (ImageException $e) {
} catch (RuntimeException $e) {
throw new FileConverterException('Failed to convert: ' . $e->getMessage(), $e->getCode(), $e);
}
// This is very unlikely but the API for `manipulateExtension()` allows for it
Expand All @@ -90,102 +93,4 @@ private function validateOptions(array $options): array
}
return $problems;
}

private function supportedByIntervention(string $format, InterventionBackend $backend): bool
{
$driver = $backend->getImageManager()->config['driver'] ?? null;

// Return early for empty values - we obviously can't support that
if ($format === '') {
return false;
}

$format = strtolower($format);

// If the driver is somehow not GD or Imagick, we have no way to know what it might support
if ($driver !== 'gd' && $driver !== 'imagick') {
$supported = false;
$this->extend('updateSupportedByIntervention', $supported, $format, $driver);
return $supported;
}

// GD and Imagick support different things.
// This follows the logic in intervention's AbstractEncoder::process() method
// and the various methods in the Encoder classes for GD and Imagick,
// excluding checking for strings that were obviously mimetypes
switch ($format) {
case 'gif':
// always supported
return true;
case 'png':
// always supported
return true;
case 'jpg':
case 'jpeg':
case 'jfif':
// always supported
return true;
case 'tif':
case 'tiff':
if ($driver === 'gd') {
false;
}
// always supported by imagick
return true;
case 'bmp':
case 'ms-bmp':
case 'x-bitmap':
case 'x-bmp':
case 'x-ms-bmp':
case 'x-win-bitmap':
case 'x-windows-bmp':
case 'x-xbitmap':
if ($driver === 'gd' && !function_exists('imagebmp')) {
return false;
}
// always supported by imagick
return true;
case 'ico':
if ($driver === 'gd') {
return false;
}
// always supported by imagick
return true;
case 'psd':
if ($driver === 'gd') {
return false;
}
// always supported by imagick
return true;
case 'webp':
if ($driver === 'gd' && !function_exists('imagewebp')) {
return false;
}
if ($driver === 'imagick' && !Imagick::queryFormats('WEBP')) {
return false;
}
return true;
case 'avif':
if ($driver === 'gd' && !function_exists('imageavif')) {
return false;
}
if ($driver === 'imagick' && !Imagick::queryFormats('AVIF')) {
return false;
}
return true;
case 'heic':
if ($driver === 'gd') {
return false;
}
if ($driver === 'imagick' && !Imagick::queryFormats('HEIC')) {
return false;
}
return true;
default:
// Anything else is not supported
return false;
}
// This should never be reached, but return false if it is
return false;
}
}
36 changes: 29 additions & 7 deletions src/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ class Image extends File
*/
private static $lazy_loading_enabled = true;

/**
* Determine whether the image generated by PreviewLink() is allowed to be animated or not.
* Used in asset admin, upload field, and WYSIWYG.
*/
private static bool $allow_animated_preview = false;

public function __construct($record = null, $isSingleton = false, $queryParams = [])
{
parent::__construct($record, $isSingleton, $queryParams);
Expand Down Expand Up @@ -99,13 +105,29 @@ public function PreviewLink($action = null)
}

// Size to width / height
$width = (int)$this->config()->get('asset_preview_width');
$height = (int)$this->config()->get('asset_preview_height');
$resized = $this->FitMax($width, $height);
if ($resized && $resized->exists()) {
$link = $resized->getAbsoluteURL();
} else {
$link = $this->getIcon();
$width = (int)static::config()->get('asset_preview_width');
$height = (int)static::config()->get('asset_preview_height');

// Temporarily disallow animated manipulations if necessary
$backend = $this->getImageBackend();
$origAllowAnimation = $backend->getAllowsAnimationInManipulations();
if ($origAllowAnimation && !static::config()->get('allow_animated_preview')) {
$backend->setAllowsAnimationInManipulations(false);
}

try {
// Get link for preview
$resized = $this->FitMax($width, $height);
if ($resized && $resized->exists()) {
$link = $resized->getAbsoluteURL();
} else {
$link = $this->getIcon();
}
} finally {
// Reset original value
if ($origAllowAnimation !== null) {
$backend->setAllowsAnimationInManipulations($origAllowAnimation);
}
}
$this->extend('updatePreviewLink', $link, $action);
return $link;
Expand Down
23 changes: 23 additions & 0 deletions src/ImageManipulation.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,29 @@ public function Fill($width, $height)
});
}

/**
* Check if the image is animated (e.g. an animated GIF).
*/
public function getIsAnimated(): bool
{
$backend = $this->getImageBackend();
if (!$backend) {
return false;
}
return $backend->getIsAnimated();
}

public function RemoveAnimation(int|string $position = 0): ?AssetContainer
{
if (!$this->getIsAnimated()) {
return $this;
}
$variant = $this->variantName(__FUNCTION__, $position);
return $this->manipulateImage($variant, function (Image_Backend $backend) use ($position) {
return $backend->removeAnimation($position);
});
}

/**
* Set the quality of the resampled image
*
Expand Down
Loading

0 comments on commit 04e0016

Please sign in to comment.