Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
77 / 77
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
1 / 1
Hsl
100.00% covered (success)
100.00%
77 / 77
100.00% covered (success)
100.00%
17 / 17
44
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 setH
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setS
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setL
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setA
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getH
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getL
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getA
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasA
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAlpha
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toRgb
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
9
 toHex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 render
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
8
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 channels
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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\Color;
16
17use OutOfRangeException;
18
19/**
20 * Pop Color HSL color class
21 *
22 * @category   Pop
23 * @package    Pop\Color
24 * @author     Nick Sagona, III <nick@popphp.org>
25 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    2.0.0
28 */
29class Hsl extends AbstractColor
30{
31
32    /**
33     * H value
34     * @var int
35     */
36    protected int $h = 0;
37
38    /**
39     * S value
40     * @var int
41     */
42    protected int $s = 0;
43
44    /**
45     * L value
46     * @var int
47     */
48    protected int $l = 0;
49
50    /**
51     * Alpha value
52     * @var ?float
53     */
54    protected ?float $a = null;
55
56    /**
57     * Constructor
58     *
59     * Instantiate the CSS HSL color object
60     *
61     * @param int|string        $h
62     * @param int|string        $s
63     * @param int|string        $l
64     * @param float|string|null $a
65     */
66    public function __construct(int|string $h, int|string $s, int|string $l, float|string|null $a = null)
67    {
68        $this->setH($h);
69        $this->setS($s);
70        $this->setL($l);
71        if ($a !== null) {
72            $this->setA($a);
73        }
74    }
75
76    /**
77     * Set H value
78     *
79     * @param  int|string $h
80     * @throws OutOfRangeException
81     * @return self
82     */
83    public function setH(int|string $h): self
84    {
85        $h = (int)$h;
86        if (($h > 360) || ($h < 0)) {
87            throw new OutOfRangeException('Error: The value of $h must be between 0 and 360.');
88        }
89        $this->h = $h;
90        return $this;
91    }
92
93    /**
94     * Set S value
95     *
96     * @param  int|string $s
97     * @throws OutOfRangeException
98     * @return self
99     */
100    public function setS(int|string $s): self
101    {
102        $s = (int)$s;
103        if (($s > 100) || ($s < 0)) {
104            throw new OutOfRangeException('Error: The value of $s must be between 0 and 100.');
105        }
106        $this->s = $s;
107        return $this;
108    }
109
110    /**
111     * Set L value
112     *
113     * @param  int|string $l
114     * @throws OutOfRangeException
115     * @return self
116     */
117    public function setL(int|string $l): self
118    {
119        $l = (int)$l;
120        if (($l > 100) || ($l < 0)) {
121            throw new OutOfRangeException('Error: The value of $l must be between 0 and 100.');
122        }
123        $this->l = $l;
124        return $this;
125    }
126
127    /**
128     * Set A value
129     *
130     * @param  float|string $a
131     * @throws OutOfRangeException
132     * @return self
133     */
134    public function setA(float|string $a): self
135    {
136        $a = (float)$a;
137        if (($a > 1) || ($a < 0)) {
138            throw new OutOfRangeException('Error: The value of $l must be between 0 and 1.');
139        }
140        $this->a = $a;
141        return $this;
142    }
143
144    /**
145     * Get H value
146     *
147     * @return int
148     */
149    public function getH(): int
150    {
151        return $this->h;
152    }
153
154    /**
155     * Get S value
156     *
157     * @return int
158     */
159    public function getS(): int
160    {
161        return $this->s;
162    }
163
164    /**
165     * Get L value
166     *
167     * @return int
168     */
169    public function getL(): int
170    {
171        return $this->l;
172    }
173
174    /**
175     * Get A value
176     *
177     * @return float|null
178     */
179    public function getA(): float|null
180    {
181        return $this->a;
182    }
183
184    /**
185     * Determine if the color object has an alpha value
186     *
187     * @return bool
188     */
189    public function hasA(): bool
190    {
191        return ($this->a !== null);
192    }
193
194    /**
195     * Determine if the color object has an alpha value (alias)
196     *
197     * @return bool
198     */
199    public function hasAlpha(): bool
200    {
201        return ($this->a !== null);
202    }
203
204    /**
205     * Convert to RGB
206     *
207     * @return Rgb
208     */
209    public function toRgb(): Rgb
210    {
211        $s = $this->s / 100;
212        $v = $this->l / 100;
213
214        if ($this->s == 0) {
215            $r = (int)round($v * 255);
216            $g = (int)round($v * 255);
217            $b = (int)round($v * 255);
218        } else {
219            $h = $this->h / 360;
220            $h = $h * 6;
221            if ($h == 6) {
222                $h = 0;
223            }
224
225            $i  = (int)floor($h);
226            $v1 = $v * (1 - $s);
227            $v2 = $v * (1 - ($s * ($h - $i)));
228            $v3 = $v * (1 - ($s * (1 - ($h - $i))));
229
230            [$r, $g, $b] = match ($i) {
231                0 => [$v, $v3, $v1],
232                1 => [$v2, $v, $v1],
233                2 => [$v1, $v, $v3],
234                3 => [$v1, $v, $v3],
235                4 => [$v3, $v1, $v],
236                default => [$v, $v1, $v2],
237            };
238
239            $r = (int)round($r * 255);
240            $g = (int)round($g * 255);
241            $b = (int)round($b * 255);
242        }
243
244        return new Rgb($r, $g, $b, $this->a);
245    }
246
247    /**
248     * Convert to hex
249     *
250     * @return Hex
251     */
252    public function toHex(): Hex
253    {
254        return $this->toRgb()->toHex();
255    }
256
257    /**
258     * Convert to array
259     *
260     * @param  bool $assoc
261     * @return array
262     */
263    public function toArray(bool $assoc = true): array
264    {
265        $hsl = [];
266
267        if ($assoc) {
268            $hsl['h'] = $this->h;
269            $hsl['s'] = $this->s . '%';
270            $hsl['l'] = $this->l . '%';
271            if ($this->a !== null) {
272                $hsl['a'] = $this->a;
273            }
274        } else {
275            $hsl[] = $this->h;
276            $hsl[] = $this->s . '%';
277            $hsl[] = $this->l . '%';
278            if ($this->a !== null) {
279                $hsl[] = $this->a;
280            }
281        }
282
283        return $hsl;
284    }
285
286    /**
287     * Convert to readable string
288     *
289     * @param  ?string $format
290     * @return string
291     */
292    public function render(?string $format = null): string
293    {
294        return match ($format) {
295            self::COMMA => $this->h . ', ' . $this->s . ', ' . $this->l . (!empty($this->a) ? ', ' . $this->a : ''),
296            self::CSS => (($this->a !== null) ? 'hsla(' : 'hsl(') . implode(', ', $this->toArray()) . ')',
297            self::PERCENT => $this->toRgb()->render($format),
298            default => $this->h . ' ' . $this->s . ' ' . $this->l . (!empty($this->a) ? ' ' . $this->a : ''),
299        };
300    }
301
302    /**
303     * Return CSS-formatted string
304     *
305     * @return string
306     */
307    public function __toString(): string
308    {
309        return $this->render(self::CSS);
310    }
311
312    /**
313     * Get the valid channel names
314     *
315     * @return array
316     */
317    protected function channels(): array
318    {
319        return ['h', 's', 'l', 'a'];
320    }
321
322}