Test Failed
Push — master ( 9d3a5c...c5f273 )
by Sebastian
08:26
created

RGBAColor::toHSV()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 13
rs 9.9666
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * File containing the class {@see RGBAColor}.
4
 *
5
 * @package Application Utils
6
 * @subpackage RGBAColor
7
 * @see RGBAColor
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
use AppUtils\RGBAColor\ArrayConverter;
15
use AppUtils\RGBAColor\ColorChannel;
16
use AppUtils\RGBAColor\ColorChannel\BrightnessChannel;
17
use AppUtils\RGBAColor\ColorComparator;
18
use AppUtils\RGBAColor\ColorException;
19
use AppUtils\RGBAColor\ColorFactory;
20
use AppUtils\RGBAColor\FormatsConverter;
21
use ArrayAccess;
22
23
/**
24
 * Container for RGB color information, with optional alpha channel.
25
 * Allows treating the objects as an array, as a drop-in replacement
26
 * for the GD color functions.
27
 *
28
 * It can be cast to string, which returns the human-readable version
29
 * of the color as returned by {@see RGBAColor::getLabel()}.
30
 *
31
 * To create an instance, the easiest way is to use the {@see ColorFactory},
32
 * which offers different data models to get the color information
33
 * from.
34
 *
35
 * @package Application Utils
36
 * @subpackage RGBAColor
37
 * @author Sebastian Mordziol <[email protected]>
38
 * @implements ArrayAccess<string,ColorChannel>
39
 */
