mirror of
https://github.com/Intervention/image.git
synced 2025-08-30 09:10:21 +02:00
Merge branch 'feature/decoding-optimization' into develop
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4"
|
||||
"intervention/gif": "^4.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
|
@@ -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);
|
||||
$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
|
||||
$data = @exif_read_data($source, null, true);
|
||||
if (is_resource($source)) {
|
||||
fclose($source);
|
||||
}
|
||||
} catch (Exception) {
|
||||
$data = [];
|
||||
}
|
||||
|
53
src/Drivers/Gd/Decoders/AbstractDecoder.php
Normal file
53
src/Drivers/Gd/Decoders/AbstractDecoder.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractDecoder as GenericAbstractDecoder;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
|
||||
abstract class AbstractDecoder extends GenericAbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Return media (mime) type of the file at given file path
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return string
|
||||
*/
|
||||
protected 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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media (mime) type of the given image data
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
protected function getMediaTypeByBinary(string $data): string
|
||||
{
|
||||
$info = getimagesizefromstring($data);
|
||||
|
||||
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'];
|
||||
}
|
||||
}
|
@@ -4,35 +4,48 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
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;
|
||||
use Intervention\Image\Modifiers\AlignRotationModifier;
|
||||
|
||||
class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
|
||||
class BinaryImageDecoder extends GdImageDecoder 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);
|
||||
|
||||
@@ -40,63 +53,23 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
|
||||
throw new DecoderException('Unable to decode input');
|
||||
}
|
||||
|
||||
if (!imageistruecolor($gd)) {
|
||||
imagepalettetotruecolor($gd);
|
||||
}
|
||||
imagesavealpha($gd, true);
|
||||
// create image instance
|
||||
$image = parent::decode($gd);
|
||||
|
||||
// build image instance
|
||||
$image = new Image(
|
||||
new Driver(),
|
||||
new Core([
|
||||
new Frame($gd)
|
||||
]),
|
||||
$this->extractExifData($input)
|
||||
);
|
||||
// extract & set exif data
|
||||
$image->setExif($this->extractExifData($input));
|
||||
|
||||
if ($info = getimagesizefromstring($input)) {
|
||||
$image->setOrigin(
|
||||
new Origin($info['mime'])
|
||||
try {
|
||||
// set mediaType on origin
|
||||
$image->origin()->setMediaType(
|
||||
$this->getMediaTypeByBinary($input)
|
||||
);
|
||||
} catch (DecoderException) {
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
// adjust image orientation
|
||||
$image->modify(new AlignRotationModifier());
|
||||
|
||||
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 $image;
|
||||
}
|
||||
}
|
||||
|
@@ -5,13 +5,17 @@ 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;
|
||||
use Intervention\Image\Modifiers\AlignRotationModifier;
|
||||
|
||||
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,11 +34,39 @@ 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
|
||||
$image = match ($mediaType) {
|
||||
// gif files might be animated and therefore cannot
|
||||
// be handled by the standard GD decoder.
|
||||
'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);
|
||||
|
||||
// extract exif
|
||||
$image->setExif($this->extractExifData($input));
|
||||
|
||||
// adjust image orientation
|
||||
$image->modify(new AlignRotationModifier());
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
42
src/Drivers/Gd/Decoders/GdImageDecoder.php
Normal file
42
src/Drivers/Gd/Decoders/GdImageDecoder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use GdImage;
|
||||
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)
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
52
src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
Normal file
52
src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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)
|
||||
);
|
||||
}
|
||||
|
||||
// create image
|
||||
$image = new Image(new Driver(), $core);
|
||||
|
||||
// set media type
|
||||
$image->origin()->setMediaType('image/gif');
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
26
src/Drivers/Gd/Modifiers/AlignRotationModifier.php
Normal file
26
src/Drivers/Gd/Modifiers/AlignRotationModifier.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||
|
||||
use Intervention\Image\Drivers\DriverSpecialized;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
|
||||
class AlignRotationModifier extends DriverSpecialized implements ModifierInterface
|
||||
{
|
||||
public function apply(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
|
||||
};
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
50
src/Drivers/Imagick/Decoders/ImagickImageDecoder.php
Normal file
50
src/Drivers/Imagick/Decoders/ImagickImageDecoder.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\Modifiers\AlignRotationModifier;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
$image = new Image(
|
||||
new Driver(),
|
||||
new Core($input)
|
||||
);
|
||||
|
||||
// adjust image rotatation
|
||||
$image->modify(new AlignRotationModifier());
|
||||
|
||||
// set media type on origin
|
||||
$image->origin()->setMediaType($input->getImageMimeType());
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
54
src/Drivers/Imagick/Modifiers/AlignRotationModifier.php
Normal file
54
src/Drivers/Imagick/Modifiers/AlignRotationModifier.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Imagick\Modifiers;
|
||||
|
||||
use Imagick;
|
||||
use Intervention\Image\Drivers\DriverSpecialized;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
|
||||
class AlignRotationModifier extends DriverSpecialized implements ModifierInterface
|
||||
{
|
||||
public function apply(ImageInterface $image): ImageInterface
|
||||
{
|
||||
switch ($image->core()->native()->getImageOrientation()) {
|
||||
case Imagick::ORIENTATION_TOPRIGHT: // 2
|
||||
$image->core()->native()->flopImage();
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_BOTTOMRIGHT: // 3
|
||||
$image->core()->native()->rotateimage("#000", 180);
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_BOTTOMLEFT: // 4
|
||||
$image->core()->native()->rotateimage("#000", 180);
|
||||
$image->core()->native()->flopImage();
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_LEFTTOP: // 5
|
||||
$image->core()->native()->rotateimage("#000", -270);
|
||||
$image->core()->native()->flopImage();
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_RIGHTTOP: // 6
|
||||
$image->core()->native()->rotateimage("#000", -270);
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_RIGHTBOTTOM: // 7
|
||||
$image->core()->native()->rotateimage("#000", -90);
|
||||
$image->core()->native()->flopImage();
|
||||
break;
|
||||
|
||||
case Imagick::ORIENTATION_LEFTBOTTOM: // 8
|
||||
$image->core()->native()->rotateimage("#000", -90);
|
||||
break;
|
||||
}
|
||||
|
||||
// set new orientation in image
|
||||
$image->core()->native()->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
@@ -40,15 +40,30 @@ class MediaTypeEncoder extends SpecializableEncoder implements EncoderInterface
|
||||
protected function encoderByMediaType(string $type): EncoderInterface
|
||||
{
|
||||
return match (strtolower($type)) {
|
||||
'image/webp' => new WebpEncoder(quality: $this->quality),
|
||||
'image/avif' => new AvifEncoder(quality: $this->quality),
|
||||
'image/jpeg' => new JpegEncoder(quality: $this->quality),
|
||||
'image/bmp' => new BmpEncoder(),
|
||||
'image/webp',
|
||||
'image/x-webp' => new WebpEncoder(quality: $this->quality),
|
||||
'image/avif',
|
||||
'image/x-avif' => new AvifEncoder(quality: $this->quality),
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/pjpeg' => new JpegEncoder(quality: $this->quality),
|
||||
'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' => new BmpEncoder(),
|
||||
'image/gif' => new GifEncoder(),
|
||||
'image/png' => new PngEncoder(),
|
||||
'image/png',
|
||||
'image/x-png' => new PngEncoder(),
|
||||
'image/tiff' => new TiffEncoder(quality: $this->quality),
|
||||
'image/jp2', 'image/jpx', 'image/jpm' => new Jpeg2000Encoder(quality: $this->quality),
|
||||
'image/heic', 'image/heif', => new HeicEncoder(quality: $this->quality),
|
||||
'image/jp2',
|
||||
'image/jpx',
|
||||
'image/jpm' => new Jpeg2000Encoder(quality: $this->quality),
|
||||
'image/heic',
|
||||
'image/heif', => new HeicEncoder(quality: $this->quality),
|
||||
default => throw new EncoderException('No encoder found for media type (' . $type . ').'),
|
||||
};
|
||||
}
|
||||
|
@@ -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}
|
||||
*
|
||||
|
@@ -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
|
||||
*
|
||||
|
9
src/Modifiers/AlignRotationModifier.php
Normal file
9
src/Modifiers/AlignRotationModifier.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Modifiers;
|
||||
|
||||
class AlignRotationModifier extends SpecializableModifier
|
||||
{
|
||||
}
|
@@ -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
|
||||
*
|
||||
|
23
tests/Analyzers/SpecializableAnalyzerTest.php
Normal file
23
tests/Analyzers/SpecializableAnalyzerTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\SpecializableAnalyzer;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Mockery;
|
||||
|
||||
class SpecializableAnalyzerTest extends TestCase
|
||||
{
|
||||
public function testAnalyzer(): void
|
||||
{
|
||||
$analyzer = Mockery::mock(SpecializableAnalyzer::class)->makePartial();
|
||||
$image = Mockery::mock(ImageInterface::class);
|
||||
$image->shouldReceive('analyze')->andReturn('test');
|
||||
|
||||
$result = $analyzer->analyze($image);
|
||||
$this->assertEquals('test', $result);
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ namespace Intervention\Image\Tests\Colors\Cmyk\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Cmyk\Color;
|
||||
use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
/**
|
||||
@@ -29,9 +30,15 @@ class StringColorDecoderTest extends TestCase
|
||||
$this->assertInstanceOf(Color::class, $result);
|
||||
$this->assertEquals([0, 100, 100, 0], $result->toArray());
|
||||
|
||||
|
||||
$result = $decoder->decode('cmyk(0%, 100%, 100%, 0%)');
|
||||
$this->assertInstanceOf(Color::class, $result);
|
||||
$this->assertEquals([0, 100, 100, 0], $result->toArray());
|
||||
}
|
||||
|
||||
public function testDecodeInvalid(): void
|
||||
{
|
||||
$decoder = new StringColorDecoder();
|
||||
$this->expectException(DecoderException::class);
|
||||
$decoder->decode(null);
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ namespace Intervention\Image\Tests\Drivers;
|
||||
use Exception;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Interfaces\CollectionInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
@@ -51,6 +52,29 @@ class AbstractDecoderTest extends TestCase
|
||||
$decoder->handle('test input');
|
||||
}
|
||||
|
||||
public function testIsGifFormat(): void
|
||||
{
|
||||
$decoder = Mockery::mock(AbstractDecoder::class)->makePartial();
|
||||
$this->assertFalse($decoder->isGifFormat($this->getTestImageData('exif.jpg')));
|
||||
$this->assertTrue($decoder->isGifFormat($this->getTestImageData('red.gif')));
|
||||
}
|
||||
|
||||
public function testExtractExifDataFromBinary(): void
|
||||
{
|
||||
$decoder = Mockery::mock(AbstractDecoder::class)->makePartial();
|
||||
$result = $decoder->extractExifData($this->getTestImageData('exif.jpg'));
|
||||
$this->assertInstanceOf(CollectionInterface::class, $result);
|
||||
$this->assertEquals('Oliver Vogel', $result->get('IFD0.Artist'));
|
||||
}
|
||||
|
||||
public function testExtractExifDataFromPath(): void
|
||||
{
|
||||
$decoder = Mockery::mock(AbstractDecoder::class)->makePartial();
|
||||
$result = $decoder->extractExifData($this->getTestImagePath('exif.jpg'));
|
||||
$this->assertInstanceOf(CollectionInterface::class, $result);
|
||||
$this->assertEquals('Oliver Vogel', $result->get('IFD0.Artist'));
|
||||
}
|
||||
|
||||
public function testParseDataUri(): void
|
||||
{
|
||||
$decoder = new class () extends AbstractDecoder
|
||||
|
42
tests/Drivers/Gd/ColorProcessorTest.php
Normal file
42
tests/Drivers/Gd/ColorProcessorTest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||
use Intervention\Image\Colors\Rgb\Color;
|
||||
use Intervention\Image\Drivers\Gd\ColorProcessor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
class ColorProcessorTest extends TestCase
|
||||
{
|
||||
public function testColorToNative(): void
|
||||
{
|
||||
$processor = new ColorProcessor();
|
||||
$result = $processor->colorToNative(new Color(255, 55, 0, 255));
|
||||
$this->assertEquals(16725760, $result);
|
||||
}
|
||||
|
||||
public function testNativeToColor(): void
|
||||
{
|
||||
$processor = new ColorProcessor();
|
||||
$result = $processor->nativeToColor(16725760);
|
||||
$this->assertInstanceOf(Color::class, $result);
|
||||
$this->assertEquals(255, $result->channel(Red::class)->value());
|
||||
$this->assertEquals(55, $result->channel(Green::class)->value());
|
||||
$this->assertEquals(0, $result->channel(Blue::class)->value());
|
||||
$this->assertEquals(255, $result->channel(Alpha::class)->value());
|
||||
}
|
||||
|
||||
public function testNativeToColorInvalid(): void
|
||||
{
|
||||
$processor = new ColorProcessor();
|
||||
$this->expectException(ColorException::class);
|
||||
$processor->nativeToColor('test');
|
||||
}
|
||||
}
|
24
tests/Drivers/Gd/Decoders/AbstractDecoderTest.php
Normal file
24
tests/Drivers/Gd/Decoders/AbstractDecoderTest.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\Gd\Decoders\AbstractDecoder;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Mockery;
|
||||
|
||||
class AbstractDecoderTest extends TestCase
|
||||
{
|
||||
public function testGetMediaTypeFromFilePath(): void
|
||||
{
|
||||
$decoder = Mockery::mock(AbstractDecoder::class)->makePartial();
|
||||
$this->assertEquals('image/jpeg', $decoder->getMediaTypeByFilePath($this->getTestImagePath('test.jpg')));
|
||||
}
|
||||
|
||||
public function testGetMediaTypeFromFileBinary(): void
|
||||
{
|
||||
$decoder = Mockery::mock(AbstractDecoder::class)->makePartial();
|
||||
$this->assertEquals('image/jpeg', $decoder->getMediaTypeByBinary($this->getTestImageData('test.jpg')));
|
||||
}
|
||||
}
|
@@ -4,7 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder;
|
||||
use Intervention\Image\Drivers\Gd\Driver;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
@@ -43,4 +47,38 @@ class DriverTest extends TestCase
|
||||
$this->assertEquals(5, $image->loops());
|
||||
$this->assertEquals(2, $image->count());
|
||||
}
|
||||
|
||||
public function testHandleInputImage(): void
|
||||
{
|
||||
$result = $this->driver->handleInput($this->getTestImagePath('test.jpg'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputColor(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff');
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputObjects(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff', [
|
||||
new HexColorDecoder()
|
||||
]);
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputClassnames(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff', [
|
||||
HexColorDecoder::class
|
||||
]);
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testColorProcessor(): void
|
||||
{
|
||||
$result = $this->driver->colorProcessor(new Colorspace());
|
||||
$this->assertInstanceOf(ColorProcessorInterface::class, $result);
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd\Modifiers;
|
||||
|
||||
use Intervention\Image\Exceptions\InputException;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Modifiers\QuantizeColorsModifier;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
@@ -26,6 +27,21 @@ class QuantizeColorsModifierTest extends TestCase
|
||||
$this->assertColorCount(4, $image);
|
||||
}
|
||||
|
||||
public function testNoColorReduction(): void
|
||||
{
|
||||
$image = $this->readTestImage('gradient.bmp');
|
||||
$this->assertColorCount(15, $image);
|
||||
$image->modify(new QuantizeColorsModifier(150));
|
||||
$this->assertColorCount(15, $image);
|
||||
}
|
||||
|
||||
public function testInvalidColorInput(): void
|
||||
{
|
||||
$image = $this->readTestImage('gradient.bmp');
|
||||
$this->expectException(InputException::class);
|
||||
$image->modify(new QuantizeColorsModifier(0));
|
||||
}
|
||||
|
||||
private function assertColorCount(int $count, ImageInterface $image): void
|
||||
{
|
||||
$colors = [];
|
||||
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd\Modifiers;
|
||||
|
||||
use Intervention\Image\Exceptions\InputException;
|
||||
use Intervention\Image\Modifiers\RemoveAnimationModifier;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Tests\Traits\CanCreateGdTestImage;
|
||||
@@ -34,4 +35,11 @@ class RemoveAnimationModifierTest extends TestCase
|
||||
$this->assertEquals(1, count($image));
|
||||
$this->assertEquals(1, count($result));
|
||||
}
|
||||
|
||||
public function testApplyInvalid(): void
|
||||
{
|
||||
$image = $this->readTestImage('animation.gif');
|
||||
$this->expectException(InputException::class);
|
||||
$image->modify(new RemoveAnimationModifier('test'));
|
||||
}
|
||||
}
|
||||
|
57
tests/Drivers/Gd/Traits/CanDecodeGifTest.php
Normal file
57
tests/Drivers/Gd/Traits/CanDecodeGifTest.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd\Traits;
|
||||
|
||||
use Intervention\Image\Drivers\Gd\Decoders\Traits\CanDecodeGif;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Mockery;
|
||||
|
||||
class CanDecodeGifTest extends TestCase
|
||||
{
|
||||
public function testDecodeGifFromBinaryAnimation(): void
|
||||
{
|
||||
$decoder = Mockery::mock(new class () {
|
||||
use CanDecodeGif;
|
||||
})->makePartial();
|
||||
|
||||
$result = $decoder->decodeGif($this->getTestImageData('animation.gif'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
$this->assertEquals('image/gif', $result->origin()->mediaType());
|
||||
}
|
||||
|
||||
public function testDecodeGifFromBinaryStatic(): void
|
||||
{
|
||||
$decoder = Mockery::mock(new class () {
|
||||
use CanDecodeGif;
|
||||
})->makePartial();
|
||||
|
||||
$result = $decoder->decodeGif($this->getTestImageData('red.gif'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
$this->assertEquals('image/gif', $result->origin()->mediaType());
|
||||
}
|
||||
|
||||
public function testDecodeGifFromPathAnimation(): void
|
||||
{
|
||||
$decoder = Mockery::mock(new class () {
|
||||
use CanDecodeGif;
|
||||
})->makePartial();
|
||||
|
||||
$result = $decoder->decodeGif($this->getTestImagePath('animation.gif'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
$this->assertEquals('image/gif', $result->origin()->mediaType());
|
||||
}
|
||||
|
||||
public function testDecodeGifFromPathStatic(): void
|
||||
{
|
||||
$decoder = Mockery::mock(new class () {
|
||||
use CanDecodeGif;
|
||||
})->makePartial();
|
||||
|
||||
$result = $decoder->decodeGif($this->getTestImagePath('red.gif'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
$this->assertEquals('image/gif', $result->origin()->mediaType());
|
||||
}
|
||||
}
|
27
tests/Drivers/Imagick/ColorProcessorTest.php
Normal file
27
tests/Drivers/Imagick/ColorProcessorTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Imagick;
|
||||
|
||||
use ImagickPixel;
|
||||
use Intervention\Image\Colors\Rgb\Color;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||
use Intervention\Image\Drivers\Imagick\ColorProcessor;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
class ColorProcessorTest extends TestCase
|
||||
{
|
||||
public function testColorToNative(): void
|
||||
{
|
||||
$processor = new ColorProcessor(new Colorspace());
|
||||
$result = $processor->colorToNative(new Color(255, 55, 0, 255));
|
||||
$this->assertInstanceOf(ImagickPixel::class, $result);
|
||||
}
|
||||
|
||||
public function testNativeToColor(): void
|
||||
{
|
||||
$processor = new ColorProcessor(new Colorspace());
|
||||
$result = $processor->nativeToColor(new ImagickPixel('rgb(255, 55, 0)'));
|
||||
}
|
||||
}
|
@@ -4,7 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Imagick;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder;
|
||||
use Intervention\Image\Drivers\Imagick\Driver;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
@@ -43,4 +47,38 @@ class DriverTest extends TestCase
|
||||
$this->assertEquals(5, $image->loops());
|
||||
$this->assertEquals(2, $image->count());
|
||||
}
|
||||
|
||||
public function testHandleInputImage(): void
|
||||
{
|
||||
$result = $this->driver->handleInput($this->getTestImagePath('test.jpg'));
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputColor(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff');
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputObjects(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff', [
|
||||
new HexColorDecoder()
|
||||
]);
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testHandleInputClassnames(): void
|
||||
{
|
||||
$result = $this->driver->handleInput('ffffff', [
|
||||
HexColorDecoder::class
|
||||
]);
|
||||
$this->assertInstanceOf(ColorInterface::class, $result);
|
||||
}
|
||||
|
||||
public function testColorProcessor(): void
|
||||
{
|
||||
$result = $this->driver->colorProcessor(new Colorspace());
|
||||
$this->assertInstanceOf(ColorProcessorInterface::class, $result);
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Imagick\Modifiers;
|
||||
|
||||
use Intervention\Image\Exceptions\InputException;
|
||||
use Intervention\Image\Modifiers\QuantizeColorsModifier;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Tests\Traits\CanCreateImagickTestImage;
|
||||
@@ -24,4 +25,19 @@ class QuantizeColorsModifierTest extends TestCase
|
||||
$image->modify(new QuantizeColorsModifier(4));
|
||||
$this->assertEquals(4, $image->core()->native()->getImageColors());
|
||||
}
|
||||
|
||||
public function testNoColorReduction(): void
|
||||
{
|
||||
$image = $this->readTestImage('gradient.bmp');
|
||||
$this->assertEquals(15, $image->core()->native()->getImageColors());
|
||||
$image->modify(new QuantizeColorsModifier(150));
|
||||
$this->assertEquals(15, $image->core()->native()->getImageColors());
|
||||
}
|
||||
|
||||
public function testInvalidColorInput(): void
|
||||
{
|
||||
$image = $this->readTestImage('gradient.bmp');
|
||||
$this->expectException(InputException::class);
|
||||
$image->modify(new QuantizeColorsModifier(0));
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Imagick\Modifiers;
|
||||
|
||||
use Intervention\Image\Exceptions\InputException;
|
||||
use Intervention\Image\Modifiers\RemoveAnimationModifier;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Tests\Traits\CanCreateImagickTestImage;
|
||||
@@ -34,4 +35,11 @@ class RemoveAnimationModifierTest extends TestCase
|
||||
$this->assertEquals(1, count($image));
|
||||
$this->assertEquals(1, count($result));
|
||||
}
|
||||
|
||||
public function testApplyInvalid(): void
|
||||
{
|
||||
$image = $this->readTestImage('animation.gif');
|
||||
$this->expectException(InputException::class);
|
||||
$image->modify(new RemoveAnimationModifier('test'));
|
||||
}
|
||||
}
|
||||
|
52
tests/Encoders/SpecializableEncoderTest.php
Normal file
52
tests/Encoders/SpecializableEncoderTest.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Encoders;
|
||||
|
||||
use Intervention\Image\EncodedImage;
|
||||
use Intervention\Image\Encoders\SpecializableEncoder;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Mockery;
|
||||
|
||||
class SpecializableEncoderTest extends TestCase
|
||||
{
|
||||
public function testConstructorDefault(): void
|
||||
{
|
||||
$encoder = new class () extends SpecializableEncoder
|
||||
{
|
||||
};
|
||||
|
||||
$this->assertEquals(75, $encoder->quality);
|
||||
}
|
||||
|
||||
public function testConstructorList(): void
|
||||
{
|
||||
$encoder = new class (1) extends SpecializableEncoder
|
||||
{
|
||||
};
|
||||
|
||||
$this->assertEquals(1, $encoder->quality);
|
||||
}
|
||||
|
||||
public function testConstructorNamed(): void
|
||||
{
|
||||
$encoder = new class (quality: 1) extends SpecializableEncoder
|
||||
{
|
||||
};
|
||||
|
||||
$this->assertEquals(1, $encoder->quality);
|
||||
}
|
||||
|
||||
public function testEncode(): void
|
||||
{
|
||||
$encoder = Mockery::mock(SpecializableEncoder::class)->makePartial();
|
||||
$image = Mockery::mock(ImageInterface::class);
|
||||
$encoded = Mockery::mock(EncodedImage::class);
|
||||
$image->shouldReceive('encode')->andReturn($encoded);
|
||||
|
||||
$result = $encoder->encode($image);
|
||||
$this->assertInstanceOf(EncodedImage::class, $result);
|
||||
}
|
||||
}
|
@@ -109,6 +109,14 @@ class ImageManagerTest extends TestCase
|
||||
$this->assertInstanceOf(ImageInterface::class, $image);
|
||||
}
|
||||
|
||||
/** @requires extension gd */
|
||||
public function testReadGdWithRotationAdjustment(): void
|
||||
{
|
||||
$manager = new ImageManager(GdDriver::class);
|
||||
$image = $manager->read(__DIR__ . '/images/orientation.jpg');
|
||||
$this->assertColor(255, 255, 255, 255, $image->pickColor(0, 24));
|
||||
}
|
||||
|
||||
/** @requires extension imagick */
|
||||
public function testCreateImagick()
|
||||
{
|
||||
@@ -174,4 +182,12 @@ class ImageManagerTest extends TestCase
|
||||
$image = $manager->read(__DIR__ . '/images/red.gif', [new BinaryImageDecoder(), new FilePathImageDecoder()]);
|
||||
$this->assertInstanceOf(ImageInterface::class, $image);
|
||||
}
|
||||
|
||||
/** @requires extension imagick */
|
||||
public function testReadImagickWithRotationAdjustment(): void
|
||||
{
|
||||
$manager = new ImageManager(ImagickDriver::class);
|
||||
$image = $manager->read(__DIR__ . '/images/orientation.jpg');
|
||||
$this->assertColor(255, 255, 255, 255, $image->pickColor(0, 24));
|
||||
}
|
||||
}
|
||||
|
23
tests/Modifiers/SpecializableModifierTest.php
Normal file
23
tests/Modifiers/SpecializableModifierTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Modifiers;
|
||||
|
||||
use Intervention\Image\Modifiers\SpecializableModifier;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Mockery;
|
||||
|
||||
class SpecializableModifierTest extends TestCase
|
||||
{
|
||||
public function testApply(): void
|
||||
{
|
||||
$modifier = Mockery::mock(SpecializableModifier::class)->makePartial();
|
||||
$image = Mockery::mock(ImageInterface::class);
|
||||
$image->shouldReceive('modify')->andReturn($image);
|
||||
|
||||
$result = $modifier->apply($image);
|
||||
$this->assertInstanceOf(ImageInterface::class, $result);
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
BIN
tests/images/orientation.jpg
Normal file
BIN
tests/images/orientation.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 571 B |
Reference in New Issue
Block a user