From 00821bd5ea06d8d21a61c8b0614348f83de42bbf Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 12 Nov 2024 18:53:18 +0900 Subject: [PATCH] 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 --- www/admin/class_test.convert.colors.php | 36 + www/lib/CoreLibs/Convert/Color/CieXyz.php | 344 +++++++++ www/lib/CoreLibs/Convert/Color/Color.php | 695 +++++++++++++----- .../Convert/Color/Coordinates/HSB.php | 51 +- .../Convert/Color/Coordinates/HSL.php | 73 +- .../Convert/Color/Coordinates/HWB.php | 73 +- .../Convert/Color/Coordinates/LCH.php | 79 +- .../Convert/Color/Coordinates/Lab.php | 61 +- .../Convert/Color/Coordinates/RGB.php | 155 +++- .../Convert/Color/Coordinates/XYZ.php | 162 ++++ www/lib/CoreLibs/Convert/Color/Stringify.php | 21 +- www/lib/CoreLibs/Convert/Colors.php | 44 +- 12 files changed, 1446 insertions(+), 348 deletions(-) create mode 100644 www/lib/CoreLibs/Convert/Color/CieXyz.php create mode 100644 www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php diff --git a/www/admin/class_test.convert.colors.php b/www/admin/class_test.convert.colors.php index a37cb2df..84d70ce5 100644 --- a/www/admin/class_test.convert.colors.php +++ b/www/admin/class_test.convert.colors.php @@ -31,6 +31,36 @@ $log = new CoreLibs\Logging\Logging([ ]); $color_class = 'CoreLibs\Convert\Colors'; +/** + * print out a color block with info + * + * @param string $color + * @param string $text + * @param string $text_add + * @return string + */ +function display(string $color, string $text, string $text_add): string +{ + $css = 'margin:5px;padding:50px;' + . 'width:10%;' + . 'text-align:center;' + . 'color:white;text-shadow: 0 0 5px black;font-weight:bold;'; + $template = << + {TEXT} + + HTML; + return str_replace( + ["{COLOR}", "{TEXT}", "{CSS}"], + [ + $color, + $text . ($text_add ? '
' . $text_add : ''), + $css + ], + $template + ); +} + $PAGE_NAME = 'TEST CLASS: CONVERT COLORS'; print ""; print "" . $PAGE_NAME . ""; @@ -133,14 +163,20 @@ $oklab = Color::rgbToOkLab(Coordinates\RGB::__constructFromArray([ 0 ])); print "OkLab: " . DgS::printAr($oklab) . "
"; +print display($oklab->toCssString(), $oklab->toCssString(), 'Oklab'); $rgb = Color::okLabToRgb($oklab); print "OkLab -> RGB: " . DgS::printAr($rgb) . "
"; +print display($rgb->toCssString(), $rgb->toCssString(), 'OkLab to RGB'); $rgb = Coordinates\RGB::__constructFromArray([250, 100, 10])->toLinear(); print "RGBlinear: " . DgS::printAr($rgb) . "
"; $rgb = Coordinates\RGB::__constructFromArray([0, 0, 0])->toLinear(); print "RGBlinear: " . DgS::printAr($rgb) . "
"; +$cie_lab = Color::okLabToLab($oklab); +print "CieLab: " . DgS::printAr($cie_lab) . "
"; +print display($cie_lab->toCssString(), $cie_lab->toCssString(), 'OkLab to Cie Lab'); + print ""; diff --git a/www/lib/CoreLibs/Convert/Color/CieXyz.php b/www/lib/CoreLibs/Convert/Color/CieXyz.php new file mode 100644 index 00000000..2dbd1253 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/CieXyz.php @@ -0,0 +1,344 @@ +fromLinear(); + } + + /** + * Convert RGB to CIE Lab + * via xyz D65 to xyz D50 + * + * @param RGB $rgb + * @return Lab + */ + public static function rgbViaXyzD65ViaXyzD50ToLab(RGB $rgb): Lab + { + return self::xyzD50ToLab( + self::xyzD65ToXyzD50( + self::linRgbToXyzD65($rgb) + ) + ); + } + + /** + * Convert CIE Lab to RGB + * via xyz D50 to xyz D65 + * + * @param Lab $lab + * @return RGB + */ + public static function labViaXyzD50ViaXyzD65ToRgb(Lab $lab): RGB + { + return self::xyzD65ToLinRgb( + self::xyzD50ToXyxD65( + self::labToXyzD50($lab) + ) + )->fromLinear(); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return Lab + */ + public static function okLabViaXyzD65ViaXyzD50ToLab(Lab $lab): Lab + { + return self::xyzD50ToLab( + self::xyzD65ToXyzD50( + self::okLabToXyzD65($lab) + ) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return Lab + */ + public static function labViaXyzD50ViaXyzD65ToOkLab(Lab $lab): Lab + { + return self::xyzD65ToOkLab( + self::xyzD50ToXyxD65( + self::labToXyzD50($lab) + ) + ); + } + + // MARK: xyzD65 <-> xyzD50 + + /** + * xyzD65 to xyzD50 whitepoint + * + * @param XYZ $xyz + * @return XYZ + */ + private static function xyzD65ToXyzD50(XYZ $xyz): XYZ + { + return XYZ::__constructFromArray(Math::multiplyMatrices( + a: [ + [1.0479298208405488, 0.022946793341019088, -0.05019222954313557], + [0.029627815688159344, 0.990434484573249, -0.01707382502938514], + [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008], + ], + b: $xyz->returnAsArray(), + ), whitepoint: 'D50'); + } + + /** + * xyzD50 to xyzD65 whitepoint + * + * @param XYZ $xyz + * @return XYZ + */ + private static function xyzD50ToXyxD65(XYZ $xyz): XYZ + { + return XYZ::__constructFromArray(Math::multiplyMatrices( + a: [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], + ], + b: $xyz->returnAsArray() + ), whitepoint: 'D65'); + } + + // MARK: xyzD50 <-> Lab + + /** + * Convert xyzD50 to Lab (Cie) + * + * @param XYZ $xyz + * @return Lab + */ + private static function xyzD50ToLab(XYZ $xyz): Lab + { + $_xyz = $xyz->returnAsArray(); + $d50 = [ + 0.3457 / 0.3585, + 1.00000, + (1.0 - 0.3457 - 0.3585) / 0.3585, + ]; + + $a = 216 / 24389; + $b = 24389 / 27; + + $_xyz = array_map( + fn ($k, $v) => $v / $d50[$k], + array_keys($_xyz), + array_values($_xyz), + ); + + $f = array_map( + fn ($v) => (($v > $a) ? + pow($v, 1 / 3) : + (($b * $v + 16) / 116) + ), + $_xyz, + ); + + return Lab::__constructFromArray([ + (116 * $f[1]) - 16, + 500 * ($f[0] - $f[1]), + 200 * ($f[1] - $f[2]), + ], colorspace: 'CIELab'); + } + + /** + * Convert Lab (Cie) to xyz D50 + * + * @param Lab $lab + * @return XYZ + */ + private static function labToXyzD50(Lab $lab): XYZ + { + $_lab = $lab->returnAsArray(); + $a = 24389 / 27; + $b = 216 / 24389; + $f = []; + $f[1] = ($_lab[0] + 16) / 116; + $f[0] = $_lab[1] / 500 + $f[1]; + $f[2] = $f[1] - $_lab[2] / 200; + $xyz = [ + // x + pow($f[0], 3) > $b ? + pow($f[0], 3) : + (116 * $f[0] - 16) / $a, + // y + $_lab[0] > $a * $b ? + pow(($_lab[0] + 16) / 116, 3) : + $_lab[0] / $a, + // z + pow($f[2], 3) > $b ? + pow($f[2], 3) : + (116 * $f[2] - 16) / $a, + ]; + + $d50 = [ + 0.3457 / 0.3585, + 1.00000, + (1.0 - 0.3457 - 0.3585) / 0.3585, + ]; + + return XYZ::__constructFromArray( + array_map( + fn ($k, $v) => $v * $d50[$k], + array_keys($xyz), + array_values($xyz), + ), + whitepoint: 'D50' + ); + } + + // MARK: xyzD65 <-> (linear)RGB + + /** + * convert linear RGB to xyz D65 + * if rgb is not flagged linear, it will be auto converted + * + * @param RGB $rgb + * @return XYZ + */ + private static function linRgbToXyzD65(RGB $rgb): XYZ + { + // if not linear, convert to linear + if (!$rgb->linear) { + $rgb->toLinear(); + } + return XYZ::__constructFromArray(Math::multiplyMatrices( + [ + [0.41239079926595934, 0.357584339383878, 0.1804807884018343], + [0.21263900587151027, 0.715168678767756, 0.07219231536073371], + [0.01933081871559182, 0.11919477979462598, 0.9505321522496607], + ], + $rgb->returnAsArray() + ), whitepoint: 'D65'); + } + + /** + * Convert xyz D65 to linear RGB + * + * @param XYZ $xyz + * @return RGB + */ + private static function xyzD65ToLinRgb(XYZ $xyz): 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 : $xyz->returnAsArray() + ), linear: true); + } + + // MARK: xyzD65 <-> OkLab + + /** + * xyz D65 to OkLab + * + * @param XYZ $xyz + * @return Lab + */ + private static function xyzD65ToOkLab(XYZ $xyz): 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: $xyz->returnAsArray(), + ), + ) + ), colorspace: 'OkLab'); + } + + /** + * xyz D65 to OkLab + * + * @param Lab $lab + * @return XYZ + */ + private static function okLabToXyzD65(Lab $lab): XYZ + { + return XYZ::__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(), + ), + ), + ), whitepoint: 'D65'); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Color.php b/www/lib/CoreLibs/Convert/Color/Color.php index c56f8c11..3b490e9b 100644 --- a/www/lib/CoreLibs/Convert/Color/Color.php +++ b/www/lib/CoreLibs/Convert/Color/Color.php @@ -9,38 +9,79 @@ * 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 | | | | | | | | - | + * | | RGB | Oklab | CieLab | + * | | | HSB | | | | | | | + * | | RGB | HSV | HSL | HWB | OkLab | OkLch | CieLab | CieLch | + * --------+-----+-----+-----+-----+-------+-------+--------+--------+ + * RGB | - | o | o | o | o | o | o | o | + * HSB/HSV | o | - | o | o | o | o | o | o | + * HSL | o | o | - | o | o | o | o | o | + * HWB | o | o | o | - | o | o | o | o | + * OkLab | o | o | o | o | - | o | o | o | + * OkLch | o | o | o | o | o | - | o | o | + * CieLab | o | o | o | o | o | o | - | o | + * CieLch | o | o | o | o | o | o | o | - | * * All color coordinates are classes * The data can then be converted to a CSS string + * + * CieXyz Class + * Not theat xyz (CIEXYZ) does not have its own conversion as it is not used in web + * applications + * Also XYZ has two different coordinate systems for the D50 an D65 whitepoint */ 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: general lab/lch + + /** + * general Lab to LCH convert + * + * @param Lab $lab + * @return array{0:float,1:float,2:float} LCH values as array + */ + private static function __labToLch(Lab $lab): array + { + // cieLab to cieLch + $a = $lab->a; + $b = $lab->b; + + $hue = atan2($b, $a) * 180 / pi(); + + return [ + $lab->L, + sqrt($a ** 2 + $b ** 2), + $hue >= 0 ? $hue : $hue + 360, + ]; + } + + /** + * general LCH to Lab convert + * + * @param LCH $lch + * @return array{0:float,1:float,2:float} Lab values as array + */ + private static function __lchToLab(LCH $lch): array + { + return [ + $lch->L, + $lch->C * cos($lch->H * pi() / 180), // a + $lch->C * sin($lch->H * pi() / 180), // b + ]; + } + // MARK: RGB <-> HSL /** @@ -267,6 +308,40 @@ class Color ]); } + // 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 <-> HSB /** @@ -318,6 +393,38 @@ class Color ]); } + // 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: HSB <-> HWB /** @@ -366,70 +473,31 @@ class Color ]); } - // MARK: RGB <-> HWB + // MARK: LAB <-> LCH + + // toLch /** - * Convert RGB to HWB - * via rgb -> hsl -> hsb -> hwb + * CIE Lab to LCH * - * @param RGB $rgb - * @return HWB + * @param Lab $lab + * @return LCH */ - public static function rgbToHwb(RGB $rgb): HWB + public static function labToLch(Lab $lab): LCH { - return self::hsbToHwb( - self::hslToHsb( - self::rgbToHsl($rgb) - ) - ); + // cieLab to cieLch + return LCH::__constructFromArray(self::__labToLch($lab), colorspace: 'CIELab'); } /** - * Convert HWB to RGB - * via hwb -> hsb -> hsl -> rgb + * Convert CIE LCH to Lab * - * @param HWB $hwb - * @return RGB + * @param LCH $lch + * @return Lab */ - public static function hwbToRgb(HWB $hwb): RGB + public static function lchToLab(LCH $lch): Lab { - 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) - ); + return Lab::__constructFromArray(self::__lchToLab($lch), colorspace: 'CIELab'); } // MARK: OkLch <-> OkLab @@ -443,16 +511,7 @@ class Color 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, - ]); + return LCH::__constructFromArray(self::__labToLch($lab), colorspace: 'OkLab'); } /** @@ -465,114 +524,7 @@ class Color { // 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(), - ), - ), - )); + return Lab::__constructFromArray(self::__lchToLab($lch), colorspace: 'OkLab'); } // MARK: rgb <-> oklab @@ -585,9 +537,7 @@ class Color */ public static function rgbToOkLab(RGB $rgb): Lab { - return self::xyzD65ToOkLab( - self::linRgbToXyzD65($rgb) - ); + return CieXyz::rgbViaXyzD65ToOkLab($rgb); } /** @@ -598,9 +548,7 @@ class Color */ public static function okLabToRgb(Lab $lab): RGB { - return self::xyzD65ToLinRgb( - self::okLabToXyzD65($lab) - )->fromLinear(); + return CieXyz::okLabViaXyzD65ToRgb($lab); } // MARK: rgb <-> oklch @@ -753,7 +701,7 @@ class Color * @param HWB $hwb * @return Lab */ - public function hwbToOkLab(HWB $hwb): Lab + public static function hwbToOkLab(HWB $hwb): Lab { return self::rgbToOkLab( self::hwbToRgb($hwb) @@ -766,7 +714,7 @@ class Color * @param Lab $lab * @return HWB */ - public function okLabToHwb(Lab $lab): HWB + public static function okLabToHwb(Lab $lab): HWB { return self::rgbToHwb( self::okLabToRgb($lab) @@ -781,7 +729,7 @@ class Color * @param HWB $hwb * @return LCH */ - public function hwbToOkLch(HWB $hwb): LCH + public static function hwbToOkLch(HWB $hwb): LCH { return self::rgbToOkLch( self::hwbToRgb($hwb) @@ -794,10 +742,367 @@ class Color * @param LCH $lch * @return HWB */ - public function okLchToHwb(LCH $lch): HWB + public static function okLchToHwb(LCH $lch): HWB { return self::rgbToHwb( self::okLchToRgb($lch) ); } + + // MARK: RGB <-> Lab (Cie) + + /** + * RGB to Lab + * via RGB -> linRgb -> xyz D65 -> xyz D50 -> Lab + * + * @param RGB $rgb + * @return Lab + */ + public static function rgbToLab(RGB $rgb): Lab + { + return CieXyz::rgbViaXyzD65ViaXyzD50ToLab($rgb); + /* return CieXyz::xyzD50ToLab( + CieXyz::xyzD65ToXyzD50( + CieXyz::linRgbToXyzD65($rgb) + ) + ); */ + } + + /** + * Lab to RGB + * via Lab -> xyz D50 -> xyz D65 -> lin RGB -> RGB + * + * @param Lab $lab + * @return RGB + */ + public static function labToRgb(Lab $lab): RGB + { + return CieXyz::labViaXyzD50ViaXyzD65ToRgb($lab); + /* return CieXyz::xyzD65ToLinRgb( + CieXyz::xyzD50ToXyxD65( + CieXyz::labToXyzD50($lab) + ) + )->fromLinear(); */ + } + + // MARK: RGB <-> Lch (Cie) + + /** + * Convert RGB to LCH (Cie) + * via RGB to Lab + * + * @param RGB $rgb + * @return LCH + */ + public static function rgbToLch(RGB $rgb): LCH + { + // return self::rgbToL + return self::labToLch( + self::rgbToLab($rgb) + ); + } + + /** + * Convert LCH (Cie) to RGB + * via Lab to RGB + * + * @param LCH $lch + * @return RGB + */ + public static function lchToRgb(LCH $lch): RGB + { + return self::labToRgb( + self::lchToLab($lch) + ); + } + + // MARK: HSL <-> Lab (CIE) + + /** + * HSL to Lab (CIE) + * + * @param HSL $hsl + * @return Lab + */ + public static function hslToLab(HSL $hsl): Lab + { + return self::rgbToLab( + self::hslToRgb($hsl) + ); + } + + /** + * Lab (CIE) to HSL + * + * @param Lab $lab + * @return HSL + */ + public static function labToHsl(Lab $lab): HSL + { + return self::rgbToHsl( + self::labToRgb($lab) + ); + } + + // MARK: HSL <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HSL $hsl + * @return LCH + */ + public static function hslToLch(HSL $hsl): LCH + { + return self::rgbToLch( + self::hslToRgb($hsl) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSL + */ + public static function lchToHsl(LCH $lch): HSL + { + return self::rgbToHsl( + self::lchToRgb($lch) + ); + } + + // MARK: HSB <-> Lab (CIE) + + /** + * Undocumented function + * + * @param HSB $hsb + * @return Lab + */ + public static function hsbToLab(HSB $hsb): Lab + { + return self::rgbToLab( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HSB + */ + public static function labToHsb(Lab $lab): HSB + { + return self::rgbToHsb( + self::labToRgb($lab) + ); + } + + // MARK: HSB <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HSB $hsb + * @return LCH + */ + public static function hsbToLch(HSB $hsb): LCH + { + return self::rgbToLch( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSB + */ + public static function lchToHsb(LCH $lch): HSB + { + return self::rgbToHsb( + self::lchToRgb($lch) + ); + } + + // MARK: HWB <-> Lab (CIE) + + /** + * Undocumented function + * + * @param HWB $hwb + * @return Lab + */ + public static function hwbToLab(HWB $hwb): Lab + { + return self::rgbToLab( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HWB + */ + public static function labToHwb(Lab $lab): HWB + { + return self::rgbToHwb( + self::labToRgb($lab) + ); + } + + // MARK: HWB <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HWB $hwb + * @return Lch + */ + public static function hwbToLch(HWB $hwb): Lch + { + return self::rgbToLch( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HWB + */ + public static function lchToHweb(LCH $lch): HWB + { + return self::rgbToHwb( + self::lchToRgb($lch) + ); + } + + // MARK: Lab (Cie) <-> OkLab + + /** + * okLab to Lab (Cie) + * + * @param Lab $lab + * @return Lab + */ + public static function okLabToLab(Lab $lab): Lab + { + return CieXyz::okLabViaXyzD65ViaXyzD50ToLab($lab); + /* return CieXyz::xyzD50ToLab( + CieXyz::xyzD65ToXyzD50( + CieXyz::okLabToXyzD65($lab) + ) + ); */ + } + + /** + * Lab (Cie) to okLab + * + * @param Lab $lab + * @return Lab + */ + public static function labToOkLab(Lab $lab): Lab + { + return CieXyz::labViaXyzD50ViaXyzD65ToOkLab($lab); + /* return CieXyz::xyzD65ToOkLab( + CieXyz::xyzD50ToXyxD65( + CieXyz::labToXyzD50($lab) + ) + ); */ + } + + // MARK: Lab (Cie) <-> Oklch + + /** + * OkLch to Lab (CIE) + * + * @param LCH $lch + * @return Lab + */ + public static function okLchToLab(LCH $lch): Lab + { + return self::okLabToLab( + self::okLchToOkLab($lch) + ); + } + + /** + * Lab (CIE) to OkLch + * + * @param Lab $lab + * @return LCH + */ + public static function labToOkLch(Lab $lab): LCH + { + return self::okLabToOkLch( + self::labToOkLab($lab) + ); + } + + // MARK: Lch (Cie) <-> OkLch + + /** + * okLch to Lch (Cie) + * via okLabToLab + * + * @param LCH $lch + * @return LCH + */ + public static function okLchToLch(LCH $lch): LCH + { + return self::labToLch( + self::okLabToLab( + self::okLchToOkLab($lch) + ) + ); + } + + /** + * Lch (Cie) to OkLch + * via labToOkLab + * + * @param LCH $lch + * @return LCH + */ + public static function lchToOkLch(LCH $lch): LCH + { + return self::labToOkLch( + self::lchToLab($lch) + ); + } + + // MARK: Lch (Cie) to OkLab + + /** + * OkLab to Lch (CIE) + * + * @param LAB $lab + * @return LCH + */ + public static function okLabToLch(LAB $lab): LCH + { + return self::labToLch( + self::okLabToLab($lab) + ); + } + + /** + * Lch (CIE) to OkLab + * + * @param LCH $lch + * @return LAB + */ + public static function lchToOkLab(LCH $lch): LAB + { + return self::labToOkLab( + self::lchToLab($lch) + ); + } } diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php index b435a9ef..7ebb2dc6 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php @@ -13,6 +13,9 @@ namespace CoreLibs\Convert\Color\Coordinates; class HSB { + /** @var array allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + /** @var float hue */ private float $H = 0.0; /** @var float saturation */ @@ -20,6 +23,9 @@ class HSB /** @var float brightness / value */ private float $B = 0.0; + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + /** * HSB (HSV) color coordinates * Hue/Saturation/Brightness or Value @@ -28,29 +34,17 @@ class HSB { } - /** - * 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 + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] * @return self */ - public static function __constructFromArray(array $hsb): self + public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self { - return (new HSB())->setAsArray($hsb); + return (new HSB())->setColorspace($colorspace)->setFromArray($colors); } /** @@ -113,6 +107,21 @@ class HSB 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: Hue, 1: Saturation, 2: Brightness @@ -128,14 +137,14 @@ class HSB * set color as array * where 0: Hue, 1: Saturation, 2: Brightness * - * @param array{0:float,1:float,2:float} $hsb + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $hsb): self + public function setFromArray(array $colors): self { - $this->__set('H', $hsb[0]); - $this->__set('S', $hsb[1]); - $this->__set('B', $hsb[2]); + $this->__set('H', $colors[0]); + $this->__set('S', $colors[1]); + $this->__set('B', $colors[2]); return $this; } diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php index 21be7fe5..15005471 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php @@ -11,14 +11,23 @@ declare(strict_types=1); namespace CoreLibs\Convert\Color\Coordinates; +use CoreLibs\Convert\Color\Stringify; + class HSL { + /** @var array allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + /** @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; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + /** * Color Coordinate HSL * Hue/Saturation/Lightness @@ -27,29 +36,17 @@ class HSL { } - /** - * 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 + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] * @return self */ - public static function __constructFromArray(array $hsl): self + public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self { - return (new HSL())->setAsArray($hsl); + return (new HSL())->setColorspace($colorspace)->setFromArray($colors); } /** @@ -110,6 +107,21 @@ class HSL 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: Hue, 1: Saturation, 2: Lightness @@ -125,16 +137,35 @@ class HSL * set color as array * where 0: Hue, 1: Saturation, 2: Lightness * - * @param array{0:float,1:float,2:float} $hsl + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $hsl): self + public function setFromArray(array $colors): self { - $this->__set('H', $hsl[0]); - $this->__set('S', $hsl[1]); - $this->__set('L', $hsl[2]); + $this->__set('H', $colors[0]); + $this->__set('S', $colors[1]); + $this->__set('L', $colors[2]); return $this; } + + /** + * convert to css string with optional opacityt + * + * @param float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = 'hsl(' + . $this->H + . ' ' + . $this->S + . ' ' + . $this->L + . Stringify::setOpacity($opacity) + . ')'; + return $string; + } } // __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php index ee6b7f63..1dd6eb23 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php @@ -11,14 +11,23 @@ declare(strict_types=1); namespace CoreLibs\Convert\Color\Coordinates; +use CoreLibs\Convert\Color\Stringify; + class HWB { + /** @var array allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + /** @var float Hue */ private float $H = 0.0; /** @var float Whiteness */ private float $W = 0.0; /** @var float Blackness */ private float $B = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + /** * Color Coordinate: HWB * Hue/Whiteness/Blackness @@ -27,29 +36,17 @@ class HWB { } - /** - * 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 + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] * @return self */ - public static function __constructFromArray(array $hwb): self + public static function __constructFromArray(array $colors, string $colorspace = 'sRGB'): self { - return (new HWB())->setAsArray($hwb); + return (new HWB())->setColorspace($colorspace)->setFromArray($colors); } /** @@ -110,6 +107,21 @@ class HWB 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: Hue, 1: Whiteness, 2: Blackness @@ -125,16 +137,35 @@ class HWB * set color as array * where 0: Hue, 1: Whiteness, 2: Blackness * - * @param array{0:float,1:float,2:float} $hwb + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $hwb): self + public function setFromArray(array $colors): self { - $this->__set('H', $hwb[0]); - $this->__set('W', $hwb[1]); - $this->__set('B', $hwb[2]); + $this->__set('H', $colors[0]); + $this->__set('W', $colors[1]); + $this->__set('B', $colors[2]); return $this; } + + /** + * convert to css string with optional opacityt + * + * @param float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = 'hwb(' + . $this->H + . ' ' + . $this->W + . ' ' + . $this->B + . Stringify::setOpacity($opacity) + . ')'; + return $string; + } } // __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php b/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php index 648fb466..872c597b 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php @@ -12,8 +12,13 @@ declare(strict_types=1); namespace CoreLibs\Convert\Color\Coordinates; +use CoreLibs\Convert\Color\Stringify; + class LCH { + /** @var array allowed colorspaces */ + private const COLORSPACES = ['OkLab', 'CIELab']; + /** @var float Lightness/Luminance * CIE: 0 to 100 * OKlch: 0.0 to 1.0 @@ -42,29 +47,17 @@ class LCH { } - /** - * 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 + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace * @return self */ - public static function __constructFromArray(array $lch): self + public static function __constructFromArray(array $colors, string $colorspace): self { - return (new LCH())->setAsArray($lch); + return (new LCH())->setColorspace($colorspace)->setFromArray($colors); } /** @@ -139,6 +132,21 @@ class LCH 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: Chroma, 2: Hue @@ -154,16 +162,45 @@ class LCH * set color as array * where 0: Lightness, 1: Chroma, 2: Hue * - * @param array{0:float,1:float,2:float} $lch + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $lch): self + public function setFromArray(array $colors): self { - $this->__set('L', $lch[0]); - $this->__set('C', $lch[1]); - $this->__set('H', $lch[2]); + $this->__set('L', $colors[0]); + $this->__set('C', $colors[1]); + $this->__set('H', $colors[2]); return $this; } + + /** + * Convert into css string with optional opacity + * + * @param null|float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = ''; + switch ($this->colorspace) { + case 'CIELab': + $string = 'lch'; + break; + case 'OkLab': + $string = 'oklch'; + break; + } + $string .= '(' + . $this->L + . ' ' + . $this->c + . ' ' + . $this->h + . Stringify::setOpacity($opacity) + . ');'; + + return $string; + } } // __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php b/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php index e2eb11a4..8e9f573a 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php @@ -12,10 +12,12 @@ declare(strict_types=1); namespace CoreLibs\Convert\Color\Coordinates; +use CoreLibs\Convert\Color\Stringify; + class Lab { /** @var array allowed colorspaces */ - private const COLORSPACES = ['Oklab', 'cie']; + private const COLORSPACES = ['OkLab', 'CIELab']; /** @var float lightness/luminance * CIE: 0f to 100f @@ -47,20 +49,6 @@ class Lab { } - /** - * 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 @@ -69,9 +57,9 @@ class Lab * @param string $colorspace * @return self */ - public static function __constructFromArray(array $lab, string $colorspace): self + public static function __constructFromArray(array $colors, string $colorspace): self { - return (new Lab())->setColorspace($colorspace)->setAsArray($lab); + return (new Lab())->setColorspace($colorspace)->setFromArray($colors); } /** @@ -162,16 +150,45 @@ class Lab * set color as array * where 0: Lightness, 1: a, 2: b * - * @param array{0:float,1:float,2:float} $lab + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $lab): self + public function setFromArray(array $colors): self { - $this->__set('L', $lab[0]); - $this->__set('a', $lab[1]); - $this->__set('b', $lab[2]); + $this->__set('L', $colors[0]); + $this->__set('a', $colors[1]); + $this->__set('b', $colors[2]); return $this; } + + /** + * Convert into css string with optional opacity + * + * @param null|float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = ''; + switch ($this->colorspace) { + case 'CIELab': + $string = 'lab'; + break; + case 'OkLab': + $string = 'oklab'; + break; + } + $string .= '(' + . $this->L + . ' ' + . $this->a + . ' ' + . $this->b + . Stringify::setOpacity($opacity) + . ');'; + + return $string; + } } // __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php index acc81952..7fc3eda0 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php @@ -11,8 +11,13 @@ declare(strict_types=1); namespace CoreLibs\Convert\Color\Coordinates; +use CoreLibs\Convert\Color\Stringify; + class RGB { + /** @var array allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + /** @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 */ @@ -20,6 +25,9 @@ class RGB /** @var float blue 0 to 255 or 0.0f to 1.0f for linear RGB */ private float $B = 0.0; + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + /** @var bool set if this is linear */ private bool $linear = false; @@ -30,31 +38,34 @@ class RGB { } - /** - * 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 array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] * @param bool $linear [default=false] * @return self */ - public static function __constructFromArray(array $rgb, bool $linear = false): self + public static function __constructFromArray(array $colors, string $colorspace = 'sRGB', bool $linear = false): self { - return (new RGB())->flagLinear($linear)->setAsArray($rgb); + return (new RGB())->setColorspace($colorspace)->flagLinear($linear)->setFromArray($colors); + } + + /** + * Undocumented function + * + * @param string $hex_string + * @param string $colorspace + * @param bool $linear + * @return self + */ + public static function __constructFromHexString( + string $hex_string, + string $colorspace = 'sRGB', + bool $linear = false + ): self { + return (new RGB())->setColorspace($colorspace)->flagLinear($linear)->setFromHex($hex_string); } /** @@ -99,6 +110,21 @@ class RGB 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: Red, 1: Green, 2: Blue @@ -114,17 +140,76 @@ class RGB * set color as array * where 0: Red, 1: Green, 2: Blue * - * @param array{0:float,1:float,2:float} $rgb + * @param array{0:float,1:float,2:float} $colors * @return self */ - public function setAsArray(array $rgb): self + public function setFromArray(array $colors): self { - $this->__set('R', $rgb[0]); - $this->__set('G', $rgb[1]); - $this->__set('B', $rgb[2]); + $this->__set('R', $colors[0]); + $this->__set('G', $colors[1]); + $this->__set('B', $colors[2]); return $this; } + /** + * Return current set RGB as hex string. with or without # prefix + * + * @param bool $hex_prefix + * @return string + */ + public function returnAsHex(bool $hex_prefix = true): string + { + // prefix + $hex_color = ''; + if ($hex_prefix === true) { + $hex_color = '#'; + } + // convert if in linear + if ($this->linear) { + $this->fromLinear(); + } + foreach ($this->returnAsArray() as $color) { + $hex_color .= str_pad(dechex((int)$color), 2, '0', STR_PAD_LEFT); + } + return $hex_color; + } + + /** + * set colors RGB from hex string + * + * @param string $hex_string + * @return self + */ + public function setFromHex(string $hex_string): self + { + $hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string + if (!is_string($hex_string)) { + throw new \InvalidArgumentException('hex_string argument cannot be empty', 1); + } + $rgbArray = []; + if (strlen($hex_string) == 6) { + // If a proper hex code, convert using bitwise operation. + // No overhead... faster + $colorVal = hexdec($hex_string); + $rgbArray = [ + 0xFF & ($colorVal >> 0x10), + 0xFF & ($colorVal >> 0x8), + 0xFF & $colorVal + ]; + } elseif (strlen($hex_string) == 3) { + // If shorthand notation, need some string manipulations + $rgbArray = [ + hexdec(str_repeat(substr($hex_string, 0, 1), 2)), + hexdec(str_repeat(substr($hex_string, 1, 1), 2)), + hexdec(str_repeat(substr($hex_string, 2, 1), 2)) + ]; + } else { + // Invalid hex color code + throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 2); + } + return $this->setFromArray($rgbArray); + } + /** * set as linear * can be used as chain call on create if input is linear RGB @@ -153,7 +238,7 @@ class RGB */ public function toLinear(): self { - $this->flagLinear(true)->setAsArray(array_map( + $this->flagLinear(true)->setFromArray(array_map( callback: function (int|float $v) { $v = (float)($v / 255); $abs = abs($v); @@ -177,7 +262,7 @@ class RGB */ public function fromLinear(): self { - $this->flagLinear(false)->setAsArray(array_map( + $this->flagLinear(false)->setFromArray(array_map( callback: function (int|float $v) { $abs = abs($v); $sign = ($v < 0) ? -1 : 1; @@ -197,29 +282,31 @@ class RGB /** * convert to css string with optional opacity - * Note: if this is a linea RGB, this data will not be correct + * Note: if this is a linear RGB, the data will converted during this operation and the converted back * * @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 = ''; + // if we are in linear mode, convert to normal mode temporary + $was_linear = false; + if ($this->linear) { + $this->fromLinear(); + $was_linear = true; } - return 'rgb(' + $string = 'rgb(' . (int)round($this->R, 0) . ' ' . (int)round($this->G, 0) . ' ' . (int)round($this->B, 0) - . $opacity + . Stringify::setOpacity($opacity) . ')'; + if ($was_linear) { + $this->toLinear(); + } + return $string; } } diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php b/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php new file mode 100644 index 00000000..7c773ed9 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php @@ -0,0 +1,162 @@ + allowed colorspaces */ + private const COLORSPACES = ['CIEXYZ']; + /** @var array allowed whitepoints + * D50: ICC profile PCS (horizon light) <-> CieLab + * D65: RGB color space (noon) <-> linear RGB + */ + private const ILLUMINANT = ['D50', 'D65']; + + /** @var float X coordinate */ + private float $X = 0.0; + /** @var float Y coordinate (Luminance) */ + private float $Y = 0.0; + /** @var float Z coordinate (blue) */ + private float $Z = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + private string $whitepoint = ''; + + /** + * Color Coordinate Lch + * for oklch + */ + public function __construct() + { + } + + /** + * set from array + * where 0: X, 1: Y, 2: Z + * + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=CIEXYZ] + * @param string $whitepoint [default=''] only D65 or D50 allowed + * @return self + */ + public static function __constructFromArray( + array $colors, + string $colorspace = 'CIEXYZ', + string $whitepoint = '' + ): self { + return (new XYZ()) + ->setColorspace($colorspace) + ->setWhitepoint($whitepoint) + ->setFromArray($colors); + } + + /** + * 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; + } + + /** + * 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; + } + + /** + * set the whitepoint flag + * + * @param string $whitepoint + * @return self + */ + private function setWhitepoint(string $whitepoint): self + { + if (empty($whitepoint)) { + $this->whitepoint = ''; + return $this; + } + if (!in_array($whitepoint, $this::ILLUMINANT)) { + throw new \InvalidArgumentException('Not allowed whitepoint', 0); + } + $this->whitepoint = $whitepoint; + return $this; + } + + /** + * 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} $colors + * @return self + */ + public function setFromArray(array $colors): self + { + $this->__set('X', $colors[0]); + $this->__set('Y', $colors[1]); + $this->__set('Z', $colors[2]); + return $this; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Stringify.php b/www/lib/CoreLibs/Convert/Color/Stringify.php index 6ca68431..72dfa020 100644 --- a/www/lib/CoreLibs/Convert/Color/Stringify.php +++ b/www/lib/CoreLibs/Convert/Color/Stringify.php @@ -20,7 +20,26 @@ use CoreLibs\Convert\Color\Coordinates\LCH; class Stringify { /** - * Undocumented function + * 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 * * @param RGB|Lab|LCH|HSL|HWB $data * @param null|float|string $opacity diff --git a/www/lib/CoreLibs/Convert/Colors.php b/www/lib/CoreLibs/Convert/Colors.php index 8ff32608..61fe9384 100644 --- a/www/lib/CoreLibs/Convert/Colors.php +++ b/www/lib/CoreLibs/Convert/Colors.php @@ -17,6 +17,9 @@ declare(strict_types=1); namespace CoreLibs\Convert; +use CoreLibs\Convert\Color\Color; +use CoreLibs\Convert\Color\Coordinates; + class Colors { /** @@ -37,7 +40,8 @@ class Colors int $blue, bool $hex_prefix = true ): string { - $hex_color = ''; + return Coordinates\RGB::__constructFromArray([$red, $green, $blue])->returnAsHex($hex_prefix); + /* $hex_color = ''; if ($hex_prefix === true) { $hex_color = '#'; } @@ -50,7 +54,7 @@ class Colors // pad left with 0 $hex_color .= str_pad(dechex($$color), 2, '0', STR_PAD_LEFT); } - return $hex_color; + return $hex_color; */ } /** @@ -69,7 +73,7 @@ class Colors bool $return_as_string = false, string $seperator = ',' ): string|array { - $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)) { throw new \InvalidArgumentException('hex_string argument cannot be empty', 1); } @@ -89,7 +93,8 @@ class Colors } else { // Invalid hex color code throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 2); - } + } */ + $rgbArray = Coordinates\RGB::__constructFromHexString($hex_string)->returnAsArray(); // returns the rgb string or the associative array return $return_as_string ? implode($seperator, $rgbArray) : $rgbArray; } @@ -108,7 +113,10 @@ class Colors */ public static function rgb2hsb(int $red, int $green, int $blue): array { - // check that rgb is from 0 to 255 + return Color::rgbToHsb( + Coordinates\RGB::__constructFromArray([$red, $green, $blue]) + )->returnAsArray(); + /* // check that rgb is from 0 to 255 foreach (['red', 'green', 'blue'] as $color) { if ($$color < 0 || $$color > 255) { throw new \LengthException('Argument value ' . $$color . ' for color ' . $color @@ -143,7 +151,7 @@ class Colors (int)round($HUE), // Hue (int)round(($DELTA / $MAX) * 100), // Saturation (int)round($MAX * 100) // Value/Brightness - ]; + ]; */ } /** @@ -159,7 +167,11 @@ class Colors */ public static function hsb2rgb(float $H, float $S, float $V): array { - // check that H is 0 to 359, 360 = 0 + return Color::hsbToRgb( + Coordinates\HSB::__constructFromArray([$H, $S, $V]) + )->returnAsArray(); + + /* // check that H is 0 to 359, 360 = 0 // and S and V are 0 to 1 if ($H == 360) { $H = 0; @@ -229,7 +241,7 @@ class Colors (int)round($red * 255), (int)round($green * 255), (int)round($blue * 255) - ]; + ]; */ } /** @@ -245,7 +257,11 @@ class Colors */ public static function rgb2hsl(int $red, int $green, int $blue): array { - // check that rgb is from 0 to 255 + return Color::rgbToHsl( + Coordinates\RGB::__constructFromArray([$red, $green, $blue]) + )->returnAsArray(); + + /* // check that rgb is from 0 to 255 foreach (['red', 'green', 'blue'] as $color) { if ($$color < 0 || $$color > 255) { throw new \LengthException('Argument value ' . $$color . ' for color ' . $color @@ -285,7 +301,7 @@ class Colors round($sat * 100, 1), round($lum * 100, 1) ]; - } + } */ } /** @@ -300,7 +316,11 @@ class Colors */ public static function hsl2rgb(float $hue, float $sat, float $lum): array { - if ($hue == 360) { + return Color::hslToRgb( + Coordinates\HSL::__constructFromArray([$hue, $sat, $lum]) + )->returnAsArray(); + + /* if ($hue == 360) { $hue = 0; } if ($hue < 0 || $hue > 359) { @@ -347,7 +367,7 @@ class Colors (int)round(255 * $hueue($hue)), (int)round(255 * $hueue($hue - (1 / 3))) ]; - } + } */ } }