mirror of
https://github.com/Intervention/image.git
synced 2025-08-21 05:01:20 +02:00
Refactor color reduction to modifer
This commit is contained in:
@@ -3,15 +3,13 @@
|
|||||||
namespace Intervention\Image\Drivers\Gd\Encoders;
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Gd\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Gd\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -19,9 +17,9 @@ class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
|
|
||||||
public function encode(ImageInterface $image): EncodedImage
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
{
|
{
|
||||||
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$data = $this->getBuffered(function () use ($gd) {
|
$data = $this->getBuffered(function () use ($image) {
|
||||||
imagebmp($gd, null, false);
|
imagebmp($image->frame()->core(), null, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new EncodedImage($data, 'image/bmp');
|
return new EncodedImage($data, 'image/bmp');
|
||||||
|
@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Gd\Encoders;
|
|||||||
|
|
||||||
use Intervention\Gif\Builder as GifBuilder;
|
use Intervention\Gif\Builder as GifBuilder;
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Gd\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Gd\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class GifEncoder extends AbstractEncoder implements EncoderInterface
|
class GifEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -24,9 +22,9 @@ class GifEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
return $this->encodeAnimated($image);
|
return $this->encodeAnimated($image);
|
||||||
}
|
}
|
||||||
|
|
||||||
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$data = $this->getBuffered(function () use ($gd) {
|
$data = $this->getBuffered(function () use ($image) {
|
||||||
imagegif($gd);
|
imagegif($image->frame()->core());
|
||||||
});
|
});
|
||||||
|
|
||||||
return new EncodedImage($data, 'image/gif');
|
return new EncodedImage($data, 'image/gif');
|
||||||
|
@@ -3,15 +3,13 @@
|
|||||||
namespace Intervention\Image\Drivers\Gd\Encoders;
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Gd\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Gd\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class PngEncoder extends AbstractEncoder implements EncoderInterface
|
class PngEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -19,9 +17,9 @@ class PngEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
|
|
||||||
public function encode(ImageInterface $image): EncodedImage
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
{
|
{
|
||||||
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$data = $this->getBuffered(function () use ($gd) {
|
$data = $this->getBuffered(function () use ($image) {
|
||||||
imagepng($gd, null, -1);
|
imagepng($image->frame()->core(), null, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new EncodedImage($data, 'image/png');
|
return new EncodedImage($data, 'image/png');
|
||||||
|
60
src/Drivers/Gd/Modifiers/LimitColorsModifier.php
Normal file
60
src/Drivers/Gd/Modifiers/LimitColorsModifier.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
|
|
||||||
|
class LimitColorsModifier implements ModifierInterface
|
||||||
|
{
|
||||||
|
public function __construct(protected int $limit = 0, protected int $threshold = 256)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
// no color limit: no reduction
|
||||||
|
if ($this->limit === 0) {
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit is over threshold: no reduction
|
||||||
|
if ($this->limit > $this->threshold) {
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
$width = $image->width();
|
||||||
|
$height = $image->height();
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
// create empty gd
|
||||||
|
$reduced = imagecreatetruecolor($width, $height);
|
||||||
|
|
||||||
|
// create matte
|
||||||
|
$matte = imagecolorallocatealpha($reduced, 255, 255, 255, 127);
|
||||||
|
|
||||||
|
// fill with matte
|
||||||
|
imagefill($reduced, 0, 0, $matte);
|
||||||
|
|
||||||
|
imagealphablending($reduced, false);
|
||||||
|
|
||||||
|
// set transparency and get transparency index
|
||||||
|
imagecolortransparent($reduced, $matte);
|
||||||
|
|
||||||
|
// copy original image
|
||||||
|
imagecopy($reduced, $frame->core(), 0, 0, 0, 0, $width, $height);
|
||||||
|
|
||||||
|
// reduce limit by one to include possible transparency in palette
|
||||||
|
$limit = imagecolortransparent($frame->core()) === -1 ? $this->limit : $this->limit - 1;
|
||||||
|
|
||||||
|
// decrease colors
|
||||||
|
imagetruecolortopalette($reduced, true, $limit);
|
||||||
|
|
||||||
|
$frame->setCore($reduced);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Intervention\Image\Drivers\Gd\Traits;
|
|
||||||
|
|
||||||
use GdImage;
|
|
||||||
|
|
||||||
trait CanReduceColors
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Reduce colors in a given GdImage to the given limit. Reduction is only
|
|
||||||
* applied when the given limit is under the given threshold
|
|
||||||
*
|
|
||||||
* @param GdImage $gd
|
|
||||||
* @param int $limit
|
|
||||||
* @param int $threshold
|
|
||||||
* @return GdImage
|
|
||||||
*/
|
|
||||||
private function maybeReduceColors(GdImage $gd, int $limit, int $threshold = 256): GdImage
|
|
||||||
{
|
|
||||||
// no color limit: no reduction
|
|
||||||
if ($limit === 0) {
|
|
||||||
return $gd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// limit is over threshold: no reduction
|
|
||||||
if ($limit > $threshold) {
|
|
||||||
return $gd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// image size
|
|
||||||
$width = imagesx($gd);
|
|
||||||
$height = imagesy($gd);
|
|
||||||
|
|
||||||
// create empty gd
|
|
||||||
$reduced = imagecreatetruecolor($width, $height);
|
|
||||||
|
|
||||||
// create matte
|
|
||||||
$matte = imagecolorallocatealpha($reduced, 255, 255, 255, 127);
|
|
||||||
|
|
||||||
// fill with matte
|
|
||||||
imagefill($reduced, 0, 0, $matte);
|
|
||||||
|
|
||||||
imagealphablending($reduced, false);
|
|
||||||
|
|
||||||
// set transparency and get transparency index
|
|
||||||
imagecolortransparent($reduced, $matte);
|
|
||||||
|
|
||||||
// copy original image
|
|
||||||
imagecopy($reduced, $gd, 0, 0, 0, 0, $width, $height);
|
|
||||||
|
|
||||||
// reduce limit by one to include possible transparency in palette
|
|
||||||
$limit = imagecolortransparent($gd) === -1 ? $limit : $limit - 1;
|
|
||||||
|
|
||||||
// decrease colors
|
|
||||||
imagetruecolortopalette($reduced, true, $limit);
|
|
||||||
|
|
||||||
return $reduced;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
|
|||||||
|
|
||||||
use Imagick;
|
use Imagick;
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Imagick\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Imagick\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -23,12 +21,12 @@ class BmpEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
$format = 'bmp';
|
$format = 'bmp';
|
||||||
$compression = Imagick::COMPRESSION_NO;
|
$compression = Imagick::COMPRESSION_NO;
|
||||||
|
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$imagick = $image->frame()->core();
|
$imagick = $image->frame()->core();
|
||||||
$imagick->setFormat($format);
|
$imagick->setFormat($format);
|
||||||
$imagick->setImageFormat($format);
|
$imagick->setImageFormat($format);
|
||||||
$imagick->setCompression($compression);
|
$imagick->setCompression($compression);
|
||||||
$imagick->setImageCompression($compression);
|
$imagick->setImageCompression($compression);
|
||||||
$this->maybeReduceColors($imagick, $this->color_limit);
|
|
||||||
|
|
||||||
return new EncodedImage($imagick->getImagesBlob(), 'image/bmp');
|
return new EncodedImage($imagick->getImagesBlob(), 'image/bmp');
|
||||||
}
|
}
|
||||||
|
@@ -5,15 +5,16 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
|
|||||||
use Imagick;
|
use Imagick;
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Imagick\Image;
|
use Intervention\Image\Drivers\Imagick\Image;
|
||||||
use Intervention\Image\Drivers\Imagick\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Imagick\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Exceptions\EncoderException;
|
use Intervention\Image\Exceptions\EncoderException;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Traits\CanCheckType;
|
||||||
|
|
||||||
class GifEncoder extends AbstractEncoder implements EncoderInterface
|
class GifEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
use CanCheckType;
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
@@ -29,13 +30,15 @@ class GifEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
throw new EncoderException('Image does not match the current driver.');
|
throw new EncoderException('Image does not match the current driver.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$image = $this->failIfNotClass($image, Image::class);
|
||||||
|
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$imagick = $image->getImagick();
|
$imagick = $image->getImagick();
|
||||||
$imagick->setFormat($format);
|
$imagick->setFormat($format);
|
||||||
$imagick->setImageFormat($format);
|
$imagick->setImageFormat($format);
|
||||||
$imagick->setCompression($compression);
|
$imagick->setCompression($compression);
|
||||||
$imagick->setImageCompression($compression);
|
$imagick->setImageCompression($compression);
|
||||||
$imagick->optimizeImageLayers();
|
$imagick->optimizeImageLayers();
|
||||||
$this->maybeReduceColors($imagick, $this->color_limit);
|
|
||||||
$imagick = $imagick->deconstructImages();
|
$imagick = $imagick->deconstructImages();
|
||||||
|
|
||||||
return new EncodedImage($imagick->getImagesBlob(), 'image/gif');
|
return new EncodedImage($imagick->getImagesBlob(), 'image/gif');
|
||||||
|
@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
|
|||||||
|
|
||||||
use Imagick;
|
use Imagick;
|
||||||
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
|
||||||
use Intervention\Image\Drivers\Imagick\Traits\CanReduceColors;
|
use Intervention\Image\Drivers\Imagick\Modifiers\LimitColorsModifier;
|
||||||
use Intervention\Image\EncodedImage;
|
use Intervention\Image\EncodedImage;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class PngEncoder extends AbstractEncoder implements EncoderInterface
|
class PngEncoder extends AbstractEncoder implements EncoderInterface
|
||||||
{
|
{
|
||||||
use CanReduceColors;
|
|
||||||
|
|
||||||
public function __construct(protected int $color_limit = 0)
|
public function __construct(protected int $color_limit = 0)
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
@@ -23,12 +21,12 @@ class PngEncoder extends AbstractEncoder implements EncoderInterface
|
|||||||
$format = 'png';
|
$format = 'png';
|
||||||
$compression = Imagick::COMPRESSION_ZIP;
|
$compression = Imagick::COMPRESSION_ZIP;
|
||||||
|
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
$imagick = $image->frame()->core();
|
$imagick = $image->frame()->core();
|
||||||
$imagick->setFormat($format);
|
$imagick->setFormat($format);
|
||||||
$imagick->setImageFormat($format);
|
$imagick->setImageFormat($format);
|
||||||
$imagick->setCompression($compression);
|
$imagick->setCompression($compression);
|
||||||
$imagick->setImageCompression($compression);
|
$imagick->setImageCompression($compression);
|
||||||
$this->maybeReduceColors($imagick, $this->color_limit);
|
|
||||||
|
|
||||||
return new EncodedImage($imagick->getImagesBlob(), 'image/png');
|
return new EncodedImage($imagick->getImagesBlob(), 'image/png');
|
||||||
}
|
}
|
||||||
|
44
src/Drivers/Imagick/Modifiers/LimitColorsModifier.php
Normal file
44
src/Drivers/Imagick/Modifiers/LimitColorsModifier.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Imagick\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
|
use Intervention\Image\Traits\CanCheckType;
|
||||||
|
use Intervention\Image\Drivers\Imagick\Image;
|
||||||
|
|
||||||
|
class LimitColorsModifier implements ModifierInterface
|
||||||
|
{
|
||||||
|
use CanCheckType;
|
||||||
|
|
||||||
|
public function __construct(protected int $limit = 0, protected $threshold = 256)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
// no color limit: no reduction
|
||||||
|
if ($this->limit === 0) {
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit is over threshold: no reduction
|
||||||
|
if ($this->limit > $this->threshold) {
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = $this->failIfNotClass($image, Image::class);
|
||||||
|
foreach ($image->getImagick() as $core) {
|
||||||
|
$core->quantizeImage(
|
||||||
|
$this->limit,
|
||||||
|
$core->getImageColorspace(),
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,38 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Intervention\Image\Drivers\Imagick\Traits;
|
|
||||||
|
|
||||||
use Imagick;
|
|
||||||
|
|
||||||
trait CanReduceColors
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns a Imagick from a given image with reduced colors to a given limit.
|
|
||||||
* Reduction is only applied when the given limit is under the given threshold
|
|
||||||
*
|
|
||||||
* @param Imagick $imagick
|
|
||||||
* @param int $limit
|
|
||||||
* @param int $threshold
|
|
||||||
* @return Imagick
|
|
||||||
*/
|
|
||||||
private function maybeReduceColors(Imagick $imagick, int $limit, int $threshold = 256): Imagick
|
|
||||||
{
|
|
||||||
if ($limit === 0) {
|
|
||||||
return $imagick;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($limit > $threshold) {
|
|
||||||
return $imagick;
|
|
||||||
}
|
|
||||||
|
|
||||||
$imagick->quantizeImage(
|
|
||||||
$limit,
|
|
||||||
$imagick->getImageColorspace(),
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
return $imagick;
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user