Passed
Push — master ( 5153e5...af085a )
by Sebastian
05:42 queued 30s
created

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