1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-29 00:29:55 +02:00

Implement HSV color space

This commit is contained in:
Oliver Vogel
2023-12-01 14:20:56 +01:00
parent 81b95db65e
commit 05059e7f1f
22 changed files with 815 additions and 151 deletions

View File

@@ -0,0 +1,91 @@
<?php
namespace Intervention\Image\Colors;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorChannelInterface;
abstract class AbstractColorChannel implements ColorChannelInterface
{
protected int $value;
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__construct()
*/
public function __construct(int $value = null, float $normalized = null)
{
$this->value = $this->validate(
match (true) {
is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())),
is_numeric($value) && is_null($normalized) => $value,
default => throw new ColorException('Color channels must either have a value or a normalized value')
}
);
}
/**
* Alias of value()
*
* @return int
*/
public function toInt(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::value()
*/
public function value(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::normalize()
*/
public function normalize($precision = 32): float
{
return round($this->value() / $this->max(), $precision);
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::validate()
*/
public function validate(mixed $value): mixed
{
if ($value < $this->min() || $value > $this->max()) {
throw new ColorException('Color channel value must be in range ' . $this->min() . ' to ' . $this->max());
}
return $value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::toString()
*/
public function toString(): string
{
return (string) $this->value();
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__toString()
*/
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -2,39 +2,10 @@
namespace Intervention\Image\Colors\Cmyk\Channels;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorChannelInterface;
use Intervention\Image\Colors\AbstractColorChannel;
class Cyan implements ColorChannelInterface
class Cyan extends AbstractColorChannel
{
protected int $value;
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__construct()
*/
public function __construct(int $value = null, float $normalized = null)
{
$this->value = $this->validate(
match (true) {
is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())),
is_numeric($value) && is_null($normalized) => $value,
default => throw new ColorException('Color channels must either have a value or a normalized value')
}
);
}
public function value(): int
{
return $this->value;
}
public function normalize($precision = 32): float
{
return round($this->value() / $this->max(), $precision);
}
public function min(): int
{
return 0;
@@ -44,23 +15,4 @@ class Cyan implements ColorChannelInterface
{
return 100;
}
public function validate(mixed $value): mixed
{
if ($value < $this->min() || $value > $this->max()) {
throw new ColorException('CMYK color values must be in range 0-100.');
}
return $value;
}
public function toString(): string
{
return (string) $this->value();
}
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -4,6 +4,8 @@ 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\Rgb\Colorspace as RgbColorspace;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
@@ -39,6 +41,7 @@ class Colorspace implements ColorspaceInterface
{
return match (get_class($color)) {
RgbColor::class => $this->convertRgbColor($color),
HsvColor::class => $this->convertRgbColor($color->convertTo(RgbColorspace::class)),
default => $color,
};
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Intervention\Image\Colors\Hsv\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\Hsv\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;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Intervention\Image\Colors\Hsv\Channels;
use Intervention\Image\Colors\AbstractColorChannel;
class Value 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/Hsv/Color.php Normal file
View File

@@ -0,0 +1,148 @@
<?php
namespace Intervention\Image\Colors\Hsv;
use Intervention\Image\Colors\Hsv\Channels\Hue;
use Intervention\Image\Colors\Hsv\Channels\Saturation;
use Intervention\Image\Colors\Hsv\Channels\Value;
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 $v)
{
$this->channels = [
new Hue($h),
new Saturation($s),
new Value($v),
];
}
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 Value channel
*
* @return ColorChannelInterface
*/
public function value(): ColorChannelInterface
{
return $this->channel(Value::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->convertColor($this);
}
public function toHex(string $prefix = ''): string
{
return $this->convertTo(RgbColorspace::class)->toHex($prefix);
}
/**
* {@inheritdoc}
*
* @see ColorInterface::toString()
*/
public function toString(): string
{
return sprintf(
'hsv(%d, %d%%, %d%%)',
$this->hue()->value(),
$this->saturation()->value(),
$this->value()->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,77 @@
<?php
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\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\Value::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 convertColor(ColorInterface $color): ColorInterface
{
return match (get_class($color)) {
CmykColor::class => $this->convertRgbColor($color->convertTo(RgbColorspace::class)),
RgbColor::class => $this->convertRgbColor($color),
default => $color,
};
}
protected function convertRgbColor(RgbColor $color): Color
{
// percentage values of rgb channels
$values = array_map(function ($channel) {
return $channel->normalize();
}, $color->channels());
// take only RGB
$values = array_slice($values, 0, 3);
// calculate chroma
$min = min(...$values);
$max = max(...$values);
$chroma = $max - $min;
// calculate value
$v = 100 * $max;
// calculate saturation
$s = 100 * ($chroma / $max);
// calculate hue
list($r, $g, $b) = $values;
$h = match (true) {
($r == $min) => 3 - (($g - $b) / $chroma),
($b == $min) => 1 - (($r - $g) / $chroma),
default => 5 - (($b - $r) / $chroma),
} * 60;
return new Color(
intval(round($h)),
intval(round($s)),
intval(round($v))
);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Intervention\Image\Colors\Hsv\Decoders;
use Intervention\Image\Colors\Hsv\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 hsv/hsb 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 = '/^hs(v|b)\((?P<h>[0-9\.]+), ?(?P<s>[0-9\.]+%?), ?(?P<v>[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['v']]);
return new Color(...$values);
}
}

View File

@@ -8,9 +8,4 @@ class Alpha extends Red
{
return strval(round($this->normalize(), 6));
}
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -2,59 +2,10 @@
namespace Intervention\Image\Colors\Rgb\Channels;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorChannelInterface;
use Intervention\Image\Colors\AbstractColorChannel;
class Red implements ColorChannelInterface
class Red extends AbstractColorChannel
{
protected int $value;
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__construct()
*/
public function __construct(int $value = null, float $normalized = null)
{
$this->value = $this->validate(
match (true) {
is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())),
is_numeric($value) && is_null($normalized) => $value,
default => throw new ColorException('Color channels must either have a value or a normalized value')
}
);
}
/**
* Alias of value()
*
* @return int
*/
public function toInt(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::value()
*/
public function value(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::normalize()
*/
public function normalize($precision = 32): float
{
return round($this->value() / $this->max(), $precision);
}
/**
* {@inheritdoc}
*
@@ -74,38 +25,4 @@ class Red implements ColorChannelInterface
{
return 255;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::validate()
*/
public function validate(mixed $value): mixed
{
if ($value < $this->min() || $value > $this->max()) {
throw new ColorException('RGB color values must be in range 0-255.');
}
return $value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::toString()
*/
public function toString(): string
{
return (string) $this->value();
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__toString()
*/
public function __toString(): string
{
return $this->toString();
}
}

View File

@@ -2,6 +2,7 @@
namespace Intervention\Image\Colors\Rgb;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
@@ -18,7 +19,7 @@ class Colorspace implements ColorspaceInterface
/**
* {@inheritdoc}
*
* @see ColorspaceInterface::createColor()
* @see ColorspaceInterface::colorFromNormalized()
*/
public function colorFromNormalized(array $normalized): ColorInterface
{
@@ -29,10 +30,16 @@ class Colorspace implements ColorspaceInterface
return new Color(...$values);
}
/**
* {@inheritdoc}
*
* @see ColorspaceInterface::convertColor()
*/
public function convertColor(ColorInterface $color): ColorInterface
{
return match (get_class($color)) {
CmykColor::class => $this->convertCmykColor($color),
HsvColor::class => $this->convertHsvColor($color),
default => $color,
};
}
@@ -45,4 +52,30 @@ class Colorspace implements ColorspaceInterface
(int) (255 * (1 - $color->yellow()->normalize()) * (1 - $color->key()->normalize())),
);
}
protected function convertHsvColor(HsvColor $color): Color
{
$chroma = $color->value()->normalize() * $color->saturation()->normalize();
$hue = $color->hue()->normalize() * 6;
$x = $chroma * (1 - abs(fmod($hue, 2) - 1));
// connect channel values
$values = match (true) {
$hue < 1 => [$chroma, $x, 0],
$hue < 2 => [$x, $chroma, 0],
$hue < 3 => [0, $chroma, $x],
$hue < 4 => [0, $x, $chroma],
$hue < 5 => [$x, 0, $chroma],
default => [$chroma, 0, $x],
};
// add to each value
$values = array_map(function ($value) use ($color, $chroma) {
return $value + ($color->value()->normalize() - $chroma);
}, $values);
array_push($values, 1); // append alpha channel value
return $this->colorFromNormalized($values);
}
}

View File

@@ -7,6 +7,7 @@ use Intervention\Image\Colors\Rgb\Decoders\StringColorDecoder as RgbStringColorD
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\Drivers\AbstractInputHandler;
use Intervention\Image\Drivers\Gd\Decoders\ImageObjectDecoder;
use Intervention\Image\Drivers\Gd\Decoders\ColorObjectDecoder;
@@ -25,6 +26,7 @@ class InputHandler extends AbstractInputHandler
RgbHexColorDecoder::class,
RgbStringColorDecoder::class,
CmykStringColorDecoder::class,
HsvStringColorDecoder::class,
TransparentColorDecoder::class,
HtmlColornameDecoder::class,
FilePointerImageDecoder::class,

View File

@@ -7,6 +7,7 @@ use Intervention\Image\Colors\Rgb\Decoders\StringColorDecoder as RgbStringColorD
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\Drivers\AbstractInputHandler;
use Intervention\Image\Drivers\Imagick\Decoders\ImageObjectDecoder;
use Intervention\Image\Drivers\Imagick\Decoders\ColorObjectDecoder;
@@ -25,6 +26,7 @@ class InputHandler extends AbstractInputHandler
RgbHexColorDecoder::class,
RgbStringColorDecoder::class,
CmykStringColorDecoder::class,
HsvStringColorDecoder::class,
TransparentColorDecoder::class,
HtmlColornameDecoder::class,
FilePointerImageDecoder::class,

View File

@@ -2,8 +2,13 @@
namespace Intervention\Image\Tests\Colors\Cmyk;
use Intervention\Image\Colors\Cmyk\Channels\Cyan;
use Intervention\Image\Colors\Cmyk\Channels\Key;
use Intervention\Image\Colors\Cmyk\Channels\Magenta;
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\Cmyk\Colorspace;
use Intervention\Image\Tests\TestCase;
@@ -13,14 +18,25 @@ use Intervention\Image\Tests\TestCase;
*/
class ColorspaceTest extends TestCase
{
public function testConvertColor(): void
public function testConvertRgbColor(): void
{
$colorspace = new Colorspace();
$this->assertInstanceOf(
CmykColor::class,
$colorspace->convertColor(
new RgbColor(0, 0, 0)
)
);
$result = $colorspace->convertColor(new RgbColor(0, 0, 255));
$this->assertInstanceOf(CmykColor::class, $result);
$this->assertEquals(100, $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());
}
public function testConvertHsvColor(): void
{
$colorspace = new Colorspace();
$result = $colorspace->convertColor(new HsvColor(240, 100, 100));
$this->assertInstanceOf(CmykColor::class, $result);
$this->assertEquals(100, $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());
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsv;
use Intervention\Image\Colors\Hsv\Channels\Hue;
use Intervention\Image\Colors\Hsv\Channels\Saturation;
use Intervention\Image\Colors\Hsv\Channels\Value;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Tests\TestCase;
/**
* @requires extension gd
* @covers \Intervention\Image\Colors\Hsv\Channels\Hue
* @covers \Intervention\Image\Colors\Hsv\Channels\Saturation
* @covers \Intervention\Image\Colors\Hsv\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 Hue(90);
$this->assertEquals(.25, $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 Value(101);
$this->expectException(ColorException::class);
new Value(-1);
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsv;
use Intervention\Image\Colors\Hsv\Channels\Hue;
use Intervention\Image\Colors\Hsv\Channels\Saturation;
use Intervention\Image\Colors\Hsv\Channels\Value;
use Intervention\Image\Colors\Hsv\Color as Color;
use Intervention\Image\Colors\Hsv\Colorspace;
use Intervention\Image\Tests\TestCase;
/**
* @covers \Intervention\Image\Colors\Hsv\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 testHueSaturationValueKey(): void
{
$color = new Color(10, 20, 30);
$this->assertInstanceOf(Hue::class, $color->hue());
$this->assertInstanceOf(Saturation::class, $color->saturation());
$this->assertInstanceOf(Value::class, $color->value());
$this->assertEquals(10, $color->hue()->value());
$this->assertEquals(20, $color->saturation()->value());
$this->assertEquals(30, $color->value()->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('hsv(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,39 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsv;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsv\Channels\Hue;
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\Hsv\Colorspace;
use Intervention\Image\Tests\TestCase;
/**
* @requires extension gd
* @covers \Intervention\Image\Colors\Hsv\Colorspace
*/
class ColorspaceTest extends TestCase
{
public function testConvertRgbColor(): void
{
$colorspace = new Colorspace();
$result = $colorspace->convertColor(new RgbColor(26, 26, 128));
$this->assertInstanceOf(HsvColor::class, $result);
$this->assertEquals(240, $result->channel(Hue::class)->value());
$this->assertEquals(80, $result->channel(Saturation::class)->value());
$this->assertEquals(50, $result->channel(Value::class)->value());
}
public function testConvertCmykColor(): void
{
$colorspace = new Colorspace();
$result = $colorspace->convertColor(new CmykColor(100, 0, 100, 0));
$this->assertInstanceOf(HsvColor::class, $result);
$this->assertEquals(120, $result->channel(Hue::class)->value());
$this->assertEquals(100, $result->channel(Saturation::class)->value());
$this->assertEquals(100, $result->channel(Value::class)->value());
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Intervention\Image\Tests\Colors\Hsv\Decoders;
use Intervention\Image\Colors\Hsv\Color;
use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder;
use Intervention\Image\Tests\TestCase;
/**
* @requires extension gd
* @covers \Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder
*/
class StringColorDecoderTest extends TestCase
{
public function testDecodeHsv(): void
{
$decoder = new StringColorDecoder();
$result = $decoder->decode('hsv(0,0,0)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([0, 0, 0], $result->toArray());
$result = $decoder->decode('hsv(0, 100, 100)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([0, 100, 100], $result->toArray());
$result = $decoder->decode('hsv(360, 100, 100)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([360, 100, 100], $result->toArray());
$result = $decoder->decode('hsv(180, 100%, 100%)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([180, 100, 100], $result->toArray());
}
public function testDecodeHsb(): void
{
$decoder = new StringColorDecoder();
$result = $decoder->decode('hsb(0,0,0)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([0, 0, 0], $result->toArray());
$result = $decoder->decode('hsb(0, 100, 100)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([0, 100, 100], $result->toArray());
$result = $decoder->decode('hsb(360, 100, 100)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([360, 100, 100], $result->toArray());
$result = $decoder->decode('hsb(180, 100%, 100%)');
$this->assertInstanceOf(Color::class, $result);
$this->assertEquals([180, 100, 100], $result->toArray());
}
}

View File

@@ -3,6 +3,10 @@
namespace Intervention\Image\Tests\Colors\Rgb;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
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\Rgb\Colorspace;
use Intervention\Image\Tests\TestCase;
@@ -13,7 +17,7 @@ use Intervention\Image\Tests\TestCase;
*/
class ColorspaceTest extends TestCase
{
public function testConvertColor(): void
public function testConvertCmykColor(): void
{
$colorspace = new Colorspace();
$this->assertInstanceOf(
@@ -23,4 +27,14 @@ class ColorspaceTest extends TestCase
)
);
}
public function testConvertHsvColor(): void
{
$colorspace = new Colorspace();
$result = $colorspace->convertColor(new HsvColor(240, 80, 50));
$this->assertInstanceOf(RgbColor::class, $result);
$this->assertEquals(26, $result->channel(Red::class)->value());
$this->assertEquals(26, $result->channel(Green::class)->value());
$this->assertEquals(128, $result->channel(Blue::class)->value());
}
}

View File

@@ -2,6 +2,8 @@
namespace Intervention\Image\Tests\Drivers\Gd;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
use Intervention\Image\Colors\Rgb\Color as RgbColor;
use Intervention\Image\Image;
use Intervention\Image\Drivers\Gd\InputHandler;
@@ -114,6 +116,22 @@ class GdInputHandlerTest extends TestCase
$this->assertEquals([10, 20, 30, 255], $result->toArray());
}
public function testHandleHsvString(): void
{
$handler = new InputHandler();
$result = $handler->handle('hsv(10, 20, 30)');
$this->assertInstanceOf(HsvColor::class, $result);
$this->assertEquals([10, 20, 30], $result->toArray());
}
public function testHandleCmykString(): void
{
$handler = new InputHandler();
$result = $handler->handle('cmyk(10, 20, 30, 40)');
$this->assertInstanceOf(CmykColor::class, $result);
$this->assertEquals([10, 20, 30, 40], $result->toArray());
}
public function testHandleTransparent(): void
{
$handler = new InputHandler();

View File

@@ -2,12 +2,14 @@
namespace Intervention\Image\Tests\Drivers\Imagick;
use SplFileInfo;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
use Intervention\Image\Colors\Rgb\Color as RgbColor;
use Intervention\Image\Image;
use Intervention\Image\Drivers\Imagick\InputHandler;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Image;
use Intervention\Image\Tests\TestCase;
use SplFileInfo;
/**
* @requires extension imagick
@@ -114,6 +116,22 @@ class InputHandlerTest extends TestCase
$this->assertEquals([10, 20, 30, 255], $result->toArray());
}
public function testHandleCmykString(): void
{
$handler = new InputHandler();
$result = $handler->handle('cmyk(10, 20, 30, 40)');
$this->assertInstanceOf(CmykColor::class, $result);
$this->assertEquals([10, 20, 30, 40], $result->toArray());
}
public function testHandleHsvString(): void
{
$handler = new InputHandler();
$result = $handler->handle('hsv(10, 20, 30)');
$this->assertInstanceOf(HsvColor::class, $result);
$this->assertEquals([10, 20, 30], $result->toArray());
}
public function testHandleTransparent(): void
{
$handler = new InputHandler();