mirror of
https://github.com/Intervention/image.git
synced 2025-01-17 12:18:14 +01:00
Refactor image decoding process of GD driver
This commit is contained in:
parent
ef8af613fb
commit
03a59283c3
@ -81,15 +81,15 @@ abstract class AbstractDecoder extends DriverSpecialized implements DecoderInter
|
||||
}
|
||||
|
||||
try {
|
||||
$input = match (true) {
|
||||
$source = 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);
|
||||
$data = @exif_read_data($source, null, true);
|
||||
if (is_resource($source)) {
|
||||
fclose($source);
|
||||
}
|
||||
} catch (Exception) {
|
||||
$data = [];
|
||||
@ -98,6 +98,26 @@ abstract class AbstractDecoder extends DriverSpecialized implements DecoderInter
|
||||
return new Collection(is_array($data) ? $data : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust image rotation of given image according to the exif data
|
||||
*
|
||||
* @param ImageInterface $image
|
||||
* @return ImageInterface
|
||||
*/
|
||||
protected function adjustImageRotation(ImageInterface $image): ImageInterface
|
||||
{
|
||||
return match ($image->exif('IFD0.Orientation')) {
|
||||
2 => $image->flop(),
|
||||
3 => $image->rotate(180),
|
||||
4 => $image->rotate(180)->flop(),
|
||||
5 => $image->rotate(270)->flop(),
|
||||
6 => $image->rotate(270),
|
||||
7 => $image->rotate(90)->flop(),
|
||||
8 => $image->rotate(90),
|
||||
default => $image
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if given input is base64 encoded data
|
||||
*
|
||||
@ -126,7 +146,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;
|
||||
|
@ -9,30 +9,43 @@ use Intervention\Image\Drivers\Gd\Frame;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Gif\Decoder as GifDecoder;
|
||||
use Intervention\Gif\Splitter as GifSplitter;
|
||||
use Intervention\Image\Drivers\Gd\Core;
|
||||
use Intervention\Image\Drivers\Gd\Decoders\Traits\CanDecodeGif;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Image;
|
||||
use Intervention\Image\Origin;
|
||||
|
||||
class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDecodeGif;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface|ColorInterface
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
if ($this->isGifFormat($input)) {
|
||||
return $this->decodeGif($input); // decode (animated) gif
|
||||
}
|
||||
$image = match ($this->isGifFormat($input)) {
|
||||
true => $this->decodeGif($input),
|
||||
default => $this->decodeBinary($input),
|
||||
};
|
||||
|
||||
return $this->decodeString($input);
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function decodeString(string $input): ImageInterface
|
||||
/**
|
||||
* Decode image from given binary data
|
||||
*
|
||||
* @param string $input
|
||||
* @return ImageInterface
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeBinary(string $input): ImageInterface
|
||||
{
|
||||
$gd = @imagecreatefromstring($input);
|
||||
|
||||
@ -54,49 +67,12 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
|
||||
$this->extractExifData($input)
|
||||
);
|
||||
|
||||
// set mediaType on origin
|
||||
if ($info = getimagesizefromstring($input)) {
|
||||
$image->setOrigin(
|
||||
new Origin($info['mime'])
|
||||
);
|
||||
$image->origin()->setMediaType($info['mime']);
|
||||
}
|
||||
|
||||
// fix image orientation
|
||||
return match ($image->exif('IFD0.Orientation')) {
|
||||
2 => $image->flop(),
|
||||
3 => $image->rotate(180),
|
||||
4 => $image->rotate(180)->flop(),
|
||||
5 => $image->rotate(270)->flop(),
|
||||
6 => $image->rotate(270),
|
||||
7 => $image->rotate(90)->flop(),
|
||||
8 => $image->rotate(90),
|
||||
default => $image
|
||||
};
|
||||
}
|
||||
|
||||
private function decodeGif(string $input): ImageInterface
|
||||
{
|
||||
$gif = GifDecoder::decode($input);
|
||||
|
||||
if (!$gif->isAnimated()) {
|
||||
return $this->decodeString($input);
|
||||
}
|
||||
|
||||
$splitter = GifSplitter::create($gif)->split();
|
||||
$delays = $splitter->getDelays();
|
||||
|
||||
// build core
|
||||
$core = new Core();
|
||||
$core->setLoops($gif->getMainApplicationExtension()?->getLoops());
|
||||
foreach ($splitter->coalesceToResources() as $key => $data) {
|
||||
$core->push(
|
||||
(new Frame($data))->setDelay($delays[$key] / 100)
|
||||
);
|
||||
}
|
||||
|
||||
$image = new Image(new Driver(), $core);
|
||||
|
||||
return $image->setOrigin(
|
||||
new Origin('image/gif')
|
||||
);
|
||||
return $this->adjustImageRotation($image);
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,16 @@ declare(strict_types=1);
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Exception;
|
||||
use Intervention\Image\Drivers\Gd\Decoders\Traits\CanDecodeGif;
|
||||
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 GdImageDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDecodeGif;
|
||||
|
||||
public function decode(mixed $input): ImageInterface|ColorInterface
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
@ -30,12 +33,58 @@ class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterfac
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
// decode image
|
||||
$image = parent::decode(file_get_contents($input));
|
||||
// detect media (mime) type
|
||||
$mediaType = $this->getMediaTypeByFilePath($input);
|
||||
|
||||
// set file path on origin
|
||||
// gif files might be animated and therefore cannot be handled by the standard GD decoder.
|
||||
$image = match ($mediaType) {
|
||||
'image/gif' => $this->decodeGif($input),
|
||||
default => parent::decode(match ($mediaType) {
|
||||
'image/jpeg', 'image/jpg', 'image/pjpeg' => imagecreatefromjpeg($input),
|
||||
'image/webp', 'image/x-webp' => imagecreatefromwebp($input),
|
||||
'image/png', 'image/x-png' => imagecreatefrompng($input),
|
||||
'image/avif', 'image/x-avif' => imagecreatefromavif($input),
|
||||
'image/bmp',
|
||||
'image/ms-bmp',
|
||||
'image/x-bitmap',
|
||||
'image/x-bmp',
|
||||
'image/x-ms-bmp',
|
||||
'image/x-win-bitmap',
|
||||
'image/x-windows-bmp',
|
||||
'image/x-xbitmap' => imagecreatefrombmp($input),
|
||||
default => throw new DecoderException('Unable to decode input'),
|
||||
}),
|
||||
};
|
||||
|
||||
// set file path & mediaType on origin
|
||||
$image->origin()->setFilePath($input);
|
||||
$image->origin()->setMediaType($mediaType);
|
||||
|
||||
return $image;
|
||||
// extract exif
|
||||
$image->setExif($this->extractExifData($input));
|
||||
|
||||
// fix image orientation
|
||||
return $this->adjustImageRotation($image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media (mime) type of the file at given file path
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return string
|
||||
*/
|
||||
private function getMediaTypeByFilePath(string $filepath): string
|
||||
{
|
||||
$info = getimagesize($filepath);
|
||||
|
||||
if (!is_array($info)) {
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
if (!array_key_exists('mime', $info)) {
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
return $info['mime'];
|
||||
}
|
||||
}
|
||||
|
43
src/Drivers/Gd/Decoders/GdImageDecoder.php
Normal file
43
src/Drivers/Gd/Decoders/GdImageDecoder.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use GdImage;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Drivers\Gd\Core;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\Drivers\Gd\Frame;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Image;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
class GdImageDecoder extends AbstractDecoder
|
||||
{
|
||||
public function decode(mixed $input): ImageInterface|ColorInterface
|
||||
{
|
||||
if (!is_object($input)) {
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
if (!($input instanceof GdImage)) {
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
if (!imageistruecolor($input)) {
|
||||
imagepalettetotruecolor($input);
|
||||
}
|
||||
|
||||
imagesavealpha($input, true);
|
||||
|
||||
// build image instance
|
||||
return new Image(
|
||||
new Driver(),
|
||||
new Core([
|
||||
new Frame($input)
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
46
src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
Normal file
46
src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders\Traits;
|
||||
|
||||
use Intervention\Gif\Decoder as GifDecoder;
|
||||
use Intervention\Gif\Splitter as GifSplitter;
|
||||
use Intervention\Image\Drivers\Gd\Core;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\Drivers\Gd\Frame;
|
||||
use Intervention\Image\Image;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
trait CanDecodeGif
|
||||
{
|
||||
/**
|
||||
* Decode image from given GIF source which can be either a file path or binary data
|
||||
*
|
||||
* @param mixed $input
|
||||
* @return ImageInterface
|
||||
*/
|
||||
protected function decodeGif(mixed $input): ImageInterface
|
||||
{
|
||||
$gif = GifDecoder::decode($input);
|
||||
$splitter = GifSplitter::create($gif)->split();
|
||||
$delays = $splitter->getDelays();
|
||||
|
||||
// build core
|
||||
$core = new Core();
|
||||
|
||||
// set loops on core
|
||||
if ($loops = $gif->getMainApplicationExtension()?->getLoops()) {
|
||||
$core->setLoops($loops);
|
||||
}
|
||||
|
||||
// add GDImage instances to core
|
||||
foreach ($splitter->coalesceToResources() as $key => $native) {
|
||||
$core->push(
|
||||
(new Frame($native))->setDelay($delays[$key] / 100)
|
||||
);
|
||||
}
|
||||
|
||||
return new Image(new Driver(), $core);
|
||||
}
|
||||
}
|
@ -37,6 +37,19 @@ class Origin
|
||||
return $this->mediaType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set media type of current instance
|
||||
*
|
||||
* @param string $type
|
||||
* @return Origin
|
||||
*/
|
||||
public function setMediaType(string $type): self
|
||||
{
|
||||
$this->mediaType = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return file path of origin
|
||||
*
|
||||
|
@ -8,16 +8,6 @@ use Intervention\Image\Origin;
|
||||
|
||||
class OriginTest extends TestCase
|
||||
{
|
||||
public function testMediaType(): void
|
||||
{
|
||||
$origin = new Origin();
|
||||
$this->assertEquals('application/octet-stream', $origin->mediaType());
|
||||
|
||||
$origin = new Origin('image/gif');
|
||||
$this->assertEquals('image/gif', $origin->mediaType());
|
||||
$this->assertEquals('image/gif', $origin->mimetype());
|
||||
}
|
||||
|
||||
public function testFilePath(): void
|
||||
{
|
||||
$origin = new Origin('image/jpeg', __DIR__ . '/tests/images/example.jpg');
|
||||
@ -32,4 +22,17 @@ class OriginTest extends TestCase
|
||||
$origin = new Origin('image/jpeg');
|
||||
$this->assertEquals('', $origin->fileExtension());
|
||||
}
|
||||
|
||||
public function testSetGetMediaType(): void
|
||||
{
|
||||
$origin = new Origin();
|
||||
$this->assertEquals('application/octet-stream', $origin->mediaType());
|
||||
|
||||
$origin = new Origin('image/gif');
|
||||
$this->assertEquals('image/gif', $origin->mediaType());
|
||||
$this->assertEquals('image/gif', $origin->mimetype());
|
||||
$result = $origin->setMediaType('image/jpeg');
|
||||
$this->assertEquals('image/jpeg', $origin->mediaType());
|
||||
$this->assertEquals('image/jpeg', $result->mediaType());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user