Commit Graph

847 Commits

Author SHA1 Message Date
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
Clemens Schwaighofer
2bd68f32ac Legacy color convert update to use new methods 2024-11-13 13:16:02 +09:00
Clemens Schwaighofer
f5964fed02 Legacy colors test update 2024-11-13 12:45:21 +09:00
Clemens Schwaighofer
625272198d Math matrix phpunit checks added 2024-11-13 11:42:24 +09:00
Clemens Schwaighofer
00821bd5ea Move all Cie XYZ to dedicated class as this is not used in direct frontend convert
Clean up old Colors class with calling new class calls

Test all and set phpstan deprecated messages

Add all missing convert functions for oklab/cielab/oklch/cielch calls

Prepare for test run creation
2024-11-12 18:53:18 +09:00
Clemens Schwaighofer
921b9cb3d9 Remove not used Color Coordinate classes and old oklab convert class 2024-11-12 18:53:02 +09:00
Clemens Schwaighofer
720b78b687 Add CIE XYZ classes for D50/D65 whitespace before clean up 2024-11-12 18:52:24 +09:00
Clemens Schwaighofer
565014e1e2 diff --git c/4dev/tests/Convert/CoreLibsConvertMathTest.php i/4dev/tests/Convert/CoreLibsConvertMathTest.php
index 9a97e37e..c98b4b2a 100644
--- c/4dev/tests/Convert/CoreLibsConvertMathTest.php
+++ i/4dev/tests/Convert/CoreLibsConvertMathTest.php
@@ -113,6 +113,8 @@ final class CoreLibsConvertMathTest extends TestCase
 			\CoreLibs\Convert\Math::initNumeric($input)
 		);
 	}
+
+	// TODO: cbrt tests
 }

 // __END__
diff --git c/www/admin/class_test.convert.colors.php i/www/admin/class_test.convert.colors.php
index 6f809691..a37cb2df 100644
--- c/www/admin/class_test.convert.colors.php
+++ i/www/admin/class_test.convert.colors.php
@@ -19,6 +19,8 @@ $LOG_FILE_ID = 'classTest-convert-colors';
 ob_end_flush();

 use CoreLibs\Convert\Colors;
+use CoreLibs\Convert\Color\Color;
+use CoreLibs\Convert\Color\Coordinates;
 use CoreLibs\Debug\Support as DgS;
 use CoreLibs\Convert\SetVarType;

@@ -52,16 +54,16 @@ try {
 	print "**Exception: " . $e->getMessage() . "<br><pre>" . print_r($e, true) . "</pre><br>";
 }
 // B(valid)
-$rgb = [10, 20, 30];
+$rgb = [50, 20, 30];
 $hex = '#0a141e';
 $hsb = [210, 67, 12];
 $hsb_f = [210.5, 67.5, 12.5];
-$hsl = [210, 50, 7.8];
+$hsb = [210, 50, 7.8];
 print "S::COLOR rgb->hex: $rgb[0], $rgb[1], $rgb[2]: " . Colors::rgb2hex($rgb[0], $rgb[1], $rgb[2]) . "<br>";
 print "S::COLOR hex->rgb: $hex: " . DgS::printAr(SetVarType::setArray(
 	Colors::hex2rgb($hex)
 )) . "<br>";
