1
0
mirror of https://github.com/Intervention/image.git synced 2025-09-03 10:53:01 +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

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