1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-19 12:11:26 +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
*/
protected function extractExifData(string $image_data): CollectionInterface
protected function extractExifData(string $path_or_data): CollectionInterface
{
if (!function_exists('exif_read_data')) {
return new Collection();
}
try {
$pointer = $this->buildFilePointer($image_data);
$data = @exif_read_data($pointer, null, true);
fclose($pointer);
$input = match (true) {
(strlen($path_or_data) <= PHP_MAXPATHLEN && is_file($path_or_data)) => $path_or_data, // path
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) {
$data = [];
}
@@ -118,7 +126,7 @@ abstract class AbstractDecoder extends DriverSpecialized implements DecoderInter
$result = preg_match($pattern, $input, $matches);
return new class ($matches, $result)
return new class($matches, $result)
{
private $matches;
private $result;

View File

@@ -6,17 +6,12 @@ namespace Intervention\Image\Drivers\Imagick\Decoders;
use Imagick;
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\Image;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface;
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
{
@@ -31,59 +26,11 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
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 ($imagick->getImageFormat() != 'JPEG') {
$imagick = $imagick->coalesceImages();
}
// decode image
$image = parent::decode($imagick);
// fix image orientation
switch ($imagick->getImageOrientation()) {
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()
));
// extract exif data
$image->setExif($this->extractExifData($input));
return $image;
}

View File

@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace Intervention\Image\Drivers\Imagick\Decoders;
use Exception;
use Imagick;
use ImagickException;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterface
class FilePathImageDecoder extends ImagickImageDecoder implements DecoderInterface
{
public function decode(mixed $input): ImageInterface|ColorInterface
{
@@ -30,12 +32,22 @@ class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterfac
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
$image = parent::decode(file_get_contents($input));
$image = parent::decode($imagick);
// set file path on origin
$image->origin()->setFilePath($input);
// extract exif data
$image->setExif($this->extractExifData($input));
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);
}
/**
* {@inheritdoc}
*
* @see ImgageInterface::setExif()
*/
public function setExif(CollectionInterface $exif): ImageInterface
{
$this->exif = $exif;
return $this;
}
/**
* {@inheritdoc}
*

View File

@@ -144,6 +144,14 @@ interface ImageInterface extends IteratorAggregate, Countable
*/
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
*