1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-30 01:00:06 +02: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)) { if ($info = getimagesizefromstring($input)) {
$image->setOrigin( $image->setOrigin(
new Origin( new Origin($info['mime'])
$info['mime'],
imagecolorstotal($gd)
),
); );
} }
@@ -95,9 +92,10 @@ class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
); );
} }
return new Image( $image = new Image(new Driver(), $core);
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'); 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; namespace Intervention\Image\Drivers\Gd\Encoders;
use Intervention\Image\Drivers\DriverSpecializedEncoder; use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage; use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class BmpEncoder extends DriverSpecializedEncoder class BmpEncoder extends DriverSpecializedEncoder
{ {
public function encode(ImageInterface $image): EncodedImage public function encode(ImageInterface $image): EncodedImage
{ {
$image = $image->modify(new LimitColorsModifier($this->color_limit)); $data = $this->getBuffered(function () use ($image) {
$gd = $image->core()->native(); imagebmp($image->core()->native(), null, false);
$data = $this->getBuffered(function () use ($gd) {
imagebmp($gd, null, false);
}); });
return new EncodedImage($data, 'image/bmp'); 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\Gif\Builder as GifBuilder;
use Intervention\Image\Drivers\DriverSpecializedEncoder; use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Modifiers\LimitColorsModifier;
use Intervention\Image\EncodedImage; use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
/**
* @property int $color_limit
*/
class GifEncoder extends DriverSpecializedEncoder class GifEncoder extends DriverSpecializedEncoder
{ {
public function encode(ImageInterface $image): EncodedImage public function encode(ImageInterface $image): EncodedImage
@@ -19,7 +15,6 @@ class GifEncoder extends DriverSpecializedEncoder
return $this->encodeAnimated($image); return $this->encodeAnimated($image);
} }
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$gd = $image->core()->native(); $gd = $image->core()->native();
$data = $this->getBuffered(function () use ($gd) { $data = $this->getBuffered(function () use ($gd) {
imagegif($gd); imagegif($gd);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
namespace Intervention\Image\Encoders; namespace Intervention\Image\Encoders;
use Intervention\Gif\Exception\EncoderException;
use Intervention\Image\Interfaces\EncodedImageInterface; use Intervention\Image\Interfaces\EncodedImageInterface;
use Intervention\Image\Interfaces\EncoderInterface; use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
@@ -10,14 +11,16 @@ class AutoEncoder implements EncoderInterface
{ {
public function encode(ImageInterface $image): EncodedImageInterface public function encode(ImageInterface $image): EncodedImageInterface
{ {
$type = $image->origin()->mimetype();
return $image->encode( return $image->encode(
match ($image->origin()->mimetype()) { match ($type) {
'image/webp' => new WebpEncoder(), 'image/webp' => new WebpEncoder(),
'image/avif' => new AvifEncoder(), 'image/avif' => new AvifEncoder(),
'image/jpeg' => new JpegEncoder(), 'image/jpeg' => new JpegEncoder(),
'image/bmp' => new BmpEncoder(), 'image/bmp' => new BmpEncoder(),
'image/gif' => new GifEncoder(), '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 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 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 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\ProfileAnalyzer;
use Intervention\Image\Analyzers\ResolutionAnalyzer; use Intervention\Image\Analyzers\ResolutionAnalyzer;
use Intervention\Image\Analyzers\WidthAnalyzer; use Intervention\Image\Analyzers\WidthAnalyzer;
use Intervention\Image\Encoders\AutoEncoder;
use Intervention\Image\Encoders\AvifEncoder; use Intervention\Image\Encoders\AvifEncoder;
use Intervention\Image\Encoders\BmpEncoder; use Intervention\Image\Encoders\BmpEncoder;
use Intervention\Image\Encoders\GifEncoder; use Intervention\Image\Encoders\GifEncoder;
@@ -78,13 +79,27 @@ use Intervention\Image\Typography\FontFactory;
final class Image implements ImageInterface, Countable final class Image implements ImageInterface, Countable
{ {
/**
* The origin from which the image was created
*
* @var Origin
*/
protected Origin $origin; protected Origin $origin;
/**
* Create new instance
*
* @param DriverInterface $driver
* @param CoreInterface $core
* @param CollectionInterface $exif
* @return void
*/
public function __construct( public function __construct(
protected DriverInterface $driver, protected DriverInterface $driver,
protected CoreInterface $core, protected CoreInterface $core,
protected CollectionInterface $exif = new Collection() protected CollectionInterface $exif = new Collection()
) { ) {
$this->origin = new Origin();
} }
/** /**
@@ -226,7 +241,7 @@ final class Image implements ImageInterface, Countable
* *
* @see ImageInterface::encode() * @see ImageInterface::encode()
*/ */
public function encode(EncoderInterface $encoder): EncodedImage public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImage
{ {
return $this->driver->resolve($encoder)->encode($this); return $this->driver->resolve($encoder)->encode($this);
} }
@@ -743,9 +758,9 @@ final class Image implements ImageInterface, Countable
* *
* @see ImageInterface::toPng() * @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() * @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() * @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() * Alias if self::toBitmap()
* *
* @param int $color_limit
* @return EncodedImageInterface * @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 * Encode image to PNG format
* *
* @param int $color_limit
* @return EncodedImageInterface * @return EncodedImageInterface
*/ */
public function toPng(int $color_limit = 0): EncodedImageInterface; public function toPng(): EncodedImageInterface;
/** /**
* Encode image to GIF format * Encode image to GIF format
* *
* @param int $color_limit
* @return EncodedImageInterface * @return EncodedImageInterface
*/ */
public function toGif(int $color_limit = 0): EncodedImageInterface; public function toGif(): EncodedImageInterface;
/** /**
* Encode image to Bitmap format * Encode image to Bitmap format
* *
* @param int $color_limit
* @return EncodedImageInterface * @return EncodedImageInterface
*/ */
public function toBitmap(int $color_limit = 0): EncodedImageInterface; public function toBitmap(): EncodedImageInterface;
/** /**
* Encode image to AVIF format * Encode image to AVIF format

View File

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

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Intervention\Image\Tests\Drivers; namespace Intervention\Image\Tests\Drivers;
use Intervention\Image\Drivers\DriverSpecializedEncoder; use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Encoders\PngEncoder; use Intervention\Image\Encoders\JpegEncoder;
use Intervention\Image\Interfaces\DriverInterface; use Intervention\Image\Interfaces\DriverInterface;
use Intervention\Image\Tests\TestCase; use Intervention\Image\Tests\TestCase;
use Mockery; use Mockery;
@@ -29,10 +29,10 @@ class DriverSpecializedEncoderTest extends TestCase
public function testGetAttributes(): void public function testGetAttributes(): void
{ {
$encoder = Mockery::mock(DriverSpecializedEncoder::class, [ $encoder = Mockery::mock(DriverSpecializedEncoder::class, [
new PngEncoder(color_limit: 123), new JpegEncoder(quality: 10),
Mockery::mock(DriverInterface::class), Mockery::mock(DriverInterface::class),
])->makePartial(); ])->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); $result = $encoder->encode($image);
$this->assertMimeType('image/gif', (string) $result); $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); $result = $encoder->encode($image);
$this->assertMimeType('image/png', (string) $result); $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); $result = $encoder->encode($image);
$this->assertMimeType(['image/bmp', 'image/x-ms-bmp'], (string) $result); $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); $result = $encoder->encode($image);
$this->assertMimeType('image/gif', (string) $result); $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); $result = $encoder->encode($image);
$this->assertMimeType('image/png', (string) $result); $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());
}
} }