1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-18 03:31:19 +02:00

Implement HSL color model & fix bugs

This commit is contained in:
Oliver Vogel
2023-12-02 12:07:30 +01:00
parent 7a1ac41cb5
commit 30ab6a6c54
19 changed files with 817 additions and 29 deletions

View File

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

View File

@@ -0,0 +1,28 @@
<?php
namespace Intervention\Image\Colors\Hsl\Channels;
use Intervention\Image\Colors\AbstractColorChannel;
class Hue extends AbstractColorChannel
{
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::min()
*/
public function min(): int
{
return 0;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::max()
*/
public function max(): int
{
return 360;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Intervention\Image\Colors\Hsl\Channels;
use Intervention\Image\Colors\AbstractColorChannel;
class Luminance extends AbstractColorChannel
{
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::min()
*/
public function min(): int
{
return 0;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::max()
*/
public function max(): int
{
return 100;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Intervention\Image\Colors\Hsl\Channels;
use Intervention\Image\Colors\AbstractColorChannel;
class Saturation extends AbstractColorChannel
{
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::min()
*/
public function min(): int
{
return 0;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::max()
*/
public function max(): int
{
return 100;
}
}

148
src/Colors/Hsl/Color.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
namespace Intervention\Image\Colors\Hsl;
use Intervention\Image\Colors\Hsl\Channels\Hue;
use Intervention\Image\Colors\Hsl\Channels\Luminance;
use Intervention\Image\Colors\Hsl\Channels\Saturation;
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
use Intervention\Image\Colors\Traits\CanHandleChannels;
use Intervention\Image\Drivers\AbstractInputHandler;
use Intervention\Image\Interfaces\ColorChannelInterface;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
class Color implements ColorInterface
{
use CanHandleChannels;
/**
* Color channels
*/
protected array $channels;
public function __construct(int $h, int $s, int $l)
{
$this->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();
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Intervention\Image\Colors\Hsl;
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\Rgb\Colorspace as RgbColorspace;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
class Colorspace implements ColorspaceInterface
{
public static $channels = [
Channels\Hue::class,
Channels\Saturation::class,
Channels\Luminance::class
];
/**
* {@inheritdoc}
*
* @see ColorspaceInterface::colorFromNormalized()
*/
public function colorFromNormalized(array $normalized): ColorInterface
{
$values = array_map(function ($classname, $value_normalized) {
return (new $classname(normalized: $value_normalized))->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)),
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Intervention\Image\Colors\Hsl\Decoders;
use Intervention\Image\Colors\Hsl\Color;
use Intervention\Image\Drivers\AbstractDecoder;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\DecoderInterface;
use Intervention\Image\Interfaces\ImageInterface;
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
{
/**
* Decode hsl color strings
*
* @param mixed $input
* @return ImageInterface|ColorInterface
*/
public function decode($input): ImageInterface|ColorInterface
{
if (! is_string($input)) {
throw new DecoderException('Unable to decode input');
}
$pattern = '/^hsl\((?P<h>[0-9\.]+), ?(?P<s>[0-9\.]+%?), ?(?P<l>[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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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,

View File

@@ -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,

View File

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

View File

@@ -0,0 +1,74 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsl;
use Intervention\Image\Colors\Hsl\Channels\Hue;
use Intervention\Image\Colors\Hsl\Channels\Saturation;
use Intervention\Image\Colors\Hsl\Channels\Luminance;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Tests\TestCase;
/**
* @covers \Intervention\Image\Colors\Hsl\Channels\Hue
* @covers \Intervention\Image\Colors\Hsl\Channels\Saturation
* @covers \Intervention\Image\Colors\Hsl\Channels\Value
*/
class ChannelTest extends TestCase
{
public function testConstructor(): void
{
$channel = new Hue(0);
$this->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);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsl;
use Intervention\Image\Colors\Hsl\Channels\Hue;
use Intervention\Image\Colors\Hsl\Channels\Luminance;
use Intervention\Image\Colors\Hsl\Channels\Saturation;
use Intervention\Image\Colors\Hsl\Color;
use Intervention\Image\Colors\Hsl\Colorspace;
use Intervention\Image\Tests\TestCase;
/**
* @covers \Intervention\Image\Colors\Hsl\Color
*/
class ColorTest extends TestCase
{
public function testConstructor(): void
{
$color = new Color(0, 0, 0);
$this->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());
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsl;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsl\Channels\Hue;
use Intervention\Image\Colors\Hsl\Channels\Luminance;
use Intervention\Image\Colors\Hsl\Channels\Saturation;
use Intervention\Image\Colors\Hsl\Color as HslColor;
use Intervention\Image\Colors\Rgb\Color as RgbColor;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
use Intervention\Image\Colors\Hsl\Colorspace;
use Intervention\Image\Tests\TestCase;
/**
* @covers \Intervention\Image\Colors\Hsl\Colorspace
*/
class ColorspaceTest extends TestCase
{
public function testImportRgbColor(): void
{
$colorspace = new Colorspace();
$result = $colorspace->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());
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsl\Decoders;
use Intervention\Image\Colors\Hsl\Color;
use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder;
use Intervention\Image\Tests\TestCase;
/**
* @requires extension gd
* @covers \Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder
*/
class StringColorDecoderTest extends TestCase
{
public function testDecode(): void
{
$decoder = new StringColorDecoder();
$result = $decoder->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());
}
}

View File

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

View File

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