Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
16 / 16
CRAP
100.00% covered (success)
100.00%
1 / 1
Hwb
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
16 / 16
42
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
 setW
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setB
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
 getW
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getB
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%
23 / 23
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%
5 / 5
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 HWB 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 Hwb extends AbstractColor
30{
31
32    /**
33     * H value
34     * @var int
35     */
36    protected int $h = 0;
37
38    /**
39     * W (whiteness) value
40     * @var int
41     */
42    protected int $w = 0;
43
44    /**
45     * B (blackness) value
46     * @var int
47     */
48    protected int $b = 0;
49
50    /**
51     * Alpha value
52     * @var ?float
53     */
54    protected ?float $a = null;
55
56    /**
57     * Constructor
58     *
59     * Instantiate the CSS HWB color object
60     *
61     * @param int|string        $h
62     * @param int|string        $w
63     * @param int|string        $b
64     * @param float|string|null $a
65     */
66    public function __construct(int|string $h, int|string $w, int|string $b, float|string|null $a = null)
67    {
68        $this->setH($h);
69        $this->setW($w);
70        $this->setB($b);
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 W value
95     *
96     * @param  int|string $w
97     * @throws OutOfRangeException
98     * @return self
99     */
100    public function setW(int|string $w): self
101    {
102        $w = (int)$w;
103        if (($w > 100) || ($w < 0)) {
104            throw new OutOfRangeException('Error: The value of $w must be between 0 and 100.');
105        }
106        $this->w = $w;
107        return $this;
108    }
109
110    /**
111     * Set B value
112     *
113     * @param  int|string $b
114     * @throws OutOfRangeException
115     * @return self
116     */
117    public function setB(int|string $b): self
118    {
119        $b = (int)$b;
120        if (($b > 100) || ($b < 0)) {
121            throw new OutOfRangeException('Error: The value of $b must be between 0 and 100.');
122        }
123        $this->b = $b;
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 W value
156     *
157     * @return int
158     */
159    public function getW(): int
160    {
161        return $this->w;
162    }
163
164    /**
165     * Get B value
166     *
167     * @return int
168     */
169    public function getB(): int
170    {
171        return $this->b;
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        $w  = $this->w / 100;
212        $bk = $this->b / 100;
213
214        if (($w + $bk) >= 1) {
215            $gray = $w / ($w + $bk);
216            $r = $g = $b = $gray;
217        } else {
218            $h6 = $this->h / 60;
219            $i  = (int)floor($h6) % 6;
220            $f  = $h6 - floor($h6);
221            $p  = 0.0;
222            $q  = 1 - $f;
223            $t  = $f;
224
225            [$r, $g, $b] = match ($i) {
226                0 => [1, $t, $p],
227                1 => [$q, 1, $p],
228                2 => [$p, 1, $t],
229                3 => [$p, $q, 1],
230                4 => [$t, $p, 1],
231                default => [1, $p, $q],
232            };
233
234            $r = $r * (1 - $w - $bk) + $w;
235            $g = $g * (1 - $w - $bk) + $w;
236            $b = $b * (1 - $w - $bk) + $w;
237        }
238
239        return new Rgb((int)round($r * 255), (int)round($g * 255), (int)round($b * 255), $this->a);
240    }
241
242    /**
243     * Convert to array
244     *
245     * @param  bool $assoc
246     * @return array
247     */
248    public function toArray(bool $assoc = true): array
249    {
250        $hwb = [];
251
252        if ($assoc) {
253            $hwb['h'] = $this->h;
254            $hwb['w'] = $this->w . '%';
255            $hwb['b'] = $this->b . '%';
256            if ($this->a !== null) {
257                $hwb['a'] = $this->a;
258            }
259        } else {
260            $hwb[] = $this->h;
261            $hwb[] = $this->w . '%';
262            $hwb[] = $this->b . '%';
263            if ($this->a !== null) {
264                $hwb[] = $this->a;
265            }
266        }
267
268        return $hwb;
269    }
270
271    /**
272     * Convert to readable string
273     *
274     * @param  ?string $format
275     * @return string
276     */
277    public function render(?string $format = null): string
278    {
279        return match ($format) {
280            self::COMMA => $this->h . ', ' . $this->w . ', ' . $this->b . (!empty($this->a) ? ', ' . $this->a : ''),
281            self::CSS => 'hwb(' . $this->h . ' ' . $this->w . '% ' . $this->b . '%' .
282                (($this->a !== null) ? ' / ' . $this->a : '') . ')',
283            self::PERCENT => $this->toRgb()->render(self::PERCENT),
284            default => $this->h . ' ' . $this->w . ' ' . $this->b . (!empty($this->a) ? ' ' . $this->a : ''),
285        };
286    }
287
288    /**
289     * Return CSS-formatted string
290     *
291     * @return string
292     */
293    public function __toString(): string
294    {
295        return $this->render(self::CSS);
296    }
297
298    /**
299     * Get the valid channel names
300     *
301     * @return array
302     */
303    protected function channels(): array
304    {
305        return ['h', 'w', 'b', 'a'];
306    }
307
308}