40
class RGBAColor implements ArrayAccess, Interface_Stringable
41
{
42
    public const ERROR_UNKNOWN_COLOR_SUBJECT = 93401;
43
    public const ERROR_INVALID_COLOR_COMPONENT = 93402;
44
    public const ERROR_INVALID_PERCENTAGE_VALUE = 93503;
45
    public const ERROR_INVALID_HEX_LENGTH = 93505;
46
    public const ERROR_UNKNOWN_COLOR_PRESET = 93507;
47
    public const ERROR_INVALID_COLOR_ARRAY = 93508;
48
49
    public const CHANNEL_RED = 'red';
50
    public const CHANNEL_GREEN = 'green';
51
    public const CHANNEL_BLUE = 'blue';
52
    public const CHANNEL_ALPHA = 'alpha';
53
54
    /**
55
     * @var array<string,ColorChannel>
56
     */
57
    private array $color;
58
59
    /**
60
     * @var string[]
61
     */
62
    public const COLOR_COMPONENTS = array(
63
        self::CHANNEL_RED,
64
        self::CHANNEL_GREEN,
65
        self::CHANNEL_BLUE,
66
        self::CHANNEL_ALPHA
67
    );
68
69
    private string $name;
70
71
    /**
72
     * @param ColorChannel $red
73
     * @param ColorChannel $green
74
     * @param ColorChannel $blue
75
     * @param ColorChannel $alpha
76
     * @param string $name
77
     */
78
    public function __construct(ColorChannel $red, ColorChannel $green, ColorChannel $blue, ColorChannel $alpha, string $name)
79
    {
80
        $this->color[self::CHANNEL_RED] = $red;
81
        $this->color[self::CHANNEL_GREEN] = $green;
82
        $this->color[self::CHANNEL_BLUE] = $blue;
83
        $this->color[self::CHANNEL_ALPHA] = $alpha;
84
        $this->name = $name;
85
    }
86
87
    /**
88
     * Retrieves the color's name, if any. Colors created from
89
     * presets for example, inherit the name from the preset.
90
     *
91
     * @return string
92
     */
93
    public function getName() : string
94
    {
95
        return $this->name;
96
    }
97
98
    /**
99
     * Human-readable label of the color. Automatically
100
     * switches between RGBA and RGB depending on whether
101
     * the color has any transparency.
102
     *
103
     * @return string
104
     */
105
    public function getLabel() : string
106
    {
107
        return FormatsConverter::color2readable($this);
108
    }
109
110
    // region: Get components
111
112
    /**
113
     * Gets the color's luminance equivalent.
114
     *
115
     * @return int Integer, from 0 to 255 (0=black, 255=white)
116
     * @see self::getBrightness()
117
     */
118
    public function getLuma() : int
119
    {
120
        return $this->toHSV()->getBrightness()->get8Bit();
121
    }
122
123
    /**
124
     * Retrieves the brightness of the color, in percent.
125
     * @return BrightnessChannel
126
     */
127
    public function getBrightness() : BrightnessChannel
128
    {
129
        return $this->toHSV()->getBrightness();
130
    }
131
132
    /**
133
     * Whether the alpha channel has a transparency value.
134
     * @return bool
135
     */
136
    public function hasTransparency() : bool
137
    {
138
        return $this->getAlpha()->get8Bit() > 0;
139
    }
140
141
    /**
142
     * The amount of red in the color.
143
     *
144
     * @return ColorChannel
145
     */
146
    public function getRed() : ColorChannel
147
    {
148
        return $this->color[self::CHANNEL_RED];
149
    }
150
151
    /**
152
     * The amount of green in the color.
153
     *
154
     * @return ColorChannel
155
     */
156
    public function getGreen() : ColorChannel
157
    {
158
        return $this->color[self::CHANNEL_GREEN];
159
    }
160
161
    /**
162
     * The amount of blue in the color.
163
     *
164
     * @return ColorChannel
165
     */
166
    public function getBlue() : ColorChannel
167
    {
168
        return $this->color[self::CHANNEL_BLUE];
169
    }
170
171
    /**
172
     * The opacity of the color (smaller value = opaque, higher value = transparent).
173
     *
174
     * @return ColorChannel
175
     */
176
    public function getAlpha() : ColorChannel
177
    {
178
        return $this->color[self::CHANNEL_ALPHA];
179
    }
180
181
    /**
182
     * Retrieves the current transparency value as a percentage.
183
     * 100 = fully transparent, 0 = fully opaque
184
     *
185
     * @return ColorChannel
186
     * @throws ColorException
187
     */
188
    public function getTransparency() : ColorChannel
189
    {
190
        return $this->color[self::CHANNEL_ALPHA]->invert();
191
    }
192
193
    /**
194
     * @param string $name
195
     * @return ColorChannel
196
     *
197
     * @throws ColorException
198
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
199
     */
200
    public function getColor(string $name) : ColorChannel
201
    {
202
        $this->requireValidComponent($name);
203
204
        return $this->color[$name];
205
    }
206
207
    // endregion
208
209
    // region: Operations
210
211
    /**
212
     * @param float $percent -100 to 100
213
     * @return RGBAColor (New instance)
214
     */
215
    public function adjustBrightness(float $percent) : RGBAColor
216
    {
217
        return $this
218
            ->toHSV()
219
            ->adjustBrightness($percent)
220
            ->toRGB();
221
    }
222
223
    // endregion
224
225
    // region: Converting
226
227
    /**
228
     * Converts the color to a HEX color value. This is either
229
     * a RRGGBB or RRGGBBAA string, depending on whether there
230
     * is an alpha channel value.
231
     *
232
     * @return string
233
     */
234
    public function toHEX() : string
235
    {
236
        return FormatsConverter::color2HEX($this);
237
    }
238
239
    public function toCSS() : string
240
    {
241
        return FormatsConverter::color2CSS($this);
242
    }
243
244
    /**
245
     * Converts the color to a Hue/Saturation/Brightness value.
246
     * Practical for adjusting the values, which is difficult
247
     * with pure RGB values.
248
     *
249
     * @return HSVColor
250
     */
251
    public function toHSV() : HSVColor
252
    {
253
        $hsv = FormatsConverter::rgb2hsv(
254
            $this->getRed()->get8Bit(),
255
            $this->getGreen()->get8Bit(),
256
            $this->getBlue()->get8Bit()
257
        );
258
259
        return new HSVColor(
260
            ColorChannel::hue($hsv['hue']),
261
            ColorChannel::saturation($hsv['saturation']),
262
            ColorChannel::brightness($hsv['brightness']),
263
            $this->getAlpha()
264
        );
265
    }
266
267
    /**
268
     * Converts the color to a color array.
269
     *
270
     * @return ArrayConverter
271
     */
272
    public function toArray() : ArrayConverter
273
    {
274
        return FormatsConverter::color2array($this);
275
    }
276
277
    // endregion
278
279
    // region: Setting color values
280
281
    /**
282
     * Returns a new instance with the modified color channel,
283
     * keeping all other color values.
284
     *
285
     * @param ColorChannel $red
286
     * @return RGBAColor
287
     */
288
    public function setRed(ColorChannel $red) : RGBAColor
289
    {
290
        return ColorFactory::create(
291
            $red,
292
            $this->getGreen(),
293
            $this->getBlue(),
294
            $this->getAlpha()
295
        );
296
    }
297
298
    /**
299
     * Returns a new instance with the modified color channel,
300
     * keeping all other color values.
301
     *
302
     * @param ColorChannel $green
303
     * @return RGBAColor
304
     */
305
    public function setGreen(ColorChannel $green) : RGBAColor
306
    {
307
        return ColorFactory::create(
308
            $this->getRed(),
309
            $green,
310
            $this->getBlue(),
311
            $this->getAlpha()
312
        );
313
    }
314
315
    /**
316
     * Returns a new instance with the modified color channel,
317
     * keeping all other color values.
318
     *
319
     * @param ColorChannel $blue
320
     * @return RGBAColor
321
     */
322
    public function setBlue(ColorChannel $blue) : RGBAColor
323
    {
324
        return ColorFactory::create(
325
            $this->getRed(),
326
            $this->getGreen(),
327
            $blue,
328
            $this->getAlpha()
329
        );
330
    }
331
332
    /**
333
     * Returns a new instance with the modified color channel,
334
     * keeping all other color values.
335
     *
336
     * @param ColorChannel $alpha
337
     * @return RGBAColor
338
     */
339
    public function setAlpha(ColorChannel $alpha) : RGBAColor
340
    {
341
        return ColorFactory::create(
342
            $this->getRed(),
343
            $this->getGreen(),
344
            $this->getBlue(),
345
            $alpha
346
        );
347
    }
348
349
    /**
350
     * Sets the transparency of the color, which is an alias
351
     * for the alpha, but inverted. Returns a new color
352
     * instance with the modified value.
353
     *
354
     * @param ColorChannel $transparency
355
     * @return RGBAColor
356
     */
357
    public function setTransparency(ColorChannel $transparency) : RGBAColor
358
    {
359
        return $this->setAlpha($transparency->invert());
360
    }
361
362
    /**
363
     * Changes the color's brightness to the specified level.
364
     *
365
     * @param int|float $brightness 0 to 100
366
     * @return RGBAColor
367
     */
368
    public function setBrightness($brightness) : RGBAColor
369
    {
370
        return $this
371
            ->toHSV()
372
            ->setBrightness($brightness)
373
            ->toRGB();
374
    }
375
376
    /**
377
     * Sets the color, and returns a new RGBAColor instance
378
     * with the target color modified.
379
     *
380
     * @param string $name
381
     * @param ColorChannel $value
382
     * @return RGBAColor
383
     *
384
     * @throws ColorException
385
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
386
     */
387
    public function setColor(string $name, ColorChannel $value) : RGBAColor
388
    {
389
        $this->requireValidComponent($name);
390
391
        $channels = array(
392
            self::CHANNEL_RED => $this->getRed(),
393
            self::CHANNEL_GREEN => $this->getGreen(),
394
            self::CHANNEL_BLUE => $this->getBlue(),
395
            self::CHANNEL_ALPHA => $this->getAlpha()
396
        );
397
398
        $channels[$name] = $value;
399
400
        return ColorFactory::create(
401
            $channels[self::CHANNEL_RED],
402
            $channels[self::CHANNEL_GREEN],
403
            $channels[self::CHANNEL_BLUE],
404
            $channels[self::CHANNEL_ALPHA]
405
        );
406
    }
407
408
    // endregion
409
410
    /**
411
     * @param string $name
412
     * @throws ColorException
413
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
414
     */
415
    private function requireValidComponent(string $name) : void
416
    {
417
        if(in_array($name, self::COLOR_COMPONENTS))
418
        {
419
            return;
420
        }
421
422
        throw new ColorException(
423
            'Invalid color component.',
424
            sprintf(
425
                'The color component [%s] is not a valid color component. Valid components are: [%s].',
426
                $name,
427
                implode(', ', self::COLOR_COMPONENTS)
428
            ),
429
            self::ERROR_INVALID_COLOR_COMPONENT
430
        );
431
    }
432
433
    /**
434
     * Whether this color is the same as the specified color.
435
     *
436
     * NOTE: Only compares the RGB color values, ignoring the
437
     * transparency. To also compare transparency, use `matchesAlpha()`.
438
     *
439
     * @param RGBAColor $targetColor
440
     * @return bool
441
     * @throws ColorException
442
     */
443
    public function matches(RGBAColor $targetColor) : bool
444
    {
445
        return ColorComparator::colorsMatch($this, $targetColor);
446
    }
447
448
    /**
449
     * Whether this color is the same as the specified color,
450
     * including the alpha channel.
451
     *
452
     * @param RGBAColor $targetColor
453
     * @return bool
454
     * @throws ColorException
455
     */
456
    public function matchesAlpha(RGBAColor $targetColor) : bool
457
    {
458
        return ColorComparator::colorsMatchAlpha($this, $targetColor);
459
    }
460
461
    public function __toString()
462
    {
463
        return $this->getLabel();
464
    }
465
466
    // region: ArrayAccess interface methods
467
468
    public function offsetExists($offset)
469
    {
470
        $key = (string)$offset;
471
472
        return isset($this->color[$key]);
473
    }
474
475
    public function offsetGet($offset)
476
    {
477
        $key = (string)$offset;
478
479
        return $this->color[$key] ?? 0;
480
    }
481
482
    public function offsetSet($offset, $value)
483
    {
484
        $this->setColor((string)$offset, $value);
485
    }
486
487
    public function offsetUnset($offset)
488
    {
489
490
    }
491
492
    // endregion
493
}
494