1
0
mirror of https://github.com/Intervention/image.git synced 2025-03-15 22:49:40 +01:00

Change encoding logic

The logic for encoding image data in connection with a limit on total
colors has been removed. The number of colors of an image can now be
reduced with the corresponding modifier, but this is not related to the
output of the encoder.

Furthermore, there is now an AutoEncoder, which resumes the original
format of the original image source and selects the encoder accordingly.
This Origin attribute is set during the decoding process so that it can
be read out again later. The AutoEncoder is now the default value of the
parameter for Image::encode().
This commit is contained in:
Oliver Vogel 2023-12-16 11:34:01 +01:00
parent dbe27c6ab5
commit 9863a1f7d7
23 changed files with 71 additions and 144 deletions

View File

@ -55,10 +55,7 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
if ($info = getimagesizefromstring($input)) {
$image->setOrigin(
new Origin(
$info['mime'],
imagecolorstotal($gd)
),
new Origin($info['mime'])
);
}
@ -95,9 +92,10 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
);
}
return new Image(
new Driver(),
$core
$image = new Image(new Driver(), $core);
return $image->setOrigin(
new Origin('image/gif')
);
}
}

View File

@ -24,6 +24,10 @@ class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterfac
throw new DecoderException('Unable to decode input');
}
return parent::decode(file_get_contents($input));
$image = parent::decode(file_get_contents($input));
// set origin
return $image;
}
}

View File

