Skip to content

Commit 1c68e5f

Browse files
authored
Fix incorrect color results after color reduction (#1410)
* 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
1 parent 436460e commit 1c68e5f

File tree

5 files changed

+97
-7
lines changed

5 files changed

+97
-7
lines changed

src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorI
3636
{
3737
$index = @imagecolorat($gd, $this->x, $this->y);
3838

39+
if (!imageistruecolor($gd)) {
40+
$index = imagecolorsforindex($gd, $index);
41+
}
42+
3943
if ($index === false) {
4044
throw new GeometryException(
4145
'The specified position is not in the valid image area.'

src/Drivers/Gd/ColorProcessor.php

+67-6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ColorProcessor implements ColorProcessorInterface
2525
*/
2626
public function __construct(protected ColorspaceInterface $colorspace = new Colorspace())
2727
{
28+
//
2829
}
2930

3031
/**
@@ -57,14 +58,29 @@ public function colorToNative(ColorInterface $color): int
5758
*/
5859
public function nativeToColor(mixed $value): ColorInterface
5960
{
60-
if (!is_int($value)) {
61-
throw new ColorException('GD driver can only decode colors in integer format.');
61+
if (!is_int($value) && !is_array($value)) {
62+
throw new ColorException('GD driver can only decode colors in integer and array format.');
6263
}
6364

64-
$a = ($value >> 24) & 0xFF;
65-
$r = ($value >> 16) & 0xFF;
66-
$g = ($value >> 8) & 0xFF;
67-
$b = $value & 0xFF;
65+
if (is_array($value)) {
66+
// array conversion
67+
if (!$this->isValidArrayColor($value)) {
68+
throw new ColorException(
69+
'GD driver can only decode array color format array{red: int, green: int, blue: int, alpha: int}.',
70+
);
71+
}
72+
73+
$r = $value['red'];
74+
$g = $value['green'];
75+
$b = $value['blue'];
76+
$a = $value['alpha'];
77+
} else {
78+
// integer conversion
79+
$a = ($value >> 24) & 0xFF;
80+
$r = ($value >> 16) & 0xFF;
81+
$g = ($value >> 8) & 0xFF;
82+
$b = $value & 0xFF;
83+
}
6884

6985
// convert gd apha integer to intervention alpha integer
7086
// ([opaque]0-127[transparent]) to ([opaque]255-0[transparent])
@@ -93,4 +109,49 @@ protected function convertRange(
93109
): float|int {
94110
return ceil(((($input - $min) * ($targetMax - $targetMin)) / ($max - $min)) + $targetMin);
95111
}
112+
113+
/**
114+
* Check if given array is valid color format
115+
* array{red: int, green: int, blue: int, alpha: int}
116+
* i.e. result of imagecolorsforindex()
117+
*
118+
* @param array<mixed> $color
119+
* @return bool
120+
*/
121+
private function isValidArrayColor(array $color): bool
122+
{
123+
if (!array_key_exists('red', $color)) {
124+
return false;
125+
}
126+
127+
if (!array_key_exists('green', $color)) {
128+
return false;
129+
}
130+
131+
if (!array_key_exists('blue', $color)) {
132+
return false;
133+
}
134+
135+
if (!array_key_exists('alpha', $color)) {
136+
return false;
137+
}
138+
139+
if (!is_int($color['red'])) {
140+
return false;
141+
}
142+
143+
if (!is_int($color['green'])) {
144+
return false;
145+
}
146+
147+
if (!is_int($color['blue'])) {
148+
return false;
149+
}
150+
151+
if (!is_int($color['alpha'])) {
152+
return false;
153+
}
154+
155+
return true;
156+
}
96157
}

tests/Unit/Drivers/Gd/ColorProcessorTest.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testColorToNative(): void
2626
$this->assertEquals(16725760, $result);
2727
}
2828

29-
public function testNativeToColor(): void
29+
public function testNativeToColorInteger(): void
3030
{
3131
$processor = new ColorProcessor();
3232
$result = $processor->nativeToColor(16725760);
@@ -37,6 +37,17 @@ public function testNativeToColor(): void
3737
$this->assertEquals(255, $result->channel(Alpha::class)->value());
3838
}
3939

40+
public function testNativeToColorArray(): void
41+
{
42+
$processor = new ColorProcessor();
43+
$result = $processor->nativeToColor(['red' => 255, 'green' => 55, 'blue' => 0, 'alpha' => 0]);
44+
$this->assertInstanceOf(Color::class, $result);
45+
$this->assertEquals(255, $result->channel(Red::class)->value());
46+
$this->assertEquals(55, $result->channel(Green::class)->value());
47+
$this->assertEquals(0, $result->channel(Blue::class)->value());
48+
$this->assertEquals(255, $result->channel(Alpha::class)->value());
49+
}
50+
4051
public function testNativeToColorInvalid(): void
4152
{
4253
$processor = new ColorProcessor();

tests/Unit/Drivers/Gd/Modifiers/QuantizeColorsModifierTest.php

+7
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,11 @@ private function assertColorCount(int $count, ImageInterface $image): void
5555

5656
$this->assertEquals(count($colors), $count);
5757
}
58+
59+
public function testVerifyColorValueAfterQuantization(): void
60+
{
61+
$image = $this->createTestImage(3, 2)->fill('f00');
62+
$image->modify(new QuantizeColorsModifier(1));
63+
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1), 4);
64+
}
5865
}

tests/Unit/Drivers/Imagick/Modifiers/QuantizeColorsModifierTest.php

+7
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,11 @@ public function testInvalidColorInput(): void
3737
$this->expectException(InputException::class);
3838
$image->modify(new QuantizeColorsModifier(0));
3939
}
40+
41+
public function testVerifyColorValueAfterQuantization(): void
42+
{
43+
$image = $this->createTestImage(3, 2)->fill('f00');
44+
$image->modify(new QuantizeColorsModifier(1));
45+
$this->assertColor(255, 0, 0, 255, $image->pickColor(1, 1));
46+
}
4047
}

0 commit comments

Comments
 (0)