Passed
Pull Request — master (#4478)
by Owen
14:38
created

Color::changeBrightness()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 13
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 2
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Style;
4
5
use PhpOffice\PhpSpreadsheet\Theme;
6
7
class Color extends Supervisor
8
{
9
    const NAMED_COLORS = [
10
        'Black',
11
        'White',
12
        'Red',
13
        'Green',
14
        'Blue',
15
        'Yellow',
16
        'Magenta',
17
        'Cyan',
18
    ];
19
20
    // Colors
21
    const COLOR_BLACK = 'FF000000';
22
    const COLOR_WHITE = 'FFFFFFFF';
23
    const COLOR_RED = 'FFFF0000';
24
    const COLOR_DARKRED = 'FF800000';
25
    const COLOR_BLUE = 'FF0000FF';
26
    const COLOR_DARKBLUE = 'FF000080';
27
    const COLOR_GREEN = 'FF00FF00';
28
    const COLOR_DARKGREEN = 'FF008000';
29
    const COLOR_YELLOW = 'FFFFFF00';
30
    const COLOR_DARKYELLOW = 'FF808000';
31
    const COLOR_MAGENTA = 'FFFF00FF';
32
    const COLOR_CYAN = 'FF00FFFF';
33
34
    const NAMED_COLOR_TRANSLATIONS = [
35
        'Black' => self::COLOR_BLACK,
36
        'White' => self::COLOR_WHITE,
37
        'Red' => self::COLOR_RED,
38
        'Green' => self::COLOR_GREEN,
39
        'Blue' => self::COLOR_BLUE,
40
        'Yellow' => self::COLOR_YELLOW,
41
        'Magenta' => self::COLOR_MAGENTA,
42
        'Cyan' => self::COLOR_CYAN,
43
    ];
44
45
    const VALIDATE_ARGB_SIZE = 8;
46
    const VALIDATE_RGB_SIZE = 6;
47
    const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i';
48
    const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i';
49
50
    private const INDEXED_COLORS = [
51
        1 => 'FF000000', //  System Colour #1 - Black
52
        2 => 'FFFFFFFF', //  System Colour #2 - White
53
        3 => 'FFFF0000', //  System Colour #3 - Red
54
        4 => 'FF00FF00', //  System Colour #4 - Green
55
        5 => 'FF0000FF', //  System Colour #5 - Blue
56
        6 => 'FFFFFF00', //  System Colour #6 - Yellow
57
        7 => 'FFFF00FF', //  System Colour #7- Magenta
58
        8 => 'FF00FFFF', //  System Colour #8- Cyan
59
        9 => 'FF800000', //  Standard Colour #9
60
        10 => 'FF008000', //  Standard Colour #10
61
        11 => 'FF000080', //  Standard Colour #11
62
        12 => 'FF808000', //  Standard Colour #12
63
        13 => 'FF800080', //  Standard Colour #13
64
        14 => 'FF008080', //  Standard Colour #14
65
        15 => 'FFC0C0C0', //  Standard Colour #15
66
        16 => 'FF808080', //  Standard Colour #16
67
        17 => 'FF9999FF', //  Chart Fill Colour #17
68
        18 => 'FF993366', //  Chart Fill Colour #18
69
        19 => 'FFFFFFCC', //  Chart Fill Colour #19
70
        20 => 'FFCCFFFF', //  Chart Fill Colour #20
71
        21 => 'FF660066', //  Chart Fill Colour #21
72
        22 => 'FFFF8080', //  Chart Fill Colour #22
73
        23 => 'FF0066CC', //  Chart Fill Colour #23
74
        24 => 'FFCCCCFF', //  Chart Fill Colour #24
75
        25 => 'FF000080', //  Chart Line Colour #25
76
        26 => 'FFFF00FF', //  Chart Line Colour #26
77
        27 => 'FFFFFF00', //  Chart Line Colour #27
78
        28 => 'FF00FFFF', //  Chart Line Colour #28
79
        29 => 'FF800080', //  Chart Line Colour #29
80
        30 => 'FF800000', //  Chart Line Colour #30
81
        31 => 'FF008080', //  Chart Line Colour #31
82
        32 => 'FF0000FF', //  Chart Line Colour #32
83
        33 => 'FF00CCFF', //  Standard Colour #33
84
        34 => 'FFCCFFFF', //  Standard Colour #34
85
        35 => 'FFCCFFCC', //  Standard Colour #35
86
        36 => 'FFFFFF99', //  Standard Colour #36
87
        37 => 'FF99CCFF', //  Standard Colour #37
88
        38 => 'FFFF99CC', //  Standard Colour #38
89
        39 => 'FFCC99FF', //  Standard Colour #39
90
        40 => 'FFFFCC99', //  Standard Colour #40
91
        41 => 'FF3366FF', //  Standard Colour #41
92
        42 => 'FF33CCCC', //  Standard Colour #42
93
        43 => 'FF99CC00', //  Standard Colour #43
94
        44 => 'FFFFCC00', //  Standard Colour #44
95
        45 => 'FFFF9900', //  Standard Colour #45
96
        46 => 'FFFF6600', //  Standard Colour #46
97
        47 => 'FF666699', //  Standard Colour #47
98
        48 => 'FF969696', //  Standard Colour #48
99
        49 => 'FF003366', //  Standard Colour #49
100
        50 => 'FF339966', //  Standard Colour #50
101
        51 => 'FF003300', //  Standard Colour #51
102
        52 => 'FF333300', //  Standard Colour #52
103
        53 => 'FF993300', //  Standard Colour #53
104
        54 => 'FF993366', //  Standard Colour #54
105
        55 => 'FF333399', //  Standard Colour #55
106
        56 => 'FF333333', //  Standard Colour #56
107
    ];
108
109
    /**
110
     * ARGB - Alpha RGB.
111
     */
112
    protected ?string $argb = null;
113
114
    private bool $hasChanged = false;
115
116
    private int $theme = -1;
117
118
    /**
119
     * Create a new Color.
120
     *
121
     * @param string $colorValue ARGB value for the colour, or named colour
122
     * @param bool $isSupervisor Flag indicating if this is a supervisor or not
123
     *                                    Leave this value at default unless you understand exactly what
124
     *                                        its ramifications are
125 10757
     * @param bool $isConditional Flag indicating if this is a conditional style or not
126
     *                                    Leave this value at default unless you understand exactly what
127
     *                                        its ramifications are
128 10757
     */
129
    public function __construct(string $colorValue = self::COLOR_BLACK, bool $isSupervisor = false, bool $isConditional = false)
130
    {
131 10757
        //    Supervisor?
132 10757
        parent::__construct($isSupervisor);
133
134
        //    Initialise values
135
        if (!$isConditional) {
136
            $this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK;
137
        }
138
    }
139
140 48
    /**
141
     * Get the shared style component for the currently active cell in currently active sheet.
142
     * Only used for style supervisor.
143 48
     */
144
    public function getSharedComponent(): self
145 48
    {
146 48
        /** @var Style $parent */
147 39
        $parent = $this->parent;
148 21
        /** @var Border|Fill $sharedComponent */
149
        $sharedComponent = $parent->getSharedComponent();
150
        if ($sharedComponent instanceof Fill) {
151 38
            if ($this->parentPropertyName === 'endColor') {
152
                return $sharedComponent->getEndColor();
153
            }
154 30
155
            return $sharedComponent->getStartColor();
156
        }
157
158
        return $sharedComponent->getColor();
159
    }
160
161
    /**
162
     * Build style array from subcomponents.
163
     *
164 49
     * @param mixed[] $array
165
     *
166
     * @return mixed[]
167 49
     */
168
    public function getStyleArray(array $array): array
169 49
    {
170
        /** @var Style $parent */
171
        $parent = $this->parent;
172
173
        return $parent->getStyleArray([$this->parentPropertyName => $array]);
174
    }
175
176
    /**
177
     * Apply styles from array.
178
     *
179
     * <code>
180
     * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
181
     * </code>
182
     *
183 181
     * @param array{rgb?: string, argb?: string, theme?: int} $styleArray Array containing style information
184
     *
185 181
     * @return $this
186 1
     */
187
    public function applyFromArray(array $styleArray): static
188 181
    {
189 74
        if ($this->isSupervisor) {
190
            $this->getActiveSheet()
191 181
                ->getStyle($this->getSelectedCells())
192 109
                ->applyFromArray(
193
                    $this->getStyleArray($styleArray)
194
                );
195
        } else {
196 181
            if (isset($styleArray['rgb'])) {
197
                $this->setRGB($styleArray['rgb']);
198
            }
199 10757
            if (isset($styleArray['argb'])) {
200
                $this->setARGB($styleArray['argb']);
201 10757
            }
202 4
            if (isset($styleArray['theme'])) {
203
                $this->setTheme($styleArray['theme']);
204 10757
            }
205 10757
        }
206 2
207
        return $this;
208 10757
    }
209 10757
210
    private function validateColor(?string $colorValue): string
211 248
    {
212 233
        if ($colorValue === null || $colorValue === '') {
213
            return self::COLOR_BLACK;
214
        }
215 18
        $named = ucfirst(strtolower($colorValue));
216
        if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) {
217
            return self::NAMED_COLOR_TRANSLATIONS[$named];
218
        }
219
        if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) {
220
            return $colorValue;
221 698
        }
222
        if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) {
223 698
            return 'FF' . $colorValue;
224 36
        }
