mirror of
https://github.com/Intervention/image.git
synced 2025-08-17 19:26:25 +02:00
Implement HSL color model & fix bugs
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
28
src/Colors/Hsl/Channels/Hue.php
Normal file
28
src/Colors/Hsl/Channels/Hue.php
Normal 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;
|
||||
}
|
||||
}
|
28
src/Colors/Hsl/Channels/Luminance.php
Normal file
28
src/Colors/Hsl/Channels/Luminance.php
Normal 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;
|
||||
}
|
||||
}
|
28
src/Colors/Hsl/Channels/Saturation.php
Normal file
28
src/Colors/Hsl/Channels/Saturation.php
Normal 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
148
src/Colors/Hsl/Color.php
Normal 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();
|
||||
}
|
||||
}
|
109
src/Colors/Hsl/Colorspace.php
Normal file
109
src/Colors/Hsl/Colorspace.php
Normal 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)),
|
||||
);
|
||||
}
|
||||
}
|
40
src/Colors/Hsl/Decoders/StringColorDecoder.php
Normal file
40
src/Colors/Hsl/Decoders/StringColorDecoder.php
Normal 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);
|
||||
}
|
||||
}
|
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
74
tests/Colors/Hsl/ChannelTest.php
Normal file
74
tests/Colors/Hsl/ChannelTest.php
Normal 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);
|
||||
}
|
||||
}
|
84
tests/Colors/Hsl/ColorTest.php
Normal file
84
tests/Colors/Hsl/ColorTest.php
Normal 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());
|
||||
}
|
||||
}
|
70
tests/Colors/Hsl/ColorspaceTest.php
Normal file
70
tests/Colors/Hsl/ColorspaceTest.php
Normal 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());
|
||||
}
|
||||
}
|
35
tests/Colors/Hsl/Decoders/StringColorDecoderTest.php
Normal file
35
tests/Colors/Hsl/Decoders/StringColorDecoderTest.php
Normal 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());
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user