-print "C::S/COLOR rgb->hext: $hex: " . DgS::printAr(SetVarType::setArray(
+print "C::S/COLOR rgb->hex: $hex: " . DgS::printAr(SetVarType::setArray(
 	CoreLibs\Convert\Colors::hex2rgb($hex)
 )) . "<br>";
 // C(to hsb/hsl)
@@ -82,9 +84,9 @@ print "S::COLOR hsb_f->rgb: $hsb_f[0], $hsb_f[1], $hsb_f[2]: "
 	. DgS::printAr(SetVarType::setArray(
 		Colors::hsb2rgb($hsb_f[0], $hsb_f[1], $hsb_f[2])
 	)) . "<br>";
-print "S::COLOR hsl->rgb: $hsl[0], $hsl[1], $hsl[2]: "
+print "S::COLOR hsl->rgb: $hsb[0], $hsb[1], $hsb[2]: "
 	. DgS::printAr(SetVarType::setArray(
-		Colors::hsl2rgb($hsl[0], $hsl[1], $hsl[2])
+		Colors::hsl2rgb($hsb[0], $hsb[1], $hsb[2])
 	)) . "<br>";

 $hsb = [0, 0, 5];
@@ -102,8 +104,44 @@ 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 hsl->rgb: <pre>" . DgS::printAr(SetVarType::setArray(Colors::hsl2rgb($h, $s, $l))) . "</pre><br>";

+$rgb = [0, 0, 0];
+print "rgb 0,0,0: " . Dgs::printAr($rgb) . " => " . Dgs::printAr(Colors::rgb2hsb($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>";
+$rgb = Color::okLabToRgb($oklab);
+print "OkLab -> RGB: " . DgS::printAr($rgb) . "<br>";
+
+$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>";
+
+
 print "</body></html>";

 // __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Color.php i/www/lib/CoreLibs/Convert/Color/Color.php
new file mode 100644
index 00000000..c56f8c11
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Color.php
@@ -0,0 +1,803 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate and Color Space conversions
+ *
+ * We convert between color cooradinates and color spaces
+ * as seen in the list below
+ *
+ * |      |         RGB           |     Oklab     |       Cie
+ * |      |     | HSB |     |     |       |       |        |        |
+ * |      | RGB | HSV | HSL | HWB | OkLab | OkLch | CieLab | CieLch |
+ * -------+-----+-----+-----+-----+-------+-------+--------+--------+
+ * RGB    |  -  |  o  |  o  |  o  |   o   |   o   |        |        |
+ * HSB/HB |  o  |  -  |  o  |  o  |       |       |        |        |
+ * HSL    |  o  |  o  |  -  |  o  |   o   |   o   |        |        |
+ * HWB    |  o  |  o  |  o  |  -  |       |       |        |        |
+ * OkLab  |  o  |     |  o  |     |   -   |       |        |        |
+ * OkLch  |  o  |     |  o  |     |       |   -   |        |        |
+ * CieLab |     |     |     |     |       |       |   -    |        |
+ * CieLch |     |     |     |     |       |       |        |   -    |
+ *
+ * All color coordinates are classes
+ * The data can then be converted to a CSS string
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color;
+
+use CoreLibs\Convert\Math;
+use CoreLibs\Convert\Color\Coordinates\RGB;
+use CoreLibs\Convert\Color\Coordinates\HSL;
+use CoreLibs\Convert\Color\Coordinates\HSB;
+use CoreLibs\Convert\Color\Coordinates\HWB;
+use CoreLibs\Convert\Color\Coordinates\LCH;
+use CoreLibs\Convert\Color\Coordinates\Lab;
+use CoreLibs\Convert\Color\Coordinates\XYZD65;
+
+class Color
+{
+	// MARK: RGB <-> HSL
+
+	/**
+	 * converts a RGB (0-255) to HSL
+	 * return:
+	 * class with hue (0-360), saturation (0-100%) and luminance (0-100%)
+	 *
+	 * @param  RGB $rgb Class for rgb
+	 * @return HSL      Class hue/sat/luminance
+	 */
+	public static function rgbToHsl(RGB $rgb): HSL
+	{
+		$red = $rgb->R / 255;
+		$green = $rgb->G / 255;
+		$blue = $rgb->B / 255;
+
+		$min = min($red, $green, $blue);
+		$max = max($red, $green, $blue);
+		$chroma = $max - $min;
+		$sat = 0;
+		$hue = 0;
+		// luminance
+		$lum = ($max + $min) / 2;
+
+		// achromatic
+		if ($chroma == 0) {
+			// H, S, L
+			return HSL::__constructFromArray([
+				0.0,
+				0.0,
+				$lum * 100,
+			]);
+		} else {
+			$sat = $chroma / (1 - abs(2 * $lum - 1));
+			if ($max == $red) {
+				$hue = fmod((($green - $blue) / $chroma), 6);
+				if ($hue < 0) {
+					$hue = (6 - fmod(abs($hue), 6));
+				}
+			} elseif ($max == $green) {
+				$hue = ($blue - $red) / $chroma + 2;
+			} elseif ($max == $blue) {
+				$hue = ($red - $green) / $chroma + 4;
+			}
+			$hue = $hue * 60;
+			// $sat = 1 - abs(2 * $lum - 1);
+			return HSL::__constructFromArray([
+				$hue,
+				$sat * 100,
+				$lum * 100,
+			]);
+		}
+	}
+
+	/**
+	 * converts an HSL to RGB
+	 * if HSL value is invalid, set this value to 0
+	 *
+	 * @param  HSL $hsl Class with hue: 0-360 (degrees),
+	 *                             saturation: 0-100,
+	 *                             luminance: 0-100
+	 * @return RGB      Class for rgb
+	 */
+	public static function hslToRgb(HSL $hsl): RGB
+	{
+		$hue = $hsl->H;
+		$sat = $hsl->S;
+		$lum = $hsl->L;
+		// calc to internal convert value for hue
+		$hue = (1 / 360) * $hue;
+		// convert to internal 0-1 format
+		$sat /= 100;
+		$lum /= 100;
+		// if saturation is 0
+		if ($sat == 0) {
+			$lum = round($lum * 255);
+			return RGB::__constructFromArray([$lum, $lum, $lum]);
+		} else {
+			$m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat);
+			$m1 = $lum * 2 - $m2;
+			$hueue = function ($base) use ($m1, $m2) {
+				// base = hue, hue > 360 (1) - 360 (1), else < 0 + 360 (1)
+				$base = $base < 0 ? $base + 1 : ($base > 1 ? $base - 1 : $base);
+				// 6: 60, 2: 180, 3: 240
+				// 2/3 = 240
+				// 1/3 = 120 (all from 360)
+				if ($base * 6 < 1) {
+					return $m1 + ($m2 - $m1) * $base * 6;
+				}
+				if ($base * 2 < 1) {
+					return $m2;
+				}
+				if ($base * 3 < 2) {
+					return $m1 + ($m2 - $m1) * ((2 / 3) - $base) * 6;
+				}
+				return $m1;
+			};
+
+			return RGB::__constructFromArray([
+				255 * $hueue($hue + (1 / 3)),
+				255 * $hueue($hue),
+				255 * $hueue($hue - (1 / 3)),
+			]);
+		}
+	}
+
+		// MARK: RGB <-> HSB
+
+	/**
+	 * rgb2hsb does not clean convert back to rgb in a round trip
+	 * converts RGB to HSB/V values
+	 * returns:
+	 * Class with hue (0-360), sat (0-100%), brightness/value (0-100%)
+	 *
+	 * @param  RGB $rgb Class for rgb
+	 * @return HSB      Class Hue, Sat, Brightness/Value
+	 */
+	public static function rgbToHsb(RGB $rgb): HSB
+	{
+		$red = $rgb->R / 255;
+		$green = $rgb->G / 255;
+		$blue = $rgb->B / 255;
+
+		$MAX = max($red, $green, $blue);
+		$MIN = min($red, $green, $blue);
+		$HUE = 0;
+		$DELTA = $MAX - $MIN;
+
+		// achromatic
+		if ($MAX == $MIN) {
+			return HSB::__constructFromArray([0, 0, $MAX * 100]);
+		}
+		if ($red == $MAX) {
+			$HUE = fmod(($green - $blue) / $DELTA, 6);
+		} elseif ($green == $MAX) {
+			$HUE = (($blue - $red) / $DELTA) + 2;
+		} elseif ($blue == $MAX) {
+			$HUE = (($red - $green) / $DELTA) + 4;
+		}
+		$HUE *= 60;
+		// avoid negative
+		if ($HUE < 0) {
+			$HUE += 360;
+		}
+
+		return HSB::__constructFromArray([
+			$HUE, // Hue
+			($DELTA / $MAX) * 100, // Saturation
+			$MAX * 100, // Brightness
+		]);
+	}
+
+	/**
+	 * hsb2rgb does not clean convert back to hsb in a round trip
+	 * converts HSB/V to RGB values RGB is full INT
+	 * if HSB/V value is invalid, sets this value to 0
+	 *
+	 * @param  HSB $hsb hue 0-360 (int),
+	 *                  saturation 0-100 (int),
+	 *                  brightness/value 0-100 (int)
+	 * @return RGB      Class for RGB
+	 */
+	public static function hsbToRgb(HSB $hsb): RGB
+	{
+		$H = $hsb->H;
+		$S = $hsb->S;
+		$V = $hsb->B;
+		// convert to internal 0-1 format
+		$S /= 100;
+		$V /= 100;
+
+		if ($S == 0) {
+			$V = $V * 255;
+			return RGB::__constructFromArray([$V, $V, $V]);
+		}
+
+		$Hi = floor($H / 60);
+		$f = ($H / 60) - $Hi;
+		$p = $V * (1 - $S);
+		$q = $V * (1 - ($S * $f));
+		$t = $V * (1 - ($S * (1 - $f)));
+
+		switch ($Hi) {
+			case 0:
+				$red = $V;
+				$green = $t;
+				$blue = $p;
+				break;
+			case 1:
+				$red = $q;
+				$green = $V;
+				$blue = $p;
+				break;
+			case 2:
+				$red = $p;
+				$green = $V;
+				$blue = $t;
+				break;
+			case 3:
+				$red = $p;
+				$green = $q;
+				$blue = $V;
+				break;
+			case 4:
+				$red = $t;
+				$green = $p;
+				$blue = $V;
+				break;
+			case 5:
+				$red = $V;
+				$green = $p;
+				$blue = $q;
+				break;
+			default:
+				$red = 0;
+				$green = 0;
+				$blue = 0;
+		}
+
+		return RGB::__constructFromArray([
+			$red * 255,
+			$green * 255,
+			$blue * 255,
+		]);
+	}
+
+	// MARK: HSL <-> HSB
+
+	/**
+	 * Convert HSL to HSB
+	 *
+	 * @param  HSL $hsl
+	 * @return HSB
+	 */
+	public static function hslToHsb(HSL $hsl): HSB
+	{
+		$saturation = $hsl->S / 100;
+		$lightness = $hsl->L / 100;
+		$value  = $lightness + $saturation * min($lightness, 1 - $lightness);
+		// check for black and white
+		$saturation = ($value === 0) ?
+			0 :
+			200 * (1 - $lightness / $value);
+		return HSB::__constructFromArray([
+			$hsl->H,
+			$saturation,
+			$value * 100,
+		]);
+	}
+
+	/**
+	 * Convert HSB to HSL
+	 *
+	 * @param  HSB $hsb
+	 * @return HSL
+	 */
+	public static function hsbToHsl(HSB $hsb): HSL
+	{
+		// hsv/toHsl
+		$hue = $hsb->H;
+		$saturation = $hsb->S / 100;
+		$value = $hsb->V / 100;
+
+		$lightness = $value * (1 - $saturation / 2);
+		// check for B/W
+		$saturation = in_array($lightness, [0, 1], true) ?
+			0 :
+			100 * ($value - $lightness) / min($lightness, 1 - $lightness)
+		;
+
+		return HSL::__constructFromArray([
+			$hue,
+			$saturation,
+			$lightness * 100,
+		]);
+	}
+
+	// MARK: HSB <-> HWB
+
+	/**
+	 * convert HSB to HWB
+	 *
+	 * @param  HSB $hsb
+	 * @return HWB
+	 */
+	public static function hsbToHwb(HSB $hsb): HWB
+	{
+		// hsv\Hwb
+		return HWB::__constructFromArray([
+			$hsb->H, // hue,
+			$hsb->B * (100 - $hsb->S) / 100, // 2: brightness, 1: saturation
+			100 - $hsb->B,
+		]);
+	}
+
+	/**
+	 * convert HWB to HSB
+	 *
+	 * @param  HWB $hwb
+	 * @return HSB
+	 */
+	public static function hwbToHsb(HWB $hwb): HSB
+	{
+		$hue = $hwb->H;
+		$whiteness = $hwb->W / 100;
+		$blackness = $hwb->B / 100;
+
+		$sum = $whiteness + $blackness;
+		// for black and white
+		if ($sum >= 1) {
+			$saturation = 0;
+			$value = $whiteness / $sum * 100;
+		} else {
+			$value = 1 - $blackness;
+			$saturation = $value === 0 ? 0 : (1 - $whiteness / $value) * 100;
+			$value *= 100;
+		}
+
+		return HSB::__constructFromArray([
+			$hue,
+			$saturation,
+			$value,
+		]);
+	}
+
+	// MARK: RGB <-> HWB
+
+	/**
+	 * Convert RGB to HWB
+	 * via rgb -> hsl -> hsb -> hwb
+	 *
+	 * @param  RGB $rgb
+	 * @return HWB
+	 */
+	public static function rgbToHwb(RGB $rgb): HWB
+	{
+		return self::hsbToHwb(
+			self::hslToHsb(
+				self::rgbToHsl($rgb)
+			)
+		);
+	}
+
+	/**
+	 * Convert HWB to RGB
+	 * via hwb -> hsb -> hsl -> rgb
+	 *
+	 * @param  HWB $hwb
+	 * @return RGB
+	 */
+	public static function hwbToRgb(HWB $hwb): RGB
+	{
+		return self::hslToRgb(
+			self::hsbToHsl(
+				self::hwbToHsb($hwb)
+			)
+		);
+	}
+
+	// MARK: HSL <-> HWB
+
+	/**
+	 * Convert HSL to HWB
+	 * via hsl -> hsb -> hwb
+	 *
+	 * @param  HSL $hsl
+	 * @return HWB
+	 */
+	public static function hslToHwb(HSL $hsl): HWB
+	{
+		return self::hsbToHwb(
+			self::hslToHsb(
+				$hsl
+			)
+		);
+	}
+
+	/**
+	 * Convert HWB to HSL
+	 * via hwb -> hsb -> hsl
+	 *
+	 * @param  HWB $hwb
+	 * @return HSL
+	 */
+	public static function hwbToHsl(HWB $hwb): HSL
+	{
+		return self::hsbToHsl(
+			self::hwbToHsb($hwb)
+		);
+	}
+
+	// MARK: OkLch <-> OkLab
+
+	/**
+	 * okLAab to okLCH
+	 *
+	 * @param  Lab $lab
+	 * @return LCH
+	 */
+	public static function okLabToOkLch(Lab $lab): LCH
+	{
+		// okLab\toOkLch
+		$a = $lab->a;
+		$b = $lab->b;
+
+		$hue = atan2($b, $a) * 180 / pi();
+
+		return LCH::__constructFromArray([
+			$lab->L,
+			sqrt($a ** 2 + $b ** 2),
+			$hue >= 0 ? $hue : $hue + 360,
+		]);
+	}
+
+	/**
+	 * okLCH to okLab
+	 *
+	 * @param  LCH $lch
+	 * @return Lab
+	 */
+	public static function okLchToOkLab(LCH $lch): Lab
+	{
+		// oklch/toOkLab
+		// oklch to oklab
+		return Lab::__constructFromArray([
+			$lch->L,
+			$lch->C * cos($lch->H * pi() / 180), // a
+			$lch->C * sin($lch->H * pi() / 180), // b
+		], 'Oklab');
+	}
+
+	// MARK: xyzD65 <-> linearRGB
+
+	/**
+	 * convert linear RGB to xyz D65
+	 * if rgb is not flagged linear, it will be auto converted
+	 *
+	 * @param  RGB  $rgb
+	 * @return XYZD65
+	 */
+	public static function linRgbToXyzD65(RGB $rgb): XYZD65
+	{
+		// if not linear, convert to linear
+		if (!$rgb->linear) {
+			$rgb->toLinear();
+		}
+		return XYZD65::__constructFromArray(Math::multiplyMatrices(
+			[
+				[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
+				[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
+				[0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
+			],
+			$rgb->returnAsArray()
+		));
+	}
+
+	/**
+	 * Convert xyz D65 to linear RGB
+	 *
+	 * @param  XYZD65 $xyzD65
+	 * @return RGB
+	 */
+	public static function xyzD65ToLinRgb(XYZD65 $xyzD65): RGB
+	{
+		// xyz D65 to linrgb
+		return RGB::__constructFromArray(Math::multiplyMatrices(
+			a : [
+				[  3.2409699419045226,  -1.537383177570094,   -0.4986107602930034  ],
+				[ -0.9692436362808796,   1.8759675015077202,   0.04155505740717559 ],
+				[  0.05563007969699366, -0.20397695888897652,  1.0569715142428786  ],
+			],
+			b : $xyzD65->returnAsArray()
+		), linear: true);
+	}
+
+	// MARK: xyzD65 <-> OkLab
+
+	/**
+	 * xyz D65 to OkLab
+	 *
+	 * @param  XYZD65 $xyzD65
+	 * @return Lab
+	 */
+	public static function xyzD65ToOkLab(XYZD65 $xyzD65): Lab
+	{
+		return Lab::__constructFromArray(Math::multiplyMatrices(
+			[
+				[0.2104542553, 0.7936177850, -0.0040720468],
+				[1.9779984951, -2.4285922050, 0.4505937099],
+				[0.0259040371, 0.7827717662, -0.8086757660],
+			],
+			array_map(
+				callback: fn ($v) => pow($v, 1 / 3),
+				array: Math::multiplyMatrices(
+					a: [
+						[0.8190224432164319, 0.3619062562801221, -0.12887378261216414],
+						[0.0329836671980271, 0.9292868468965546, 0.03614466816999844],
+						[0.048177199566046255, 0.26423952494422764, 0.6335478258136937],
+					],
+					b: $xyzD65->returnAsArray(),
+				),
+			)
+		), 'Oklab');
+	}
+
+	/**
+	 * xyz D65 to OkLab
+	 *
+	 * @param  Lab    $lab
+	 * @return XYZD65
+	 */
+	public static function okLabToXyzD65(Lab $lab): XYZD65
+	{
+		return XYZD65::__constructFromArray(Math::multiplyMatrices(
+			a: [
+					[1.2268798733741557, -0.5578149965554813, 0.28139105017721583],
+					[-0.04057576262431372, 1.1122868293970594, -0.07171106666151701],
+					[-0.07637294974672142, -0.4214933239627914, 1.5869240244272418],
+			],
+			b: array_map(
+				callback: fn ($v) => is_numeric($v) ? $v ** 3 : 0,
+				array: Math::multiplyMatrices(
+					a: [
+							[0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339],
+							[1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402],
+							[1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399],
+					],
+					// Divide $lightness by 100 to convert from CSS OkLab
+					b: $lab->returnAsArray(),
+				),
+			),
+		));
+	}
+
+	// MARK: rgb <-> oklab
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  RGB $rgb
+	 * @return Lab
+	 */
+	public static function rgbToOkLab(RGB $rgb): Lab
+	{
+		return self::xyzD65ToOkLab(
+			self::linRgbToXyzD65($rgb)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  Lab $lab
+	 * @return RGB
+	 */
+	public static function okLabToRgb(Lab $lab): RGB
+	{
+		return self::xyzD65ToLinRgb(
+			self::okLabToXyzD65($lab)
+		)->fromLinear();
+	}
+
+	// MARK: rgb <-> oklch
+
+	/**
+	 * convert rgb to OkLch
+	 * via rgb -> linear rgb -> xyz D65 -> OkLab -> OkLch
+	 *
+	 * @param  RGB $rbh
+	 * @return LCH
+	 */
+	public static function rgbToOkLch(RGB $rgb): LCH
+	{
+		return self::okLabToOkLch(
+			self::rgbToOkLab($rgb)
+		);
+	}
+
+	/**
+	 * Convert OkLch to rgb
+	 * via OkLab -> OkLch -> xyz D65 -> linear rgb -> rgb
+	 *
+	 * @param  LCH $lch
+	 * @return RGB
+	 */
+	public static function okLchToRgb(LCH $lch): RGB
+	{
+		return self::okLabToRgb(
+			self::okLchToOkLab($lch)
+		);
+	}
+
+	// MARK: HSL <-> OKLab
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HSL $hsl
+	 * @return Lab
+	 */
+	public static function hslToOkLab(HSL $hsl): Lab
+	{
+		return self::rgbToOkLab(
+			self::hslToRgb($hsl)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  Lab $lab
+	 * @return HSL
+	 */
+	public static function okLabToHsl(Lab $lab): HSL
+	{
+		return self::rgbToHsl(
+			self::okLabToRgb($lab)
+		);
+	}
+
+	// MARK: HSL <-> OKLCH
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HSL $hsl
+	 * @return LCH
+	 */
+	public static function hslToOkLch(HSL $hsl): LCH
+	{
+		return self::rgbToOkLch(
+			self::hslToRgb($hsl)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  LCH $lch
+	 * @return HSL
+	 */
+	public static function okLchToHsl(LCH $lch): HSL
+	{
+		return self::rgbToHsl(
+			self::okLchToRgb($lch)
+		);
+	}
+
+	// MARK: HSB <-> OKLab
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HSB $hsb
+	 * @return Lab
+	 */
+	public static function hsbToOkLab(HSB $hsb): Lab
+	{
+		return self::rgbToOkLab(
+			self::hsbToRgb($hsb)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  Lab $lab
+	 * @return HSB
+	 */
+	public static function okLabToHsb(Lab $lab): HSB
+	{
+		return self::rgbToHsb(
+			self::okLabToRgb($lab)
+		);
+	}
+
+	// MARK: HSB <-> OKLCH
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HSB $hsb
+	 * @return LCH
+	 */
+	public static function hsbToOkLch(HSB $hsb): LCH
+	{
+		return self::rgbToOkLch(
+			self::hsbToRgb($hsb)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  LCH $lch
+	 * @return HSB
+	 */
+	public static function okLchToHsb(LCH $lch): HSB
+	{
+		return self::rgbToHsb(
+			self::okLchToRgb($lch)
+		);
+	}
+
+	// MARK: HWB <-> OKLab
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HWB $hwb
+	 * @return Lab
+	 */
+	public function hwbToOkLab(HWB $hwb): Lab
+	{
+		return self::rgbToOkLab(
+			self::hwbToRgb($hwb)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  Lab $lab
+	 * @return HWB
+	 */
+	public function okLabToHwb(Lab $lab): HWB
+	{
+		return self::rgbToHwb(
+			self::okLabToRgb($lab)
+		);
+	}
+
+	// MARK: HWB <-> OKLCH
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  HWB $hwb
+	 * @return LCH
+	 */
+	public function hwbToOkLch(HWB $hwb): LCH
+	{
+		return self::rgbToOkLch(
+			self::hwbToRgb($hwb)
+		);
+	}
+
+	/**
+	 * Undocumented function
+	 *
+	 * @param  LCH $lch
+	 * @return HWB
+	 */
+	public function okLchToHwb(LCH $lch): HWB
+	{
+		return self::rgbToHwb(
+			self::okLchToRgb($lch)
+		);
+	}
+}
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php i/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php
new file mode 100644
index 00000000..b435a9ef
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: HSB/HSV
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class HSB
+{
+	/** @var float hue */
+	private float $H = 0.0;
+	/** @var float saturation */
+	private float $S = 0.0;
+	/** @var float brightness / value */
+	private float $B = 0.0;
+
+	/**
+	 * HSB (HSV) color coordinates
+	 * Hue/Saturation/Brightness or Value
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $H Hue
+	 * @param  float $S Saturation
+	 * @param  float $B Brightness
+	 * @return self
+	 */
+	public static function __constructFromSet(float $H, float $S, float $B): self
+	{
+		return (new HSB())->setAsArray([$H, $S, $B]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Hue, 1: Saturation, 2: Brightness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hsb
+	 * @return self
+	 */
+	public static function __constructFromArray(array $hsb): self
+	{
+		return (new HSB())->setAsArray($hsb);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		$name = strtoupper($name);
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		switch ($name) {
+			case 'H':
+				if ($value == 360) {
+					$value = 0;
+				}
+				if ($value < 0 || $value > 359) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for hue is not in the range of 0 to 359',
+						1
+					);
+				}
+				break;
+			case 'S':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
+						2
+					);
+				}
+				break;
+			case 'B':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for brightness is not in the range of 0 to 100',
+						3
+					);
+				}
+				break;
+		}
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		$name = strtoupper($name);
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Hue, 1: Saturation, 2: Brightness
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->H, $this->S, $this->B];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Hue, 1: Saturation, 2: Brightness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hsb
+	 * @return self
+	 */
+	public function setAsArray(array $hsb): self
+	{
+		$this->__set('H', $hsb[0]);
+		$this->__set('S', $hsb[1]);
+		$this->__set('B', $hsb[2]);
+		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('HSB is not available as CSS color string', 0);
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php i/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php
new file mode 100644
index 00000000..21be7fe5
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: HSL
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class HSL
+{
+	/** @var float hue */
+	private float $H = 0.0;
+	/** @var float saturation */
+	private float $S = 0.0;
+	/** @var float lightness (luminance) */
+	private float $L = 0.0;
+	/**
+	 * Color Coordinate HSL
+	 * Hue/Saturation/Lightness
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $H Hue
+	 * @param  float $S Saturation
+	 * @param  float $L Lightness
+	 * @return self
+	 */
+	public static function __constructFromSet(float $H, float $S, float $L): self
+	{
+		return (new HSL())->setAsArray([$H, $S, $L]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Hue, 1: Saturation, 2: Lightness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hsl
+	 * @return self
+	 */
+	public static function __constructFromArray(array $hsl): self
+	{
+		return (new HSL())->setAsArray($hsl);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		switch ($name) {
+			case 'H':
+				if ($value == 360) {
+					$value = 0;
+				}
+				if ($value < 0 || $value > 359) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for hue is not in the range of 0 to 359',
+						1
+					);
+				}
+				break;
+			case 'S':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
+						2
+					);
+				}
+				break;
+			case 'L':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for luminance is not in the range of 0 to 100',
+						3
+					);
+				}
+				break;
+		}
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Hue, 1: Saturation, 2: Lightness
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->H, $this->S, $this->L];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Hue, 1: Saturation, 2: Lightness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hsl
+	 * @return self
+	 */
+	public function setAsArray(array $hsl): self
+	{
+		$this->__set('H', $hsl[0]);
+		$this->__set('S', $hsl[1]);
+		$this->__set('L', $hsl[2]);
+		return $this;
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php i/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php
new file mode 100644
index 00000000..ee6b7f63
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php
@@ -0,0 +1,140 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: HWB
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class HWB
+{
+	/** @var float Hue */
+	private float $H = 0.0;
+	/** @var float Whiteness */
+	private float $W = 0.0;
+	/** @var float Blackness */
+	private float $B = 0.0;
+	/**
+	 * Color Coordinate: HWB
+	 * Hue/Whiteness/Blackness
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $H Hue
+	 * @param  float $W Whiteness
+	 * @param  float $B Blackness
+	 * @return self
+	 */
+	public static function __constructFromSet(float $H, float $W, float $B): self
+	{
+		return (new HWB())->setAsArray([$H, $W, $B]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Hue, 1: Whiteness, 2: Blackness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hwb
+	 * @return self
+	 */
+	public static function __constructFromArray(array $hwb): self
+	{
+		return (new HWB())->setAsArray($hwb);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		switch ($name) {
+			case 'H':
+				if ($value == 360) {
+					$value = 0;
+				}
+				if ($value < 0 || $value > 360) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
+						1
+					);
+				}
+				break;
+			case 'W':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
+						2
+					);
+				}
+				break;
+			case 'B':
+				if ($value < 0 || $value > 100) {
+					throw new \LengthException(
+						'Argument value ' . $value . ' for luminance is not in the range of 0 to 100',
+						3
+					);
+				}
+				break;
+		}
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Hue, 1: Whiteness, 2: Blackness
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->H, $this->W, $this->B];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Hue, 1: Whiteness, 2: Blackness
+	 *
+	 * @param  array{0:float,1:float,2:float} $hwb
+	 * @return self
+	 */
+	public function setAsArray(array $hwb): self
+	{
+		$this->__set('H', $hwb[0]);
+		$this->__set('W', $hwb[1]);
+		$this->__set('B', $hwb[2]);
+		return $this;
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php i/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php
new file mode 100644
index 00000000..648fb466
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: Lch
+ * for oklch or cie
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class LCH
+{
+	/** @var float Lightness/Luminance
+	 * CIE: 0 to 100
+	 * OKlch: 0.0 to 1.0
+	 * BOTH: 0% to 100%
+	 */
+	private float $L = 0.0;
+	/** @var float Chroma
+	 * CIE: 0 to 150, cannot be more than 230
+	 * OkLch: 0 to 0.4, does not exceed 0.5
+	 * BOTH: 0% to 100% (0 to 150, 0 to 0.4)
+	 */
+	private float $C = 0.0;
+	/** @var float Hue
+	 * 0 to 360 deg
+	 */
+	private float $H = 0.0;
+
+	/** @var string color space: either ok or cie */
+	private string $colorspace = '';
+
+	/**
+	 * Color Coordinate Lch
+	 * for oklch
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $L
+	 * @param  float $c
+	 * @param  float $h
+	 * @return self
+	 */
+	public static function __constructFromSet(float $L, float $c, float $h): self
+	{
+		return (new LCH())->setAsArray([$L, $c, $h]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Lightness, 1: Chroma, 2: Hue
+	 *
+	 * @param  array{0:float,1:float,2:float} $lch
+	 * @return self
+	 */
+	public static function __constructFromArray(array $lch): self
+	{
+		return (new LCH())->setAsArray($lch);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		switch ($name) {
+			// case 'L':
+			// 	if ($this->colorspace == 'cie' && ($value < 0 || $value > 100)) {
+			// 		throw new \LengthException(
+			// 			'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(
+						'Argument value ' . $value . ' for lightness is not in the range of 0 to 360',
+						1
+					);
+				}
+				break;
+		}
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Lightness, 1: Chroma, 2: Hue
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->L, $this->C, $this->H];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Lightness, 1: Chroma, 2: Hue
+	 *
+	 * @param  array{0:float,1:float,2:float} $lch
+	 * @return self
+	 */
+	public function setAsArray(array $lch): self
+	{
+		$this->__set('L', $lch[0]);
+		$this->__set('C', $lch[1]);
+		$this->__set('H', $lch[2]);
+		return $this;
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php i/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php
new file mode 100644
index 00000000..e2eb11a4
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: Lab
+ * for oklab or cie
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class Lab
+{
+	/** @var array<string> allowed colorspaces */
+	private const COLORSPACES = ['Oklab', 'cie'];
+
+	/** @var float lightness/luminance
+	 * CIE: 0f to 100f
+	 * OKlab: 0.0 to 1.0
+	 * BOTH: 0% to 100%
+	 */
+	private float $L = 0.0;
+	/** @var float a axis distance
+	 * CIE: -125 to 125, cannot be more than +/- 160
+	 * OKlab: -0.4 to 0.4, cannot exceed +/- 0.5
+	 * BOTH: -100% to 100% (+/-125 or 0.4)
+	 */
+	private float $a = 0.0;
+	/** @var float b axis distance
+	 * CIE: -125 to 125, cannot be more than +/- 160
+	 * OKlab: -0.4 to 0.4, cannot exceed +/- 0.5
+	 * BOTH: -100% to 100% (+/-125 or 0.4)
+	 */
+	private float $b = 0.0;
+
+	/** @var string color space: either ok or cie */
+	private string $colorspace = '';
+
+	/**
+	 * Color Coordinate: Lab
+	 * for oklab or cie
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $L
+	 * @param  float $a
+	 * @param  float $b
+	 * @param  string $colorspace
+	 * @return self
+	 */
+	public static function __constructFromSet(float $L, float $a, float $b, string $colorspace): self
+	{
+		return (new Lab())->setColorspace($colorspace)->setAsArray([$L, $a, $b]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Lightness, 1: a, 2: b
+	 *
+	 * @param  array{0:float,1:float,2:float} $rgb
+	 * @param  string $colorspace
+	 * @return self
+	 */
+	public static function __constructFromArray(array $lab, string $colorspace): self
+	{
+		return (new Lab())->setColorspace($colorspace)->setAsArray($lab);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		// switch ($name) {
+		// 	case 'L':
+		// 		if ($value == 360) {
+		// 			$value = 0;
+		// 		}
+		// 		if ($value < 0 || $value > 360) {
+		// 			throw new \LengthException(
+		// 				'Argument value ' . $value . ' for lightness is not in the range of 0 to 360',
+		// 				1
+		// 			);
+		// 		}
+		// 		break;
+		// 	case 'a':
+		// 		if ($value < 0 || $value > 100) {
+		// 			throw new \LengthException(
+		// 				'Argument value ' . $value . ' for a is not in the range of 0 to 100',
+		// 				2
+		// 			);
+		// 		}
+		// 		break;
+		// 	case 'b':
+		// 		if ($value < 0 || $value > 100) {
+		// 			throw new \LengthException(
+		// 				'Argument value ' . $value . ' for b is not in the range of 0 to 100',
+		// 				3
+		// 			);
+		// 		}
+		// 		break;
+		// }
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * set the colorspace
+	 *
+	 * @param  string $colorspace
+	 * @return self
+	 */
+	private function setColorspace(string $colorspace): self
+	{
+		if (!in_array($colorspace, $this::COLORSPACES)) {
+			throw new \InvalidArgumentException('Not allowed colorspace', 0);
+		}
+		$this->colorspace = $colorspace;
+		return $this;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Lightness, 1: a, 2: b
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->L, $this->a, $this->b];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Lightness, 1: a, 2: b
+	 *
+	 * @param  array{0:float,1:float,2:float} $lab
+	 * @return self
+	 */
+	public function setAsArray(array $lab): self
+	{
+		$this->__set('L', $lab[0]);
+		$this->__set('a', $lab[1]);
+		$this->__set('b', $lab[2]);
+		return $this;
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php i/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php
new file mode 100644
index 00000000..acc81952
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: RGB
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class RGB
+{
+	/** @var float red 0 to 255 or 0.0f to 1.0f for linear RGB */
+	private float $R = 0.0;
+	/** @var float green 0 to 255 or 0.0f to 1.0f for linear RGB */
+	private float $G = 0.0;
+	/** @var float blue 0 to 255 or 0.0f to 1.0f for linear RGB */
+	private float $B = 0.0;
+
+	/** @var bool set if this is linear */
+	private bool $linear = false;
+
+	/**
+	 * Color Coordinate RGB
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $R Red
+	 * @param  float $G Green
+	 * @param  float $B Blue
+	 * @param  bool $linear [default=false]
+	 * @return self
+	 */
+	public static function __constructFromSet(float $R, float $G, float $B, bool $linear = false): self
+	{
+		return (new RGB())->flagLinear($linear)->setAsArray([$R, $G, $B]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: Red, 1: Green, 2: Blue
+	 *
+	 * @param  array{0:float,1:float,2:float} $rgb
+	 * @param  bool $linear [default=false]
+	 * @return self
+	 */
+	public static function __constructFromArray(array $rgb, bool $linear = false): self
+	{
+		return (new RGB())->flagLinear($linear)->setAsArray($rgb);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		// do not allow setting linear from outside
+		if ($name == 'linear') {
+			return;
+		}
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		// if not linear
+		if (!$this->linear && ($value < 0 || $value > 255)) {
+			throw new \LengthException('Argument value ' . $value . ' for color ' . $name
+				. ' is not in the range of 0 to 255', 1);
+		} elseif ($this->linear && ($value < -10E10 || $value > 1)) {
+			// not allow very very small negative numbers
+			throw new \LengthException('Argument value ' . $value . ' for color ' . $name
+				. ' is not in the range of 0 to 1 for linear rgb', 1);
+		}
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float|bool
+	 */
+	public function __get(string $name): float|bool
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: Red, 1: Green, 2: Blue
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->R, $this->G, $this->B];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: Red, 1: Green, 2: Blue
+	 *
+	 * @param  array{0:float,1:float,2:float} $rgb
+	 * @return self
+	 */
+	public function setAsArray(array $rgb): self
+	{
+		$this->__set('R', $rgb[0]);
+		$this->__set('G', $rgb[1]);
+		$this->__set('B', $rgb[2]);
+		return $this;
+	}
+
+	/**
+	 * set as linear
+	 * can be used as chain call on create if input is linear RGB
+	 * RGB::__construct**(...)->flagLinear();
+	 * as it returns self
+	 *
+	 * @return self
+	 */
+	private function flagLinear(bool $linear): self
+	{
+		$this->linear = $linear;
+		return $this;
+	}
+
+	/**
+	 * Both function source:
+	 * https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
+	 * but reverse f: fromLinear and f_inv for toLinear
+	 * Code copied from here:
+	 * https://stackoverflow.com/a/12894053
+	 *
+	 * converts RGB to linear
+	 * We come from 0-255 so we need to divide by 255
+	 *
+	 * @return self
+	 */
+	public function toLinear(): self
+	{
+		$this->flagLinear(true)->setAsArray(array_map(
+			callback: function (int|float $v) {
+				$v = (float)($v / 255);
+				$abs = abs($v);
+				$sign = ($v < 0) ? -1 : 1;
+				return (float)(
+					$abs <= 0.04045 ?
+						$v / 12.92 :
+						$sign * pow(($abs + 0.055) / 1.055, 2.4)
+				);
+			},
+			array: $this->returnAsArray(),
+		));
+		return $this;
+	}
+
+	/**
+	 * convert back to normal sRGB from linear RGB
+	 * we go to 0-255 rgb so we multiply by 255
+	 *
+	 * @return self
+	 */
+	public function fromLinear(): self
+	{
+		$this->flagLinear(false)->setAsArray(array_map(
+			callback: function (int|float $v) {
+				$abs  = abs($v);
+				$sign = ($v < 0) ? -1 : 1;
+				// during reverse in some situations the values can become negative in very small ways
+				// like -...E16 and ...E17
+				return ($ret = (float)(255 * (
+					$abs <= 0.0031308 ?
+						$v * 12.92 :
+						$sign * (1.055 * pow($abs, 1.0 / 2.4) - 0.055)
+				))) < 0 ? 0 : $ret;
+			},
+			array: $this->returnAsArray(),
+		));
+		// $this->linear = false;
+		return $this;
+	}
+
+	/**
+	 * convert to css string with optional opacity
+	 * Note: if this is a linea RGB, this data will not be correct
+	 *
+	 * @param  float|string|null $opacity
+	 * @return string
+	 */
+	public function toCssString(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 'rgb('
+			. (int)round($this->R, 0)
+			. ' '
+			. (int)round($this->G, 0)
+			. ' '
+			. (int)round($this->B, 0)
+			. $opacity
+			. ')';
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Coordinates/XYZD65.php i/www/lib/CoreLibs/Convert/Color/Coordinates/XYZD65.php
new file mode 100644
index 00000000..ebdf633d
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Coordinates/XYZD65.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Color Coordinate: XYZ (Cie)
+ * Note, this is only for the D65 whitepoint
+ * https://en.wikipedia.org/wiki/CIE_1931_color_space#Construction_of_the_CIE_XYZ_color_space_from_the_Wright%E2%80%93Guild_data
+ * https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color\Coordinates;
+
+class XYZD65
+{
+	private float $X = 0.0;
+	private float $Y = 0.0;
+	private float $Z = 0.0;
+
+	/**
+	 * Color Coordinate Lch
+	 * for oklch
+	 */
+	public function __construct()
+	{
+	}
+
+	/**
+	 * set with each value as parameters
+	 *
+	 * @param  float $X
+	 * @param  float $Y
+	 * @param  float $Z
+	 * @return self
+	 */
+	public static function __constructFromSet(float $X, float $Y, float $Z): self
+	{
+		return (new XYZD65())->setAsArray([$X, $Y, $Z]);
+	}
+
+	/**
+	 * set from array
+	 * where 0: X, 1: Y, 2: Z
+	 *
+	 * @param  array{0:float,1:float,2:float} $xyzD65
+	 * @return self
+	 */
+	public static function __constructFromArray(array $xyzD65): self
+	{
+		return (new XYZD65())->setAsArray($xyzD65);
+	}
+
+	/**
+	 * set color
+	 *
+	 * @param  string $name
+	 * @param  float  $value
+	 * @return void
+	 */
+	public function __set(string $name, float $value): void
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		// if ($value < 0 || $value > 255) {
+		// 	throw new \LengthException('Argument value ' . $value . ' for color ' . $name
+		// 		. ' is not in the range of 0 to 255', 1);
+		// }
+		$this->$name = $value;
+	}
+
+	/**
+	 * get color
+	 *
+	 * @param string $name
+	 * @return float
+	 */
+	public function __get(string $name): float
+	{
+		if (!property_exists($this, $name)) {
+			throw new \ErrorException('Creation of dynamic property is not allowed', 0);
+		}
+		return $this->$name;
+	}
+
+	/**
+	 * Returns the color as array
+	 * where 0: X, 1: Y, 2: Z
+	 *
+	 * @return array{0:float,1:float,2:float}
+	 */
+	public function returnAsArray(): array
+	{
+		return [$this->X, $this->Y, $this->Z];
+	}
+
+	/**
+	 * set color as array
+	 * where 0: X, 1: Y, 2: Z
+	 *
+	 * @param  array{0:float,1:float,2:float} $xyzD65
+	 * @return self
+	 */
+	public function setAsArray(array $xyzD65): self
+	{
+		$this->__set('X', $xyzD65[0]);
+		$this->__set('Y', $xyzD65[1]);
+		$this->__set('Z', $xyzD65[2]);
+		return $this;
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/OkLab.php i/www/lib/CoreLibs/Convert/Color/OkLab.php
new file mode 100644
index 00000000..2bbdbbc2
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/OkLab.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/7
+ * DESCRIPTION:
+ * oklab conversions
+ * rgb -> oklab
+ * oklab -> rgb
+ * rgb -> okhsl
+ * okshl -> rgb
+ * rgb -> okhsv
+ * okhsv -> rgb
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color;
+
+class OkLab
+{
+	/**
+	 * lines sRGB to oklab
+	 *
+	 * @param  int   $red
+	 * @param  int   $green
+	 * @param  int   $blue
+	 * @return array<float>
+	 */
+	public static function srgb2okLab(int $red, int $green, int $blue): array
+	{
+		$l = (float)0.4122214708 * (float)$red +
+			(float)0.5363325363 * (float)$green +
+			(float)0.0514459929 * (float)$blue;
+		$m = (float)0.2119034982 * (float)$red +
+			(float)0.6806995451 * (float)$green +
+			(float)0.1073969566 * (float)$blue;
+		$s = (float)0.0883024619 * (float)$red +
+			(float)0.2817188376 * (float)$green +
+			(float)0.6299787005 * (float)$blue;
+
+		// cbrtf = 3 root (val)
+		$l_ = pow($l, 1.0 / 3);
+		$m_ = pow($m, 1.0 / 3);
+		$s_ = pow($s, 1.0 / 3);
+
+		return [
+			(float)0.2104542553 * $l_ + (float)0.7936177850 * $m_ - (float)0.0040720468 * $s_,
+			(float)1.9779984951 * $l_ - (float)2.4285922050 * $m_ + (float)0.4505937099 * $s_,
+			(float)0.0259040371 * $l_ + (float)0.7827717662 * $m_ - (float)0.8086757660 * $s_,
+		];
+	}
+
+	/**
+	 * convert okLab to linear sRGB
+	 *
+	 * @param  float $L
+	 * @param  float $a
+	 * @param  float $b
+	 * @return array<int>
+	 */
+	public static function okLab2srgb(float $L, float $a, float $b): array
+	{
+		$l_ = $L + (float)0.3963377774 * $a + (float)0.2158037573 * $b;
+		$m_ = $L - (float)0.1055613458 * $a - (float)0.0638541728 * $b;
+		$s_ = $L - (float)0.0894841775 * $a - (float)1.2914855480 * $b;
+
+		$l = $l_ * $l_ * $l_;
+		$m = $m_ * $m_ * $m_;
+		$s = $s_ * $s_ * $s_;
+
+		return [
+			(int)round(+(float)4.0767416621 * $l - (float)3.3077115913 * $m + (float)0.2309699292 * $s),
+			(int)round(-(float)1.2684380046 * $l + (float)2.6097574011 * $m - (float)0.3413193965 * $s),
+			(int)round(-(float)0.0041960863 * $l - (float)0.7034186147 * $m + (float)1.7076147010 * $s),
+		];
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Color/Stringify.php i/www/lib/CoreLibs/Convert/Color/Stringify.php
new file mode 100644
index 00000000..6ca68431
--- /dev/null
+++ i/www/lib/CoreLibs/Convert/Color/Stringify.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * AUTHOR: Clemens Schwaighofer
+ * CREATED: 2024/11/11
+ * DESCRIPTION:
+ * Convert color coordinate to CSS string
+*/
+
+declare(strict_types=1);
+
+namespace CoreLibs\Convert\Color;
+
+use CoreLibs\Convert\Color\Coordinates\RGB;
+use CoreLibs\Convert\Color\Coordinates\HSL;
+use CoreLibs\Convert\Color\Coordinates\HWB;
+use CoreLibs\Convert\Color\Coordinates\Lab;
+use CoreLibs\Convert\Color\Coordinates\LCH;
+
+class Stringify
+{
+	/**
+	 * Undocumented function
+	 *
+	 * @param  RGB|Lab|LCH|HSL|HWB $data
+	 * @param  null|float|string   $opacity
+	 * @return string
+	 */
+	public static function toCssString(RGB|Lab|LCH|HSL|HWB $data, null|float|string $opacity): string
+	{
+		return $data->toCssString($opacity);
+	}
+}
+
+// __END__
diff --git c/www/lib/CoreLibs/Convert/Colors.php i/www/lib/CoreLibs/Convert/Colors.php
index f9f56171..8ff32608 100644
--- c/www/lib/CoreLibs/Convert/Colors.php
+++ i/www/lib/CoreLibs/Convert/Colors.php
@@ -120,26 +120,29 @@ class Colors
 		$MAX = max($red, $green, $blue);
 		$MIN = min($red, $green, $blue);
 		$HUE = 0;
+		$DELTA = $MAX - $MIN;

+		// achromatic
 		if ($MAX == $MIN) {
 			return [0, 0, round($MAX * 100)];
 		}
 		if ($red == $MAX) {
-			$HUE = ($green - $blue) / ($MAX - $MIN);
+			$HUE = fmod(($green - $blue) / $DELTA, 6);
 		} elseif ($green == $MAX) {
-			$HUE = 2 + (($blue - $red) / ($MAX - $MIN));
+			$HUE = (($blue - $red) / $DELTA) + 2;
 		} elseif ($blue == $MAX) {
-			$HUE = 4 + (($red - $green) / ($MAX - $MIN));
+			$HUE = (($red - $green) / $DELTA) + 4;
 		}
 		$HUE *= 60;
+		// avoid negative
 		if ($HUE < 0) {
 			$HUE += 360;
 		}

 		return [
-			(int)round($HUE),
-			(int)round((($MAX - $MIN) / $MAX) * 100),
-			(int)round($MAX * 100)
+			(int)round($HUE), // Hue
+			(int)round(($DELTA / $MAX) * 100), // Saturation
+			(int)round($MAX * 100) // Value/Brightness
 		];
 	}

diff --git c/www/lib/CoreLibs/Convert/Math.php i/www/lib/CoreLibs/Convert/Math.php
index 205abbf1..26739c5f 100644
--- c/www/lib/CoreLibs/Convert/Math.php
+++ i/www/lib/CoreLibs/Convert/Math.php
@@ -56,6 +56,95 @@ class Math
 			return (float)$number;
 		}
 	}
+
+	/**
+	 * calc cube root
+	 *
+	 * @param  float $number Number to cubic root
+	 * @return float         Calculated value
+	 */
+	public static function cbrt(float $number): float
+	{
+		return pow($number, 1.0 / 3);
+	}
+
+	/**
+	 * This function is directly inspired by the multiplyMatrices() function in color.js
+	 * form Lea Verou and Chris Lilley.
+	 * (see https://github.com/LeaVerou/color.js/blob/main/src/multiply-matrices.js)
+	 * From:
+	 * 3842cf51c9/src/utils/utils.php (L507)
+	 *
+	 * 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
+	 * @param  array<array<int|float>> $b n x p matrice
+	 *
+	 * @return array<array<int|float>>    m x p product
+	 */
+	public static function multiplyMatrices(array $a, array $b): array
+	{
+		$m = count($a);
+
+		if (!is_array($a[0] ?? null)) {
+			// $a is vector, convert to [[a, b, c, ...]]
+			$a = [ $a ];
+		}
+
+		if (!is_array($b[0])) {
+			// $b is vector, convert to [[a], [b], [c], ...]]
+			$b = array_map(
+				callback: fn ($v) => [ $v ],
+				array: $b,
+			);
+		}
+
+		$p = count($b[0]);
+
+		// transpose $b:
+		$bCols = array_map(
+			callback: fn ($k) => \array_map(
+				(fn ($i) => $i[$k]),
+				$b,
+			),
+			array: array_keys($b[0]),
+		);
+
+		$product = array_map(
+			callback: fn ($row) => array_map(
+				callback: fn ($col) => is_array($row) ?
+					array_reduce(
+						array: $row,
+						callback: fn ($a, $v, $i = null) => $a + $v * (
+							$col[$i ?? array_search($v, $row)] ?? 0
+						),
+						initial: 0,
+					) :
+					array_reduce(
+						array: $col,
+						callback: fn ($a, $v) => $a + $v * $row,
+						initial: 0,
+					),
+				array: $bCols,
+			),
+			array: $a,
+		);
+
+		if ($m === 1) {
+			// Avoid [[a, b, c, ...]]:
+			$product = $product[0];
+		}
+
+		if ($p === 1) {
+			// Avoid [[a], [b], [c], ...]]:
+			return array_map(
+				callback: fn ($v) => $v[0],
+				array: $product,
+			);
+		}
+
+		return $product;
+	}
 }

 // __END__
2024-11-11 18:48:56 +09:00
Clemens Schwaighofer
d9bcb577d7 some minor test page code fixes 2024-11-07 12:05:23 +09:00
Clemens Schwaighofer
8613e8977b UrlRequests curl: move options set logic to main curl wrapper call
change the curlRequest call to options array and build the options array
there.
Remove any options check + pre build from the get/request calls

Update phpunit tests with string type body return
v9.18.1
2024-11-07 11:22:36 +09:00
Clemens Schwaighofer
0c51a3be87 Add phpunit tests for header key/value exceptions v9.18.0 2024-11-06 18:49:48 +09:00
Clemens Schwaighofer
f9cf36524e UrlRequests auth set allowed in requests call
Removed the parseHeaders public call, headers must be set as array

Throw errors on invalid headers before sending them: Key/Value check
Add headers invalid check in phpunit

Auth headers can be set per call and will override global settings if matching
2024-11-06 18:42:35 +09:00
Clemens Schwaighofer
bacb9881ac Fix UrlRequests Interface name, fix header build
Header default build was not done well, pass original headers inside and
set them. On new default start with empty array.

Switch to CoreLibs Json calls, because we use this libarary anyway already
2024-11-06 14:28:15 +09:00
Clemens Schwaighofer
f0fae1f76d Fix Composer package phpunit test url for UrlRequests 2024-11-06 13:35:00 +09:00
Clemens Schwaighofer
1653e6b684 Allow http_errors unset/set on each call
If set or not set, on each call this option can be set.
If set to null on call, the original value or default config value is used
2024-11-06 13:29:19 +09:00
Clemens Schwaighofer
c8bc0062ad URL Requests change error response
Instead of just throwing exception on 401 auth, throw exception for any
error code from 400 on
This can be turned off with the option "http_errors" set to false

Also updaed the exception content to match 400 or 500 error type with
more information attached

General Exception error codes:
Cnnn: Curl errors (FAILURE)
Rnnn: general class errors (ERROR)
Hnnn: http response errors (ERROR)
2024-11-06 12:48:01 +09:00
Clemens Schwaighofer
5c8a2ef8da Update test paths for URLRequests tests 2024-11-06 10:38:30 +09:00
Clemens Schwaighofer
d8379a10d9 URL Request phpunit test added 2024-11-06 10:33:05 +09:00
Clemens Schwaighofer
30e2f33620 Test calls update for admin area 2024-11-06 10:03:33 +09:00
Clemens Schwaighofer
a4f16f4ca9 Various updates and fixes during testing
Move the build auth content to dedicated variables
Add a default User-Agent that is always sent
Default headers like Authorization and User-Agent are always set, even when
request is sent with headers null
Fix timeout, was sent as is and not converted to milliseconds
Fix headers not correctly set to null if array entry was set to null
2024-11-06 10:03:14 +09:00
Clemens Schwaighofer
6e7b9cd033 phpunit URL Requests backend test file 2024-11-01 14:43:10 +09:00
Clemens Schwaighofer
4bc2ad8fa0 URL Requests basic tests file 2024-11-01 14:42:43 +09:00
Clemens Schwaighofer
0d4e959f39 Remove the nice formatter for now 2024-11-01 14:42:04 +09:00
Clemens Schwaighofer
95d567545a URL Requests via curl, a simple library 2024-11-01 14:41:46 +09:00
Clemens Schwaighofer
d89c6d1bde UrlRequests target file renamed 2024-10-29 18:28:19 +09:00
Clemens Schwaighofer
337ebb9032 Add a localhost entry to the hosts config 2024-10-29 18:28:07 +09:00
Clemens Schwaighofer
9538ebce7b Merge branch 'NewFeatures' into Feature-UrlRequestsCurl 2024-10-29 14:14:10 +09:00
Clemens Schwaighofer
1bff19f4b6 Update UrlRequests with patch, admin test page for it
Also update delete to have optional body (content)
2024-10-28 17:05:49 +09:00
Clemens Schwaighofer
66dc72ec67 phpunit test text doc typo fix 2024-10-21 10:19:29 +09:00
Clemens Schwaighofer
f781b5e55f Name update for params/query
Order in methods
url: mandatory
payload: mandatory in post/put
header = []
query = ""

old "params" -> "payload"
2024-10-21 09:52:49 +09:00
Clemens Schwaighofer
934db50b3a Merge branch 'NewFeatures' into Feature-UrlRequestsCurl 2024-10-21 09:52:09 +09:00
Clemens Schwaighofer
573588ad3c Ignore www composer data 2024-10-21 09:36:50 +09:00
Clemens Schwaighofer
d04addba81 Add a SQL lite media folder 2024-10-21 09:33:46 +09:00
Clemens Schwaighofer
a50a38fd40 DB IO Query Placeholder tests 2024-10-21 09:33:18 +09:00
Clemens Schwaighofer
3c5200cd99 Test run for Curl URL Requests 2024-10-21 09:32:20 +09:00
Clemens Schwaighofer
50a4b88f55 UrlRequests\Curl class
Basic interface class to CURL calls

Open:
clean up and check code is neutral
write tests, for this we need a running localhost server for tests to request to
2024-10-21 09:25:39 +09:00
Clemens Schwaighofer
e82929f512 core composer install update 2024-10-18 09:37:18 +09:00
Clemens Schwaighofer
5fc55c53b8 Update composer and ignore composer/vendor and phive/tools folders 2024-10-18 09:28:27 +09:00
Clemens Schwaighofer
47da4d02ff ignore local vendor/composer and tools folder 2024-10-18 09:19:10 +09:00
Clemens Schwaighofer
9d131cf6dd Bug: Remove echo in call v9.17.6 2024-10-17 13:58:52 +09:00
Clemens Schwaighofer
dfcae20f64 Update DB\IO and do not print call steck on DB_INFO calls v9.17.5 2024-10-17 13:52:44 +09:00
Clemens Schwaighofer
61e489ee4c Remove entries from an array wrapper
just wrapper around array_diff
2024-10-17 09:44:24 +09:00
Clemens Schwaighofer
29982f90bc Admin\Backend change non filled dat part comment
the not filled data or data_binary part is a JSON with "type" set to the
type that is used with a general message

To decode try to read both sides if data = JSON + "type" and "message" set
then data is in data_binary else data_binary holds the type on the left side
v9.17.4
2024-10-16 16:45:13 +09:00
Clemens Schwaighofer
7cced63c4b Update the Admin\Backend edit log call with query params and different data compressors
All queries uses now Params

On load checks for valid write types for edit log write, eg if bzip and lzip compression
are avaiable

adbEditLog:
Also add JSON type encoding for data outside STRING/SERIAL and BINARY/BZIP (bzip compressed)
Add ZLIB as altnerative to BZIP
Add alert if invalid type was set
Auto fallback to JSON if other write types are not available

adbLiveQueue:
Also convert the live queue query to a params style call
v9.17.3
2024-10-16 16:21:51 +09:00