Passed
Push — master ( 689ec5...45f08c )
by Sebastian
05:26
created

RGBAColor::__toString()   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 $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
    /**
69
     * @var string
70
     */
71
    private $name;
72
73
    /**
74
     * @param ColorChannel $red
75
     * @param ColorChannel $green
76
     * @param ColorChannel $blue
77
     * @param ColorChannel $opacity
78
     * @param string $name
79
     */
80
    public function __construct(ColorChannel $red, ColorChannel $green, ColorChannel $blue, ColorChannel $opacity, string $name)
81
    {
82
        $this->color[self::CHANNEL_RED] = $red;
83
        $this->color[self::CHANNEL_GREEN] = $green;
84
        $this->color[self::CHANNEL_BLUE] = $blue;
85
        $this->color[self::CHANNEL_ALPHA] = $opacity;
86
        $this->name = $name;
87
    }
88
89
    /**
90
     * Retrieves the color's name, if any. Colors created from
91
     * presets for example, inherit the name from the preset.
92
     *
93
     * @return string
94
     */
95
    public function getName() : string
96
    {
97
        return $this->name;
98
    }
99
100
    /**
101
     * Whether the alpha channel has a transparency value.
102
     * @return bool
103
     */
104
    public function hasTransparency() : bool
105
    {
106
        return $this->getOpacity()->get8Bit() < 255;
107
    }
108
109
    /**
110
     * Human-readable label of the color. Automatically
111
     * switches between RGBA and RGB depending on whether
112
     * the color has any transparency.
113
     *
114
     * @return string
115
     */
116
    public function getLabel() : string
117
    {
118
        return FormatsConverter::color2readable($this);
119
    }
120
121
    /**
122
     * The amount of red in the color.
123
     *
124
     * @return ColorChannel
125
     */
126
    public function getRed() : ColorChannel
127
    {
128
        return $this->color[self::CHANNEL_RED];
129
    }
130
131
    /**
132
     * The amount of green in the color.
133
     *
134
     * @return ColorChannel
135
     */
136
    public function getGreen() : ColorChannel
137
    {
138
        return $this->color[self::CHANNEL_GREEN];
139
    }
140
141
    /**
142
     * The amount of blue in the color.
143
     *
144
     * @return ColorChannel
145
     */
146
    public function getBlue() : ColorChannel
147
    {
148
        return $this->color[self::CHANNEL_BLUE];
149
    }
150
151
    /**
152
     * The opacity of the color (smaller value = transparent, higher value = opaque).
153
     *
154
     * @return ColorChannel
155
     */
156
    public function getOpacity() : ColorChannel
157
    {
158
        return $this->color[self::CHANNEL_ALPHA];
159
    }
160
161
    /**
162
     * Retrieves the current transparency value as a percentage.
163
     * 100 = fully transparent, 0 = fully opaque
164
     *
165
     * @return ColorChannel
166
     * @throws ColorException
167
     */
168
    public function getTransparency() : ColorChannel
169
    {
170
        return $this->color[self::CHANNEL_ALPHA]->invert();
171
    }
172
173
    /**
174
     * @param string $name
175
     * @return ColorChannel
176
     *
177
     * @throws ColorException
178
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
179
     */
180
    public function getColor(string $name) : ColorChannel
181
    {
182
        $this->requireValidComponent($name);
183
184
        return $this->color[$name];
185
    }
186
187
    // region: Converting
188
189
    /**
190
     * Converts the color to a HEX color value. This is either
191
     * a RRGGBB or RRGGBBAA string, depending on whether there
192
     * is an alpha channel value.
193
     *
194
     * @return string
195
     */
196
    public function toHEX() : string
197
    {
198
        return FormatsConverter::color2HEX($this);
199
    }
200
201
    public function toCSS() : string
202
    {
203
        return FormatsConverter::color2CSS($this);
204
    }
205
206
    /**
207
     * Converts the color to a color array.
208
     *
209
     * @return ArrayConverter
210
     */
211
    public function toArray() : ArrayConverter
212
    {
213
        return FormatsConverter::color2array($this);
214
    }
215
216
    // endregion
217
218
    // region: Setting color values
219
220
    /**
221
     * Returns a new instance with the modified color channel,
222
     * keeping all other color values.
223
     *
224
     * @param ColorChannel $red
225
     * @return RGBAColor
226
     */
227
    public function setRed(ColorChannel $red) : RGBAColor
228
    {
229
        return ColorFactory::create(
230
            $red,
231
            $this->getGreen(),
232
            $this->getBlue(),
233
            $this->getOpacity()
234
        );
235
    }
236
237
    /**
238
     * Returns a new instance with the modified color channel,
239
     * keeping all other color values.
240
     *
241
     * @param ColorChannel $green
242
     * @return RGBAColor
243
     */
244
    public function setGreen(ColorChannel $green) : RGBAColor
