Compare commits

..

11 Commits

Author SHA1 Message Date
Clemens Schwaighofer
35cc6dbf91 Minor fixes for some calls 2024-11-18 14:52:36 +09:00
Clemens Schwaighofer
cb3d5e1f27 Matrix multiplication fixes 2024-11-18 14:44:18 +09:00
Clemens Schwaighofer
0a45300c21 fix the deprecation version for Colors class calls 2024-11-18 10:12:48 +09:00
Clemens Schwaighofer
54ce378ae2 Text fix for deprecation message 2024-11-18 10:10:39 +09:00
Clemens Schwaighofer
4ac659f7d9 Colors deprecation messages and remove Class Basic color convert calls 2024-11-18 09:50:24 +09:00
Clemens Schwaighofer
497833ca71 phpunit test updated to removal of __get 2024-11-15 19:45:36 +09:00
Clemens Schwaighofer
e5a9b149b1 phpstan fixes with move away from __get to dedicated get 2024-11-15 19:43:30 +09:00
Clemens Schwaighofer
5213805a58 phan updates 2024-11-15 18:18:45 +09:00
Clemens Schwaighofer
a9f1d878f7 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
2024-11-15 18:13:16 +09:00
Clemens Schwaighofer
3845bc7ff5 Color Coordinates class udpates
move creation into the main constructor and do not rely on "::create" or
any other pass through creation.

Make all constructors equal with options array so we can create an Interface

Remove all outsite setters. Once a color is set this color stays
2024-11-14 14:51:31 +09:00
Clemens Schwaighofer
32c192a362 Basic colors test add started
Also fixes for various things that come up during test writing

Test phpunit not yet finished (exceptions, etc)

