diff --git a/src/Drivers/AbstractDecoder.php b/src/Drivers/AbstractDecoder.php index 41e4ee07..52188bbe 100644 --- a/src/Drivers/AbstractDecoder.php +++ b/src/Drivers/AbstractDecoder.php @@ -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; diff --git a/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php b/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php index 07679e5d..cd99ad43 100644 --- a/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php +++ b/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php @@ -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; } diff --git a/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php b/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php index 687a2375..7d3d2274 100644 --- a/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php +++ b/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php @@ -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; } } diff --git a/src/Drivers/Imagick/Decoders/ImagickImageDecoder.php b/src/Drivers/Imagick/Decoders/ImagickImageDecoder.php new file mode 100644 index 00000000..ec3f30a2 --- /dev/null +++ b/src/Drivers/Imagick/Decoders/ImagickImageDecoder.php @@ -0,0 +1,85 @@ +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; + } +} diff --git a/src/Image.php b/src/Image.php index 75103904..60968ac5 100644 --- a/src/Image.php +++ b/src/Image.php @@ -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} * diff --git a/src/Interfaces/ImageInterface.php b/src/Interfaces/ImageInterface.php index 4694e524..41633a5e 100644 --- a/src/Interfaces/ImageInterface.php +++ b/src/Interfaces/ImageInterface.php @@ -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 *