Skip to content

Commit

Permalink
Fix incorrect color results after color reduction (#1410)
Browse files Browse the repository at this point in the history
* Add tests to verify issue 1409
* Enable GD's ColorProcessor to resolve array color format
* Fix bug when reading colors from palette GDImage
* Add detailed type hint
  • Loading branch information
olivervogel authored Jan 4, 2025
1 parent 436460e commit 1c68e5f
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorI
{
$index = @imagecolorat($gd, $this->x, $this->y);

if (!imageistruecolor($gd)) {
$index = imagecolorsforindex($gd, $index);
}

if ($index === false) {
throw new GeometryException(
'The specified position is not in the valid image area.'
Expand Down
73 changes: 67 additions & 6 deletions src/Drivers/Gd/ColorProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ColorProcessor implements ColorProcessorInterface
*/
public function __construct(protected ColorspaceInterface $colorspace = new Colorspace())
{
//
}

/**
Expand Down Expand Up @@ -57,14 +58,29 @@ public function colorToNative(ColorInterface $color): int
*/
public function nativeToColor(mixed $value): ColorInterface
{
if (!is_int($value)) {
throw new ColorException('GD driver can only decode colors in integer format.');
if (!is_int($value) && !is_array($value)) {
throw new ColorException('GD driver can only decode colors in integer and array format.');
}

$a = ($value >> 24) & 0xFF;
$r = ($value >> 16) & 0xFF;
$g = ($value >> 8) & 0xFF;
$b = $value & 0xFF;
if (is_array($value)) {
// array conversion
if (!$this->isValidArrayColor($value)) {
throw new ColorException(
'GD driver can only decode array color format array{red: int, green: int, blue: int, alpha: int}.',
);
}

$r = $value['red'];
$g = $value['green'];
$b = $value['blue'];
$a = $value['alpha'];
} else {
// integer conversion
$a = ($value >> 24) & 0xFF;
$r = ($value >> 16) & 0xFF;
$g = ($value >> 8) & 0xFF;
$b = $value & 0xFF;
}

// convert gd apha integer to intervention alpha integer
// ([opaque]0-127[transparent]) to ([opaque]255-0[transparent])
Expand Down Expand Up @@ -93,4 +109,49 @@ protected function convertRange(
): float|int {
return ceil(((($input - $min) * ($targetMax - $targetMin)) / ($max - $min)) + $targetMin);
}

/**
* Check if given array is valid color format
* array{red: int, green: int, blue: int, alpha: int}
* i.e. result of imagecolorsforindex()
*
* @param array<mixed> $color
* @return bool
*/
private function isValidArrayColor(array $color): bool
{
if (!array_key_exists('red', $color)) {
return false;
}

if (!array_key_exists('green', $color)) {
return false;
}

if (!array_key_exists('blue', $color)) {
return false;
}

if (!array_key_exists('alpha', $color)) {
return false;
}

if (!is_int($color['red'])) {
return false;
}

if (!is_int($color['green'])) {
return false;
}

if (!is_int($color['blue'])) {
return false;
}

if (!is_int($color['alpha'])) {
return false;
}

return true;
}
}
13 changes: 12 additions & 1 deletion tests/Unit/Drivers/Gd/ColorProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function testColorToNative(): void
$this->assertEquals(16725760, $result);
}

public function testNativeToColor(): void
public function testNativeToColorInteger(): void
{
$processor = new ColorProcessor();
$result = $processor->nativeToColor(16725760);
Expand All @@ -37,6 +37,17 @@ public function testNativeToColor(): void
$this->assertEquals(255, $result->channel(Alpha::class)->value());
}

public function testNativeToColorArray(): void
{
$processor = new ColorProcessor();
$result = $processor->nativeToColor(['red' => 255, 'green' => 55, 'blue' => 0, 'alpha' => 0]);
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals(255, $result->channel(Red::class)->value());
$this->assertEquals(55, $result->channel(Green::class)->value());
$this->assertEquals(0, $result->channel(Blue::class)->value());
$this->assertEquals(255, $result->channel(Alpha::class)->value());
}

public function testNativeToColorInvalid(): void
{
$processor = new ColorProcessor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ private function assertColorCount(int $count, ImageInterface $image): void

$this->assertEquals(count($colors), $count);
}

public function testVerifyColorValueAfterQuantization(): void
{
$image = $this->createTestImage(3, 2)->fill('f00');
$image->modify(new QuantizeColorsModifier(1));
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1), 4);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,11 @@ public function testInvalidColorInput(): void
$this->expectException(InputException::class);
$image->modify(new QuantizeColorsModifier(0));
}

public function testVerifyColorValueAfterQuantization(): void
{
$image = $this->createTestImage(3, 2)->fill('f00');
$image->modify(new QuantizeColorsModifier(1));
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
}
}

0 comments on commit 1c68e5f

Please sign in to comment.