Note: a lot of checks for extreme values are (int) so we do not fail
for small float values
2024-11-13 19:19:35 +09:00
20 changed files with 2172 additions and 505 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -142,7 +142,6 @@ final class CoreLibsConvertMathTest extends TestCase
*/ */
public function testCbrt(float|int $number, float|string $expected, int $round_to): void public function testCbrt(float|int $number, float|string $expected, int $round_to): void
{ {
print "OUT: " . \CoreLibs\Convert\Math::cbrt($number) . "\n";
$this->assertEquals( $this->assertEquals(
$expected, $expected,
round(\CoreLibs\Convert\Math::cbrt($number), $round_to) round(\CoreLibs\Convert\Math::cbrt($number), $round_to)
@@ -157,12 +156,32 @@ final class CoreLibsConvertMathTest extends TestCase
public function providerMultiplyMatrices(): array public function providerMultiplyMatrices(): array
{ {
return [ return [
'single' => [ '[3] x [3] => [3x1]' => [
[1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3],
[14] [14]
], ],
'double first' => [ '[3] x [3x1]' => [
[1, 2, 3],
[[1], [2], [3]],
[14]
],
'[3] x [3x1]' => [
[1, 2, 3],
[[1], [2], [3]],
[14]
],
'[1x3L] x [3x1]' => [
[[1, 2, 3]],
[[1], [2], [3]],
[14]
],
'[1x3] x [3x1]' => [
[[1], [2], [3]],
[[1], [2], [3]],
[1, 2, 3]
],
'[2x3] x [3] => [3x1]' => [
[ [
[1, 2, 3], [1, 2, 3],
[1, 2, 3] [1, 2, 3]
@@ -173,7 +192,18 @@ final class CoreLibsConvertMathTest extends TestCase
14 14
] ]
], ],
'double both' => [ '[2x3] x [3x1]' => [
[
[1, 2, 3],
[1, 2, 3]
],
[[1], [2], [3]],
[
14,
14
]
],
'[2x3] x [2x3] => [3x3]' => [
[ [
[1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3],
@@ -187,7 +217,37 @@ final class CoreLibsConvertMathTest extends TestCase
[3, 6, 9] [3, 6, 9]
] ]
], ],
'tripple first, single second' => [ '[2x3] x [3x3]' => [
[
[1, 2, 3],
[1, 2, 3],
],
[
[1, 2, 3],
[1, 2, 3],
[0, 0, 0],
],
[
[3, 6, 9],
[3, 6, 9]
]
],
'[2x3] x [3x2]' => [
'a' => [
[1, 2, 3],
[1, 2, 3],
],
'b' => [
[1, 1],
[2, 2],
[3, 3],
],
'prod' => [
[14, 14],
[14, 14],
]
],
'[3x3] x [3] => [1x3]' => [
[ [
[1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3],
@@ -200,7 +260,7 @@ final class CoreLibsConvertMathTest extends TestCase
14 14
] ]
], ],
'tripple first, double second' => [ '[3x3] x [2x3] => [3x3]' => [
[ [
[1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3],
@@ -216,7 +276,24 @@ final class CoreLibsConvertMathTest extends TestCase
[3, 6, 9], [3, 6, 9],
] ]
], ],
'single first, tripple second' => [ '[3x3] x [3x3]' => [
[
[1, 2, 3],
[1, 2, 3],
[1, 2, 3],
],
[
[1, 2, 3],
[1, 2, 3],
// [0, 0, 0],
],
[
[3, 6, 9],
[3, 6, 9],
[3, 6, 9],
]
],
'[3] x [3x3]' => [
[1, 2, 3], [1, 2, 3],
[ [
[1, 2, 3], [1, 2, 3],
@@ -227,7 +304,7 @@ final class CoreLibsConvertMathTest extends TestCase
[6, 12, 18], [6, 12, 18],
] ]
], ],
'double first, tripple second' => [ '[2x3] x [3x3]' => [
[ [
[1, 2, 3], [1, 2, 3],
[1, 2, 3], [1, 2, 3],
@@ -264,6 +341,130 @@ final class CoreLibsConvertMathTest extends TestCase
\CoreLibs\Convert\Math::multiplyMatrices($input_a, $input_b) \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__ // __END__

View File

@@ -10,7 +10,6 @@
"phpstan/phpstan": "^1.12", "phpstan/phpstan": "^1.12",
"phan/phan": "^5.4", "phan/phan": "^5.4",
"phpstan/extension-installer": "^1.4", "phpstan/extension-installer": "^1.4",
"phpstan/phpstan-strict-rules": "^1.6",
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",
"phpstan/phpstan-deprecation-rules": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.2",
"yamadashy/phpstan-friendly-formatter": "^1.1" "yamadashy/phpstan-friendly-formatter": "^1.1"

View File

@@ -54,7 +54,7 @@ function display(string $color, string $text, string $text_add): string
["{COLOR}", "{TEXT}", "{CSS}"], ["{COLOR}", "{TEXT}", "{CSS}"],
[ [
$color, $color,
$text . ($text_add ? '<br>' . $text_add : ''), $text . (!empty($text_add) ? '<br>' . $text_add : ''),
$css $css
], ],
$template $template
@@ -68,21 +68,71 @@ print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>'; print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>'; print '<div><h1>' . $PAGE_NAME . '</h1></div>';
// out of bounds test
// define a list of from to color sets for conversion test // define a list of from to color sets for conversion test
$hwb = Color::hsbToHwb(new Coordinates\HSB([
160,
0,
50,
]));
print "HWB: " . DgS::printAr($hwb) . "<br>";
$hsb = Color::hwbToHsb($hwb);
print "HSB: " . DgS::printAr($hsb) . "<br>";
$oklch = Color::rgbToOkLch(Coordinates\RGB::create([
250,
0,
0
]));
print "OkLch: " . DgS::printAr($oklch) . "<br>";
$rgb = Color::okLchToRgb($oklch);
print "OkLch -> RGB: " . DgS::printAr($rgb) . "<br>";
$oklab = Color::rgbToOkLab(Coordinates\RGB::create([
250,
0,
0
]));
print "OkLab: " . DgS::printAr($oklab) . "<br>";
print display($oklab->toCssString(), $oklab->toCssString(), 'Oklab');
$rgb = Color::okLabToRgb($oklab);
print "OkLab -> RGB: " . DgS::printAr($rgb) . "<br>";
print display($rgb->toCssString(), $rgb->toCssString(), 'OkLab to RGB');
$rgb = Coordinates\RGB::create([250, 100, 10])->toLinear();
print "RGBlinear: " . DgS::printAr($rgb) . "<br>";
$rgb = Coordinates\RGB::create([0, 0, 0])->toLinear();
print "RGBlinear: " . DgS::printAr($rgb) . "<br>";
$cie_lab = Color::okLabToLab($oklab);
print "CieLab: " . DgS::printAr($cie_lab) . "<br>";
print display($cie_lab->toCssString(), $cie_lab->toCssString(), 'OkLab to Cie Lab');
$rgb = Coordinates\RGB::create([0, 0, 60]);
$hsb = Color::rgbToHsb($rgb);
$rgb_b = Color::hsbToRgb($hsb);
print "RGB: " . DgS::printAr($rgb) . "<br>";
print "RGB->HSB: " . DgS::printAr($hsb) . "<br>";
print "HSB->RGB: " . DgS::printAr($rgb_b) . "<br>";
$hsl = Coordinates\HSL::create([0, 20, 0]);
$hsb = Coordinates\HSB::create([0, 20, 0]);
$hsl_from_hsb = Color::hsbToHsl($hsb);
print "HSL from HSB: " . DgS::printAr($hsl_from_hsb) . "<br>";
print "<hr>";
// A(out of bounds) // A(out of bounds)
try { try {
print "C::S/COLOR invalid rgb->hex (gray 125): -1, -1, -1: " print "C::S/COLOR invalid rgb->hex (gray 125): -1, -1, -1: "
. CoreLibs\Convert\Colors::rgb2hex(-1, -1, -1) . "<br>"; . (new Coordinates\RGB([-1, -1, -1]))->returnAsHex() . "<br>";
} catch (\LengthException $e) { } catch (\LengthException $e) {
print "*Exception: " . $e->getMessage() . "<br>" . $e . "<br>"; print "*Exception: " . $e->getMessage() . "<br><pre>" . print_r($e, true) . "</pre><br>";
}
try {
print "\$C::S/COLOR invalid rgb->hex (gray 125): -1, -1, -1: "
. $color_class::rgb2hex(-1, -1, -1) . "<br>";
} catch (\LengthException $e) {
print "**Exception: " . $e->getMessage() . "<br><pre>" . print_r($e, true) . "</pre><br>";
} }
print "<hr>";
print "<h2>LEGACY</h2>";
// B(valid) // B(valid)
$rgb = [50, 20, 30]; $rgb = [50, 20, 30];
$hex = '#0a141e'; $hex = '#0a141e';
@@ -125,58 +175,26 @@ print "S::COLOR hsb->rgb: $hsb[0], $hsb[1], $hsb[2]: "
Colors::hsb2rgb($hsb[0], $hsb[1], $hsb[2]) Colors::hsb2rgb($hsb[0], $hsb[1], $hsb[2])
)) . "<br>"; )) . "<br>";
print "<hr>";
// Random text // Random text
$h = rand(0, 359); $h = rand(0, 359);
$s = rand(15, 70); $s = rand(15, 70);
$b = 100; $b = 100;
$l = 50; $l = 50;
print "RANDOM IN: H: " . $h . ", S: " . $s . ", B/L: " . $b . "/" . $l . "<br>"; print "RANDOM IN: H: " . $h . ", S: " . $s . ", B/L: " . $b . "/" . $l . "<br>";
print "RANDOM hsb->rgb: <pre>" . DgS::printAr(SetVarType::setArray(Colors::hsb2rgb($h, $s, $b))) . "</pre><br>"; print "RANDOM hsb->rgb: <pre>"
print "RANDOM hsl->rgb: <pre>" . DgS::printAr(SetVarType::setArray(Colors::hsl2rgb($h, $s, $l))) . "</pre><br>"; . DgS::printAr(SetVarType::setArray(Color::hsbToRgb(new Coordinates\HSB([$h, $s, $b])))) . "</pre><br>";
print "RANDOM hsl->rgb: <pre>"
. DgS::printAr(SetVarType::setArray(Color::hslToRgb(new Coordinates\HSL([$h, $s, $l])))) . "</pre><br>";
print "<hr>";
$rgb = [0, 0, 0]; $rgb = [0, 0, 0];
print "rgb 0,0,0: " . Dgs::printAr($rgb) . " => " . Dgs::printAr(Colors::rgb2hsb($rgb[0], $rgb[1], $rgb[2])) . "<br>"; print "rgb 0,0,0: " . Dgs::printAr($rgb) . " => "
. Dgs::printAr(Color::rgbToHsb(new Coordinates\RGB([$rgb[0], $rgb[1], $rgb[2]]))) . "<br>";
// TODO: run compare check input must match output
$hwb = Color::hsbToHwb(Coordinates\HSB::__constructFromArray([
160,
0,
50,
]));
print "HWB: " . DgS::printAr($hwb) . "<br>";
$hsb = Color::hwbToHsb($hwb);
print "HSB: " . DgS::printAr($hsb) . "<br>";
$oklch = Color::rgbToOkLch(Coordinates\RGB::__constructFromArray([
250,
0,
0
]));
print "OkLch: " . DgS::printAr($oklch) . "<br>";
$rgb = Color::okLchToRgb($oklch);
print "OkLch -> RGB: " . DgS::printAr($rgb) . "<br>";
$oklab = Color::rgbToOkLab(Coordinates\RGB::__constructFromArray([
250,
0,
0
]));
print "OkLab: " . DgS::printAr($oklab) . "<br>";
print display($oklab->toCssString(), $oklab->toCssString(), 'Oklab');
$rgb = Color::okLabToRgb($oklab);
print "OkLab -> RGB: " . DgS::printAr($rgb) . "<br>";
print display($rgb->toCssString(), $rgb->toCssString(), 'OkLab to RGB');
$rgb = Coordinates\RGB::__constructFromArray([250, 100, 10])->toLinear();
print "RGBlinear: " . DgS::printAr($rgb) . "<br>";
$rgb = Coordinates\RGB::__constructFromArray([0, 0, 0])->toLinear();
print "RGBlinear: " . DgS::printAr($rgb) . "<br>";
$cie_lab = Color::okLabToLab($oklab);
print "CieLab: " . DgS::printAr($cie_lab) . "<br>";
print display($cie_lab->toCssString(), $cie_lab->toCssString(), 'OkLab to Cie Lab');
print "<hr>";
print "</body></html>"; print "</body></html>";

View File

@@ -23,7 +23,6 @@ $log = new CoreLibs\Logging\Logging([
'log_file_id' => $LOG_FILE_ID, 'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true, 'log_per_date' => true,
]); ]);
$_math = new CoreLibs\Convert\Math();
$math_class = 'CoreLibs\Convert\Math'; $math_class = 'CoreLibs\Convert\Math';
// define a list of from to color sets for conversion test // define a list of from to color sets for conversion test
@@ -35,13 +34,9 @@ print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>'; print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>'; print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "FCEIL: " . $_math->fceil(5.1234567890, 5) . "<br>";
print "FLOORP: " . $_math->floorp(5123456, -3) . "<br>";
print "FLOORP: " . $_math->floorp(5123456, -10) . "<br>";
print "INITNUMERIC: " . $_math->initNumeric('123') . "<br>";
print "S-FCEIL: " . $math_class::fceil(5.1234567890, 5) . "<br>"; print "S-FCEIL: " . $math_class::fceil(5.1234567890, 5) . "<br>";
print "S-FLOORP: " . $math_class::floorp(5123456, -3) . "<br>"; print "S-FLOORP: " . $math_class::floorp(5123456, -3) . "<br>";
print "S-FLOORP: " . $math_class::floorp(5123456, -10) . "<br>";
print "S-INITNUMERIC: " . $math_class::initNumeric(123) . "<br>"; print "S-INITNUMERIC: " . $math_class::initNumeric(123) . "<br>";
print "S-INITNUMERIC: " . $math_class::initNumeric(123.456) . "<br>"; print "S-INITNUMERIC: " . $math_class::initNumeric(123.456) . "<br>";
print "S-INITNUMERIC: " . $math_class::initNumeric('123') . "<br>"; print "S-INITNUMERIC: " . $math_class::initNumeric('123') . "<br>";

View File

@@ -1139,118 +1139,6 @@ class Basic
// *** BETTER PASSWORD OPTIONS END *** // *** BETTER PASSWORD OPTIONS END ***
// *** COLORS ***
// [!!! DEPRECATED !!!]
// moved to \CoreLibs\Convert\Colors
/**
* converts a hex RGB color to the int numbers
* @param string $hexStr RGB hexstring
* @param bool $returnAsString flag to return as string
* @param string $seperator string seperator: default: ","
* @return string|array<mixed>|bool false on error or array with RGB or
* a string with the seperator
* @deprecated use \CoreLibs\Convert\Colors::hex2rgb() instead
*/
public static function hex2rgb(string $hexStr, bool $returnAsString = false, string $seperator = ',')
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hex2rgb()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::hex2rgb($hexStr, $returnAsString, $seperator);
}
/**
* converts the rgb values from int data to the valid rgb html hex string
* optional can turn of leading #
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @param bool $hex_prefix default true, prefix with "#"
* @return string|bool rgb in hex values with leading # if set
* @deprecated use \CoreLibs\Convert\Colors::rgb2hex() instead
*/
public static function rgb2hex(int $red, int $green, int $blue, bool $hex_prefix = true)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, $hex_prefix);
}
/**
* converts and int RGB to the HTML color string in hex format
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return string|bool hex rgb string
* @deprecated use rgb2hex instead
*/
public static function rgb2html(int $red, int $green, int $blue)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED);
// check that each color is between 0 and 255
return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, true);
}
/**
* converts RGB to HSB/V values
* returns:
* array with hue (0-360), sat (0-100%), brightness/value (0-100%)
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return array<mixed>|bool Hue, Sat, Brightness/Value
* @deprecated use \CoreLibs\Convert\Colors::rgb2hsb() instead
*/
public static function rgb2hsb(int $red, int $green, int $blue)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsb()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::rgb2hsb($red, $green, $blue);
}
/**
* converts HSB/V to RGB values RGB is full INT
* @param int $H hue 0-360
* @param float $S saturation 0-1 (float)
* @param float $V brightness/value 0-1 (float)
* @return array<mixed>|bool 0 red/1 green/2 blue array
* @deprecated use \CoreLibs\Convert\Colors::hsb2rgb() instead
*/
public static function hsb2rgb(int $H, float $S, float $V)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsb2rgb()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::hsb2rgb($H, (int)round($S * 100), (int)round($V * 100));
}
/**
* converts a RGB (0-255) to HSL
* return:
* array with hue (0-360), saturation (0-100%) and luminance (0-100%)
* @param int $r red 0-255
* @param int $g green 0-255
* @param int $b blue 0-255
* @return array<mixed>|bool hue/sat/luminance
* @deprecated use \CoreLibs\Convert\Colors::rgb2hsl() instead
*/
public static function rgb2hsl(int $r, int $g, int $b)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsl()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::rgb2hsb($r, $g, $b);
}
/**
* converts an HSL to RGB
* @param int $h hue: 0-360 (degrees)
* @param float $s saturation: 0-1
* @param float $l luminance: 0-1
* @return array<mixed>|bool red/blue/green 0-255 each
* @deprecated use \CoreLibs\Convert\Colors::hsl2rgb() instead
*/
public static function hsl2rgb(int $h, float $s, float $l)
{
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsl2rgb()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Colors::hsl2rgb($h, $s * 100, $l * 100);
}
// *** COLORS END ***
// *** EMAIL FUNCTIONS *** // *** EMAIL FUNCTIONS ***
// [!!! DEPRECATED !!!] // [!!! DEPRECATED !!!]
// Moved to \CoreLibs\Check\Email // Moved to \CoreLibs\Check\Email

