Skip to content

Commit

Permalink
Implement Blending Color
Browse files Browse the repository at this point in the history
  • Loading branch information
olivervogel committed Jan 6, 2024
1 parent ceacb64 commit b198fe6
Show file tree
Hide file tree
Showing 21 changed files with 286 additions and 160 deletions.
89 changes: 89 additions & 0 deletions src/Drivers/Gd/Cloner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Intervention\Image\Drivers\Gd;

use GdImage;
use Intervention\Image\Colors\Rgb\Color;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\SizeInterface;

class Cloner
{
/**
* Create a clone of the given GdImage
*
* @param GdImage $gd
* @return GdImage
*/
public static function clone(GdImage $gd): GdImage
{
// create empty canvas with same size
$clone = static::cloneEmpty($gd);

// transfer actual image to clone
imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd));

return $clone;
}

/**
* Create an "empty" clone of the given GdImage
*
* This only retains the basic data without transferring the actual image.
* It is optionally possible to change the size of the result and set a
* background color.
*
* @param GdImage $gd
* @param null|SizeInterface $size
* @param ColorInterface $background
* @return GdImage
*/
public static function cloneEmpty(
GdImage $gd,
?SizeInterface $size = null,
ColorInterface $background = new Color(255, 255, 255, 0)
): GdImage {
// define size
$size = match (true) {
is_null($size) => new Rectangle(imagesx($gd), imagesy($gd)),
default => $size,
};

// create new gd image with same size or new given size
$clone = imagecreatetruecolor($size->width(), $size->height());

// copy resolution to clone
$resolution = imageresolution($gd);
if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) {
imageresolution($clone, $resolution[0], $resolution[1]);
}

// fill with background
$processor = new ColorProcessor();
imagefill($clone, 0, 0, $processor->colorToNative($background));
imagealphablending($clone, true);
imagesavealpha($clone, true);

return $clone;
}

/**
* Create a clone of an GdImage that is positioned on the specified background color.
* Possible transparent areas are mixed with this color.
*
* @param GdImage $gd
* @param ColorInterface $background
* @return GdImage
*/
public static function cloneBlended(GdImage $gd, ColorInterface $background): GdImage
{
// create empty canvas with same size
$clone = static::cloneEmpty($gd, background: $background);

// transfer actual image to clone
imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd));

return $clone;
}
}
11 changes: 5 additions & 6 deletions src/Drivers/Gd/Decoders/BinaryImageDecoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Gif\Decoder as GifDecoder;
use Intervention\Gif\Splitter as GifSplitter;
use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\Core;
use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\Exceptions\DecoderException;
Expand Down Expand Up @@ -38,17 +39,15 @@ private function decodeString(string $input): ImageInterface
throw new DecoderException('Unable to decode input');
}

if (!imageistruecolor($gd)) {
imagepalettetotruecolor($gd);
}

imagesavealpha($gd, true);
// clone image to normalize transparency to #ffffff00
$normalized = Cloner::clone($gd);
imagedestroy($gd);

// build image instance
$image = new Image(
new Driver(),
new Core([
new Frame($gd)
new Frame($normalized)
]),
$this->extractExifData($input)
);
Expand Down
8 changes: 5 additions & 3 deletions src/Drivers/Gd/Encoders/JpegEncoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Intervention\Image\Drivers\Gd\Encoders;

use Intervention\Image\Drivers\DriverSpecializedEncoder;
use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\EncodedImage;
use Intervention\Image\Interfaces\ImageInterface;

