1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-20 12:41:23 +02:00

Refactor color reduction to modifer

This commit is contained in:
Oliver Vogel
2023-10-31 18:13:59 +01:00
parent 92c37c5dc6
commit b5e31e574f
10 changed files with 126 additions and 126 deletions

View File

@@ -3,15 +3,13 @@
namespace Intervention\Image\Drivers\Gd\Encoders;
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\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class BmpEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
public function __construct(protected int $color_limit = 0)
{
//
@@ -19,9 +17,9 @@ class BmpEncoder extends AbstractEncoder implements EncoderInterface
public function encode(ImageInterface $image): EncodedImage
{
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
$data = $this->getBuffered(function () use ($gd) {
imagebmp($gd, null, false);
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$data = $this->getBuffered(function () use ($image) {
imagebmp($image->frame()->core(), null, false);
});
return new EncodedImage($data, 'image/bmp');

View File

@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Gd\Encoders;
use Intervention\Gif\Builder as GifBuilder;
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\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class GifEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
public function __construct(protected int $color_limit = 0)
{
//
@@ -24,9 +22,9 @@ class GifEncoder extends AbstractEncoder implements EncoderInterface
return $this->encodeAnimated($image);
}
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
$data = $this->getBuffered(function () use ($gd) {
imagegif($gd);
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$data = $this->getBuffered(function () use ($image) {
imagegif($image->frame()->core());
});
return new EncodedImage($data, 'image/gif');

View File

@@ -3,15 +3,13 @@
namespace Intervention\Image\Drivers\Gd\Encoders;
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\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class PngEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
public function __construct(protected int $color_limit = 0)
{
//
@@ -19,9 +17,9 @@ class PngEncoder extends AbstractEncoder implements EncoderInterface
public function encode(ImageInterface $image): EncodedImage
{
$gd = $this->maybeReduceColors($image->frame()->core(), $this->color_limit);
$data = $this->getBuffered(function () use ($gd) {
imagepng($gd, null, -1);
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$data = $this->getBuffered(function () use ($image) {
imagepng($image->frame()->core(), null, -1);
});
return new EncodedImage($data, 'image/png');

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
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\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class BmpEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
public function __construct(protected int $color_limit = 0)
{
//
@@ -23,12 +21,12 @@ class BmpEncoder extends AbstractEncoder implements EncoderInterface
$format = 'bmp';
$compression = Imagick::COMPRESSION_NO;
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$imagick = $image->frame()->core();
$imagick->setFormat($format);
$imagick->setImageFormat($format);
$imagick->setCompression($compression);
$imagick->setImageCompression($compression);
$this->maybeReduceColors($imagick, $this->color_limit);
return new EncodedImage($imagick->getImagesBlob(), 'image/bmp');
}

View File

@@ -5,15 +5,16 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
use Intervention\Image\Drivers\Abstract\Encoders\AbstractEncoder;
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\Exceptions\EncoderException;
use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Traits\CanCheckType;
class GifEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
use CanCheckType;
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.');
}
$image = $this->failIfNotClass($image, Image::class);
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$imagick = $image->getImagick();
$imagick->setFormat($format);
$imagick->setImageFormat($format);
$imagick->setCompression($compression);
$imagick->setImageCompression($compression);
$imagick->optimizeImageLayers();
$this->maybeReduceColors($imagick, $this->color_limit);
$imagick = $imagick->deconstructImages();
return new EncodedImage($imagick->getImagesBlob(), 'image/gif');

View File

@@ -4,15 +4,13 @@ namespace Intervention\Image\Drivers\Imagick\Encoders;
use Imagick;
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\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class PngEncoder extends AbstractEncoder implements EncoderInterface
{
use CanReduceColors;
public function __construct(protected int $color_limit = 0)
{
//
@@ -23,12 +21,12 @@ class PngEncoder extends AbstractEncoder implements EncoderInterface
$format = 'png';
$compression = Imagick::COMPRESSION_ZIP;
$image = $image->modify(new LimitColorsModifier($this->color_limit));
$imagick = $image->frame()->core();
$imagick->setFormat($format);
$imagick->setImageFormat($format);
$imagick->setCompression($compression);
$imagick->setImageCompression($compression);
$this->maybeReduceColors($imagick, $this->color_limit);
return new EncodedImage($imagick->getImagesBlob(), 'image/png');
}

View 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;
}
}

View File

@@ -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;
}
}