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:
@@ -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;
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
src/Drivers/Imagick/Decoders/ImagickImageDecoder.php
Normal file
85
src/Drivers/Imagick/Decoders/ImagickImageDecoder.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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}
|
||||||
*
|
*
|
||||||
|
@@ -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
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user