Expand All @@ -13,9 +14,10 @@ class JpegEncoder extends DriverSpecializedEncoder
{
public function encode(ImageInterface $image): EncodedImage
{
$gd = $image->core()->native();
$data = $this->getBuffered(function () use ($gd) {
imagejpeg($gd, null, $this->quality);
$output = Cloner::cloneBlended($image->core()->native(), background: $image->blendingColor());

$data = $this->getBuffered(function () use ($output) {
imagejpeg($output, null, $this->quality);
});

return new EncodedImage($data, 'image/jpeg');
Expand Down
31 changes: 1 addition & 30 deletions src/Drivers/Gd/Frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,35 +107,6 @@ public function setOffsetTop(int $offset): FrameInterface
*/
public function __clone(): void
{
// create new clone image
$width = imagesx($this->native);
$height = imagesy($this->native);
$clone = match (imageistruecolor($this->native)) {
true => imagecreatetruecolor($width, $height),
default => imagecreate($width, $height),
};

// transfer resolution to clone
$resolution = imageresolution($this->native);
if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) {
imageresolution($clone, $resolution[0], $resolution[1]);
}

// transfer transparency to clone
$transIndex = imagecolortransparent($this->native);
if ($transIndex != -1) {
$rgba = imagecolorsforindex($clone, $transIndex);
$transColor = imagecolorallocatealpha($clone, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
imagefill($clone, 0, 0, $transColor);
imagecolortransparent($clone, $transColor);
} else {
imagealphablending($clone, false);
imagesavealpha($clone, true);
}

// transfer actual image to clone
imagecopy($clone, $this->native, 0, 0, 0, 0, $width, $height);

$this->native = $clone;
$this->native = Cloner::clone($this->native);
}
}
24 changes: 9 additions & 15 deletions src/Drivers/Gd/Modifiers/ContainModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
use Intervention\Image\Colors\Rgb\Channels\Blue;
use Intervention\Image\Colors\Rgb\Channels\Green;
use Intervention\Image\Colors\Rgb\Channels\Red;
use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\SpecializedModifier;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Modifiers\FillModifier;

/**
* @method SizeInterface getCropSize(ImageInterface $image)
Expand All @@ -27,9 +27,10 @@ public function apply(ImageInterface $image): ImageInterface
$crop = $this->getCropSize($image);
$resize = $this->getResizeSize($image);
$background = $this->driver()->handleInput($this->background);
$blendingColor = $image->blendingColor();

foreach ($image as $frame) {
$this->modify($frame, $crop, $resize, $background);
$this->modify($frame, $crop, $resize, $background, $blendingColor);
}

return $image;
Expand All @@ -39,26 +40,19 @@ protected function modify(
FrameInterface $frame,
SizeInterface $crop,
SizeInterface $resize,
ColorInterface $background
ColorInterface $background,
ColorInterface $blendingColor
): void {
// create new gd image
$modified = $this->driver()->createImage(
$resize->width(),
$resize->height()
)->modify(
new FillModifier($background)
)->core()->native();

// retain resolution
$this->copyResolution($frame->native(), $modified);
$modified = Cloner::cloneEmpty($frame->native(), $resize, $background);

// make image area transparent to keep transparency
// even if background-color is set
$transparent = imagecolorallocatealpha(
$modified,
$background->channel(Red::class)->value(),
$background->channel(Green::class)->value(),
$background->channel(Blue::class)->value(),
$blendingColor->channel(Red::class)->value(),
$blendingColor->channel(Green::class)->value(),
$blendingColor->channel(Blue::class)->value(),
127,
);
imagealphablending($modified, false); // do not blend / just overwrite
Expand Down
24 changes: 3 additions & 21 deletions src/Drivers/Gd/Modifiers/CoverModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Intervention\Image\Drivers\Gd\Modifiers;

use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\SpecializedModifier;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
Expand All @@ -28,31 +29,12 @@ public function apply(ImageInterface $image): ImageInterface
protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void
{
// create new image
$modified = $this->driver()->createImage(
$resize->width(),
$resize->height()
)->core()->native();

// get original image
$original = $frame->native();

// retain resolution
$this->copyResolution($original, $modified);

// preserve transparency
$transIndex = imagecolortransparent($original);

if ($transIndex != -1) {
$rgba = imagecolorsforindex($modified, $transIndex);
$transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
imagefill($modified, 0, 0, $transColor);
imagecolortransparent($modified, $transColor);
}
$modified = Cloner::cloneEmpty($frame->native(), $resize);

// copy content from resource
imagecopyresampled(
$modified,
$original,
$frame->native(),
0,
0,
$crop->pivot()->x(),
Expand Down
24 changes: 3 additions & 21 deletions src/Drivers/Gd/Modifiers/CropModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Intervention\Image\Drivers\Gd\Modifiers;

use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\SpecializedModifier;
use Intervention\Image\Interfaces\FrameInterface;
use Intervention\Image\Interfaces\ImageInterface;
Expand All @@ -28,31 +29,12 @@ public function apply(ImageInterface $image): ImageInterface
protected function cropFrame(FrameInterface $frame, SizeInterface $resizeTo): void
{
// create new image
$modified = $this->driver()
->createImage($resizeTo->width(), $resizeTo->height())
->core()
->native();

// get original image
$original = $frame->native();

// retain resolution
$this->copyResolution($original, $modified);

// preserve transparency
$transIndex = imagecolortransparent($original);

if ($transIndex != -1) {
$rgba = imagecolorsforindex($modified, $transIndex);
$transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
imagefill($modified, 0, 0, $transColor);
imagecolortransparent($modified, $transColor);
}
$modified = Cloner::cloneEmpty($frame->native(), $resizeTo);

// copy content from resource
imagecopyresampled(
$modified,
$original,
$frame->native(),
0,
0,
$resizeTo->pivot()->x() + $this->offset_x,
Expand Down
6 changes: 2 additions & 4 deletions src/Drivers/Gd/Modifiers/QuantizeColorsModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Intervention\Image\Drivers\Gd\Modifiers;

use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\SpecializedModifier;
use Intervention\Image\Exceptions\InputException;
use Intervention\Image\Interfaces\ImageInterface;
Expand Down Expand Up @@ -33,10 +34,7 @@ public function apply(ImageInterface $image): ImageInterface

foreach ($image as $frame) {
// create new image for color quantization
$reduced = imagecreatetruecolor($width, $height);

// retain resolution
$this->copyResolution($frame->native(), $reduced);
$reduced = Cloner::cloneEmpty($frame->native(), background: $image->blendingColor());

// fill with background
imagefill($reduced, 0, 0, $background);
Expand Down
15 changes: 4 additions & 11 deletions src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Intervention\Image\Colors\Rgb\Channels\Blue;
use Intervention\Image\Colors\Rgb\Channels\Green;
use Intervention\Image\Colors\Rgb\Channels\Red;
use Intervention\Image\Drivers\Gd\Cloner;
use Intervention\Image\Drivers\Gd\SpecializedModifier;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\FrameInterface;
Expand Down Expand Up @@ -35,16 +36,8 @@ protected function modify(
SizeInterface $resize,
ColorInterface $background,
): void {
// create new gd image
$modified = $this->driver()->createImage(
$resize->width(),
$resize->height()
)->modify(
new FillModifier($background)
)->core()->native();

// retain resolution
$this->copyResolution($frame->native(), $modified);
// create new canvas with target size & target background color
$modified = Cloner::cloneEmpty($frame->native(), $resize, $background);

// make image area transparent to keep transparency
// even if background-color is set
Expand All @@ -57,7 +50,7 @@ protected function modify(
);

imagealphablending($modified, false); // do not blend / just overwrite
imagecolortransparent($modified, $transparent);
// imagecolortransparent($modified, $transparent);
imagefilledrectangle(
$modified,
$resize->pivot()->x() * -1,
Expand Down
Loading

0 comments on commit b198fe6

Please sign in to comment.