Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
68 / 68
100.00% covered (success)
100.00%
16 / 16
CRAP
100.00% covered (success)
100.00%
1 / 1
Hsv
100.00% covered (success)
100.00%
68 / 68
100.00% covered (success)
100.00%
16 / 16
40
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
 setV
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
 getV
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%
19 / 19
100.00% covered (success)
100.00%
1 / 1
8
 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%
3 / 3
100.00% covered (success)
100.00%
1 / 1
6
 __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 HSV 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 Hsv 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     * V value
46     * @var int
47     */
48    protected int $v = 0;
49
50    /**
51     * Alpha value
52     * @var ?float
53     */
54    protected ?float $a = null;
55
56    /**
57     * Constructor
58     *
59     * Instantiate the HSV color object
60     *
61     * @param int|string        $h
62     * @param int|string        $s
63     * @param int|string        $v
64     * @param float|string|null $a
65     */
66    public function __construct(int|string $h, int|string $s, int|string $v, float|string|null $a = null)
67    {
68        $this->setH($h);
69        $this->setS($s);
70        $this->setV($v);
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 V value
112     *
113     * @param  int|string $v
114     * @throws OutOfRangeException
115     * @return self
116     */
117    public function setV(int|string $v): self
118    {
119        $v = (int)$v;
120        if (($v > 100) || ($v < 0)) {
121            throw new OutOfRangeException('Error: The value of $v must be between 0 and 100.');
122        }
123        $this->v = $v;
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 $a 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 V value
166     *
167     * @return int
168     */
169    public function getV(): int
170    {
171        return $this->v;
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->v / 100;
213
214        if ($s == 0) {
215            $r = $g = $b = $v;
216        } else {
217            $h6 = $this->h / 60;
218            $i  = (int)floor($h6) % 6;
219            $f  = $h6 - floor($h6);
220            $p  = $v * (1 - $s);
221            $q  = $v * (1 - ($s * $f));
222            $t  = $v * (1 - ($s * (1 - $f)));
223
224            [$r, $g, $b] = match ($i) {
225                0 => [$v, $t, $p],
226                1 => [$q, $v, $p],
227                2 => [$p, $v, $t],
228                3 => [$p, $q, $v],
229                4 => [$t, $p, $v],
230                default => [$v, $p, $q],
231            };
232        }
233
234        return new Rgb((int)round($r * 255), (int)round($g * 255), (int)round($b * 255), $this->a);
235    }
236
237    /**
238     * Convert to array
239     *
240     * @param  bool $assoc
241     * @return array
242     */
243    public function toArray(bool $assoc = true): array
244    {
245        $hsv = [];
246
247        if ($assoc) {
248            $hsv['h'] = $this->h;
249            $hsv['s'] = $this->s . '%';
250            $hsv['v'] = $this->v . '%';
251            if ($this->a !== null) {
252                $hsv['a'] = $this->a;
253            }
254        } else {
255            $hsv[] = $this->h;
256            $hsv[] = $this->s . '%';
257            $hsv[] = $this->v . '%';
258            if ($this->a !== null) {
259                $hsv[] = $this->a;
260            }
261        }
262
263        return $hsv;
264    }
265
266    /**
267     * Convert to readable string
268     *
269     * @param  ?string $format
270     * @return string
271     */
272    public function render(?string $format = null): string
273    {
274        return match ($format) {
275            self::COMMA => $this->h . ', ' . $this->s . ', ' . $this->v . (!empty($this->a) ? ', ' . $this->a : ''),
276            self::CSS, self::PERCENT => $this->toRgb()->render($format),
277            default => $this->h . ' ' . $this->s . ' ' . $this->v . (!empty($this->a) ? ' ' . $this->a : ''),
278        };
279    }
280
281    /**
282     * Return CSS-formatted string
283     *
284     * @return string
285     */
286    public function __toString(): string
287    {
288        return $this->render(self::CSS);
289    }
290
291    /**
292     * Get the valid channel names
293     *
294     * @return array
295     */
296    protected function channels(): array
297    {
298        return ['h', 's', 'v', 'a'];
299    }
300
301}