Passed
Push — master ( 8eb6c3...8951af )
by Sebastian
08:56
created

RGBAColor::hasTransparency()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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