225
226
        return '';
227 698
    }
228
229
    /**
230
     * Get ARGB.
231
     */
232
    public function getARGB(): ?string
233
    {
234
        if ($this->isSupervisor) {
235
            return $this->getSharedComponent()->getARGB();
236
        }
237 991
238
        return $this->argb;
239 991
    }
240 991
241 991
    /**
242 991
     * Set ARGB.
243 18
     *
244
     * @param ?string $colorValue  ARGB value, or a named color
245
     *
246
     * @return $this
247 989
     */
248 28
    public function setARGB(?string $colorValue = self::COLOR_BLACK, bool $nullStringOkay = false): static
249 28
    {
250
        $this->hasChanged = true;
251 989
        if (!$nullStringOkay || $colorValue !== '') {
252
            $colorValue = $this->validateColor($colorValue);
253
            if ($colorValue === '') {
254 989
                return $this;
255
            }
256
        }
257
258
        if ($this->isSupervisor) {
259
            $styleArray = $this->getStyleArray(['argb' => $colorValue]);
260 731
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
261
        } else {
262 731
            $this->argb = $colorValue;
263 20
        }
264
265
        return $this;
266 731
    }
267
268
    /**
269
     * Get RGB.
270
     */
271
    public function getRGB(): string
272
    {
273
        if ($this->isSupervisor) {
274
            return $this->getSharedComponent()->getRGB();
275
        }
276 250
277
        return substr($this->argb ?? '', 2);
278 250
    }