View File

@@ -83,7 +83,7 @@ class CieXyz
} }
/** /**
* Undocumented function * Convert from oklab to cie lab
* *
* @param Lab $lab * @param Lab $lab
* @return Lab * @return Lab
@@ -98,7 +98,7 @@ class CieXyz
} }
/** /**
* Undocumented function * Convert from cie lab to oklab
* *
* @param Lab $lab * @param Lab $lab
* @return Lab * @return Lab
@@ -112,6 +112,21 @@ class CieXyz
); );
} }
// MARK: helper convert any array to array{float, float, float}
/**
* This is a hack for phpstan until we write a proper matrix to class
* conversion wrapper function
*
* @param array<array<float|int>|float|int> $_array
* @return array{0:float,1:float,2:float}
*/
private static function convertArray(array $_array): array
{
/** @var array{0:float,1:float,2:float} */
return [$_array[0], $_array[1], $_array[2]];
}
// MARK: xyzD65 <-> xyzD50 // MARK: xyzD65 <-> xyzD50
/** /**
@@ -122,14 +137,14 @@ class CieXyz
*/ */
private static function xyzD65ToXyzD50(XYZ $xyz): XYZ private static function xyzD65ToXyzD50(XYZ $xyz): XYZ
{ {
return XYZ::__constructFromArray(Math::multiplyMatrices( return new XYZ(self::convertArray(Math::multiplyMatrices(
a: [ a: [
[1.0479298208405488, 0.022946793341019088, -0.05019222954313557], [1.0479298208405488, 0.022946793341019088, -0.05019222954313557],
[0.029627815688159344, 0.990434484573249, -0.01707382502938514], [0.029627815688159344, 0.990434484573249, -0.01707382502938514],
[-0.009243058152591178, 0.015055144896577895, 0.7518742899580008], [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008],
], ],
b: $xyz->returnAsArray(), b: $xyz->returnAsArray(),
), whitepoint: 'D50'); )), options: ["whitepoint" => 'D50']);
} }
/** /**
@@ -140,14 +155,14 @@ class CieXyz
*/ */
private static function xyzD50ToXyxD65(XYZ $xyz): XYZ private static function xyzD50ToXyxD65(XYZ $xyz): XYZ
{ {
return XYZ::__constructFromArray(Math::multiplyMatrices( return new XYZ(self::convertArray(Math::multiplyMatrices(
a: [ a: [
[0.9554734527042182, -0.023098536874261423, 0.0632593086610217], [0.9554734527042182, -0.023098536874261423, 0.0632593086610217],
[-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008],
[0.012314001688319899, -0.020507696433477912, 1.3303659366080753], [0.012314001688319899, -0.020507696433477912, 1.3303659366080753],
], ],
b: $xyz->returnAsArray() b: $xyz->returnAsArray()
), whitepoint: 'D65'); )), options: ["whitepoint" => 'D65']);
} }
// MARK: xyzD50 <-> Lab // MARK: xyzD50 <-> Lab
@@ -184,7 +199,7 @@ class CieXyz
$_xyz, $_xyz,
); );
return Lab::__constructFromArray([ return new Lab([
(116 * $f[1]) - 16, (116 * $f[1]) - 16,
500 * ($f[0] - $f[1]), 500 * ($f[0] - $f[1]),
200 * ($f[1] - $f[2]), 200 * ($f[1] - $f[2]),
@@ -227,13 +242,13 @@ class CieXyz
(1.0 - 0.3457 - 0.3585) / 0.3585, (1.0 - 0.3457 - 0.3585) / 0.3585,
]; ];
return XYZ::__constructFromArray( return new XYZ(
array_map( self::convertArray(array_map(
fn ($k, $v) => $v * $d50[$k], fn ($k, $v) => $v * $d50[$k],
array_keys($xyz), array_keys($xyz),
array_values($xyz), array_values($xyz),
), )),
whitepoint: 'D50' options: ["whitepoint" => 'D50']
); );
} }
@@ -249,17 +264,17 @@ class CieXyz
private static function linRgbToXyzD65(RGB $rgb): XYZ private static function linRgbToXyzD65(RGB $rgb): XYZ
{ {
// if not linear, convert to linear // if not linear, convert to linear
if (!$rgb->linear) { if (!(bool)$rgb->get('linear')) {
$rgb->toLinear(); $rgb = (new RGB($rgb->returnAsArray()))->toLinear();
} }
return XYZ::__constructFromArray(Math::multiplyMatrices( return new XYZ(self::convertArray(Math::multiplyMatrices(
[ [
[0.41239079926595934, 0.357584339383878, 0.1804807884018343], [0.41239079926595934, 0.357584339383878, 0.1804807884018343],
[0.21263900587151027, 0.715168678767756, 0.07219231536073371], [0.21263900587151027, 0.715168678767756, 0.07219231536073371],
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607], [0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
], ],
$rgb->returnAsArray() $rgb->returnAsArray()
), whitepoint: 'D65'); )), options: ["whitepoint" => 'D65']);
} }
/** /**
@@ -271,14 +286,14 @@ class CieXyz
private static function xyzD65ToLinRgb(XYZ $xyz): RGB private static function xyzD65ToLinRgb(XYZ $xyz): RGB
{ {
// xyz D65 to linrgb // xyz D65 to linrgb
return RGB::__constructFromArray(Math::multiplyMatrices( return new RGB(self::convertArray(Math::multiplyMatrices(
a : [ a : [
[ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ], [ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ],
[ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ], [ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ],
[ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ], [ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ],
], ],
b : $xyz->returnAsArray() b : $xyz->returnAsArray()
), linear: true); )), options: ["linear" => true]);
} }
// MARK: xyzD65 <-> OkLab // MARK: xyzD65 <-> OkLab
@@ -291,14 +306,14 @@ class CieXyz
*/ */
private static function xyzD65ToOkLab(XYZ $xyz): Lab private static function xyzD65ToOkLab(XYZ $xyz): Lab
{ {
return Lab::__constructFromArray(Math::multiplyMatrices( return new Lab(self::convertArray(Math::multiplyMatrices(
[ [
[0.2104542553, 0.7936177850, -0.0040720468], [0.2104542553, 0.7936177850, -0.0040720468],
[1.9779984951, -2.4285922050, 0.4505937099], [1.9779984951, -2.4285922050, 0.4505937099],
[0.0259040371, 0.7827717662, -0.8086757660], [0.0259040371, 0.7827717662, -0.8086757660],
], ],
array_map( array_map(
callback: fn ($v) => pow($v, 1 / 3), callback: fn ($v) => pow((float)$v, 1 / 3),
array: Math::multiplyMatrices( array: Math::multiplyMatrices(
a: [ a: [
[0.8190224432164319, 0.3619062562801221, -0.12887378261216414], [0.8190224432164319, 0.3619062562801221, -0.12887378261216414],
@@ -308,7 +323,7 @@ class CieXyz
b: $xyz->returnAsArray(), b: $xyz->returnAsArray(),
), ),
) )
), colorspace: 'OkLab'); )), colorspace: 'OkLab');
} }
/** /**
@@ -319,7 +334,7 @@ class CieXyz
*/ */
private static function okLabToXyzD65(Lab $lab): XYZ private static function okLabToXyzD65(Lab $lab): XYZ
{ {
return XYZ::__constructFromArray(Math::multiplyMatrices( return new XYZ(self::convertArray(Math::multiplyMatrices(
a: [ a: [
[1.2268798733741557, -0.5578149965554813, 0.28139105017721583], [1.2268798733741557, -0.5578149965554813, 0.28139105017721583],
[-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701],
@@ -337,7 +352,7 @@ class CieXyz
b: $lab->returnAsArray(), b: $lab->returnAsArray(),
), ),
), ),
), whitepoint: 'D65'); )), options: ["whitepoint" => 'D65']);
} }
} }

View File

