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 17, 2024
1 parent 85402b1 commit de44096
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 432 deletions.
15 changes: 15 additions & 0 deletions _config/image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,18 @@ 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:
constructor:
driver: '%$InterventionImageDriver'

---
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;
}
}
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
104 changes: 46 additions & 58 deletions src/Image_Backend.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,42 +36,36 @@ interface Image_Backend
public function __construct(AssetContainer $assetContainer = null);

/**
* @return int The width of the image
* Get the width of the image
*/
public function getWidth();
public function getWidth(): int;

/**
* @return int The height of the image
* Get the height of the image
*/
public function getHeight();
public function getHeight(): int;

/**
* Populate the backend with a given object
*
* @param AssetContainer $assetContainer Object to load from
*/
public function loadFromContainer(AssetContainer $assetContainer);
public function loadFromContainer(AssetContainer $assetContainer): static;

/**
* Populate the backend from a local path
*
* @param string $path
*/
public function loadFrom($path);
public function loadFrom(string $path): static;

/**
* Get the currently assigned image resource
*
* @return mixed
*/
public function getImageResource();
public function getImageResource(): mixed;

/**
* Set the currently assigned image resource
*
* @param mixed $resource
*/
public function setImageResource($resource);
public function setImageResource($resource): static;

/**
* Write to the given asset store
Expand All @@ -84,87 +78,81 @@ public function setImageResource($resource);
* @return array Tuple associative array (Filename, Hash, Variant) Unless storing a variant, the hash
* will be calculated from the given data.
*/
public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = []);
public function writeToStore(AssetStore $assetStore, string $filename, ?string $hash = null, ?string $variant = null, array $config = []): array;

/**
* Write the backend to a local path
*
* @param string $path
* @return bool if the write was successful
*/
public function writeTo($path);
public function writeTo(string $path): bool;

/**
* Set the quality to a value between 0 and 100
*
* @param int $quality
*/
public function setQuality($quality);
public function setQuality(int $quality): static;

/**
* Get the current quality (between 0 and 100).
*/
public function getQuality(): int;

/**
* Resize an image, skewing it as necessary.
*
* @param int $width
* @param int $height
* @return static
*/
public function resize($width, $height);
public function resize(int $width, int $height): ?static;

/**
* Resize the image by preserving aspect ratio. By default, it will keep the image inside the maxWidth
* and maxHeight. Passing useAsMinimum will make the smaller dimension equal to the maximum corresponding dimension
*
* @param int $width
* @param int $height
* @param bool $useAsMinimum If true, image will be sized outside of these dimensions.
* If false (default) image will be sized inside these dimensions.
* @return static
* Resize the image by preserving aspect ratio. By default, the image cannot be resized to be larger
* than its current size.
* Passing true to useAsMinimum will allow the image to be scaled up.
*/
public function resizeRatio($width, $height, $useAsMinimum = false);
public function resizeRatio(int $width, int $height, bool $useAsMinimum = false): ?static;

/**
* Resize an image by width. Preserves aspect ratio.
*
* @param int $width
* @return static
*/
public function resizeByWidth($width);
public function resizeByWidth(int $width): ?static;

/**
* Resize an image by height. Preserves aspect ratio.
*
* @param int $height
* @return static
*/
public function resizeByHeight($height);
public function resizeByHeight(int $height): ?static;

/**
* Return a clone of this image resized, with space filled in with the given colour
*
* @param int $width
* @param int $height
* @param string $backgroundColor
* @param int $transparencyPercent
* @return static
* Return a clone of this image resized, with space filled in with the given colour.
*/
public function paddedResize($width, $height, $backgroundColor = "FFFFFF", $transparencyPercent = 0);
public function paddedResize(string $width, string $height, string $backgroundColour = 'FFFFFF', int $transparencyPercent = 0): ?static;

/**
* Resize an image to cover the given width/height completely, and crop off any overhanging edges.
*
* @param int $width
* @param int $height
* @return static
*/
public function croppedResize($width, $height);
public function croppedResize(int $width, int $height, string $position = 'center'): ?static;

/**
* Crop's part of image.
* @param int $top y position of left upper corner of crop rectangle
* @param int $left x position of left upper corner of crop rectangle
* @param int $top Amount of pixels the cutout will be moved on the y (vertical) axis
* @param int $left Amount of pixels the cutout will be moved on the x (horizontal) axis
* @param int $width rectangle width
* @param int $height rectangle height
* @return Image_Backend
* @param string $position Postion at which the cutout will be aligned
* @param string $backgroundColour Colour to fill any newly created areas
*/
public function crop(int $top, int $left, int $width, int $height, string $position, string $backgroundColour = 'FFFFFF'): ?static;

/**
* Check if the image is animated (e.g. an animated GIF).
*/
public function getIsAnimated(): bool;

/**
* Discards all animation frames of the current image instance except the one at the given position. Turns an animated image into a static one.
*
* @param integer|string $position Which frame to use as the still image.
* If an integer is passed, it represents the exact frame number to use (starting at 0). If that frame doesn't exist, an exception is thrown.
* If a string is passed, it must be in the form of a percentage (e.g. '0%' or '50%'). The frame to use is then determined based
* on this percentage (e.g. if '50%' is passed, a frame halfway through the animation is used).
*/
public function crop($top, $left, $width, $height);
public function removeAnimation(int|string $position): ?static;
}
Loading

0 comments on commit de44096

Please sign in to comment.