279
280
    /**
281
     * Set RGB.
282
     *
283
     * @param ?string $colorValue RGB value, or a named color
284
     *
285
     * @return $this
286
     */
287
    public function setRGB(?string $colorValue = self::COLOR_BLACK): static
288
    {
289
        return $this->setARGB($colorValue);
290
    }
291 247
292
    /**
293 247
     * Get a specified colour component of an RGB value.
294 247
     *
295 1
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
296
     * @param int $offset Position within the RGB value to extract
297
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
298 247
     *                                    decimal value
299
     *
300
     * @return int|string The extracted colour component
301
     */
302
    private static function getColourComponent(string $rgbValue, int $offset, bool $hex = true): string|int
303
    {
304
        $colour = substr($rgbValue, $offset, 2) ?: '';
305
        if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) {
306
            $colour = '00';
307
        }
308
309
        return ($hex) ? $colour : (int) hexdec($colour);
310 234
    }
311
312 234
    /**
313
     * Get the red colour component of an RGB value.
314
     *
315
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
316
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
317
     *                                    decimal value
318
     *
319
     * @return int|string The red colour component
320
     */
321
    public static function getRed(string $rgbValue, bool $hex = true)
322
    {
323
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex);
324 234
    }
325
326 234
    /**
327
     * Get the green colour component of an RGB value.
328
     *
329
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
330
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
331
     *                                    decimal value
332
     *
333
     * @return int|string The green colour component
334
     */
