Passed
Push — master ( 200d88...9b81d7 )
by Alec
02:43 queued 15s
created

ColorConverter   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 83
dl 0
loc 151
rs 10
c 0
b 0
f 0
wmc 22

6 Methods

Rating   Name   Duplication   Size   Complexity  
A refineRGB() 0 11 3
A gradients() 0 11 3
A assertColor() 0 14 3
B toHSL() 0 39 8
A gradient() 0 14 2
A toRGB() 0 37 3
1
<?php
2
3
declare(strict_types=1);
4
// 23.03.23
5
namespace AlecRabbit\Spinner\Extras\Color;
6
7
use AlecRabbit\Spinner\Contract\Color\IColor;
8
use AlecRabbit\Spinner\Core\Color\HSLColor;
9
use AlecRabbit\Spinner\Core\Color\RGBColor;
0 ignored issues
show
Bug introduced by
The type AlecRabbit\Spinner\Core\Color\RGBColor was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use AlecRabbit\Spinner\Exception\InvalidArgumentException;
11
use AlecRabbit\Spinner\Extras\Contract\IColorConverter;
12
use AlecRabbit\Spinner\Helper\Asserter;
13
use AlecRabbit\Spinner\Helper\Value;
14
use Generator;
15
use Traversable;
16
17
use function abs;
18
use function fmod;
19
use function is_object;
20
use function is_string;
21
use function max;
22
use function min;
23
use function round;
24
use function sprintf;
25
26
final class ColorConverter implements IColorConverter
27
{
28
    /** @inheritdoc */
29
    public function toHSL(string|IColor $color): HSLColor
30
    {
31
        if ($color instanceof HSLColor) {
32
            return $color;
33
        }
34
35
        $rgb = $this->refineRGB($color);
36
37
        $r = $rgb->red / 255;
38
        $g = $rgb->green / 255;
39
        $b = $rgb->blue / 255;
40
41
        $max = max($r, $g, $b);
42
        $min = min($r, $g, $b);
43
44
        $h = 0;
45
        $s = 0;
46
        $l = ($max + $min) / 2;
47
48
        if ($max !== $min) {
49
            $d = $max - $min;
50
            $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);
51
52
            switch ($max) {
53
                case $r:
54
                    $h = ($g - $b) / $d + ($g < $b ? 6 : 0);
55
                    break;
56
                case $g:
57
                    $h = ($b - $r) / $d + 2;
58
                    break;
59
                case $b:
60
                    $h = ($r - $g) / $d + 4;
61
                    break;
62
            }
63
64
            $h /= 6;
65
        }
66
67
        return new HSLColor((int)round($h * 360), $s, $l, $rgb->alpha);
68
    }
69
70
    /**
71
     * @throws InvalidArgumentException
72
     */
73
    protected function refineRGB(string|IColor $color): RGBColor
74
    {
75
        if ($color instanceof RGBColor) {
76
            return $color;
77
        }
78
79
        if ($color instanceof HSLColor) {
80
            return $this->toRGB($color);
81
        }
82
        /** @var string $color */
83
        return RGBColor::fromHex($color);
84
    }
85
86
    /** @inheritdoc */
87
    public function toRGB(string|IColor $color): RGBColor
88
    {
89
        if ($color instanceof RGBColor) {
90
            return $color;
91
        }
92
        if (is_string($color)) {
93
            return RGBColor::fromHex($color);
94
        }
95
        /** @var HSLColor $color */
96
        $hue = $color->hue;
97
        $saturation = $color->saturation;
98
        $lightness = $color->lightness;
99
100
        $h = $hue / 360;
101
        $c = (1 - abs(2 * $lightness - 1)) * $saturation;
102
        $x = $c * (1 - abs(fmod($h * 6, 2) - 1));
103
        $m = $lightness - $c / 2;
104
105
        $r = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $r is dead and can be removed.
Loading history...
106
        $g = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $g is dead and can be removed.
Loading history...
107
        $b = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $b is dead and can be removed.
Loading history...
108
109
        match (true) {
110
            $h < 1 / 6 => [$r, $g] = [$c, $x],
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
111
            $h < 2 / 6 => [$r, $g] = [$x, $c],
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
112
            $h < 3 / 6 => [$g, $b] = [$c, $x],
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
113
            $h < 4 / 6 => [$g, $b] = [$x, $c],
114
            $h < 5 / 6 => [$r, $b] = [$x, $c],
0 ignored issues
show
Comprehensibility Best Practice introduced by
This list assign is not used and could be removed.
Loading history...
115
            default => [$r, $b] = [$c, $x],
116
        };
117
118
        return
119
            new RGBColor(
120
                (int)(($r + $m) * 255),
121
                (int)(($g + $m) * 255),
122
                (int)(($b + $m) * 255),
123
                $color->alpha
124
            );
125
    }
126
127
    /** @inheritdoc */
128
    public function gradients(Traversable $colors, int $steps = 10, null|string|IColor $fromColor = null): Generator
129
    {
130
        /** @var string|IColor $color */
131
        foreach ($colors as $color) {
132
            self::assertColor($color);
133
            if (null === $fromColor) {
134
                $fromColor = $color;
135
                continue;
136
            }
137
            yield from $this->gradient($fromColor, $color, $steps);
138
            $fromColor = $color;
139
        }
140
    }
141
142
    /**
143
     * @throws InvalidArgumentException
144
     */
145
    protected static function assertColor(mixed $color): void
146
    {
147
        if (is_string($color)) {
148
            Asserter::assertHexStringColor($color);
149
            return;
150
        }
151
152
        if (is_object($color)) {
153
            Asserter::isSubClass($color, IColor::class);
154
            return;
155
        }
156
157
        throw new InvalidArgumentException(
158
            sprintf('Invalid color type: %s.', Value::stringify($color))
159
        );
160
    }
161
162
    /** @inheritdoc */
163
    public function gradient(string|IColor $from, string|IColor $to, int $steps = 100): Generator
164
    {
165
        $from = $this->refineRGB($from);
166
        $to = $this->refineRGB($to);
167
168
        $rStep = ($to->red - $from->red) / $steps;
169
        $gStep = ($to->green - $from->green) / $steps;
170
        $bStep = ($to->blue - $from->blue) / $steps;
171
172
        for ($i = 0; $i < $steps; $i++) {
173
            yield new RGBColor(
174
                (int)round($from->red + $rStep * $i),
175
                (int)round($from->green + $gStep * $i),
176
                (int)round($from->blue + $bStep * $i),
177
            );
178
        }
179
    }
180
}