245
    {
246
        return ColorFactory::create(
247
            $this->getRed(),
248
            $green,
249
            $this->getBlue(),
250
            $this->getOpacity()
251
        );
252
    }
253
254
    /**
255
     * Returns a new instance with the modified color channel,
256
     * keeping all other color values.
257
     *
258
     * @param ColorChannel $blue
259
     * @return RGBAColor
260
     */
261
    public function setBlue(ColorChannel $blue) : RGBAColor
262
    {
263
        return ColorFactory::create(
264
            $this->getRed(),
265
            $this->getGreen(),
266
            $blue,
267
            $this->getOpacity()
268
        );
269
    }
270
271
    /**
272
     * Returns a new instance with the modified color channel,
273
     * keeping all other color values.
274
     *
275
     * @param ColorChannel $opacity
276
     * @return RGBAColor
277
     */
278
    public function setOpacity(ColorChannel $opacity) : RGBAColor
279
    {
280
        return ColorFactory::create(
281
            $this->getRed(),
282
            $this->getGreen(),
283
            $this->getBlue(),
284
            $opacity
285
        );
286
    }
287
288
    /**
289
     * Sets the transparency of the color, which is an alias
290
     * for the opacity, but inverted. Returns a new color
291
     * instance with the modified value.
292
     *
293
     * @param ColorChannel $transparency
294
     * @return RGBAColor
295
     */
296
    public function setTransparency(ColorChannel $transparency) : RGBAColor
297
    {
298
        return $this->setOpacity($transparency->invert());
299
    }
300
301
    /**
302
     * Sets the color, and returns a new RGBAColor instance
303
     * with the target color modified.
304
     *
305
     * @param string $name
306
     * @param ColorChannel $value
307
     * @return RGBAColor
308
     *
309
     * @throws ColorException
310
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
311
     */
312
    public function setColor(string $name, ColorChannel $value) : RGBAColor
313
    {
314
        $this->requireValidComponent($name);
315
316
        $channels = array(
317
            self::CHANNEL_RED => $this->getRed(),
318
            self::CHANNEL_GREEN => $this->getGreen(),
319
            self::CHANNEL_BLUE => $this->getBlue(),
320
            self::CHANNEL_ALPHA => $this->getOpacity()
321
        );
322
323
        $channels[$name] = $value;
324
325
        return ColorFactory::create(
326
            $channels[self::CHANNEL_RED],
327
            $channels[self::CHANNEL_GREEN],
328
            $channels[self::CHANNEL_BLUE],
329
            $channels[self::CHANNEL_ALPHA]
330
        );
331
    }
332
333
    // endregion
334
335
    /**
336
     * @param string $name
337
     * @throws ColorException
338
     * @see RGBAColor::ERROR_INVALID_COLOR_COMPONENT
339
     */
340
    private function requireValidComponent(string $name) : void
341
    {
342
        if(in_array($name, self::COLOR_COMPONENTS))
343
        {
344
            return;
345
        }
346
347
        throw new ColorException(
348
            'Invalid color component.',
349
            sprintf(
350
                'The color component [%s] is not a valid color component. Valid components are: [%s].',
351
                $name,
352
                implode(', ', self::COLOR_COMPONENTS)
353
            ),
354
            self::ERROR_INVALID_COLOR_COMPONENT
355
        );
356
    }
357
358
    /**
359
     * Whether this color is the same as the specified color.
360
     *
361
     * NOTE: Only compares the RGB color values, ignoring the
362
     * transparency. To also compare transparency, use `matchesAlpha()`.
363
     *
364
     * @param RGBAColor $targetColor
365
     * @return bool
366
     * @throws ColorException
367
     */
368
    public function matches(RGBAColor $targetColor) : bool
369
    {
370
        return ColorComparator::colorsMatch($this, $targetColor);
371
    }
372
373
    /**
374
     * Whether this color is the same as the specified color,
375
     * including the alpha channel.
376
     *
377
     * @param RGBAColor $targetColor
378
     * @return bool
379
     * @throws ColorException
380
     */
381
    public function matchesAlpha(RGBAColor $targetColor) : bool
382
    {
383
        return ColorComparator::colorsMatchAlpha($this, $targetColor);
384
    }
385
386
    public function __toString()
387
    {
388
        return $this->getLabel();
389
    }
390
391
    // region: ArrayAccess interface methods
392
393
    public function offsetExists($offset)
394
    {
395
        $key = (string)$offset;
396
397
        return isset($this->color[$key]);
398
    }
399
400
    public function offsetGet($offset)
401
    {
402
        $key = (string)$offset;
403
404
        return $this->color[$key] ?? 0;
405
    }
406
407
    public function offsetSet($offset, $value)
408
    {
409
        $this->setColor((string)$offset, $value);
410
    }
411
412
    public function offsetUnset($offset)
413
    {
414
415
    }
416
417
    // endregion
418
}
419