1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-20 12:41:23 +02:00

Optimize image decoding with Imagick driver

File paths are now read directly via Imagick::readImage() instead of
reading everything with file_get_contents() and passing it to
BinaryImageDecoder.
This commit is contained in:
Oliver Vogel
2024-01-22 16:48:05 +01:00
parent d37e47ba5f
commit d27fdd75e5
6 changed files with 139 additions and 67 deletions

View File

@@ -68,21 +68,29 @@ abstract class AbstractDecoder extends DriverSpecialized implements DecoderInter
} }
/** /**
* Extract and return EXIF data from given image data string * Extract and return EXIF data from given input which can be binary image
* data or a file path.
* *
* @param string $image_data * @param string $path_or_data
* @return CollectionInterface * @return CollectionInterface
*/ */
protected function extractExifData(string $image_data): CollectionInterface protected function extractExifData(string $path_or_data): CollectionInterface
{ {
if (!function_exists('exif_read_data')) { if (!function_exists('exif_read_data')) {
return new Collection(); return new Collection();
} }
try { try {
$pointer = $this->buildFilePointer($image_data); $input = match (true) {
$data = @exif_read_data($pointer, null, true); (strlen($path_or_data) <= PHP_MAXPATHLEN && is_file($path_or_data)) => $path_or_data, // path
fclose($pointer); default => $this->buildFilePointer($path_or_data), // data
};
// extract exif data via file path
$data = @exif_read_data($input, null, true);
if (is_resource($input)) {
fclose($input);
}
} catch (Exception) { } catch (Exception) {
$data = []; $data = [];
} }
@@ -118,7 +126,7 @@ abstract class AbstractDecoder extends DriverSpecialized implements DecoderInter
$result = preg_match($pattern, $input, $matches); $result = preg_match($pattern, $input, $matches);
return new class ($matches, $result) return new class($matches, $result)
{ {
private $matches; private $matches;
private $result; private $result;

View File

@@ -6,17 +6,12 @@ namespace Intervention\Image\Drivers\Imagick\Decoders;
use Imagick; use Imagick;
use ImagickException; use ImagickException;
use Intervention\Image\Drivers\AbstractDecoder;
use Intervention\Image\Drivers\Imagick\Core;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Image;
use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Origin;
class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface class BinaryImageDecoder extends ImagickImageDecoder implements DecoderInterface
{ {
public function decode(mixed $input): ImageInterface|ColorInterface public function decode(mixed $input): ImageInterface|ColorInterface
{ {
@@ -31,59 +26,11 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
throw new DecoderException('Unable to decode input'); throw new DecoderException('Unable to decode input');
} }
// For some JPEG formats, the "coalesceImages()" call leads to an image // decode image
// completely filled with background color. The logic behind this is $image = parent::decode($imagick);
// incomprehensible for me; could be an imagick bug.
if ($imagick->getImageFormat() != 'JPEG') {
$imagick = $imagick->coalesceImages();
}
// fix image orientation // extract exif data
switch ($imagick->getImageOrientation()) { $image->setExif($this->extractExifData($input));
case Imagick::ORIENTATION_TOPRIGHT: // 2
$imagick->flopImage();
break;
case Imagick::ORIENTATION_BOTTOMRIGHT: // 3
$imagick->rotateimage("#000", 180);
break;
case Imagick::ORIENTATION_BOTTOMLEFT: // 4
$imagick->rotateimage("#000", 180);
$imagick->flopImage();
break;
case Imagick::ORIENTATION_LEFTTOP: // 5
$imagick->rotateimage("#000", -270);
$imagick->flopImage();
break;
case Imagick::ORIENTATION_RIGHTTOP: // 6
$imagick->rotateimage("#000", -270);
break;
case Imagick::ORIENTATION_RIGHTBOTTOM: // 7
$imagick->rotateimage("#000", -90);
$imagick->flopImage();
break;
case Imagick::ORIENTATION_LEFTBOTTOM: // 8
$imagick->rotateimage("#000", -90);
break;
}
// set new orientation in image
$imagick->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
$image = new Image(
new Driver(),
new Core($imagick),
$this->extractExifData($input)
);
$image->setOrigin(new Origin(
$imagick->getImageMimeType()
));
return $image; return $image;
} }

View File

@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Decoders; namespace Intervention\Image\Drivers\Imagick\Decoders;
use Exception; use Exception;
use Imagick;
use ImagickException;
use Intervention\Image\Exceptions\DecoderException; use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface; use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterface class FilePathImageDecoder extends ImagickImageDecoder implements DecoderInterface
{ {
public function decode(mixed $input): ImageInterface|ColorInterface public function decode(mixed $input): ImageInterface|ColorInterface
{ {
@@ -30,12 +32,22 @@ class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterfac
throw new DecoderException('Unable to decode input'); throw new DecoderException('Unable to decode input');
} }
try {
$imagick = new Imagick();
$imagick->readImage($input);
} catch (ImagickException) {
throw new DecoderException('Unable to decode input');
}
// decode image // decode image
$image = parent::decode(file_get_contents($input)); $image = parent::decode($imagick);
// set file path on origin // set file path on origin
$image->origin()->setFilePath($input); $image->origin()->setFilePath($input);
// extract exif data
$image->setExif($this->extractExifData($input));
return $image; return $image;
} }
} }

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Decoders;
use Imagick;
use Intervention\Image\Drivers\AbstractDecoder;
use Intervention\Image\Drivers\Imagick\Core;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Image;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Origin;
class ImagickImageDecoder extends AbstractDecoder implements DecoderInterface
{
public function decode(mixed $input): ImageInterface|ColorInterface
{
if (!is_object($input)) {
throw new DecoderException('Unable to decode input');
}
if (!($input instanceof Imagick)) {
throw new DecoderException('Unable to decode input');
}
// For some JPEG formats, the "coalesceImages()" call leads to an image
// completely filled with background color. The logic behind this is
// incomprehensible for me; could be an imagick bug.
if ($input->getImageFormat() != 'JPEG') {
$input = $input->coalesceImages();
}
// fix image orientation
switch ($input->getImageOrientation()) {
case Imagick::ORIENTATION_TOPRIGHT: // 2
$input->flopImage();
break;
case Imagick::ORIENTATION_BOTTOMRIGHT: // 3
$input->rotateimage("#000", 180);
break;
case Imagick::ORIENTATION_BOTTOMLEFT: // 4
$input->rotateimage("#000", 180);
$input->flopImage();
break;
case Imagick::ORIENTATION_LEFTTOP: // 5
$input->rotateimage("#000", -270);
$input->flopImage();
break;
case Imagick::ORIENTATION_RIGHTTOP: // 6
$input->rotateimage("#000", -270);
break;
case Imagick::ORIENTATION_RIGHTBOTTOM: // 7
$input->rotateimage("#000", -90);
$input->flopImage();
break;
case Imagick::ORIENTATION_LEFTBOTTOM: // 8
$input->rotateimage("#000", -90);
break;
}
// set new orientation in image
$input->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
$image = new Image(
new Driver(),
new Core($input)
);
$image->setOrigin(new Origin(
$input->getImageMimeType()
));
return $image;
}
}

View File

@@ -249,6 +249,18 @@ final class Image implements ImageInterface
return is_null($query) ? $this->exif : $this->exif->get($query); return is_null($query) ? $this->exif : $this->exif->get($query);
} }
/**
* {@inheritdoc}
*
* @see ImgageInterface::setExif()
*/
public function setExif(CollectionInterface $exif): ImageInterface
{
$this->exif = $exif;
return $this;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
* *

View File

@@ -144,6 +144,14 @@ interface ImageInterface extends IteratorAggregate, Countable
*/ */
public function exif(?string $query = null): mixed; public function exif(?string $query = null): mixed;
/**
* Set exif data for the image object
*
* @param CollectionInterface $exif
* @return ImageInterface
*/
public function setExif(CollectionInterface $exif): ImageInterface;
/** /**
* Return image resolution/density * Return image resolution/density
* *