@@ -55,13 +55,13 @@ class Color
private static function __labToLch(Lab $lab): array private static function __labToLch(Lab $lab): array
{ {
// cieLab to cieLch // cieLab to cieLch
$a = $lab->a; $a = (float)$lab->get('a');
$b = $lab->b; $b = (float)$lab->get('b');
$hue = atan2($b, $a) * 180 / pi(); $hue = atan2($b, $a) * 180 / pi();
return [ return [
$lab->L, (float)$lab->get('L'),
sqrt($a ** 2 + $b ** 2), sqrt($a ** 2 + $b ** 2),
$hue >= 0 ? $hue : $hue + 360, $hue >= 0 ? $hue : $hue + 360,
]; ];
@@ -76,9 +76,9 @@ class Color
private static function __lchToLab(LCH $lch): array private static function __lchToLab(LCH $lch): array
{ {
return [ return [
$lch->L, (float)$lch->get('L'),
$lch->C * cos($lch->H * pi() / 180), // a (float)$lch->get('C') * cos((float)$lch->get('H') * pi() / 180), // a
$lch->C * sin($lch->H * pi() / 180), // b (float)$lch->get('C') * sin((float)$lch->get('H') * pi() / 180), // b
]; ];
} }
@@ -94,9 +94,9 @@ class Color
*/ */
public static function rgbToHsl(RGB $rgb): HSL public static function rgbToHsl(RGB $rgb): HSL
{ {
$red = $rgb->R / 255; $red = (float)$rgb->get('R') / 255;
$green = $rgb->G / 255; $green = (float)$rgb->get('G') / 255;
$blue = $rgb->B / 255; $blue = (float)$rgb->get('B') / 255;
$min = min($red, $green, $blue); $min = min($red, $green, $blue);
$max = max($red, $green, $blue); $max = max($red, $green, $blue);
@@ -109,7 +109,7 @@ class Color
// achromatic // achromatic
if ($chroma == 0) { if ($chroma == 0) {
// H, S, L // H, S, L
return HSL::__constructFromArray([ return new HSL([
0.0, 0.0,
0.0, 0.0,
$lum * 100, $lum * 100,
@@ -128,7 +128,7 @@ class Color
} }
$hue = $hue * 60; $hue = $hue * 60;
// $sat = 1 - abs(2 * $lum - 1); // $sat = 1 - abs(2 * $lum - 1);
return HSL::__constructFromArray([ return new HSL([
$hue, $hue,
$sat * 100, $sat * 100,
$lum * 100, $lum * 100,
@@ -147,9 +147,9 @@ class Color
*/ */
public static function hslToRgb(HSL $hsl): RGB public static function hslToRgb(HSL $hsl): RGB
{ {
$hue = $hsl->H; $hue = (float)$hsl->get('H');
$sat = $hsl->S; $sat = (float)$hsl->get('S');
$lum = $hsl->L; $lum = (float)$hsl->get('L');
// calc to internal convert value for hue // calc to internal convert value for hue
$hue = (1 / 360) * $hue; $hue = (1 / 360) * $hue;
// convert to internal 0-1 format // convert to internal 0-1 format
@@ -158,7 +158,7 @@ class Color
// if saturation is 0 // if saturation is 0
if ($sat == 0) { if ($sat == 0) {
$lum = round($lum * 255); $lum = round($lum * 255);
return RGB::__constructFromArray([$lum, $lum, $lum]); return new RGB([$lum, $lum, $lum]);
} else { } else {
$m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat); $m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat);
$m1 = $lum * 2 - $m2; $m1 = $lum * 2 - $m2;
@@ -180,7 +180,7 @@ class Color
return $m1; return $m1;
}; };
return RGB::__constructFromArray([ return new RGB([
255 * $hueue($hue + (1 / 3)), 255 * $hueue($hue + (1 / 3)),
255 * $hueue($hue), 255 * $hueue($hue),
255 * $hueue($hue - (1 / 3)), 255 * $hueue($hue - (1 / 3)),
@@ -201,9 +201,9 @@ class Color
*/ */
public static function rgbToHsb(RGB $rgb): HSB public static function rgbToHsb(RGB $rgb): HSB
{ {
$red = $rgb->R / 255; $red = (float)$rgb->get('R') / 255;
$green = $rgb->G / 255; $green = (float)$rgb->get('G') / 255;
$blue = $rgb->B / 255; $blue = (float)$rgb->get('B') / 255;
$MAX = max($red, $green, $blue); $MAX = max($red, $green, $blue);
$MIN = min($red, $green, $blue); $MIN = min($red, $green, $blue);
@@ -212,7 +212,7 @@ class Color
// achromatic // achromatic
if ($MAX == $MIN) { if ($MAX == $MIN) {
return HSB::__constructFromArray([0, 0, $MAX * 100]); return new HSB([0, 0, $MAX * 100]);
} }
if ($red == $MAX) { if ($red == $MAX) {
$HUE = fmod(($green - $blue) / $DELTA, 6); $HUE = fmod(($green - $blue) / $DELTA, 6);
@@ -227,7 +227,7 @@ class Color
$HUE += 360; $HUE += 360;
} }
return HSB::__constructFromArray([ return new HSB([
$HUE, // Hue $HUE, // Hue
($DELTA / $MAX) * 100, // Saturation ($DELTA / $MAX) * 100, // Saturation
$MAX * 100, // Brightness $MAX * 100, // Brightness
@@ -246,16 +246,16 @@ class Color
*/ */
public static function hsbToRgb(HSB $hsb): RGB public static function hsbToRgb(HSB $hsb): RGB
{ {
$H = $hsb->H; $H = (float)$hsb->get('H');
$S = $hsb->S; $S = (float)$hsb->get('S');
$V = $hsb->B; $V = (float)$hsb->get('B');
// convert to internal 0-1 format // convert to internal 0-1 format
$S /= 100; $S /= 100;
$V /= 100; $V /= 100;
if ($S == 0) { if ($S == 0) {
$V = $V * 255; $V = $V * 255;
return RGB::__constructFromArray([$V, $V, $V]); return new RGB([$V, $V, $V]);
} }
$Hi = floor($H / 60); $Hi = floor($H / 60);
@@ -301,7 +301,7 @@ class Color
$blue = 0; $blue = 0;
} }
return RGB::__constructFromArray([ return new RGB([
$red * 255, $red * 255,
$green * 255, $green * 255,
$blue * 255, $blue * 255,
@@ -352,17 +352,23 @@ class Color
*/ */
public static function hslToHsb(HSL $hsl): HSB public static function hslToHsb(HSL $hsl): HSB
{ {
$saturation = $hsl->S / 100; $saturation = (float)$hsl->get('S') / 100;
$lightness = $hsl->L / 100; $lightness = (float)$hsl->get('L') / 100;
$value = $lightness + $saturation * min($lightness, 1 - $lightness); // if lightness is 0, then we cannot return convert to hsb
$value = $lightness + $saturation * min($lightness, 1 - $lightness);
// print "Orig: " . print_r($hsl, true) . "\n";
// print "SAT: " . $saturation . ", Lightness: " . $lightness . ", Value: " . $value . "\n";
// var_dump($value);
// check for black and white // check for black and white
$saturation = ($value === 0) ? $saturation = $value == 0 ?
0 : 0 :
200 * (1 - $lightness / $value); 200 * (1 - $lightness / $value);
return HSB::__constructFromArray([ $value *= 100;
$hsl->H, return new HSB([
(float)$hsl->get('H'),
$saturation, $saturation,
$value * 100, $value,
]); ]);
} }
@@ -375,18 +381,18 @@ class Color
public static function hsbToHsl(HSB $hsb): HSL public static function hsbToHsl(HSB $hsb): HSL
{ {
// hsv/toHsl // hsv/toHsl
$hue = $hsb->H; $hue = (float)$hsb->get('H');
$saturation = $hsb->S / 100; $saturation = (float)$hsb->get('S') / 100;
$value = $hsb->V / 100; $value = (float)$hsb->get('B') / 100;
$lightness = $value * (1 - $saturation / 2); $lightness = $value * (1 - $saturation / 2);
// check for B/W // check for B/W
$saturation = in_array($lightness, [0, 1], true) ? $saturation = in_array($lightness, [0, 1]) ?
0 : 0 :
100 * ($value - $lightness) / min($lightness, 1 - $lightness) 100 * ($value - $lightness) / min($lightness, 1 - $lightness)
; ;
return HSL::__constructFromArray([ return new HSL([
$hue, $hue,
$saturation, $saturation,
$lightness * 100, $lightness * 100,
@@ -436,10 +442,10 @@ class Color
public static function hsbToHwb(HSB $hsb): HWB public static function hsbToHwb(HSB $hsb): HWB
{ {
// hsv\Hwb // hsv\Hwb
return HWB::__constructFromArray([ return new HWB([
$hsb->H, // hue, (float)$hsb->get('H'), // hue,
$hsb->B * (100 - $hsb->S) / 100, // 2: brightness, 1: saturation (float)$hsb->get('B') * (100 - (float)$hsb->get('S')) / 100, // 2: brightness, 1: saturation
100 - $hsb->B, 100 - (float)$hsb->get('B'),
]); ]);
} }
@@ -451,22 +457,23 @@ class Color
*/ */
public static function hwbToHsb(HWB $hwb): HSB public static function hwbToHsb(HWB $hwb): HSB
{ {
$hue = $hwb->H; $hue = (float)$hwb->get('H');
$whiteness = $hwb->W / 100; $whiteness = (float)$hwb->get('W') / 100;
$blackness = $hwb->B / 100; $blackness = (float)$hwb->get('B') / 100;
$sum = $whiteness + $blackness; $sum = $whiteness + $blackness;
// print "S: B/W: " . $sum . " /W: " . $whiteness . " /B: " . $blackness . "\n";
// for black and white // for black and white
if ($sum >= 1) { if ($sum >= 1) {
$saturation = 0; $saturation = 0;
$value = $whiteness / $sum * 100; $value = $whiteness / $sum * 100;
} else { } else {
$value = 1 - $blackness; $value = 1 - $blackness;
$saturation = $value === 0 ? 0 : (1 - $whiteness / $value) * 100; $saturation = $value === 0.0 ? 0 : (1 - $whiteness / $value) * 100;
$value *= 100; $value *= 100;
} }
return HSB::__constructFromArray([ return new HSB([
$hue, $hue,
$saturation, $saturation,
$value, $value,
@@ -475,8 +482,6 @@ class Color
// MARK: LAB <-> LCH // MARK: LAB <-> LCH
// toLch
/** /**
* CIE Lab to LCH * CIE Lab to LCH
* *
@@ -486,7 +491,7 @@ class Color
public static function labToLch(Lab $lab): LCH public static function labToLch(Lab $lab): LCH
{ {
// cieLab to cieLch // cieLab to cieLch
return LCH::__constructFromArray(self::__labToLch($lab), colorspace: 'CIELab'); return new LCH(self::__labToLch($lab), colorspace: 'CIELab');
} }
/** /**
@@ -497,7 +502,7 @@ class Color
*/ */
public static function lchToLab(LCH $lch): Lab public static function lchToLab(LCH $lch): Lab
{ {
return Lab::__constructFromArray(self::__lchToLab($lch), colorspace: 'CIELab'); return new Lab(self::__lchToLab($lch), colorspace: 'CIELab');
} }
// MARK: OkLch <-> OkLab // MARK: OkLch <-> OkLab
@@ -511,7 +516,7 @@ class Color
public static function okLabToOkLch(Lab $lab): LCH public static function okLabToOkLch(Lab $lab): LCH
{ {
// okLab\toOkLch // okLab\toOkLch
return LCH::__constructFromArray(self::__labToLch($lab), colorspace: 'OkLab'); return new LCH(self::__labToLch($lab), colorspace: 'OkLab');
} }
/** /**
@@ -524,7 +529,7 @@ class Color
{ {
// oklch/toOkLab // oklch/toOkLab
// oklch to oklab // oklch to oklab
return Lab::__constructFromArray(self::__lchToLab($lch), colorspace: 'OkLab'); return new Lab(self::__lchToLab($lch), colorspace: 'OkLab');
} }
// MARK: rgb <-> oklab // MARK: rgb <-> oklab
@@ -557,7 +562,7 @@ class Color
* convert rgb to OkLch * convert rgb to OkLch
* via rgb -> linear rgb -> xyz D65 -> OkLab -> OkLch * via rgb -> linear rgb -> xyz D65 -> OkLab -> OkLch
* *
* @param RGB $rbh * @param RGB $rgb
* @return LCH * @return LCH
*/ */
public static function rgbToOkLch(RGB $rgb): LCH public static function rgbToOkLch(RGB $rgb): LCH
@@ -761,11 +766,6 @@ class Color
public static function rgbToLab(RGB $rgb): Lab public static function rgbToLab(RGB $rgb): Lab
{ {
return CieXyz::rgbViaXyzD65ViaXyzD50ToLab($rgb); return CieXyz::rgbViaXyzD65ViaXyzD50ToLab($rgb);
/* return CieXyz::xyzD50ToLab(
CieXyz::xyzD65ToXyzD50(
CieXyz::linRgbToXyzD65($rgb)
)
); */
} }
/** /**
@@ -778,11 +778,6 @@ class Color
public static function labToRgb(Lab $lab): RGB public static function labToRgb(Lab $lab): RGB
{ {
return CieXyz::labViaXyzD50ViaXyzD65ToRgb($lab); return CieXyz::labViaXyzD50ViaXyzD65ToRgb($lab);
/* return CieXyz::xyzD65ToLinRgb(
CieXyz::xyzD50ToXyxD65(
CieXyz::labToXyzD50($lab)
)
)->fromLinear(); */
} }
// MARK: RGB <-> Lch (Cie) // MARK: RGB <-> Lch (Cie)

View File

@@ -11,7 +11,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
class HSB use CoreLibs\Convert\Color\Utils;
class HSB implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['sRGB']; private const COLORSPACES = ['sRGB'];
@@ -29,22 +31,43 @@ class HSB
/** /**
* HSB (HSV) color coordinates * HSB (HSV) color coordinates
* Hue/Saturation/Brightness or Value * Hue/Saturation/Brightness or Value
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
{ {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: Hue, 1: Saturation, 2: Brightness * where 0: Hue, 1: Saturation, 2: Brightness
* *
* @param array{0:float,1:float,2:float} $colors * @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB] * @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
{ {
return (new HSB())->setColorspace($colorspace)->setFromArray($colors); return new HSB($colors, $colorspace, $options);
}
/**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
return $this;
} }
/** /**
@@ -54,7 +77,7 @@ class HSB
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
$name = strtoupper($name); $name = strtoupper($name);
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
@@ -62,18 +85,20 @@ class HSB
} }
switch ($name) { switch ($name) {
case 'H': case 'H':
if ($value == 360) { if ($value == 360.0) {
$value = 0; $value = 0;
} }
if ($value < 0 || $value > 359) { // if ($value < 0 || $value > 360) {
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for hue is not in the range of 0 to 359', 'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
1 1
); );
} }
break; break;
case 'S': case 'S':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for saturation is not in the range of 0 to 100', 'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
2 2
@@ -81,7 +106,8 @@ class HSB
} }
break; break;
case 'B': case 'B':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for brightness is not in the range of 0 to 100', 'Argument value ' . $value . ' for brightness is not in the range of 0 to 100',
3 3
@@ -98,7 +124,7 @@ class HSB
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
$name = strtoupper($name); $name = strtoupper($name);
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
@@ -140,11 +166,11 @@ class HSB
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('H', $colors[0]); $this->set('H', $colors[0]);
$this->__set('S', $colors[1]); $this->set('S', $colors[1]);
$this->__set('B', $colors[2]); $this->set('B', $colors[2]);
return $this; return $this;
} }

View File

@@ -11,9 +11,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Convert\Color\Stringify; use CoreLibs\Convert\Color\Utils;
class HSL class HSL implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['sRGB']; private const COLORSPACES = ['sRGB'];
@@ -31,22 +31,43 @@ class HSL
/** /**
* Color Coordinate HSL * Color Coordinate HSL
* Hue/Saturation/Lightness * Hue/Saturation/Lightness
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
{ {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: Hue, 1: Saturation, 2: Lightness * where 0: Hue, 1: Saturation, 2: Lightness
* *
* @param array{0:float,1:float,2:float} $colors * @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB] * @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
{ {
return (new HSL())->setColorspace($colorspace)->setFromArray($colors); return new HSL($colors, $colorspace, $options);
}
/**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
return $this;
} }
/** /**
@@ -56,25 +77,27 @@ class HSL
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
switch ($name) { switch ($name) {
case 'H': case 'H':
if ($value == 360) { if ($value == 360.0) {
$value = 0; $value = 0;
} }
if ($value < 0 || $value > 359) { // if ($value < 0 || $value > 360) {
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for hue is not in the range of 0 to 359', 'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
1 1
); );
} }
break; break;
case 'S': case 'S':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for saturation is not in the range of 0 to 100', 'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
2 2
@@ -82,9 +105,10 @@ class HSL
} }
break; break;
case 'L': case 'L':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for luminance is not in the range of 0 to 100', 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100',
3 3
); );
} }
@@ -99,7 +123,7 @@ class HSL
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -140,11 +164,11 @@ class HSL
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('H', $colors[0]); $this->set('H', $colors[0]);
$this->__set('S', $colors[1]); $this->set('S', $colors[1]);
$this->__set('L', $colors[2]); $this->set('L', $colors[2]);
return $this; return $this;
} }
@@ -162,7 +186,7 @@ class HSL
. $this->S . $this->S
. ' ' . ' '
. $this->L . $this->L
. Stringify::setOpacity($opacity) . Utils::setOpacity($opacity)
. ')'; . ')';
return $string; return $string;
} }

View File

@@ -11,9 +11,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Convert\Color\Stringify; use CoreLibs\Convert\Color\Utils;
class HWB class HWB implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['sRGB']; private const COLORSPACES = ['sRGB'];
@@ -31,22 +31,43 @@ class HWB
/** /**
* Color Coordinate: HWB * Color Coordinate: HWB
* Hue/Whiteness/Blackness * Hue/Whiteness/Blackness
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
{ {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: Hue, 1: Whiteness, 2: Blackness * where 0: Hue, 1: Whiteness, 2: Blackness
* *
* @param array{0:float,1:float,2:float} $colors * @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=sRGB] * @param string $colorspace [default=sRGB]
* @param array<string,string> $options [default=[]]
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
{ {
return (new HWB())->setColorspace($colorspace)->setFromArray($colors); return new HWB($colors, $colorspace, $options);
}
/**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
return $this;
} }
/** /**
@@ -56,17 +77,18 @@ class HWB
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
switch ($name) { switch ($name) {
case 'H': case 'H':
if ($value == 360) { if ($value == 360.0) {
$value = 0; $value = 0;
} }
if ($value < 0 || $value > 360) { // if ($value < 0 || $value > 360) {
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for hue is not in the range of 0 to 360', 'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
1 1
@@ -74,17 +96,19 @@ class HWB
} }
break; break;
case 'W': case 'W':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for saturation is not in the range of 0 to 100', 'Argument value ' . $value . ' for whiteness is not in the range of 0 to 100',
2 2
); );
} }
break; break;
case 'B': case 'B':
if ($value < 0 || $value > 100) { // if ($value < 0 || $value > 100) {
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for luminance is not in the range of 0 to 100', 'Argument value ' . $value . ' for blackness is not in the range of 0 to 100',
3 3
); );
} }
@@ -99,7 +123,7 @@ class HWB
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -140,11 +164,11 @@ class HWB
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('H', $colors[0]); $this->set('H', $colors[0]);
$this->__set('W', $colors[1]); $this->set('W', $colors[1]);
$this->__set('B', $colors[2]); $this->set('B', $colors[2]);
return $this; return $this;
} }
@@ -162,7 +186,7 @@ class HWB
. $this->W . $this->W
. ' ' . ' '
. $this->B . $this->B
. Stringify::setOpacity($opacity) . Utils::setOpacity($opacity)
. ')'; . ')';
return $string; return $string;
} }

View File

@@ -0,0 +1,53 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: Ymd
* DESCRIPTION:
* DescriptionHere
*/
declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates\Interface;
interface CoordinatesInterface
{
/**
* create class via "Class::create()" call
* was used for multiple create interfaces
* no longer needed, use "new Class()" instead
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default='']
* @param array<string,string|bool|int> $options [default=[]]
* @return self
*/
public static function create(string|array $colors, string $colorspace = '', array $options = []): self;
/**
* get color
*
* @param string $name
* @return float
*/
public function get(string $name): float|string|bool;
/**
* Returns the color as array
* where 0: Lightness, 1: a, 2: b
*
* @return array{0:float,1:float,2:float}
*/
public function returnAsArray(): array;
/**
* Convert into css string with optional opacity
*
* @param null|float|string|null $opacity
* @return string
*/
public function toCssString(null|float|string $opacity = null): string;
}
// __END__

View File

@@ -12,9 +12,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Convert\Color\Stringify; use CoreLibs\Convert\Color\Utils;
class LCH class LCH implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['OkLab', 'CIELab']; private const COLORSPACES = ['OkLab', 'CIELab'];
@@ -42,22 +42,43 @@ class LCH
/** /**
* Color Coordinate Lch * Color Coordinate Lch
* for oklch * for oklch
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default='']
* @param array<string,string> $options [default=[]]
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = '', array $options = [])
{ {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: Lightness, 1: Chroma, 2: Hue * where 0: Lightness, 1: Chroma, 2: Hue
* *
* @param array{0:float,1:float,2:float} $colors * @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace * @param string $colorspace [default='']
* @param array<string,string> $options [default=[]]
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace): self public static function create(string|array $colors, string $colorspace = '', array $options = []): self
{ {
return (new LCH())->setColorspace($colorspace)->setFromArray($colors); return new LCH($colors, $colorspace, $options);
}
/**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
return $this;
} }
/** /**
@@ -67,49 +88,49 @@ class LCH
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
switch ($name) { switch ($name) {
// case 'L': case 'L':
// if ($this->colorspace == 'cie' && ($value < 0 || $value > 100)) { // if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) {
// throw new \LengthException( if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) {
// 'Argument value ' . $value . ' for lightness is not in the range of '
// . '0 to 100',
// 3
// );
// } elseif ($this->colorspace == 'ok' && ($value < 0 || $value > 1)) {
// throw new \LengthException(
// 'Argument value ' . $value . ' for lightness is not in the range of '
// . '0 to 1',
// 3
// );
// }
// break;
// case 'c':
// if ($this->colorspace == 'cie' && ($value < 0 || $value > 230)) {
// throw new \LengthException(
// 'Argument value ' . $value . ' for chroma is not in the range of '
// . '0 to 230 with normal upper limit of 150',
// 3
// );
// } elseif ($this->colorspace == 'ok' && ($value < 0 || $value > 0.5)) {
// throw new \LengthException(
// 'Argument value ' . $value . ' for chroma is not in the range of '
// . '0 to 0.5 with normal upper limit of 0.5',
// 3
// );
// }
// break;
case 'h':
if ($value == 360) {
$value = 0;
}
if ($value < 0 || $value > 360) {
throw new \LengthException( throw new \LengthException(
'Argument value ' . $value . ' for lightness is not in the range of 0 to 360', 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab',
1
);
// } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) {
} elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab',
1
);
}
break;
case 'C':
// if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 230)) {
if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 230.0, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for chroma is not in the range of '
. '0 to 150 and a maximum of 230 for CIE Lab',
1
);
// } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 0.55)) {
} elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 0.55, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for lightness is not in the range of '
. '0.0 to 0.4 and a maximum of 0.5 for OkLab',
1
);
}
break;
case 'H':
// if ($value < 0 || $value > 360) {
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for hue is not in the range of 0.0 to 360.0',
1 1
); );
} }
@@ -124,7 +145,7 @@ class LCH
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -165,11 +186,11 @@ class LCH
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('L', $colors[0]); $this->set('L', $colors[0]);
$this->__set('C', $colors[1]); $this->set('C', $colors[1]);
$this->__set('H', $colors[2]); $this->set('H', $colors[2]);
return $this; return $this;
} }
@@ -193,10 +214,10 @@ class LCH
$string .= '(' $string .= '('
. $this->L . $this->L
. ' ' . ' '
. $this->c . $this->C
. ' ' . ' '
. $this->h . $this->H
. Stringify::setOpacity($opacity) . Utils::setOpacity($opacity)
. ');'; . ');';
return $string; return $string;

View File

@@ -12,9 +12,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Convert\Color\Stringify; use CoreLibs\Convert\Color\Utils;
class Lab class Lab implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['OkLab', 'CIELab']; private const COLORSPACES = ['OkLab', 'CIELab'];
@@ -44,22 +44,43 @@ class Lab
/** /**
* Color Coordinate: Lab * Color Coordinate: Lab
* for oklab or cie * for oklab or cie
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default='']
* @param array<string,string> $options [default=[]]
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = '', array $options = [])
{ {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: Lightness, 1: a, 2: b * where 0: Lightness, 1: a, 2: b
* *
* @param array{0:float,1:float,2:float} $rgb * @param array{0:float,1:float,2:float} $colors
* @param string $colorspace * @param string $colorspace [default='']
* @param array<string,string> $options [default=[]]
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace): self public static function create(string|array $colors, string $colorspace = '', array $options = []): self
{ {
return (new Lab())->setColorspace($colorspace)->setFromArray($colors); return new Lab($colors, $colorspace, $options);
}
/**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
return $this;
} }
/** /**
@@ -69,40 +90,58 @@ class Lab
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
// switch ($name) { switch ($name) {
// case 'L': case 'L':
// if ($value == 360) { // if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) {
// $value = 0; if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) {
// } throw new \LengthException(
// if ($value < 0 || $value > 360) { 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab',
// throw new \LengthException( 1
// 'Argument value ' . $value . ' for lightness is not in the range of 0 to 360', );
// 1 // } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) {
// ); } elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) {
// } throw new \LengthException(
// break; 'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab',
// case 'a': 1
// if ($value < 0 || $value > 100) { );
// throw new \LengthException( }
// 'Argument value ' . $value . ' for a is not in the range of 0 to 100', break;
// 2 case 'a':
// ); // if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) {
// } if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) {
// break; throw new \LengthException(
// case 'b': 'Argument value ' . $value . ' for a is not in the range of -125 to 125 for CIE Lab',
// if ($value < 0 || $value > 100) { 2
// throw new \LengthException( );
// 'Argument value ' . $value . ' for b is not in the range of 0 to 100', // } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) {
// 3 } elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) {
// ); throw new \LengthException(
// } 'Argument value ' . $value . ' for a is not in the range of -0.5 to 0.5 for OkLab',
// break; 2
// } );
}
break;
case 'b':
// if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) {
if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for b is not in the range of -125 to 125 for CIE Lab',
3
);
// } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) {
} elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) {
throw new \LengthException(
'Argument value ' . $value . ' for b is not in the range of -0.5 to 0.5 for OkLab',
3
);
}
break;
}
$this->$name = $value; $this->$name = $value;
} }
@@ -112,7 +151,7 @@ class Lab
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -153,11 +192,11 @@ class Lab
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('L', $colors[0]); $this->set('L', $colors[0]);
$this->__set('a', $colors[1]); $this->set('a', $colors[1]);
$this->__set('b', $colors[2]); $this->set('b', $colors[2]);
return $this; return $this;
} }
@@ -184,7 +223,7 @@ class Lab
. $this->a . $this->a
. ' ' . ' '
. $this->b . $this->b
. Stringify::setOpacity($opacity) . Utils::setOpacity($opacity)
. ');'; . ');';
return $string; return $string;

View File

@@ -11,9 +11,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
use CoreLibs\Convert\Color\Stringify; use CoreLibs\Convert\Color\Utils;
class RGB class RGB implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['sRGB']; private const COLORSPACES = ['sRGB'];
@@ -33,41 +33,48 @@ class RGB
/** /**
* Color Coordinate RGB * Color Coordinate RGB
* @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string
* @param string $colorspace [default=sRGB]
* @param array<string,bool> $options [default=[]] only "linear" allowed at the moment
*/ */
public function __construct() public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
{ {
$this->setColorspace($colorspace)->parseOptions($options);
if (is_array($colors)) {
$this->setFromArray($colors);
} else {
$this->setFromHex($colors);
}
} }
/** /**
* set from array * set from array or string
* where 0: Red, 1: Green, 2: Blue * where 0: Red, 1: Green, 2: Blue
* OR #ffffff or ffffff
* *
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string
* @param string $colorspace [default=sRGB] * @param string $colorspace [default=sRGB]
* @param bool $linear [default=false] * @param array<string,bool> $options [default=[]] only "linear" allowed at the moment
* @return self * @return self
*/ */
public static function __constructFromArray(array $colors, string $colorspace = 'sRGB', bool $linear = false): self public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
{ {
return (new RGB())->setColorspace($colorspace)->flagLinear($linear)->setFromArray($colors); return new RGB($colors, $colorspace, $options);
} }
/** /**
* Undocumented function * parse options
* *
* @param string $hex_string * @param array<string,bool> $options
* @param string $colorspace
* @param bool $linear
* @return self * @return self
*/ */
public static function __constructFromHexString( private function parseOptions(array $options): self
string $hex_string, {
string $colorspace = 'sRGB', $this->flagLinear($options['linear'] ?? false);
bool $linear = false return $this;
): self {
return (new RGB())->setColorspace($colorspace)->flagLinear($linear)->setFromHex($hex_string);
} }
/** /**
* set color * set color
* *
@@ -75,7 +82,7 @@ class RGB
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
// do not allow setting linear from outside // do not allow setting linear from outside
if ($name == 'linear') { if ($name == 'linear') {
@@ -85,13 +92,15 @@ class RGB
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
// if not linear // if not linear
if (!$this->linear && ($value < 0 || $value > 255)) { if (!$this->linear && ((int)$value < 0 || (int)$value > 255)) {
throw new \LengthException('Argument value ' . $value . ' for color ' . $name throw new \LengthException('Argument value ' . $value . ' for color ' . $name
. ' is not in the range of 0 to 255', 1); . ' is not in the range of 0 to 255', 1);
} elseif ($this->linear && ($value < -10E10 || $value > 1)) { } elseif (
// not allow very very small negative numbers // $this->linear && ($value < 0.0 || $value > 1.0)
$this->linear && Utils::compare(0.0, $value, 1.0, 0.000001)
) {
throw new \LengthException('Argument value ' . $value . ' for color ' . $name throw new \LengthException('Argument value ' . $value . ' for color ' . $name
. ' is not in the range of 0 to 1 for linear rgb', 1); . ' is not in the range of 0 to 1 for linear rgb', 2);
} }
$this->$name = $value; $this->$name = $value;
} }
@@ -102,7 +111,7 @@ class RGB
* @param string $name * @param string $name
* @return float|bool * @return float|bool
*/ */
public function __get(string $name): float|bool public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -143,11 +152,11 @@ class RGB
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('R', $colors[0]); $this->set('R', $colors[0]);
$this->__set('G', $colors[1]); $this->set('G', $colors[1]);
$this->__set('B', $colors[2]); $this->set('B', $colors[2]);
return $this; return $this;
} }
@@ -180,11 +189,11 @@ class RGB
* @param string $hex_string * @param string $hex_string
* @return self * @return self
*/ */
public function setFromHex(string $hex_string): self private function setFromHex(string $hex_string): self
{ {
$hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string $hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string
if (!is_string($hex_string)) { if (empty($hex_string) || !is_string($hex_string)) {
throw new \InvalidArgumentException('hex_string argument cannot be empty', 1); throw new \InvalidArgumentException('hex_string argument cannot be empty', 3);
} }
$rgbArray = []; $rgbArray = [];
if (strlen($hex_string) == 6) { if (strlen($hex_string) == 6) {
@@ -205,7 +214,7 @@ class RGB
]; ];
} else { } else {
// Invalid hex color code // Invalid hex color code
throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 2); throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 4);
} }
return $this->setFromArray($rgbArray); return $this->setFromArray($rgbArray);
} }
@@ -238,6 +247,10 @@ class RGB
*/ */
public function toLinear(): self public function toLinear(): self
{ {
// if linear, as is
if ($this->linear) {
return $this;
}
$this->flagLinear(true)->setFromArray(array_map( $this->flagLinear(true)->setFromArray(array_map(
callback: function (int|float $v) { callback: function (int|float $v) {
$v = (float)($v / 255); $v = (float)($v / 255);
@@ -262,6 +275,10 @@ class RGB
*/ */
public function fromLinear(): self public function fromLinear(): self
{ {
// if not linear, as is
if (!$this->linear) {
return $this;
}
$this->flagLinear(false)->setFromArray(array_map( $this->flagLinear(false)->setFromArray(array_map(
callback: function (int|float $v) { callback: function (int|float $v) {
$abs = abs($v); $abs = abs($v);
@@ -276,7 +293,6 @@ class RGB
}, },
array: $this->returnAsArray(), array: $this->returnAsArray(),
)); ));
// $this->linear = false;
return $this; return $this;
} }
@@ -301,7 +317,7 @@ class RGB
. (int)round($this->G, 0) . (int)round($this->G, 0)
. ' ' . ' '
. (int)round($this->B, 0) . (int)round($this->B, 0)
. Stringify::setOpacity($opacity) . Utils::setOpacity($opacity)
. ')'; . ')';
if ($was_linear) { if ($was_linear) {
$this->toLinear(); $this->toLinear();

View File

@@ -15,7 +15,9 @@ declare(strict_types=1);
namespace CoreLibs\Convert\Color\Coordinates; namespace CoreLibs\Convert\Color\Coordinates;
class XYZ // use CoreLibs\Convert\Color\Utils;
class XYZ implements Interface\CoordinatesInterface
{ {
/** @var array<string> allowed colorspaces */ /** @var array<string> allowed colorspaces */
private const COLORSPACES = ['CIEXYZ']; private const COLORSPACES = ['CIEXYZ'];
@@ -35,34 +37,58 @@ class XYZ
/** @var string color space: either ok or cie */ /** @var string color space: either ok or cie */
private string $colorspace = ''; private string $colorspace = '';
/** @var string illuminat white point: only D50 and D65 are allowed */
private string $whitepoint = ''; private string $whitepoint = '';
/** /**
* Color Coordinate Lch * Color Coordinate Lch
* for oklch * for oklch conversion
*
* @param string|array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=CIEXYZ]
* @param array<string,string> $options [default=[]] Only "whitepoint" option allowed
* @throws \InvalidArgumentException only array colors allowed
*/ */
public function __construct() public function __construct(
{ string|array $colors,
string $colorspace = 'CIEXYZ',
array $options = [],
) {
if (!is_array($colors)) {
throw new \InvalidArgumentException('Only array colors allowed', 0);
}
$this->setColorspace($colorspace)
->parseOptions($options)
->setFromArray($colors);
} }
/** /**
* set from array * set from array
* where 0: X, 1: Y, 2: Z * where 0: X, 1: Y, 2: Z
* *
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @param string $colorspace [default=CIEXYZ] * @param string $colorspace [default=CIEXYZ]
* @param string $whitepoint [default=''] only D65 or D50 allowed * @param array<string,string> $options [default=[]] Only "whitepoint" option allowed
* @return self * @return self
*/ */
public static function __constructFromArray( public static function create(
array $colors, string|array $colors,
string $colorspace = 'CIEXYZ', string $colorspace = 'CIEXYZ',
string $whitepoint = '' array $options = [],
): self { ): self {
return (new XYZ()) return new XYZ($colors, $colorspace, $options);
->setColorspace($colorspace) }
->setWhitepoint($whitepoint)
->setFromArray($colors); /**
* parse options
*
* @param array<string,string> $options
* @return self
*/
private function parseOptions(array $options): self
{
$this->setWhitepoint($options['whitepoint'] ?? '');
return $this;
} }
/** /**
@@ -72,14 +98,16 @@ class XYZ
* @param float $value * @param float $value
* @return void * @return void
*/ */
public function __set(string $name, float $value): void private function set(string $name, float $value): void
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
} }
// if ($value < 0 || $value > 255) { // TODO: setup XYZ value limits
// X: 0 to 95.047, Y: 0 to 100, Z: 0 to 108.88
// if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL))) {
// throw new \LengthException('Argument value ' . $value . ' for color ' . $name // throw new \LengthException('Argument value ' . $value . ' for color ' . $name
// . ' is not in the range of 0 to 255', 1); // . ' is not in the range of 0 to 100.0', 1);
// } // }
$this->$name = $value; $this->$name = $value;
} }
@@ -90,7 +118,7 @@ class XYZ
* @param string $name * @param string $name
* @return float * @return float
*/ */
public function __get(string $name): float public function get(string $name): float|string|bool
{ {
if (!property_exists($this, $name)) { if (!property_exists($this, $name)) {
throw new \ErrorException('Creation of dynamic property is not allowed', 0); throw new \ErrorException('Creation of dynamic property is not allowed', 0);
@@ -150,13 +178,25 @@ class XYZ
* @param array{0:float,1:float,2:float} $colors * @param array{0:float,1:float,2:float} $colors
* @return self * @return self
*/ */
public function setFromArray(array $colors): self private function setFromArray(array $colors): self
{ {
$this->__set('X', $colors[0]); $this->set('X', $colors[0]);
$this->__set('Y', $colors[1]); $this->set('Y', $colors[1]);
$this->__set('Z', $colors[2]); $this->set('Z', $colors[2]);
return $this; return $this;
} }
/**
* no hsb in css
*
* @param float|string|null $opacity
* @return string
* @throws \ErrorException
*/
public function toCssString(null|float|string $opacity = null): string
{
throw new \ErrorException('XYZ is not available as CSS color string', 0);
}
} }
// __END__ // __END__

View File

@@ -19,25 +19,6 @@ use CoreLibs\Convert\Color\Coordinates\LCH;
class Stringify class Stringify
{ {
/**
* Build the opactiy sub string part and return it
*
* @param null|float|string|null $opacity
* @return string
*/
public static function setOpacity(null|float|string $opacity = null): string
{
// set opacity, either a string or float
if (is_string($opacity)) {
$opacity = ' / ' . $opacity;
} elseif ($opacity !== null) {
$opacity = ' / ' . $opacity;
} else {
$opacity = '';
}
return $opacity;
}
/** /**
* return the CSS string including optional opacity * return the CSS string including optional opacity
* *

View File

@@ -0,0 +1,56 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: 2024/11/14
* DESCRIPTION:
* Utils for color
*/
declare(strict_types=1);
namespace CoreLibs\Convert\Color;
use CoreLibs\Convert\Math;
class Utils
{
/** @var float deviation allowed for valid data checks, small */
public const EPSILON_SMALL = 0.000000000001;
/** @var float deviation allowed for valid data checks, medium */
public const EPSILON_MEDIUM = 0.0000001;
/** @var float deviation allowed for valid data checks, big */
public const ESPILON_BIG = 0.0001;
public static function compare(float $lower, float $value, float $upper, float $epslion): bool
{
if (
Math::compareWithEpsilon($value, '<', $lower, $epslion) ||
Math::compareWithEpsilon($value, '>', $upper, $epslion)
) {
return true;
}
return false;
}
/**
* Build the opactiy sub string part and return it
*
* @param null|float|string|null $opacity
* @return string
*/
public static function setOpacity(null|float|string $opacity = null): string
{
// set opacity, either a string or float
if (is_string($opacity)) {
$opacity = ' / ' . $opacity;
} elseif ($opacity !== null) {
$opacity = ' / ' . $opacity;
} else {
$opacity = '';
}
return $opacity;
}
}
// __END__

View File

@@ -33,6 +33,7 @@ class Colors
* @param bool $hex_prefix default true, prefix with "#" * @param bool $hex_prefix default true, prefix with "#"
* @return string rgb in hex values with leading # if set, * @return string rgb in hex values with leading # if set,
* @throws \LengthException If any argument is not in the range of 0~255 * @throws \LengthException If any argument is not in the range of 0~255
* @deprecated v9.20.0 use: new Coordinates\RGB([$red, $green, $blue]))->returnAsHex(true/false for #)
*/ */
public static function rgb2hex( public static function rgb2hex(
int $red, int $red,
@@ -40,7 +41,7 @@ class Colors
int $blue, int $blue,
bool $hex_prefix = true bool $hex_prefix = true
): string { ): string {
return Coordinates\RGB::__constructFromArray([$red, $green, $blue])->returnAsHex($hex_prefix); return (new Coordinates\RGB([$red, $green, $blue]))->returnAsHex($hex_prefix);
} }
/** /**
@@ -53,6 +54,7 @@ class Colors
* or a string with the seperator * or a string with the seperator
* @throws \InvalidArgumentException if hex string is empty * @throws \InvalidArgumentException if hex string is empty
* @throws \UnexpectedValueException if the hex string value is not valid * @throws \UnexpectedValueException if the hex string value is not valid
* @deprecated v9.20.0 use: new Coordinates\RGB($hex_string) (build string/array from return data)
*/ */
public static function hex2rgb( public static function hex2rgb(
string $hex_string, string $hex_string,
@@ -61,7 +63,8 @@ class Colors
): string|array { ): string|array {
$rgbArray = []; $rgbArray = [];
// rewrite to previous r/g/b key output // rewrite to previous r/g/b key output
foreach (Coordinates\RGB::__constructFromHexString($hex_string)->returnAsArray() as $p => $el) { foreach ((new Coordinates\RGB($hex_string))->returnAsArray() as $p => $el) {
$k = '';
switch ($p) { switch ($p) {
case 0: case 0:
$k = 'r'; $k = 'r';
@@ -90,13 +93,14 @@ class Colors
* @param int $blue blue 0-255 * @param int $blue blue 0-255
* @return array<int|float> Hue, Sat, Brightness/Value * @return array<int|float> Hue, Sat, Brightness/Value
* @throws \LengthException If any argument is not in the range of 0~255 * @throws \LengthException If any argument is not in the range of 0~255
* @deprecated v9.20.0 use: Color::rgbToHsb(...)->returnAsArray() will return float unrounded
*/ */
public static function rgb2hsb(int $red, int $green, int $blue): array public static function rgb2hsb(int $red, int $green, int $blue): array
{ {
return array_map( return array_map(
fn ($v) => (int)round($v), fn ($v) => (int)round($v),
Color::rgbToHsb( Color::rgbToHsb(
Coordinates\RGB::__constructFromArray([$red, $green, $blue]) new Coordinates\RGB([$red, $green, $blue])
)->returnAsArray() )->returnAsArray()
); );
} }
@@ -111,13 +115,14 @@ class Colors
* @param float $V brightness/value 0-100 (int) * @param float $V brightness/value 0-100 (int)
* @return array<int> 0 red/1 green/2 blue array as 0-255 * @return array<int> 0 red/1 green/2 blue array as 0-255
* @throws \LengthException If any argument is not in the valid range * @throws \LengthException If any argument is not in the valid range
* @deprecated v9.20.0 use: Color::hsbToRgb(...)->returnAsArray() will return float unrounded
*/ */
public static function hsb2rgb(float $H, float $S, float $V): array public static function hsb2rgb(float $H, float $S, float $V): array
{ {
return array_map( return array_map(
fn ($v) => (int)round($v), fn ($v) => (int)round($v),
Color::hsbToRgb( Color::hsbToRgb(
Coordinates\HSB::__constructFromArray([$H, $S, $V]) new Coordinates\HSB([$H, $S, $V])
)->returnAsArray() )->returnAsArray()
); );
} }
@@ -132,13 +137,14 @@ class Colors
* @param int $blue blue 0-255 * @param int $blue blue 0-255
* @return array<float> hue/sat/luminance * @return array<float> hue/sat/luminance
* @throws \LengthException If any argument is not in the range of 0~255 * @throws \LengthException If any argument is not in the range of 0~255
* @deprecated v9.20.0 use: Color::rgbToHsl(...)->returnAsArray() will return float unrounded
*/ */
public static function rgb2hsl(int $red, int $green, int $blue): array public static function rgb2hsl(int $red, int $green, int $blue): array
{ {
return array_map( return array_map(
fn ($v) => round($v, 1), fn ($v) => round($v, 1),
Color::rgbToHsl( Color::rgbToHsl(
Coordinates\RGB::__constructFromArray([$red, $green, $blue]) new Coordinates\RGB([$red, $green, $blue])
)->returnAsArray() )->returnAsArray()
); );
} }
@@ -152,13 +158,14 @@ class Colors
* @param float $lum luminance: 0-100 * @param float $lum luminance: 0-100
* @return array<int,float|int> red/blue/green 0-255 each * @return array<int,float|int> red/blue/green 0-255 each
* @throws \LengthException If any argument is not in the valid range * @throws \LengthException If any argument is not in the valid range
* @deprecated v9.20.0 use: Color::hslToRgb(...)->returnAsArray() will return float unrounded
*/ */
public static function hsl2rgb(float $hue, float $sat, float $lum): array public static function hsl2rgb(float $hue, float $sat, float $lum): array
{ {
return array_map( return array_map(
fn ($v) => round($v), fn ($v) => round($v),
Color::hslToRgb( Color::hslToRgb(
Coordinates\HSL::__constructFromArray([$hue, $sat, $lum]) new Coordinates\HSL([$hue, $sat, $lum])
)->returnAsArray() )->returnAsArray()
); );
} }

View File

@@ -68,6 +68,65 @@ class Math
return pow((float)$number, 1.0 / 3); return pow((float)$number, 1.0 / 3);
} }
/**
* use PHP_FLOAT_EPSILON to compare if two float numbers are matching
*
* @param float $x
* @param float $y
* @param float $epsilon [default=PHP_FLOAT_EPSILON]
* @return bool True equal
*/
public static function equalWithEpsilon(float $x, float $y, float $epsilon = PHP_FLOAT_EPSILON): bool
{
if (abs($x - $y) < $epsilon) {
return true;
}
return false;
}
/**
* Compare two value base on direction given
* The default delta is PHP_FLOAT_EPSILON
*
* @param float $value
* @param string $compare
* @param float $limit
* @param float $epsilon [default=PHP_FLOAT_EPSILON]
* @return bool True on smaller/large or equal
*/
public static function compareWithEpsilon(
float $value,
string $compare,
float $limit,
float $epsilon = PHP_FLOAT_EPSILON
): bool {
switch ($compare) {
case '<':
if ($value < ($limit - $epsilon)) {
return true;
}
break;
case '<=':
if ($value <= ($limit - $epsilon)) {
return true;
}
break;
case '==':
return self::equalWithEpsilon($value, $limit, $epsilon);
case '>':
if ($value > ($limit + $epsilon)) {
return true;
}
break;
case '>=':
if ($value >= ($limit + $epsilon)) {
return true;
}
break;
}
return false;
}
/** /**
* This function is directly inspired by the multiplyMatrices() function in color.js * This function is directly inspired by the multiplyMatrices() function in color.js
* form Lea Verou and Chris Lilley. * form Lea Verou and Chris Lilley.
@@ -77,10 +136,32 @@ class Math
* *
* It returns an array which is the product of the two number matrices passed as parameters. * It returns an array which is the product of the two number matrices passed as parameters.
* *
* @param array<array<int|float>> $a m x n matrice * NOTE:
* @param array<array<int|float>> $b n x p matrice * if the right side (B matrix) has a missing row, this row will be fillwed with 0 instead of
* throwing an error:
* A:
* [
* [1, 2, 3],
* [4, 5, 6],
* ]
* B:
* [
* [7, 8, 9],
* [10, 11, 12],
* ]
* The B will get a third row with [0, 0, 0] added to make the multiplication work as it will be
* rewritten as
* B-rewrite:
* [
* [7, 10, 0],
* [8, 11, 12],
* [0, 0, 0] <- automatically added
* ]
* *
* @return array<array<int|float>> m x p product * @param array<float|int|array<int|float>> $a m x n matrice
* @param array<float|int|array<int|float>> $b n x p matrice
*
* @return array<float|int|array<int|float>> m x p product
*/ */
public static function multiplyMatrices(array $a, array $b): array public static function multiplyMatrices(array $a, array $b): array
{ {
@@ -88,7 +169,7 @@ class Math
if (!is_array($a[0] ?? null)) { if (!is_array($a[0] ?? null)) {
// $a is vector, convert to [[a, b, c, ...]] // $a is vector, convert to [[a, b, c, ...]]
$a = [ $a ]; $a = [$a];
} }
if (!is_array($b[0])) { if (!is_array($b[0])) {
@@ -102,9 +183,10 @@ class Math
$p = count($b[0]); $p = count($b[0]);
// transpose $b: // transpose $b:
// so that we can multiply row by row
$bCols = array_map( $bCols = array_map(
callback: fn ($k) => \array_map( callback: fn ($k) => array_map(
(fn ($i) => $i[$k]), (fn ($i) => is_array($i) ? $i[$k] : 0),
$b, $b,
), ),
array: array_keys($b[0]), array: array_keys($b[0]),
@@ -116,7 +198,8 @@ class Math
array_reduce( array_reduce(
array: $row, array: $row,
callback: fn ($a, $v, $i = null) => $a + $v * ( callback: fn ($a, $v, $i = null) => $a + $v * (
$col[$i ?? array_search($v, $row)] ?? 0 // if last entry missing for full copy add a 0 to it
$col[$i ?? array_search($v, $row, true)] ?? 0 /** @phpstan-ignore-line */
), ),
initial: 0, initial: 0,
) : ) :
@@ -132,13 +215,13 @@ class Math
if ($m === 1) { if ($m === 1) {
// Avoid [[a, b, c, ...]]: // Avoid [[a, b, c, ...]]:
$product = $product[0]; return $product[0];
} }
if ($p === 1) { if ($p === 1) {
// Avoid [[a], [b], [c], ...]]: // Avoid [[a], [b], [c], ...]]:
return array_map( return array_map(
callback: fn ($v) => $v[0], callback: fn ($v) => $v[0] ?? 0,
array: $product, array: $product,
); );
} }