@ -3,21 +3,15 @@
namespace Intervention\Image\Drivers\Gd\Encoders;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class BmpEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
{
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$gd = $image->core()->native();
$data = $this->getBuffered(function () use ($gd) {
imagebmp($gd, null, false);
$data = $this->getBuffered(function () use ($image) {
imagebmp($image->core()->native(), null, false);
});
return new EncodedImage($data, 'image/bmp');

View File

@ -4,13 +4,9 @@ namespace Intervention\Image\Drivers\Gd\Encoders;
use Intervention\Gif\Builder as GifBuilder;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class GifEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
@ -19,7 +15,6 @@ class GifEncoder extends DriverSpecializedEncoder
return $this->encodeAnimated($image);
}
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$gd = $image->core()->native();
$data = $this->getBuffered(function () use ($gd) {
imagegif($gd);

View File

@ -5,19 +5,13 @@ namespace Intervention\Image\Drivers\Gd\Encoders;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Modifiers\LimitColorsModifier;
/**
* @property int $color_limit
*/
class PngEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
{
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$gd = $image->core()->native();
$data = $this->getBuffered(function () use ($gd) {
imagepng($gd, null, -1);
$data = $this->getBuffered(function () use ($image) {
imagepng($image->core()->native(), null, -1);
});
return new EncodedImage($data, 'image/png');

View File

@ -27,8 +27,11 @@ class LimitColorsModifier extends DriverSpecializedModifier
$height = $image->height();
foreach ($image as $frame) {
// create empty gd
$reduced = imagecreatetruecolor($width, $height);
// limitied color image must be palette (indexed) GDImage
$reduced = imagecreate($width, $height);
// save alpha channel
imagesavealpha($reduced, true);
// create matte
$matte = imagecolorallocatealpha($reduced, 255, 255, 255, 127);
@ -36,24 +39,24 @@ class LimitColorsModifier extends DriverSpecializedModifier
// fill with matte
imagefill($reduced, 0, 0, $matte);
// disable alpha blending for the copy process
imagealphablending($reduced, false);
// set transparency and get transparency index
imagecolortransparent($reduced, $matte);
// copy original image
// copy original image (colors are limited automatically in the copy process)
imagecopy($reduced, $frame->native(), 0, 0, 0, 0, $width, $height);
// reduce limit by one to include possible transparency in palette
$limit = imagecolortransparent($frame->native()) === -1 ? $this->limit : $this->limit - 1;
// $limit = imagecolortransparent($frame->native()) === -1 ? $this->limit : $this->limit - 1;
// decrease colors
imagetruecolortopalette($reduced, true, $limit);
// imagetruecolortopalette($reduced, true, $limit);
$frame->setNative($reduced);
}
return $image;
}
}

View File

@ -56,8 +56,7 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
);
$image->setOrigin(new Origin(
$imagick->getImageMimeType(),
$imagick->getImageColors()
$imagick->getImageMimeType()
));
return $image;

View File

@ -4,13 +4,9 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class BmpEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
@ -18,7 +14,6 @@ class BmpEncoder extends DriverSpecializedEncoder
$format = 'bmp';
$compression = Imagick::COMPRESSION_NO;
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$imagick = $image->core()->native();
$imagick->setFormat($format);
$imagick->setImageFormat($format);

View File

@ -4,20 +4,13 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class GifEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
{
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$format = 'gif';
$compression = Imagick::COMPRESSION_LZW;

View File

@ -4,13 +4,9 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class PngEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
@ -18,7 +14,6 @@ class PngEncoder extends DriverSpecializedEncoder
$format = 'png';
$compression = Imagick::COMPRESSION_ZIP;
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$imagick = $image->core()->native();
$imagick->setFormat($format);
$imagick->setImageFormat($format);

View File

@ -2,6 +2,7 @@
namespace Intervention\Image\Encoders;
use Intervention\Gif\Exception\EncoderException;
use Intervention\Image\Interfaces\EncodedImageInterface;
use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
@ -10,14 +11,16 @@ class AutoEncoder implements EncoderInterface
{
public function encode(ImageInterface $image): EncodedImageInterface
{
$type = $image->origin()->mimetype();
return $image->encode(
match ($image->origin()->mimetype()) {
match ($type) {
'image/webp' => new WebpEncoder(),
'image/avif' => new AvifEncoder(),
'image/jpeg' => new JpegEncoder(),
'image/bmp' => new BmpEncoder(),
'image/gif' => new GifEncoder(),
default => new PngEncoder(),
'image/png' => new PngEncoder(),
default => throw new EncoderException('No encoder found for media type (' . $type . ').'),
}
);
}

View File

@ -4,7 +4,4 @@ namespace Intervention\Image\Encoders;
class BmpEncoder extends AbstractEncoder
{
public function __construct(public int $color_limit = 0)
{
}
}

View File

@ -4,7 +4,4 @@ namespace Intervention\Image\Encoders;
class GifEncoder extends AbstractEncoder
{
public function __construct(public int $color_limit = 0)
{
}
}

View File

@ -4,7 +4,4 @@ namespace Intervention\Image\Encoders;
class PngEncoder extends AbstractEncoder
{
public function __construct(public int $color_limit = 0)
{
}
}

View File

@ -11,6 +11,7 @@ use Intervention\Image\Analyzers\PixelColorsAnalyzer;
use Intervention\Image\Analyzers\ProfileAnalyzer;
use Intervention\Image\Analyzers\ResolutionAnalyzer;
use Intervention\Image\Analyzers\WidthAnalyzer;
use Intervention\Image\Encoders\AutoEncoder;
use Intervention\Image\Encoders\AvifEncoder;
use Intervention\Image\Encoders\BmpEncoder;
use Intervention\Image\Encoders\GifEncoder;
@ -78,13 +79,27 @@ use Intervention\Image\Typography\FontFactory;
final class Image implements ImageInterface, Countable
{
/**
* The origin from which the image was created
*
* @var Origin
*/
protected Origin $origin;
/**
* Create new instance
*
* @param DriverInterface $driver
* @param CoreInterface $core
* @param CollectionInterface $exif
* @return void
*/
public function __construct(
protected DriverInterface $driver,
protected CoreInterface $core,
protected CollectionInterface $exif = new Collection()
) {
$this->origin = new Origin();
}
/**
@ -226,7 +241,7 @@ final class Image implements ImageInterface, Countable
*
* @see ImageInterface::encode()
*/
public function encode(EncoderInterface $encoder): EncodedImage
public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImage
{
return $this->driver->resolve($encoder)->encode($this);
}
@ -743,9 +758,9 @@ final class Image implements ImageInterface, Countable
*
* @see ImageInterface::toPng()
*/
public function toPng(int $color_limit = 0): EncodedImageInterface
public function toPng(): EncodedImageInterface
{
return $this->encode(new PngEncoder($color_limit));
return $this->encode(new PngEncoder());
}
/**
@ -753,9 +768,9 @@ final class Image implements ImageInterface, Countable
*
* @see ImageInterface::toGif()
*/
public function toGif(int $color_limit = 0): EncodedImageInterface
public function toGif(): EncodedImageInterface
{
return $this->encode(new GifEncoder($color_limit));
return $this->encode(new GifEncoder());
}
/**
@ -773,20 +788,19 @@ final class Image implements ImageInterface, Countable
*
* @see ImageInterface::toBitmap()
*/
public function toBitmap(int $color_limit = 0): EncodedImageInterface
public function toBitmap(): EncodedImageInterface
{
return $this->encode(new BmpEncoder($color_limit));
return $this->encode(new BmpEncoder());
}
/**
* Alias if self::toBitmap()
*
* @param int $color_limit
* @return EncodedImageInterface
*/
public function toBmp(int $color_limit = 0): EncodedImageInterface
public function toBmp(): EncodedImageInterface
{
return $this->toBitmap($color_limit);
return $this->toBitmap();
}
/**

View File

@ -567,26 +567,23 @@ interface ImageInterface extends IteratorAggregate, Countable
/**
* Encode image to PNG format
*
* @param int $color_limit
* @return EncodedImageInterface
*/
public function toPng(int $color_limit = 0): EncodedImageInterface;
public function toPng(): EncodedImageInterface;
/**
* Encode image to GIF format
*
* @param int $color_limit
* @return EncodedImageInterface
*/
public function toGif(int $color_limit = 0): EncodedImageInterface;
public function toGif(): EncodedImageInterface;
/**
* Encode image to Bitmap format
*
* @param int $color_limit
* @return EncodedImageInterface
*/
public function toBitmap(int $color_limit = 0): EncodedImageInterface;
public function toBitmap(): EncodedImageInterface;
/**
* Encode image to AVIF format

View File

@ -4,9 +4,19 @@ namespace Intervention\Image;
class Origin
{
/**
* Create new origin instance
*
* @param string $mimetype
* @return void
*/
public function __construct(
protected string $mimetype,
protected int $colors
protected string $mimetype = 'application/octet-stream'
) {
}
public function mimetype(): string
{
return $this->mimetype;
}
}

View File

@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests\Drivers;
use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Encoders\PngEncoder;
use Intervention\Image\Encoders\JpegEncoder;
use Intervention\Image\Interfaces\DriverInterface;
use Intervention\Image\Tests\TestCase;
use Mockery;
@ -29,10 +29,10 @@ class DriverSpecializedEncoderTest extends TestCase
public function testGetAttributes(): void
{
$encoder = Mockery::mock(DriverSpecializedEncoder::class, [
new PngEncoder(color_limit: 123),
new JpegEncoder(quality: 10),
Mockery::mock(DriverInterface::class),
])->makePartial();
$this->assertEquals(123, $encoder->color_limit);
$this->assertEquals(10, $encoder->quality);
}
}

View File

@ -46,15 +46,4 @@ class GifEncoderTest extends TestCase
$result = $encoder->encode($image);
$this->assertMimeType('image/gif', (string) $result);
}
public function testEncodeReduced(): void
{
$image = $this->readTestImage('gradient.gif');
$gd = $image->core()->native();
$this->assertEquals(15, imagecolorstotal($gd));
$encoder = new GifEncoder(2);
$result = $encoder->encode($image);
$gd = imagecreatefromstring((string) $result);
$this->assertEquals(2, imagecolorstotal($gd));
}
}

View File

@ -35,15 +35,4 @@ class PngEncoderTest extends TestCase
$result = $encoder->encode($image);
$this->assertMimeType('image/png', (string) $result);
}
public function testEncodeReduced(): void
{
$image = $this->readTestImage('tile.png');
$gd = $image->core()->native();
$this->assertEquals(3, imagecolorstotal($gd));
$encoder = new PngEncoder(2);
$result = $encoder->encode($image);
$gd = imagecreatefromstring((string) $result);
$this->assertEquals(2, imagecolorstotal($gd));
}
}

View File

@ -37,16 +37,4 @@ class BmpEncoderTest extends TestCase
$result = $encoder->encode($image);
$this->assertMimeType(['image/bmp', 'image/x-ms-bmp'], (string) $result);
}
public function testEncodeReduced(): void
{
$image = $this->readTestImage('gradient.bmp');
$imagick = $image->core()->native();
$this->assertEquals(15, $imagick->getImageColors());
$encoder = new BmpEncoder(2);
$result = $encoder->encode($image);
$imagick = new Imagick();
$imagick->readImageBlob((string) $result);
$this->assertEquals(2, $imagick->getImageColors());
}
}

View File

@ -51,16 +51,4 @@ class GifEncoderTest extends TestCase
$result = $encoder->encode($image);
$this->assertMimeType('image/gif', (string) $result);
}
public function testEncodeReduced(): void
{
$image = $this->readTestImage('gradient.gif');
$imagick = $image->core()->native();
$this->assertEquals(15, $imagick->getImageColors());
$encoder = new GifEncoder(2);
$result = $encoder->encode($image);
$imagick = new Imagick();
$imagick->readImageBlob((string) $result);
$this->assertEquals(2, $imagick->getImageColors());
}
}

View File

@ -38,16 +38,4 @@ final class PngEncoderTest extends TestCase
$result = $encoder->encode($image);
$this->assertMimeType('image/png', (string) $result);
}
public function testEncodeReduced(): void
{
$image = $this->readTestImage('tile.png');
$imagick = $image->core()->native();
$this->assertEquals(3, $imagick->getImageColors());
$encoder = new PngEncoder(2);
$result = $encoder->encode($image);
$imagick = new Imagick();
$imagick->readImageBlob((string) $result);
$this->assertEquals(2, $imagick->getImageColors());
}
}