335
    public static function getGreen(string $rgbValue, bool $hex = true)
336
    {
337
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex);
338 235
    }
339
340 235
    /**
341
     * Get the blue colour component of an RGB value.
342
     *
343
     * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
344
     * @param bool $hex Flag indicating whether the component should be returned as a hex or a
345
     *                                    decimal value
346
     *
347
     * @return int|string The blue colour component
348
     */
349
    public static function getBlue(string $rgbValue, bool $hex = true)
350
    {
351 228
        return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex);
352
    }
353 228
354 228
    /**
355
     * Adjust the brightness of a color.
356
     *
357 228
     * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
358
     * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
359 228
     *
360
     * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
361 228
     */
362
    public static function changeBrightness(string $hexColourValue, float $adjustPercentage): string
363 228
    {
364
        $rgba = (strlen($hexColourValue) === 8);
365
        $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage));
366
367
        /** @var int $red */
368
        $red = self::getRed($hexColourValue, false);
369
        /** @var int $green */
370
        $green = self::getGreen($hexColourValue, false);
371
        /** @var int $blue */
372
        $blue = self::getBlue($hexColourValue, false);
373
374 103
        return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
375
    }
376
377 103
    /**
378
     * Get indexed color.
379 103
     *
380 100
     * @param int $colorIndex Index entry point into the colour array
381 45
     * @param bool $background Flag to indicate whether default background or foreground colour
382
     *                                            should be returned if the indexed colour doesn't exist
383
     * @param null|string[] $palette
384 4
     */
385 4
    public static function indexedColor(int $colorIndex, bool $background = false, ?array $palette = null): self
386
    {
387
        // Clean parameter
388
        $colorIndex = (int) $colorIndex;
389 69
390
        if (empty($palette)) {
391
            if (isset(self::INDEXED_COLORS[$colorIndex])) {
392
                return new self(self::INDEXED_COLORS[$colorIndex]);
393
            }
394
        } else {
395
            if (isset($palette[$colorIndex])) {
396
                return new self($palette[$colorIndex]);
397 1311
            }
398
        }
399 1311
400 1
        return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK);
401
    }
402
403 1311
    /**
404 1311
     * Get hash code.
405 1311
     *
406 1311
     * @return string Hash code
407
     */
408
    public function getHashCode(): string
409
    {
410 14
        if ($this->isSupervisor) {
411
            return $this->getSharedComponent()->getHashCode();
412 14
        }
413 14
414
        return md5(
415 14
            $this->argb
416
            . (string) $this->theme
417
            . __CLASS__
418 1258
        );
419
    }
420 1258
421 8
    /** @return mixed[] */
422
    protected function exportArray1(): array
423
    {
424 1258
        $exportedArray = [];
425
        $this->exportArray2($exportedArray, 'argb', $this->getARGB());
426
        $this->exportArray2($exportedArray, 'theme', $this->getTheme());
427
428
        return $exportedArray;
429
    }
430
431
    public function getHasChanged(): bool
432
    {
433
        if ($this->isSupervisor) {
434
            return $this->getSharedComponent()->hasChanged;
435
        }
436
437
        return $this->hasChanged;
438
    }
439
440
    public function getTheme(): int
441
    {
442
        if ($this->isSupervisor) {
443
            return $this->getSharedComponent()->getTheme();
444
        }
445
446
        return $this->theme;
447
    }
448
449
    public function setTheme(int $theme): self
450
    {
451
        $this->hasChanged = true;
452
453
        if ($this->isSupervisor) {
454
            $styleArray = $this->getStyleArray(['theme' => $theme]);
455
            $this->getActiveSheet()
456
                ->getStyle($this->getSelectedCells())
457
                ->applyFromArray($styleArray);
458
        } else {
459
            $this->theme = $theme;
460
        }
461
462
        return $this;
463
    }
464
465
    public function setHyperlinkTheme(): self
466
    {
467
        return $this->setTheme(Theme::HYPERLINK_THEME);
468
    }
469
}
470