From 30ab6a6c5439de155e5487aaced28f4ef682f39b Mon Sep 17 00:00:00 2001 From: Oliver Vogel Date: Sat, 2 Dec 2023 12:07:30 +0100 Subject: [PATCH] Implement HSL color model & fix bugs --- src/Colors/Cmyk/Colorspace.php | 2 + src/Colors/Hsl/Channels/Hue.php | 28 ++++ src/Colors/Hsl/Channels/Luminance.php | 28 ++++ src/Colors/Hsl/Channels/Saturation.php | 28 ++++ src/Colors/Hsl/Color.php | 148 ++++++++++++++++++ src/Colors/Hsl/Colorspace.php | 109 +++++++++++++ .../Hsl/Decoders/StringColorDecoder.php | 40 +++++ src/Colors/Hsv/Colorspace.php | 22 ++- src/Colors/Rgb/Colorspace.php | 31 ++++ src/Drivers/Gd/ColorProcessor.php | 2 +- src/Drivers/Gd/InputHandler.php | 2 + src/Drivers/Imagick/InputHandler.php | 2 + tests/Colors/Cmyk/ColorspaceTest.php | 41 ++++- tests/Colors/Hsl/ChannelTest.php | 74 +++++++++ tests/Colors/Hsl/ColorTest.php | 84 ++++++++++ tests/Colors/Hsl/ColorspaceTest.php | 70 +++++++++ .../Hsl/Decoders/StringColorDecoderTest.php | 35 +++++ tests/Colors/Hsv/ColorspaceTest.php | 47 +++++- tests/Colors/Rgb/ColorspaceTest.php | 53 +++++-- 19 files changed, 817 insertions(+), 29 deletions(-) create mode 100644 src/Colors/Hsl/Channels/Hue.php create mode 100644 src/Colors/Hsl/Channels/Luminance.php create mode 100644 src/Colors/Hsl/Channels/Saturation.php create mode 100644 src/Colors/Hsl/Color.php create mode 100644 src/Colors/Hsl/Colorspace.php create mode 100644 src/Colors/Hsl/Decoders/StringColorDecoder.php create mode 100644 tests/Colors/Hsl/ChannelTest.php create mode 100644 tests/Colors/Hsl/ColorTest.php create mode 100644 tests/Colors/Hsl/ColorspaceTest.php create mode 100644 tests/Colors/Hsl/Decoders/StringColorDecoderTest.php diff --git a/src/Colors/Cmyk/Colorspace.php b/src/Colors/Cmyk/Colorspace.php index 45e1065a..e67072b2 100644 --- a/src/Colors/Cmyk/Colorspace.php +++ b/src/Colors/Cmyk/Colorspace.php @@ -5,6 +5,7 @@ namespace Intervention\Image\Colors\Cmyk; use Intervention\Image\Colors\Rgb\Color as RgbColor; use Intervention\Image\Colors\Cmyk\Color as CmykColor; use Intervention\Image\Colors\Hsv\Color as HsvColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -42,6 +43,7 @@ class Colorspace implements ColorspaceInterface return match (get_class($color)) { RgbColor::class => $this->importRgbColor($color), HsvColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), + HslColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), default => $color, }; } diff --git a/src/Colors/Hsl/Channels/Hue.php b/src/Colors/Hsl/Channels/Hue.php new file mode 100644 index 00000000..e795aebb --- /dev/null +++ b/src/Colors/Hsl/Channels/Hue.php @@ -0,0 +1,28 @@ +channels = [ + new Hue($h), + new Saturation($s), + new Luminance($l), + ]; + } + + public function colorspace(): ColorspaceInterface + { + return new Colorspace(); + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::create() + */ + public static function create(mixed $input): ColorInterface + { + return (new class ([ + Decoders\StringColorDecoder::class, + ]) extends AbstractInputHandler + { + })->handle($input); + } + + /** + * Return the Hue channel + * + * @return ColorChannelInterface + */ + public function hue(): ColorChannelInterface + { + return $this->channel(Hue::class); + } + + /** + * Return the Saturation channel + * + * @return ColorChannelInterface + */ + public function saturation(): ColorChannelInterface + { + return $this->channel(Saturation::class); + } + + /** + * Return the Luminance channel + * + * @return ColorChannelInterface + */ + public function luminance(): ColorChannelInterface + { + return $this->channel(Luminance::class); + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::toArray() + */ + public function toArray(): array + { + return array_map(function (ColorChannelInterface $channel) { + return $channel->value(); + }, $this->channels()); + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::convertTo() + */ + public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface + { + $colorspace = match (true) { + is_object($colorspace) => $colorspace, + default => new $colorspace(), + }; + + return $colorspace->importColor($this); + } + + public function toHex(string $prefix = ''): string + { + return $this->convertTo(RgbColorspace::class)->toHex($prefix); + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::toString() + */ + public function toString(): string + { + return sprintf( + 'hsl(%d, %d%%, %d%%)', + $this->hue()->value(), + $this->saturation()->value(), + $this->luminance()->value() + ); + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::isGreyscale() + */ + public function isGreyscale(): bool + { + return $this->saturation()->value() == 0; + } + + /** + * {@inheritdoc} + * + * @see ColorInterface::__toString() + */ + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/src/Colors/Hsl/Colorspace.php b/src/Colors/Hsl/Colorspace.php new file mode 100644 index 00000000..7a26cfde --- /dev/null +++ b/src/Colors/Hsl/Colorspace.php @@ -0,0 +1,109 @@ +value(); + }, self::$channels, $normalized); + + return new Color(...$values); + } + + public function importColor(ColorInterface $color): ColorInterface + { + return match (get_class($color)) { + CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), + RgbColor::class => $this->importRgbColor($color), + HsvColor::class => $this->importHsvColor($color), + default => $color, + }; + } + + protected function importRgbColor(RgbColor $color): ColorInterface + { + // normalized values of rgb channels + $values = array_map(function ($channel) { + return $channel->normalize(); + }, $color->channels()); + + // take only RGB + $values = array_slice($values, 0, 3); + + // calculate Luminance + $min = min(...$values); + $max = max(...$values); + $luminance = ($max + $min) / 2; + $delta = $max - $min; + + // calculate saturation + $saturation = match (true) { + $delta == 0 => 0, + default => $delta / (1 - abs(2 * $luminance - 1)), + }; + + // calculate hue + list($r, $g, $b) = $values; + $hue = match (true) { + ($delta == 0) => 0, + ($max == $r) => 60 * fmod((($g - $b) / $delta), 6), + ($max == $g) => 60 * ((($b - $r) / $delta) + 2), + ($max == $b) => 60 * ((($r - $g) / $delta) + 4), + default => 0, + }; + + $hue = ($hue + 360) % 360; // normalize hue + + return new Color( + intval(round($hue)), + intval(round($saturation * 100)), + intval(round($luminance * 100)), + ); + } + + protected function importHsvColor(HsvColor $color): ColorInterface + { + // normalized values of hsv channels + list($h, $s, $v) = array_map(function ($channel) { + return $channel->normalize(); + }, $color->channels()); + + // calculate Luminance + $luminance = (2 - $s) * $v / 2; + + // calculate Saturation + $saturation = match (true) { + $luminance == 0 => $s, + $luminance == 1 => 0, + $luminance < .5 => $s * $v / ($luminance * 2), + default => $s * $v / (2 - $luminance * 2), + }; + + return new Color( + intval(round($h * 360)), + intval(round($saturation * 100)), + intval(round($luminance * 100)), + ); + } +} diff --git a/src/Colors/Hsl/Decoders/StringColorDecoder.php b/src/Colors/Hsl/Decoders/StringColorDecoder.php new file mode 100644 index 00000000..21944354 --- /dev/null +++ b/src/Colors/Hsl/Decoders/StringColorDecoder.php @@ -0,0 +1,40 @@ +[0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)?\)$/i'; + if (preg_match($pattern, $input, $matches) != 1) { + throw new DecoderException('Unable to decode input'); + } + + $values = array_map(function ($value) { + return match (strpos($value, '%')) { + false => intval(trim($value)), + default => intval(trim(str_replace('%', '', $value))), + }; + }, [$matches['h'], $matches['s'], $matches['l']]); + + return new Color(...$values); + } +} diff --git a/src/Colors/Hsv/Colorspace.php b/src/Colors/Hsv/Colorspace.php index 419e1392..229ffaf8 100644 --- a/src/Colors/Hsv/Colorspace.php +++ b/src/Colors/Hsv/Colorspace.php @@ -4,6 +4,7 @@ namespace Intervention\Image\Colors\Hsv; use Intervention\Image\Colors\Cmyk\Color as CmykColor; use Intervention\Image\Colors\Rgb\Color as RgbColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -40,13 +41,14 @@ class Colorspace implements ColorspaceInterface return match (get_class($color)) { CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)), RgbColor::class => $this->importRgbColor($color), + HslColor::class => $this->importHslColor($color), default => $color, }; } protected function importRgbColor(RgbColor $color): ColorInterface { - // percentage values of rgb channels + // normalized values of rgb channels $values = array_map(function ($channel) { return $channel->normalize(); }, $color->channels()); @@ -62,6 +64,11 @@ class Colorspace implements ColorspaceInterface // calculate value $v = 100 * $max; + if ($chroma == 0) { + // greyscale color + return new Color(0, 0, intval(round($v))); + } + // calculate saturation $s = 100 * ($chroma / $max); @@ -79,4 +86,17 @@ class Colorspace implements ColorspaceInterface intval(round($v)) ); } + + protected function importHslColor(HslColor $color): ColorInterface + { + // normalized values of hsl channels + list($h, $s, $l) = array_map(function ($channel) { + return $channel->normalize(); + }, $color->channels()); + + $v = $l + $s * min($l, 1 - $l); + $s = ($v == 0) ? 0 : 2 * (1 - $l / $v); + + return $this->colorFromNormalized([$h, $s, $v]); + } } diff --git a/src/Colors/Rgb/Colorspace.php b/src/Colors/Rgb/Colorspace.php index 83adc479..12487e71 100644 --- a/src/Colors/Rgb/Colorspace.php +++ b/src/Colors/Rgb/Colorspace.php @@ -3,6 +3,7 @@ namespace Intervention\Image\Colors\Rgb; use Intervention\Image\Colors\Hsv\Color as HsvColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Cmyk\Color as CmykColor; use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; @@ -40,6 +41,7 @@ class Colorspace implements ColorspaceInterface return match (get_class($color)) { CmykColor::class => $this->importCmykColor($color), HsvColor::class => $this->importHsvColor($color), + HslColor::class => $this->importHslColor($color), default => $color, }; } @@ -78,4 +80,33 @@ class Colorspace implements ColorspaceInterface return $this->colorFromNormalized($values); } + + protected function importHslColor(HslColor $color): ColorInterface + { + // normalized values of hsl channels + list($h, $s, $l) = array_map(function ($channel) { + return $channel->normalize(); + }, $color->channels()); + + $c = (1 - abs(2 * $l - 1)) * $s; + $x = $c * (1 - abs(fmod($h * 6, 2) - 1)); + $m = $l - $c / 2; + + $values = match (true) { + $h < 1 / 6 => [$c, $x, 0], + $h < 2 / 6 => [$x, $c, 0], + $h < 3 / 6 => [0, $c, $x], + $h < 4 / 6 => [0, $x, $c], + $h < 5 / 6 => [$x, 0, $c], + default => [$c, 0, $x], + }; + + $values = array_map(function ($value) use ($m) { + return $value + $m; + }, $values); + + array_push($values, 1); // append alpha channel value + + return $this->colorFromNormalized($values); + } } diff --git a/src/Drivers/Gd/ColorProcessor.php b/src/Drivers/Gd/ColorProcessor.php index 795fcabd..caceff21 100644 --- a/src/Drivers/Gd/ColorProcessor.php +++ b/src/Drivers/Gd/ColorProcessor.php @@ -21,7 +21,7 @@ class ColorProcessor implements ColorProcessorInterface public function colorToNative(ColorInterface $color): int { - // convert color to image colorspace + // convert color to colorspace $color = $color->convertTo($this->colorspace); // gd only supports rgb so the channels can be accessed directly diff --git a/src/Drivers/Gd/InputHandler.php b/src/Drivers/Gd/InputHandler.php index 96a0e162..50a0e55f 100644 --- a/src/Drivers/Gd/InputHandler.php +++ b/src/Drivers/Gd/InputHandler.php @@ -8,6 +8,7 @@ use Intervention\Image\Colors\Rgb\Decoders\HtmlColornameDecoder; use Intervention\Image\Colors\Rgb\Decoders\TransparentColorDecoder; use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder as CmykStringColorDecoder; use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder as HsvStringColorDecoder; +use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder as HslStringColorDecoder; use Intervention\Image\Drivers\AbstractInputHandler; use Intervention\Image\Drivers\Gd\Decoders\ImageObjectDecoder; use Intervention\Image\Drivers\Gd\Decoders\ColorObjectDecoder; @@ -27,6 +28,7 @@ class InputHandler extends AbstractInputHandler RgbStringColorDecoder::class, CmykStringColorDecoder::class, HsvStringColorDecoder::class, + HslStringColorDecoder::class, TransparentColorDecoder::class, HtmlColornameDecoder::class, FilePointerImageDecoder::class, diff --git a/src/Drivers/Imagick/InputHandler.php b/src/Drivers/Imagick/InputHandler.php index 7afaa2a9..621585a1 100644 --- a/src/Drivers/Imagick/InputHandler.php +++ b/src/Drivers/Imagick/InputHandler.php @@ -8,6 +8,7 @@ use Intervention\Image\Colors\Rgb\Decoders\HtmlColornameDecoder; use Intervention\Image\Colors\Rgb\Decoders\TransparentColorDecoder; use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder as CmykStringColorDecoder; use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder as HsvStringColorDecoder; +use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder as HslStringColorDecoder; use Intervention\Image\Drivers\AbstractInputHandler; use Intervention\Image\Drivers\Imagick\Decoders\ImageObjectDecoder; use Intervention\Image\Drivers\Imagick\Decoders\ColorObjectDecoder; @@ -27,6 +28,7 @@ class InputHandler extends AbstractInputHandler RgbStringColorDecoder::class, CmykStringColorDecoder::class, HsvStringColorDecoder::class, + HslStringColorDecoder::class, TransparentColorDecoder::class, HtmlColornameDecoder::class, FilePointerImageDecoder::class, diff --git a/tests/Colors/Cmyk/ColorspaceTest.php b/tests/Colors/Cmyk/ColorspaceTest.php index 94f78bb9..69fca686 100644 --- a/tests/Colors/Cmyk/ColorspaceTest.php +++ b/tests/Colors/Cmyk/ColorspaceTest.php @@ -9,34 +9,61 @@ use Intervention\Image\Colors\Cmyk\Channels\Yellow; use Intervention\Image\Colors\Cmyk\Color as CmykColor; use Intervention\Image\Colors\Rgb\Color as RgbColor; use Intervention\Image\Colors\Hsv\Color as HsvColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Cmyk\Colorspace; use Intervention\Image\Tests\TestCase; /** - * @requires extension gd * @covers \Intervention\Image\Colors\Cmyk\Colorspace */ class ColorspaceTest extends TestCase { - public function testConvertRgbColor(): void + public function testImportRgbColor(): void { $colorspace = new Colorspace(); - $result = $colorspace->importColor(new RgbColor(0, 0, 255)); + + $result = $colorspace->importColor(new RgbColor(255, 0, 255)); $this->assertInstanceOf(CmykColor::class, $result); - $this->assertEquals(100, $result->channel(Cyan::class)->value()); + $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(100, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(0, $result->channel(Key::class)->value()); + + $result = $colorspace->importColor(new RgbColor(127, 127, 127)); + $this->assertInstanceOf(CmykColor::class, $result); + $this->assertEquals(0, $result->channel(Cyan::class)->value()); + $this->assertEquals(0, $result->channel(Magenta::class)->value()); + $this->assertEquals(0, $result->channel(Yellow::class)->value()); + $this->assertEquals(50, $result->channel(Key::class)->value()); } - public function testConvertHsvColor(): void + public function testImportHsvColor(): void { $colorspace = new Colorspace(); - $result = $colorspace->importColor(new HsvColor(240, 100, 100)); + $result = $colorspace->importColor(new HsvColor(0, 0, 50)); $this->assertInstanceOf(CmykColor::class, $result); - $this->assertEquals(100, $result->channel(Cyan::class)->value()); + $this->assertEquals(0, $result->channel(Cyan::class)->value()); + $this->assertEquals(0, $result->channel(Magenta::class)->value()); + $this->assertEquals(0, $result->channel(Yellow::class)->value()); + $this->assertEquals(50, $result->channel(Key::class)->value()); + } + + public function testImportHslColor(): void + { + $colorspace = new Colorspace(); + + $result = $colorspace->importColor(new HslColor(300, 100, 50)); + $this->assertInstanceOf(CmykColor::class, $result); + $this->assertEquals(0, $result->channel(Cyan::class)->value()); $this->assertEquals(100, $result->channel(Magenta::class)->value()); $this->assertEquals(0, $result->channel(Yellow::class)->value()); $this->assertEquals(0, $result->channel(Key::class)->value()); + + $result = $colorspace->importColor(new HslColor(0, 0, 50)); + $this->assertInstanceOf(CmykColor::class, $result); + $this->assertEquals(0, $result->channel(Cyan::class)->value()); + $this->assertEquals(0, $result->channel(Magenta::class)->value()); + $this->assertEquals(0, $result->channel(Yellow::class)->value()); + $this->assertEquals(50, $result->channel(Key::class)->value()); } } diff --git a/tests/Colors/Hsl/ChannelTest.php b/tests/Colors/Hsl/ChannelTest.php new file mode 100644 index 00000000..2c7f9c27 --- /dev/null +++ b/tests/Colors/Hsl/ChannelTest.php @@ -0,0 +1,74 @@ +assertInstanceOf(Hue::class, $channel); + + $channel = new Hue(value: 0); + $this->assertInstanceOf(Hue::class, $channel); + + $channel = new Hue(normalized: 0); + $this->assertInstanceOf(Hue::class, $channel); + + $this->expectException(ColorException::class); + $channel = new Hue(); + + $this->expectException(ColorException::class); + $channel = new Hue(normalized: 2); + } + + public function testValue(): void + { + $channel = new Hue(10); + $this->assertEquals(10, $channel->value()); + } + + public function testNormalize(): void + { + $channel = new Hue(360); + $this->assertEquals(1, $channel->normalize()); + $channel = new Hue(180); + $this->assertEquals(0.5, $channel->normalize()); + $channel = new Hue(0); + $this->assertEquals(0, $channel->normalize()); + $channel = new Luminance(90); + $this->assertEquals(.9, $channel->normalize()); + } + + public function testValidate(): void + { + $this->expectException(ColorException::class); + new Hue(361); + + $this->expectException(ColorException::class); + new Hue(-1); + + $this->expectException(ColorException::class); + new Saturation(101); + + $this->expectException(ColorException::class); + new Saturation(-1); + + $this->expectException(ColorException::class); + new Luminance(101); + + $this->expectException(ColorException::class); + new Luminance(-1); + } +} diff --git a/tests/Colors/Hsl/ColorTest.php b/tests/Colors/Hsl/ColorTest.php new file mode 100644 index 00000000..9039309c --- /dev/null +++ b/tests/Colors/Hsl/ColorTest.php @@ -0,0 +1,84 @@ +assertInstanceOf(Color::class, $color); + } + + public function testColorspace(): void + { + $color = new Color(0, 0, 0); + $this->assertInstanceOf(Colorspace::class, $color->colorspace()); + } + + public function testChannels(): void + { + $color = new Color(10, 20, 30); + $this->assertIsArray($color->channels()); + $this->assertCount(3, $color->channels()); + } + + public function testChannel(): void + { + $color = new Color(10, 20, 30); + $channel = $color->channel(Hue::class); + $this->assertInstanceOf(Hue::class, $channel); + $this->assertEquals(10, $channel->value()); + } + + public function testHueSaturationLuminanceKey(): void + { + $color = new Color(10, 20, 30); + $this->assertInstanceOf(Hue::class, $color->hue()); + $this->assertInstanceOf(Saturation::class, $color->saturation()); + $this->assertInstanceOf(Luminance::class, $color->luminance()); + $this->assertEquals(10, $color->hue()->value()); + $this->assertEquals(20, $color->saturation()->value()); + $this->assertEquals(30, $color->luminance()->value()); + } + + public function testToArray(): void + { + $color = new Color(10, 20, 30); + $this->assertEquals([10, 20, 30], $color->toArray()); + } + + public function testNormalize(): void + { + $color = new Color(180, 50, 25); + $this->assertEquals([.5, 0.5, 0.25], $color->normalize()); + } + + public function testToString(): void + { + $color = new Color(100, 50, 20, 0); + $this->assertEquals('hsl(100, 50%, 20%)', (string) $color); + } + + public function testIsGreyscale(): void + { + $color = new Color(0, 1, 0); + $this->assertFalse($color->isGreyscale()); + + $color = new Color(1, 0, 0); + $this->assertTrue($color->isGreyscale()); + + $color = new Color(0, 0, 1); + $this->assertTrue($color->isGreyscale()); + } +} diff --git a/tests/Colors/Hsl/ColorspaceTest.php b/tests/Colors/Hsl/ColorspaceTest.php new file mode 100644 index 00000000..7ab48c50 --- /dev/null +++ b/tests/Colors/Hsl/ColorspaceTest.php @@ -0,0 +1,70 @@ +importColor(new RgbColor(255, 0, 255)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(300, $result->channel(Hue::class)->value()); + $this->assertEquals(100, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + + $result = $colorspace->importColor(new RgbColor(127, 127, 127)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + } + + public function testImportCmykColor(): void + { + $colorspace = new Colorspace(); + + $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(300, $result->channel(Hue::class)->value()); + $this->assertEquals(100, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + + $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + } + + public function testImportHsvColor(): void + { + $colorspace = new Colorspace(); + + $result = $colorspace->importColor(new HsvColor(300, 100, 100)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(300, $result->channel(Hue::class)->value()); + $this->assertEquals(100, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + + $result = $colorspace->importColor(new HsvColor(0, 0, 50)); + $this->assertInstanceOf(HslColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Luminance::class)->value()); + } +} diff --git a/tests/Colors/Hsl/Decoders/StringColorDecoderTest.php b/tests/Colors/Hsl/Decoders/StringColorDecoderTest.php new file mode 100644 index 00000000..351ba02d --- /dev/null +++ b/tests/Colors/Hsl/Decoders/StringColorDecoderTest.php @@ -0,0 +1,35 @@ +decode('hsl(0,0,0)'); + $this->assertInstanceOf(Color::class, $result); + $this->assertEquals([0, 0, 0], $result->toArray()); + + $result = $decoder->decode('hsl(0, 100, 50)'); + $this->assertInstanceOf(Color::class, $result); + $this->assertEquals([0, 100, 50], $result->toArray()); + + $result = $decoder->decode('hsl(360, 100, 50)'); + $this->assertInstanceOf(Color::class, $result); + $this->assertEquals([360, 100, 50], $result->toArray()); + + $result = $decoder->decode('hsl(180, 100%, 50%)'); + $this->assertInstanceOf(Color::class, $result); + $this->assertEquals([180, 100, 50], $result->toArray()); + } +} diff --git a/tests/Colors/Hsv/ColorspaceTest.php b/tests/Colors/Hsv/ColorspaceTest.php index d956bfbd..297b6960 100644 --- a/tests/Colors/Hsv/ColorspaceTest.php +++ b/tests/Colors/Hsv/ColorspaceTest.php @@ -8,32 +8,63 @@ use Intervention\Image\Colors\Hsv\Channels\Saturation; use Intervention\Image\Colors\Hsv\Channels\Value; use Intervention\Image\Colors\Hsv\Color as HsvColor; use Intervention\Image\Colors\Rgb\Color as RgbColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Hsv\Colorspace; use Intervention\Image\Tests\TestCase; /** - * @requires extension gd * @covers \Intervention\Image\Colors\Hsv\Colorspace */ class ColorspaceTest extends TestCase { - public function testConvertRgbColor(): void + public function testImportRgbColor(): void { $colorspace = new Colorspace(); - $result = $colorspace->importColor(new RgbColor(26, 26, 128)); + + $result = $colorspace->importColor(new RgbColor(255, 0, 255)); $this->assertInstanceOf(HsvColor::class, $result); - $this->assertEquals(240, $result->channel(Hue::class)->value()); - $this->assertEquals(80, $result->channel(Saturation::class)->value()); + $this->assertEquals(300, $result->channel(Hue::class)->value()); + $this->assertEquals(100, $result->channel(Saturation::class)->value()); + $this->assertEquals(100, $result->channel(Value::class)->value()); + + $result = $colorspace->importColor(new RgbColor(127, 127, 127)); + $this->assertInstanceOf(HsvColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); $this->assertEquals(50, $result->channel(Value::class)->value()); } - public function testConvertCmykColor(): void + public function testImportCmykColor(): void { $colorspace = new Colorspace(); - $result = $colorspace->importColor(new CmykColor(100, 0, 100, 0)); + + $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); $this->assertInstanceOf(HsvColor::class, $result); - $this->assertEquals(120, $result->channel(Hue::class)->value()); + $this->assertEquals(300, $result->channel(Hue::class)->value()); $this->assertEquals(100, $result->channel(Saturation::class)->value()); $this->assertEquals(100, $result->channel(Value::class)->value()); + + $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); + $this->assertInstanceOf(HsvColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Value::class)->value()); + } + + public function testImportHslColor(): void + { + $colorspace = new Colorspace(); + + $result = $colorspace->importColor(new HslColor(300, 100, 50)); + $this->assertInstanceOf(HsvColor::class, $result); + $this->assertEquals(300, $result->channel(Hue::class)->value()); + $this->assertEquals(100, $result->channel(Saturation::class)->value()); + $this->assertEquals(100, $result->channel(Value::class)->value()); + + $result = $colorspace->importColor(new HslColor(0, 0, 50)); + $this->assertInstanceOf(HsvColor::class, $result); + $this->assertEquals(0, $result->channel(Hue::class)->value()); + $this->assertEquals(0, $result->channel(Saturation::class)->value()); + $this->assertEquals(50, $result->channel(Value::class)->value()); } } diff --git a/tests/Colors/Rgb/ColorspaceTest.php b/tests/Colors/Rgb/ColorspaceTest.php index 042de1b9..1ea0eb44 100644 --- a/tests/Colors/Rgb/ColorspaceTest.php +++ b/tests/Colors/Rgb/ColorspaceTest.php @@ -8,33 +8,62 @@ use Intervention\Image\Colors\Rgb\Channels\Blue; use Intervention\Image\Colors\Rgb\Channels\Green; use Intervention\Image\Colors\Rgb\Channels\Red; use Intervention\Image\Colors\Rgb\Color as RgbColor; +use Intervention\Image\Colors\Hsl\Color as HslColor; use Intervention\Image\Colors\Rgb\Colorspace; use Intervention\Image\Tests\TestCase; /** - * @requires extension gd * @covers \Intervention\Image\Colors\Rgb\Colorspace */ class ColorspaceTest extends TestCase { - public function testConvertCmykColor(): void + public function testImportCmykColor(): void { $colorspace = new Colorspace(); - $this->assertInstanceOf( - RgbColor::class, - $colorspace->importColor( - new CmykColor(0, 0, 0, 0) - ) - ); + $result = $colorspace->importColor(new CmykColor(0, 100, 0, 0)); + $this->assertInstanceOf(RgbColor::class, $result); + $this->assertEquals(255, $result->channel(Red::class)->value()); + $this->assertEquals(0, $result->channel(Green::class)->value()); + $this->assertEquals(255, $result->channel(Blue::class)->value()); + + $result = $colorspace->importColor(new CmykColor(0, 0, 0, 50)); + $this->assertInstanceOf(RgbColor::class, $result); + $this->assertEquals(127, $result->channel(Red::class)->value()); + $this->assertEquals(127, $result->channel(Green::class)->value()); + $this->assertEquals(127, $result->channel(Blue::class)->value()); } - public function testConvertHsvColor(): void + public function testImportHsvColor(): void { $colorspace = new Colorspace(); - $result = $colorspace->importColor(new HsvColor(240, 80, 50)); + + $result = $colorspace->importColor(new HsvColor(300, 100, 100)); $this->assertInstanceOf(RgbColor::class, $result); - $this->assertEquals(26, $result->channel(Red::class)->value()); - $this->assertEquals(26, $result->channel(Green::class)->value()); + $this->assertEquals(255, $result->channel(Red::class)->value()); + $this->assertEquals(0, $result->channel(Green::class)->value()); + $this->assertEquals(255, $result->channel(Blue::class)->value()); + + $result = $colorspace->importColor(new HsvColor(0, 0, 50)); + $this->assertInstanceOf(RgbColor::class, $result); + $this->assertEquals(128, $result->channel(Red::class)->value()); + $this->assertEquals(128, $result->channel(Green::class)->value()); + $this->assertEquals(128, $result->channel(Blue::class)->value()); + } + + public function testImportHslColor(): void + { + $colorspace = new Colorspace(); + + $result = $colorspace->importColor(new HslColor(300, 100, 50)); + $this->assertInstanceOf(RgbColor::class, $result); + $this->assertEquals(255, $result->channel(Red::class)->value()); + $this->assertEquals(0, $result->channel(Green::class)->value()); + $this->assertEquals(255, $result->channel(Blue::class)->value()); + + $result = $colorspace->importColor(new HslColor(0, 0, 50)); + $this->assertInstanceOf(RgbColor::class, $result); + $this->assertEquals(128, $result->channel(Red::class)->value()); + $this->assertEquals(128, $result->channel(Green::class)->value()); $this->assertEquals(128, $result->channel(Blue::class)->value()); } }