Math: add epsilon compare for float, update Color Coordinate calls

Math has a compare with epsilon for float numbers.

Use this for fixing sligth color conversion issues.

NOTE: this might need some adjustment over time

All phpunint tests written and checked
This commit is contained in:
Clemens Schwaighofer
2024-11-15 18:13:16 +09:00
parent 3845bc7ff5
commit a9f1d878f7
15 changed files with 1246 additions and 152 deletions

View File

@@ -16,6 +16,8 @@ final class CoreLibsConvertColorTest extends TestCase
{
// 12 precision allowed, RGB and back has a lot of float imprecisions here
private const DELTA = 0.000000000001;
// rgb to oklab and back will have slight shift
private const DELTA_OKLAB = 1.05;
// sRGB base convert test, should round around and come out the same
// use for RGB 0, 0, 0 in 60 steps and then max 255
@@ -32,10 +34,12 @@ final class CoreLibsConvertColorTest extends TestCase
);
}
// MARK: single test
public function testSingle()
{
$this->assertTrue(true, 'Single test');
// $rgb = Color\Coordinates\RGB::__constructFromArray([0, 0, 60]);
// $rgb = new Color\Coordinates\RGB([0, 0, 60]);
// print "IN: " . print_r($rgb, true) . "\n";
// $hsl = Color\Color::rgbToHsl($rgb);
// print "to HSL: " . print_r($hsl, true) . "\n";
@@ -51,7 +55,7 @@ final class CoreLibsConvertColorTest extends TestCase
// $rgb_r = Color\Color::hslToRgb($hsl_r);
// print "R to RGB: " . print_r($rgb_r, true) . "\n";
// $hsl = Color\Coordinates\HSL::__constructFromArray([0, 0, 0]);
// $hsl = new Color\Coordinates\HSL([0, 0, 0]);
// print "IN HSL: " . print_r($hsl, true) . "\n";
// $hsb = Color\Color::hslToHsb($hsl);
// print "to HSB: " . print_r($hsb, true) . "\n";
@@ -63,7 +67,7 @@ final class CoreLibsConvertColorTest extends TestCase
// $hsl_r = Color\Color::hsbToHsl($hsb_r);
// print "R to HSL: " . print_r($hsl_r, true) . "\n";
// print "--------\n";
// $hsb = Color\Coordinates\HSB::__constructFromArray([0, 20, 0]);
// $hsb = new Color\Coordinates\HSB([0, 20, 0]);
// print "IN HSB: " . print_r($hsb, true) . "\n";
// $hsl = Color\Color::hsbToHsl($hsb);
// print "to HSL: " . print_r($hsl, true) . "\n";
@@ -75,7 +79,7 @@ final class CoreLibsConvertColorTest extends TestCase
// $hsb_r = Color\Color::hslToHsb($hsl_r);
// print "R to HSL: " . print_r($hsb_r, true) . "\n";
// print "--------\n";
// $hwb = Color\Coordinates\HWB::__constructFromArray([0, 20, 100]);
// $hwb = new Color\Coordinates\HWB([0, 20, 100]);
// print "IN: " . print_r($hwb, true) . "\n";
// $hsl = Color\Color::hwbToHsl($hwb);
// print "to HSL: " . print_r($hsl, true) . "\n";
@@ -87,6 +91,8 @@ final class CoreLibsConvertColorTest extends TestCase
// print "HSL to HWB: " . print_r($hwb_r, true) . "\n";
}
// MARK: RGB base
/**
* From/To RGB <-> ... conversion tests
*
@@ -116,7 +122,7 @@ final class CoreLibsConvertColorTest extends TestCase
$b = 255;
}
// base is always the same
$color = Color\Coordinates\RGB::__constructFromArray([$r, $g, $b]);
$color = new Color\Coordinates\RGB([$r, $g, $b]);
$base = 'rgb';
foreach (['hsb', 'hsl', 'hwb'] as $coord) {
// print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n";
@@ -145,6 +151,8 @@ final class CoreLibsConvertColorTest extends TestCase
// HSB: saturation or brightness 0
// HWB: blackness >= 80 and whitness >= 20 or B>=20 & W>=20 or B>=50 & W>=50
// MARK: HSL base
/**
* Undocumented function
*
@@ -165,7 +173,7 @@ final class CoreLibsConvertColorTest extends TestCase
if (($L == 0 or $L == 100)) {
continue;
}
$color = Color\Coordinates\HSL::__constructFromArray([$H, $S, $L]);
$color = new Color\Coordinates\HSL([$H, $S, $L]);
$base = 'hsl';
foreach (['hsb', 'hwb', 'rgb'] as $coord) {
// for rgb hue on S = 0 is irrelevant (B/W)
@@ -190,6 +198,8 @@ final class CoreLibsConvertColorTest extends TestCase
}
}
// MARK: HSB
/**
* Undocumented function
*
@@ -210,7 +220,7 @@ final class CoreLibsConvertColorTest extends TestCase
if ($S == 0 or $B == 0) {
continue;
}
$color = Color\Coordinates\HSB::__constructFromArray([$H, $S, $B]);
$color = new Color\Coordinates\HSB([$H, $S, $B]);
$base = 'hsb';
foreach (['hwb', 'hsl', 'rgb'] as $coord) {
$target = $base . 'To' . ucfirst($coord);
@@ -231,6 +241,7 @@ final class CoreLibsConvertColorTest extends TestCase
}
}
// MARK: HWB
/**
* Undocumented function
@@ -258,7 +269,7 @@ final class CoreLibsConvertColorTest extends TestCase
continue;
}
$base = 'hwb';
$color = Color\Coordinates\HWB::__constructFromArray([$H, $W, $B]);
$color = new Color\Coordinates\HWB([$H, $W, $B]);
foreach (['hsl', 'hsb', 'rgb'] as $coord) {
// for rgb hue on S = 0 is irrelevant (B/W)
if ($H > 0 && $coord == 'rgb') {
@@ -282,12 +293,13 @@ final class CoreLibsConvertColorTest extends TestCase
}
}
// MARK: RGB to hex
/**
* Undocumented function
*
* covers ::returnAsHex()
* covers ::__constructFromHexString()
* @testdox Convert from RGB to hex and back
* @covers ::returnAsHex()
* @testdox Convert from and to RGB via hex
*
* @return void
*/
@@ -308,10 +320,10 @@ final class CoreLibsConvertColorTest extends TestCase
}
// with or without prefix
foreach ([true, false] as $hex_prefix) {
$hex_color = Color\Coordinates\RGB::__constructFromArray([$r, $g, $b])
$hex_color = (new Color\Coordinates\RGB([$r, $g, $b]))
->returnAsHex($hex_prefix);
// parse into hex to rgb and see if we get the same r/g/b
$color = Color\Coordinates\RGB::__constructFromHexString($hex_color)->returnAsArray();
$color = (new Color\Coordinates\RGB($hex_color))->returnAsArray();
//
$this->assertEquals(
[$r, $g, $b],
@@ -325,20 +337,849 @@ final class CoreLibsConvertColorTest extends TestCase
}
}
// oklab
// MARK: RGB Linear
// cie lab
// create exceptions for all color spaces
public function testExceptionHSB(): void
/**
* linear RGB conversion tests
*
* @covers ::fromLinear
* @covers ::toLinear
* @testdox Convert from and to RGB linear conversion check
*
* @return void
*/
public function testRgbFromToLinear()
{
$rgb = (new Color\Coordinates\RGB([10, 20, 30]))->toLinear();
$this->assertEquals(
true,
$rgb->linear,
'On create flagged linear missing'
);
$rgb_color = $rgb->returnAsArray();
$rgb->toLinear();
$this->assertEquals(
$rgb_color,
$rgb->returnAsArray(),
'Double linear call does double linear encoding'
);
$rgb->fromLinear();
$this->assertEquals(
false,
$rgb->linear,
'On reverse linear, flag is missing'
);
$rgb_color = $rgb->returnAsArray();
$this->assertEquals(
$rgb_color,
$rgb->returnAsArray(),
'Double linear inverse call does double linear decoding'
);
$rgb = new Color\Coordinates\RGB([20, 30, 40]);
$rgb_color = $rgb->returnAsArray();
$this->assertEquals(
false,
$rgb->linear,
'On create without linear flag is linear'
);
$rgb->toLinear();
$this->assertEquals(
true,
$rgb->linear,
'On linear call flag is not linear'
);
$rgb->fromLinear();
$this->assertEquals(
$rgb_color,
$rgb->returnAsArray(),
'conversion to and from linear not matching'
);
}
// MARK: okLab
/**
* From/To RGB <-> OkLab / OkLch
*
* @covers ::rgbToOkLab
* @covers ::rgbToOkLch
* @covers ::okLabToRgb
* @covers ::okLchToRgb
* @testdox Convert from and to RGB to OkLab / OkLch
*
* @return void
*/
public function testRgbColorCoordinateConvertToAndBackBackOkLab()
{
for ($r = 0; $r <= 300; $r += 60) {
for ($g = 0; $g <= 300; $g += 60) {
for ($b = 0; $b <= 300; $b += 60) {
// for this test we stay in the correct lane
if ($r > 255) {
$r = 255;
}
if ($g > 255) {
$g = 255;
}
if ($b > 255) {
$b = 255;
}
// base is always the same
$color = new Color\Coordinates\RGB([$r, $g, $b]);
$base = 'rgb';
foreach (['okLab', 'okLch'] as $coord) {
// print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n";
// rgb to X and back must be same
$target = $base . 'To' . ucfirst($coord);
$source = $coord . 'To' . ucfirst($base);
$converted_color = Color\Color::$target($color);
$color_b = Color\Color::$source($converted_color);
// $converted_color = Color\Color::rgbToHsb($color);
// $rgb_b = Color\Color::hsbToRgb($converted_color);
$this->assertEqualsWithDelta(
$color->returnAsArray(),
$color_b->returnAsArray(),
self::DELTA_OKLAB,
'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/'
. print_r($color_b->returnAsArray(), true)
);
}
}
}
}
}
/**
* internal oklab/oklch conversion
*
* @covers ::okLchToOkLab
* @covers ::okLabToOkLch
* @testdox Convert from and to OkLab / OkLch
*
* @return void
*/
public function testOkLabOkLchColorCoordinateConvertToFrom()
{
for ($L = 0.0; $L <= 1.0; $L += 0.2) {
for ($C = 0.0; $C <= 0.5; $C += 0.1) {
for ($H = 0.0; $H <= 360.0; $H += 60.0) {
// chroma 0.0 is B/W skip it
if ($C == 0.0) {
continue;
}
$color = new Color\Coordinates\LCH([$L, $C, $H], 'OkLab');
$base = 'okLch';
foreach (['okLab'] as $coord) {
// rgb to X and back must be same
$target = $base . 'To' . ucfirst($coord);
$source = $coord . 'To' . ucfirst($base);
$converted_color = Color\Color::$target($color);
$color_b = Color\Color::$source($converted_color);
// $converted_color = Color\Color::rgbToHsb($color);
// $rgb_b = Color\Color::hsbToRgb($converted_color);
$this->assertEqualsWithDelta(
$color->returnAsArray(),
$color_b->returnAsArray(),
self::DELTA,
'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/'
. print_r($color_b->returnAsArray(), true)
);
}
}
}
}
}
// MARK: CIELab
/**
* From/To RGB <-> Cie lab / Cie lch
*
* @covers ::rgbToLab
* @covers ::rgbToLch
* @covers ::labToRgb
* @covers ::lchToRgb
* @testdox Convert from and to RGB to Cie Lab / Cie Lch
*
* @return void
*/
public function testRgbColorCoordinateConvertToAndBackBackCieLab()
{
for ($r = 0; $r <= 300; $r += 60) {
for ($g = 0; $g <= 300; $g += 60) {
for ($b = 0; $b <= 300; $b += 60) {
// for this test we stay in the correct lane
if ($r > 255) {
$r = 255;
}
if ($g > 255) {
$g = 255;
}
if ($b > 255) {
$b = 255;
}
// base is always the same
$color = new Color\Coordinates\RGB([$r, $g, $b]);
$base = 'rgb';
foreach (['lab', 'lch'] as $coord) {
// print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n";
// rgb to X and back must be same
$target = $base . 'To' . ucfirst($coord);
$source = $coord . 'To' . ucfirst($base);
$converted_color = Color\Color::$target($color);
$color_b = Color\Color::$source($converted_color);
// $converted_color = Color\Color::rgbToHsb($color);
// $rgb_b = Color\Color::hsbToRgb($converted_color);
$this->assertEqualsWithDelta(
$color->returnAsArray(),
$color_b->returnAsArray(),
self::DELTA_OKLAB,
'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/'
. print_r($color_b->returnAsArray(), true)
);
}
}
}
}
}
/**
* internal cie lab/cie lch conversion
*
* @covers ::lchToLab
* @covers ::labToLch
* @testdox Convert from and to Cie Lab / Cie Lch
*
* @return void
*/
public function testLabLchColorCoordinateConvertToFrom()
{
for ($L = 0.0; $L <= 1.0; $L += 0.2) {
for ($C = 0.0; $C <= 0.5; $C += 0.1) {
for ($H = 0.0; $H <= 360.0; $H += 60.0) {
// chroma 0.0 is B/W skip it
if ($C == 0.0) {
continue;
}
$color = new Color\Coordinates\LCH([$L, $C, $H], 'OkLab');
$base = 'lch';
foreach (['lab'] as $coord) {
// rgb to X and back must be same
$target = $base . 'To' . ucfirst($coord);
$source = $coord . 'To' . ucfirst($base);
$converted_color = Color\Color::$target($color);
$color_b = Color\Color::$source($converted_color);
// $converted_color = Color\Color::rgbToHsb($color);
// $rgb_b = Color\Color::hsbToRgb($converted_color);
$this->assertEqualsWithDelta(
$color->returnAsArray(),
$color_b->returnAsArray(),
self::DELTA,
'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/'
. print_r($color_b->returnAsArray(), true)
);
}
}
}
}
}
// MARK: Exceptions
/**
* Undocumented function
*
* @return array
*/
public function providerHueBased(): array
{
// all HSB/V HSL HWB have the same value range, create test data for all of them
return [
'H' => [
'color' => [900, 10, 10],
'error_code' => 1,
'error_string' => '/ for hue is not in the range of 0 to 360$/'
],
'H' => [
'color' => [-1, 10, 10], 'error_code' => 1,
'error_string' => '/ for hue is not in the range of 0 to 360$/',
],
'H close' => [
'color' => [360.1, 10, 10],
'error_code' => 1,
'error_string' => '/ for hue is not in the range of 0 to 360$/'
],
'H close' => [
'color' => [-0.1, 10, 10], 'error_code' => 1,
'error_string' => '/ for hue is not in the range of 0 to 360$/',
],
'S/W' => [
'color' => [90, 900, 10], 'error_code' => 2,
'error_string' => 'is not in the range of 0 to 100',
],
'S/W' => [
'color' => [90, -1, 10], 'error_code' => 2,
'error_string' => 'is not in the range of 0 to 100',
],
'S/W close' => [
'color' => [90, 100.1, 10], 'error_code' => 2,
'error_string' => 'is not in the range of 0 to 100',
],
'S/W close' => [
'color' => [90, -0.1, 10], 'error_code' => 2,
'error_string' => 'is not in the range of 0 to 100',
],
'L/B' => [
'color' => [90, 10, 900], 'error_code' => 3,
'error_string' => 'is not in the range of 0 to 100',
],
'L/B' => [
'color' => [90, 10, -1], 'error_code' => 3,
'error_string' => 'is not in the range of 0 to 100',
],
'L/B close' => [
'color' => [90, 10, 100.1], 'error_code' => 3,
'error_string' => 'is not in the range of 0 to 100',
],
'L/B close' => [
'color' => [90, 10, -0.1], 'error_code' => 3,
'error_string' => 'is not in the range of 0 to 100',
],
];
}
// MARK: HSB Exceptions
/**
* Undocumented function
*
* @dataProvider providerHueBased
* @testdox Exception handling for HSB for error $error_code [$_dataName]
*
* @param array $color
* @param int $error_code
* @param string $error_string
* @return void
*/
public function testExceptionHSB(array $color, int $error_code, string $error_string): void
{
// error string based on code
switch ($error_code) {
case 2:
$error_string = "/ for saturation $error_string$/";
break;
case 3:
$error_string = "/ for brightness $error_string$/";
break;
}
// for H/S/B exception the same
$this->expectException(\LengthException::class);
Color\Coordinates\HSB::__constructFromArray([900, 10, 10]);
$this->expectExceptionCode($error_code);
$this->expectExceptionMessageMatches($error_string);
new Color\Coordinates\HSB($color);
}
/**
* Undocumented function
*
* @testdox Exception handling for HSB general calls
*
* @return void
*/
public function testExceptionHSBGeneral()
{
// allow
$b = new Color\Coordinates\HSB([0, 0, 0], 'sRGB');
// invalid access to class
// $this->expectException(\ErrorException::class);
$b = new Color\Coordinates\HSB([0, 0, 0]);
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->g;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\HSB('string');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\HSB([0, 0, 0], 'FOO_BAR');
}
// MARK: HSL Exceptions
/**
* Undocumented function
*
* @dataProvider providerHueBased
* @testdox Exception handling for HSL for error $error_code [$_dataName]
*
* @param array $color
* @param int $error_code
* @param string $error_string
* @return void
*/
public function testExceptionHSL(array $color, int $error_code, string $error_string): void
{
// error string based on code
switch ($error_code) {
case 2:
$error_string = "/ for saturation $error_string$/";
break;
case 3:
$error_string = "/ for lightness $error_string$/";
break;
}
// for H/S/B exception the same
$this->expectException(\LengthException::class);
$this->expectExceptionCode($error_code);
$this->expectExceptionMessageMatches($error_string);
new Color\Coordinates\HSL($color);
}
/**
* Undocumented function
*
* @testdox Exception handling for HSL general calls
*
* @return void
*/
public function testExceptionHSLGeneral()
{
// allow
$b = new Color\Coordinates\HSL([0, 0, 0], 'sRGB');
// invalid access to class
$b = new Color\Coordinates\HSL([0, 0, 0]);
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->g;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\HSL('string');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\HSL([0, 0, 0], 'FOO_BAR');
}
// MARK: HWB Exceptions
/**
* Undocumented function
*
* @dataProvider providerHueBased
* @testdox Exception handling for HWB for error $error_code [$_dataName]
*
* @param array $color
* @param int $error_code
* @param string $error_string
* @return void
*/
public function testExceptionHWB(array $color, int $error_code, string $error_string): void
{
// error string based on code
switch ($error_code) {
case 2:
$error_string = "/ for whiteness $error_string$/";
break;
case 3:
$error_string = "/ for blackness $error_string$/";
break;
}
// for H/S/B exception the same
$this->expectException(\LengthException::class);
$this->expectExceptionCode($error_code);
$this->expectExceptionMessageMatches($error_string);
new Color\Coordinates\HWB($color);
}
/**
* Undocumented function
*
* @testdox Exception handling for HWB general calls
*
* @return void
*/
public function testExceptionHWBGeneral()
{
// allow
$b = new Color\Coordinates\HWB([0, 0, 0], 'sRGB');
// invalid access to class
$b = new Color\Coordinates\HWB([0, 0, 0]);
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->g;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\HWB('string');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\HWB([0, 0, 0], 'FOO_BAR');
}
// MARK: RGB Exceptions
/**
* Undocumented function
*
* @return array
*/
public function providerRgbBased(): array
{
// all HSB/V HSL HWB have the same value range, create test data for all of them
return [
'R' => [
'color' => [900, 10, 10],
'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'R' => [
'color' => [-1, 10, 10], 'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'G' => [
'color' => [90, 900, 10], 'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'G' => [
'color' => [90, -1, 10], 'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'B' => [
'color' => [90, 10, 900], 'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'B' => [
'color' => [90, 10, -1], 'error_code' => 1,
'error_string' => '/ is not in the range of 0 to 255$/',
'linear' => false,
],
'R linear' => [
'color' => [2, 0.5, 0.5],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
'R linear' => [
'color' => [-1, 0.5, 0.5],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
'G linear' => [
'color' => [0.5, 2, 0.5],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
'G linear' => [
'color' => [0.5, -1, 0.5],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
'B linear' => [
'color' => [0.5, 0.5, 2],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
'B linear' => [
'color' => [0.5, 0.5, -1],
'error_code' => 2,
'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/',
'linear' => true,
],
];
}
/**
* Undocumented function
*
* @dataProvider providerRgbBased
* @testdox Exception handling for RGB for error $error_code [$_dataName]
*
* @param string|array $color
* @param int $error_code
* @param string $error_string
* @param bool $linear
* @return void
*/
public function testExceptionRGB(string|array $color, int $error_code, string $error_string, bool $linear): void
{
// for RGB exception the same
$this->expectException(\LengthException::class);
$this->expectExceptionCode($error_code);
$this->expectExceptionMessageMatches($error_string);
new Color\Coordinates\RGB($color, options: ["linear" => $linear]);
}
/**
* Undocumented function
*
* @covers ::__get
* @testdox Exception handling for RGB general calls
*
* @return void
*/
public function testExceptionRGBGeneral()
{
// allow
$b = new Color\Coordinates\RGB([0, 0, 0], 'sRGB');
// invalid access to class
$b = new Color\Coordinates\RGB([0, 0, 0]);
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->h;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\RGB([0, 0, 0], 'FOO_BAR');
}
/**
* Undocumented function
*
* @covers ::setFromHex
* @testdox Exception handling for RGB setFromHex failues
*
* @return void
*/
public function testExceptionRGBFromHex()
{
$color = '';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(3);
$this->expectExceptionMessage('hex_string argument cannot be empty');
new Color\Coordinates\RGB($color);
$color = 'zshj';
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(3);
$this->expectExceptionMessage('hex_string argument cannot be empty');
new Color\Coordinates\RGB($color);
$color = 'aabff';
$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionCode(4);
$this->expectExceptionMessageMatches('/^Invalid hex_string: /');
new Color\Coordinates\RGB($color);
}
// MARK: Lab Exceptions
/**
* Undocumented function
*
* @return array
*/
public function providerLabBased(): array
{
// all HSB/V HSL HWB have the same value range, create test data for all of them
return [
'L CieLab' => [
'color' => [900, 10, 10], 'error_code' => 1,
'error_string' => '/ for lightness is not in the range of 0 to 100 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'L CieLab' => [
'color' => [-1, 10, 10], 'error_code' => 1,
'error_string' => '/ for lightness is not in the range of 0 to 100 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'L OkLab' => [
'color' => [900, 0.2, 0.2], 'error_code' => 1,
'error_string' => '/ for lightness is not in the range of 0.0 to 1.0 for OkLab$/',
'colorspace' => 'OkLab',
],
'L OkLab' => [
'color' => [-1, 0.2, 0.2], 'error_code' => 1,
'error_string' => '/ for lightness is not in the range of 0.0 to 1.0 for OkLab$/',
'colorspace' => 'OkLab',
],
'a CieLab' => [
'color' => [90, 900, 10], 'error_code' => 2,
'error_string' => '/ for a is not in the range of -125 to 125 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'a CieLab' => [
'color' => [90, -900, 10], 'error_code' => 2,
'error_string' => '/ for a is not in the range of -125 to 125 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'a OkLab' => [
'color' => [0.5, 900, 0.2], 'error_code' => 2,
'error_string' => '/ for a is not in the range of -0.5 to 0.5 for OkLab$/',
'colorspace' => 'OkLab',
],
'a OkLab' => [
'color' => [0.6, -900, 0.2], 'error_code' => 2,
'error_string' => '/ for a is not in the range of -0.5 to 0.5 for OkLab$/',
'colorspace' => 'OkLab',
],
'b CieLab' => [
'color' => [90, 10, 900], 'error_code' => 3,
'error_string' => '/ for b is not in the range of -125 to 125 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'b CieLab' => [
'color' => [90, 10, -999], 'error_code' => 3,
'error_string' => '/ for b is not in the range of -125 to 125 for CIE Lab$/',
'colorspace' => 'CIELab',
],
'b OkLab' => [
'color' => [0.6, 0.2, 900], 'error_code' => 3,
'error_string' => '/ for b is not in the range of -0.5 to 0.5 for OkLab$/',
'colorspace' => 'OkLab',
],
'b OkLab' => [
'color' => [0.6, 0.2, -999], 'error_code' => 3,
'error_string' => '/ for b is not in the range of -0.5 to 0.5 for OkLab$/',
'colorspace' => 'OkLab',
],
];
}
/**
* Undocumented function
*
* @dataProvider providerLabBased
* @testdox Exception handling for Lab for error $error_code [$_dataName]
*
* @param string|array $color
* @param int $error_code
* @param string $error_string
* @param string $colorspace
* @return void
*/
public function testExceptionLab(
string|array $color,
int $error_code,
string $error_string,
string $colorspace
): void {
// for RGB exception the same
$this->expectException(\LengthException::class);
$this->expectExceptionCode($error_code);
$this->expectExceptionMessageMatches($error_string);
new Color\Coordinates\Lab($color, colorspace: $colorspace);
}
/**
* Undocumented function
*
* @covers ::__get
* @testdox Exception handling for Lab general calls
*
* @return void
*/
public function testExceptionLabGeneral()
{
// allow
$b = new Color\Coordinates\Lab([0, 0, 0], 'OkLab');
// invalid access to class
$b = new Color\Coordinates\Lab([0, 0, 0], 'CIELab');
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->x;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\Lab('string', 'CIELab');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\Lab([0, 0, 0], 'FOO_BAR');
}
// MARK: LCH Exceptions
// public function testExceptionLch(string|array $color, int $error_code, string $error_string): void
/**
* Undocumented function
*
* @covers ::__get
* @testdox Exception handling for LCH general calls
*
* @return void
*/
public function testExceptionLchGeneral()
{
// allow
$b = new Color\Coordinates\LCH([0, 0, 0], 'OkLab');
// invalid access to class
$b = new Color\Coordinates\LCH([0, 0, 0], 'CIELab');
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->x;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\LCH('string', 'CIELab');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\LCH([0, 0, 0], 'FOO_BAR');
}
// MARK: XYZ Exceptions
// Note, we do not check for value exceptions here
// public function testExceptionXyz(string|array $color, int $error_code, string $error_string): void
/**
* Undocumented function
*
* @covers ::__get
* @testdox Exception handling for XYZ general calls
*
* @return void
*/
public function testExceptionXyzGeneral()
{
// allow
$b = new Color\Coordinates\XYZ([0, 0, 0], 'CIEXYZ');
// invalid access to class
$b = new Color\Coordinates\XYZ([0, 0, 0]);
$this->expectException(\ErrorException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Creation of dynamic property is not allowed");
$b->x;
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Only array colors allowed");
new Color\Coordinates\XYZ('string');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Not allowed colorspace");
new Color\Coordinates\XYZ([0, 0, 0], 'FOO_BAR');
}
}

View File

@@ -142,7 +142,6 @@ final class CoreLibsConvertMathTest extends TestCase
*/
public function testCbrt(float|int $number, float|string $expected, int $round_to): void
{
print "OUT: " . \CoreLibs\Convert\Math::cbrt($number) . "\n";
$this->assertEquals(
$expected,
round(\CoreLibs\Convert\Math::cbrt($number), $round_to)
@@ -264,6 +263,130 @@ final class CoreLibsConvertMathTest extends TestCase
\CoreLibs\Convert\Math::multiplyMatrices($input_a, $input_b)
);
}
/**
* Undocumented function
*
* @return array
*/
public function providerEqualWithEpsilon(): array
{
return [
'equal' => [
'a' => 0.000000000000000222,
'b' => 0.000000000000000222,
'epsilon' => PHP_FLOAT_EPSILON,
'equal' => true,
],
'almost equal' => [
'a' => 0.000000000000000222,
'b' => 0.000000000000000232,
'epsilon' => PHP_FLOAT_EPSILON,
'equal' => true,
],
'not equal' => [
'a' => 0.000000000000000222,
'b' => 0.000000000000004222,
'epsilon' => PHP_FLOAT_EPSILON,
'equal' => false,
],
'equal, different epsilon' => [
'a' => 0.000000000000000222,
'b' => 0.000000000000004222,
'epsilon' => 0.0001,
'equal' => true,
],
'not equal, different epsilon' => [
'a' => 0.0001,
'b' => 0.0002,
'epsilon' => 0.0001,
'equal' => false,
]
];
}
/**
* Undocumented function
*
* @covers ::equalWithEpsilon
* @dataProvider providerEqualWithEpsilon
* @testdox equalWithEpsilon with $a and $b and Epsilon: $epsilon must be equal: $equal [$_dataName]
*
* @return void
*/
public function testEqualWithEpsilon(float $a, float $b, float $epsilon, bool $equal): void
{
$this->assertEquals(
$equal,
\CoreLibs\Convert\Math::equalWithEpsilon($a, $b, $epsilon)
);
}
/**
* Undocumented function
*
* @return array
*/
public function providerCompareWithEpsilon(): array
{
return [
'smaller, true' => [
'value' => 0.0001,
'compare' => '<',
'limit' => 0.0002,
'epsilon' => 0.00001,
'match' => true,
],
'smaller, false' => [
'value' => 0.0001,
'compare' => '<',
'limit' => 0.0001,
'epsilon' => 0.00001,
'match' => false,
],
'bigger, true' => [
'value' => 0.0002,
'compare' => '>',
'limit' => 0.0001,
'epsilon' => 0.00001,
'match' => true,
],
'bigger, false' => [
'value' => 0.0001,
'compare' => '>',
'limit' => 0.0001,
'epsilon' => 0.00001,
'match' => false,
],
];
}
/**
* Undocumented function
*
* @covers ::compareWithEpsilon
* @dataProvider providerCompareWithEpsilon
* @testdox compareWithEpsilon $value $compare $limit with $epsilon must match: $match [$_dataName]
*
* @param float $value
* @param string $compare
* @param float $limit
* @param float $epslion
* @param bool $match
* @return void
*/
public function testCompareWithEpsilon(
float $value,
string $compare,
float $limit,
float $epsilon,
bool $match
): void {
$this->assertEquals(
$match,
\CoreLibs\Convert\Math::compareWithEpsilon($value, $compare, $limit, $epsilon)
);
}
}
// __END__