Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
Color
100.00% covered (success)
100.00%
54 / 54
100.00% covered (success)
100.00%
17 / 17
42
100.00% covered (success)
100.00%
1 / 1
 rgb
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hsl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cmyk
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 grayscale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hsv
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hsb
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hwb
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lab
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 oklab
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 oklch
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
14
 parseCmyk
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseHwb
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 parseLabFamily
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 parseColorValues
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Color;
16
17use ArgumentCountError;
18use OutOfRangeException;
19use TypeError;
20
21/**
22 * Pop color class
23 *
24 * @category   Pop
25 * @package    Pop\Color
26 * @author     Nick Sagona, III <nick@popphp.org>
27 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    2.0.0
30 */
31class Color
32{
33
34    /**
35     * Instantiate an RGB color object
36     *
37     * @param  int    $r
38     * @param  int    $g
39     * @param  int    $b
40     * @param  ?float $a
41     * @return Color\Rgb
42     */
43    public static function rgb(int $r, int $g, int $b, ?float $a = null): Color\Rgb
44    {
45        return new Color\Rgb($r, $g, $b, $a);
46    }
47
48    /**
49     * Instantiate an HSL color object
50     *
51     * @param  int    $h
52     * @param  int    $s
53     * @param  int    $l
54     * @param  ?float $a
55     * @return Color\Hsl
56     */
57    public static function hsl(int $h, int $s, int $l, ?float $a = null): Color\Hsl
58    {
59        return new Color\Hsl($h, $s, $l, $a);
60    }
61
62    /**
63     * Instantiate a Hex color object
64     *
65     * @param  string $hex
66     * @return Color\Hex
67     */
68    public static function hex(string $hex): Color\Hex
69    {
70        return new Color\Hex($hex);
71    }
72
73    /**
74     * Instantiate a CMYK color object
75     *
76     * @param  int $c
77     * @param  int $m
78     * @param  int $y
79     * @param  int $k
80     * @return Color\Cmyk
81     */
82    public static function cmyk(int $c, int $m, int $y, int $k): Color\Cmyk
83    {
84        return new Color\Cmyk($c, $m, $y, $k);
85    }
86
87    /**
88     * Instantiate a grayscale color object
89     *
90     * @param  int $gray
91     * @return Color\Grayscale
92     */
93    public static function grayscale(int $gray): Color\Grayscale
94    {
95        return new Color\Grayscale($gray);
96    }
97
98    /**
99     * Instantiate an HSV color object
100     *
101     * @param  int    $h
102     * @param  int    $s
103     * @param  int    $v
104     * @param  ?float $a
105     * @return Color\Hsv
106     */
107    public static function hsv(int $h, int $s, int $v, ?float $a = null): Color\Hsv
108    {
109        return new Color\Hsv($h, $s, $v, $a);
110    }
111
112    /**
113     * Instantiate an HSB color object
114     *
115     * @param  int    $h
116     * @param  int    $s
117     * @param  int    $b
118     * @param  ?float $a
119     * @return Color\Hsb
120     */
121    public static function hsb(int $h, int $s, int $b, ?float $a = null): Color\Hsb
122    {
123        return new Color\Hsb($h, $s, $b, $a);
124    }
125
126    /**
127     * Instantiate an HWB color object
128     *
129     * @param  int    $h
130     * @param  int    $w
131     * @param  int    $b
132     * @param  ?float $a
133     * @return Color\Hwb
134     */
135    public static function hwb(int $h, int $w, int $b, ?float $a = null): Color\Hwb
136    {
137        return new Color\Hwb($h, $w, $b, $a);
138    }
139
140    /**
141     * Instantiate a CIE Lab color object
142     *
143     * @param  float|string      $l
144     * @param  float|string      $a
145     * @param  float|string      $b
146     * @param  float|string|null $alpha
147     * @return Color\Lab
148     */
149    public static function lab(float|string $l, float|string $a, float|string $b, float|string|null $alpha = null): Color\Lab
150    {
151        return new Color\Lab($l, $a, $b, $alpha);
152    }
153
154    /**
155     * Instantiate a CIE LCH color object
156     *
157     * @param  float|string      $l
158     * @param  float|string      $c
159     * @param  int|string        $h
160     * @param  float|string|null $alpha
161     * @return Color\Lch
162     */
163    public static function lch(float|string $l, float|string $c, int|string $h, float|string|null $alpha = null): Color\Lch
164    {
165        return new Color\Lch($l, $c, $h, $alpha);
166    }
167
168    /**
169     * Instantiate an Oklab color object
170     *
171     * @param  float|string      $l
172     * @param  float|string      $a
173     * @param  float|string      $b
174     * @param  float|string|null $alpha
175     * @return Color\Oklab
176     */
177    public static function oklab(float|string $l, float|string $a, float|string $b, float|string|null $alpha = null): Color\Oklab
178    {
179        return new Color\Oklab($l, $a, $b, $alpha);
180    }
181
182    /**
183     * Instantiate an Oklch color object
184     *
185     * @param  float|string      $l
186     * @param  float|string      $c
187     * @param  int|string        $h
188     * @param  float|string|null $alpha
189     * @return Color\Oklch
190     */
191    public static function oklch(float|string $l, float|string $c, int|string $h, float|string|null $alpha = null): Color\Oklch
192    {
193        return new Color\Oklch($l, $c, $h, $alpha);
194    }
195
196    /**
197     * Parse color from string
198     *
199     * @param  string $colorString
200     * @throws Color\Exception
201     * @return Color\ColorInterface
202     */
203    public static function parse(string $colorString): Color\ColorInterface
204    {
205        $colorString = strtolower($colorString);
206
207        return match (true) {
208            str_starts_with($colorString, 'rgb') => new Color\Rgb(...self::parseColorValues($colorString)),
209            str_starts_with($colorString, 'hsl') => new Color\Hsl(...self::parseColorValues($colorString)),
210            str_starts_with($colorString, 'hsv') => new Color\Hsv(...self::parseColorValues($colorString)),
211            str_starts_with($colorString, 'hsb') => new Color\Hsb(...self::parseColorValues($colorString)),
212            str_starts_with($colorString, 'hwb') => self::parseHwb($colorString),
213            str_starts_with($colorString, 'oklab') => self::parseLabFamily($colorString, Color\Oklab::class),
214            str_starts_with($colorString, 'oklch') => self::parseLabFamily($colorString, Color\Oklch::class),
215            str_starts_with($colorString, 'lab')   => self::parseLabFamily($colorString, Color\Lab::class),
216            str_starts_with($colorString, 'lch')   => self::parseLabFamily($colorString, Color\Lch::class),
217            str_starts_with($colorString, '#')     => new Color\Hex($colorString),
218            substr_count($colorString, ' ') == 3   => self::parseCmyk($colorString),
219            is_numeric($colorString)               => new Color\Grayscale($colorString),
220            default                                => throw new Color\Exception('Error: The string was not in the correct color format.'),
221        };
222    }
223
224    /**
225     * Parse a space-separated CMYK string, converting an out-of-range OutOfRangeException
226     * into the documented Color\Exception rather than letting it leak from parse()
227     *
228     * @param  string $colorString
229     * @throws Color\Exception
230     * @return Color\Cmyk
231     */
232    private static function parseCmyk(string $colorString): Color\Cmyk
233    {
234        try {
235            return new Color\Cmyk(...self::parseColorValues($colorString, false));
236        } catch (OutOfRangeException) {
237            throw new Color\Exception('Error: The string was not in the correct color format.');
238        }
239    }
240
241    /**
242     * Parse an HWB string, accepting either comma-separated values or native
243     * CSS Color 4 space-separated syntax (with an optional `/ alpha` suffix), converting
244     * an out-of-range or malformed-input exception into the documented Color\Exception
245     * rather than letting it leak from parse()
246     *
247     * @param  string $colorString
248     * @throws Color\Exception
249     * @return Color\Hwb
250     */
251    private static function parseHwb(string $colorString): Color\Hwb
252    {
253        if ((str_contains($colorString, '(')) && (str_contains($colorString, ')'))) {
254            $colorString = substr($colorString, (strpos($colorString, '(') + 1));
255            $colorString = substr($colorString, 0, strpos($colorString, ')'));
256        }
257
258        if (str_contains($colorString, ',')) {
259            $params = array_map('trim', explode(',', $colorString));
260        } else {
261            $colorString = str_replace('/', ' ', $colorString);
262            $params = array_values(array_filter(array_map('trim', explode(' ', $colorString)), fn($v) => $v !== ''));
263        }
264
265        try {
266            return new Color\Hwb(...$params);
267        } catch (OutOfRangeException|ArgumentCountError|TypeError) {
268            throw new Color\Exception('Error: The string was not in the correct color format.');
269        }
270    }
271
272    /**
273     * Parse a Lab-family (Lab/Lch/Oklab/Oklch) string, accepting either comma-separated values
274     * or native CSS Color 4 space-separated syntax (with an optional `/ alpha` suffix), converting
275     * an out-of-range or malformed-input exception into the documented Color\Exception rather than
276     * letting it leak from parse()
277     *
278     * @param  string $colorString
279     * @param  string $class
280     * @throws Color\Exception
281     * @return Color\Lab|Color\Lch|Color\Oklab|Color\Oklch
282     */
283    private static function parseLabFamily(string $colorString, string $class): Color\Lab|Color\Lch|Color\Oklab|Color\Oklch
284    {
285        if ((str_contains($colorString, '(')) && (str_contains($colorString, ')'))) {
286            $colorString = substr($colorString, (strpos($colorString, '(') + 1));
287            $colorString = substr($colorString, 0, strpos($colorString, ')'));
288        }
289
290        if (str_contains($colorString, ',')) {
291            $params = array_map('trim', explode(',', $colorString));
292        } else {
293            $colorString = str_replace('/', ' ', $colorString);
294            $params = array_values(array_filter(array_map('trim', explode(' ', $colorString)), fn($v) => $v !== ''));
295        }
296
297        try {
298            return new $class(...$params);
299        } catch (OutOfRangeException|ArgumentCountError|TypeError) {
300            throw new Color\Exception('Error: The string was not in the correct color format.');
301        }
302    }
303
304    /**
305     * Parse color values from string
306     *
307     * @param  string $colorString
308     * @param  bool   $comma
309     * @return array
310     */
311    public static function parseColorValues(string $colorString, $comma = true): array
312    {
313        if ((str_contains($colorString, '(')) && (str_contains($colorString, ')'))) {
314            $colorString = substr($colorString, (strpos($colorString, '(') + 1));
315            $colorString = substr($colorString, 0, strpos($colorString, ')'));
316        }
317
318        $values = ($comma) ? explode(',' , $colorString) : explode(' ', $colorString);
319        return array_map('trim', $values);
320    }
321
322}