Skip to content

Commit d0d749f

Browse files
committed
Refactor color math and test the conversion trough the full color spectrum back and forth
1 parent b0b29d5 commit d0d749f

File tree

13 files changed

+6245
-400
lines changed

13 files changed

+6245
-400
lines changed

Build/Travis/.phpunit.result.cache

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

Build/Travis/composer.lock

Lines changed: 5770 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Classes/Domain/ValueObject/AbstractColor.php

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ public function getHexString(): string
2222
$rgba = $this->asRgba();
2323
if ($rgba->getAlpha() == 255) {
2424
return '#'
25-
.str_pad(dechex($rgba->getRed()), 2, '0')
26-
.str_pad(dechex($rgba->getGreen()), 2, '0')
27-
.str_pad(dechex($rgba->getBlue()), 2, '0');
25+
.str_pad(dechex((int)round($rgba->getRed())), 2, '0')
26+
.str_pad(dechex((int)round($rgba->getGreen())), 2, '0')
27+
.str_pad(dechex((int)round($rgba->getBlue())), 2, '0');
2828
} else {
2929
return '#'
30-
.str_pad(dechex($rgba->getRed()), 2, '0')
31-
.str_pad(dechex($rgba->getGreen()), 2, '0')
32-
.str_pad(dechex($rgba->getBlue()), 2, '0')
33-
.str_pad(dechex($rgba->getAlpha()), 2, '0');
30+
.str_pad(dechex((int)round($rgba->getRed())), 2, '0')
31+
.str_pad(dechex((int)round($rgba->getGreen())), 2, '0')
32+
.str_pad(dechex((int)round($rgba->getBlue())), 2, '0')
33+
.str_pad(dechex((int)round($rgba->getAlpha())), 2, '0');
3434
}
3535
}
3636

@@ -40,10 +40,10 @@ public function getHexString(): string
4040
public function getHslaString(): string
4141
{
4242
$hslaColor = $this->asHsla();
43-
if ($hslaColor->getAlpha() == 255) {
44-
return sprintf('hsl(%s, %s%%, %s%%)', $hslaColor->getHue(), $hslaColor->getSaturation(), $hslaColor->getLightness());
43+
if ($hslaColor->getAlpha() == 1) {
44+
return sprintf('hsl(%s, %s%%, %s%%)', round($hslaColor->getHue()), round($hslaColor->getSaturation()), round($hslaColor->getLightness()));
4545
} else {
46-
return sprintf('hsla(%s, %s%%, %s%%, %s)', $hslaColor->getHue(), $hslaColor->getSaturation(), $hslaColor->getLightness(), round($hslaColor->getAlpha() / 255, 2));
46+
return sprintf('hsla(%s, %s%%, %s%%, %s)', round($hslaColor->getHue()), round($hslaColor->getSaturation()), round($hslaColor->getLightness()), round($hslaColor->getAlpha(), 2));
4747
}
4848
}
4949

@@ -54,9 +54,9 @@ public function getRgbaString(): string
5454
{
5555
$rgbColor = $this->asRgba();
5656
if ($rgbColor->getAlpha() == 255) {
57-
return sprintf('rgb(%s, %s, %s)', $rgbColor->getRed(), $rgbColor->getGreen(), $rgbColor->getBlue());
57+
return sprintf('rgb(%s, %s, %s)', round($rgbColor->getRed()), round($rgbColor->getGreen()), round($rgbColor->getBlue()));
5858
} else {
59-
return sprintf('rgba(%s, %s, %s, %s)', $rgbColor->getRed(), $rgbColor->getGreen(), $rgbColor->getBlue(), $rgbColor->getAlpha());
59+
return sprintf('rgba(%s, %s, %s, %s)', round($rgbColor->getRed()), round($rgbColor->getGreen()), round($rgbColor->getBlue()), $rgbColor->getAlpha());
6060
}
6161
}
6262

@@ -97,11 +97,11 @@ public function withMixedColor(ColorInterface $color, int $weight = 50): ColorIn
9797
}
9898

9999
/**
100-
* @param int $delta
100+
* @param float $delta
101101
*
102102
* @return ColorInterface
103103
*/
104-
public function withAdjustedLightness(int $delta): ColorInterface
104+
public function withAdjustedLightness(float $delta): ColorInterface
105105
{
106106
$hslaColor = $this->asHsla();
107107
$lightness = $hslaColor->getLightness() + $delta;
@@ -121,11 +121,11 @@ public function withAdjustedLightness(int $delta): ColorInterface
121121
}
122122

123123
/**
124-
* @param int $delta
124+
* @param float $delta
125125
*
126126
* @return ColorInterface
127127
*/
128-
public function withAdjustedSaturation(int $delta): ColorInterface
128+
public function withAdjustedSaturation(float $delta): ColorInterface
129129
{
130130
$hslaColor = $this->asHsla();
131131
$saturation = $hslaColor->getSaturation() + $delta;
@@ -145,11 +145,11 @@ public function withAdjustedSaturation(int $delta): ColorInterface
145145
}
146146

147147
/**
148-
* @param int $delta
148+
* @param float $delta
149149
*
150150
* @return ColorInterface
151151
*/
152-
public function withAdjustedHue(int $delta): ColorInterface
152+
public function withAdjustedHue(float $delta): ColorInterface
153153
{
154154
$hslaColor = $this->asHsla();
155155
$hue = ($hslaColor->getHue() + $delta) % 360;

Classes/Domain/ValueObject/ColorInterface.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ public function asHsla(): HslaColor;
2323
*/
2424
public function equals(self $color): bool;
2525

26+
/**
27+
* @param ColorInterface $color
28+
* @param float $maxDist
29+
* @return bool
30+
*/
31+
public function isSimilarTo(ColorInterface $color, float $maxDist = 2): bool;
32+
2633
/**
2734
* @return string
2835
*/
@@ -52,30 +59,30 @@ public function getRgbaString(): string;
5259
public function withMixedColor(self $color, int $weight): self;
5360

5461
/**
55-
* @param int $delta
62+
* @param float $delta
5663
*
5764
* @return ColorInterface
5865
*/
59-
public function withAdjustedLightness(int $delta): self;
66+
public function withAdjustedLightness(float $delta): self;
6067

6168
/**
62-
* @param int $delta
69+
* @param float $delta
6370
*
6471
* @return ColorInterface
6572
*/
66-
public function withAdjustedSaturation(int $delta): self;
73+
public function withAdjustedSaturation(float $delta): self;
6774

6875
/**
69-
* @param int $delta
76+
* @param float $delta
7077
*
7178
* @return ColorInterface
7279
*/
73-
public function withAdjustedHue(int $delta): self;
80+
public function withAdjustedHue(float $delta): self;
7481

7582
/**
76-
* @param int $delta
83+
* @param float $delta 0..100
7784
*
7885
* @return ColorInterface
7986
*/
80-
public function withAdjustedAlpha(int $delta): self;
87+
public function withAdjustedAlpha(float $delta): self;
8188
}

Classes/Domain/ValueObject/HslaColor.php

Lines changed: 80 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,45 @@
77
class HslaColor extends AbstractColor implements ColorInterface
88
{
99
/**
10-
* @var int
10+
* @var float
1111
*/
1212
private $hue;
1313

1414
/**
15-
* @var int
15+
* @var float
1616
*/
1717
private $saturation;
1818

1919
/**
20-
* @var int
20+
* @var float
2121
*/
2222
private $lightness;
2323
/**
24-
* @var int
24+
* @var float
2525
*/
2626
private $alpha;
2727

2828
/**
2929
* HslaColor constructor.
3030
*
31-
* @param int $hue
32-
* @param int $saturation
33-
* @param int $lightness
34-
* @param int $alpha
31+
* @param float $hue
32+
* @param float $saturation
33+
* @param float $lightness
34+
* @param float $alpha
3535
*/
36-
public function __construct(int $hue, int $saturation, int $lightness, int $alpha = 255)
36+
public function __construct(float $hue, float $saturation, float $lightness, float $alpha = 1)
3737
{
38-
if ($hue < 0 || $hue > 359) {
39-
throw new \InvalidArgumentException('argument hue has to be an integer between 0 and 359, '.$hue.' was given.');
38+
if ($hue < 0 || $hue > 360) {
39+
throw new \InvalidArgumentException('argument hue has to be a float between 0 and 359, '.$hue.' was given.');
4040
}
4141
if ($saturation < 0 || $saturation > 100) {
42-
throw new \InvalidArgumentException('argument saturation has to be an integer between 0 and 100, '.$saturation.' was given.');
42+
throw new \InvalidArgumentException('argument saturation has to be a float between 0 and 100, '.$saturation.' was given.');
4343
}
4444
if ($lightness < 0 || $lightness > 100) {
45-
throw new \InvalidArgumentException('argument luminosity has to be an integer between 0 and 100, '.$lightness.' was given.');
45+
throw new \InvalidArgumentException('argument luminosity has to be a float between 0 and 100, '.$lightness.' was given.');
4646
}
47-
if ($alpha < 0 || $alpha > 255) {
48-
throw new \InvalidArgumentException('argument alpha has to be an integer between 0 and 255, '.$alpha.' was given');
47+
if ($alpha < 0 || $alpha > 1) {
48+
throw new \InvalidArgumentException('argument alpha has to be a float between 0 and 1, '.$alpha.' was given');
4949
}
5050

5151
$this->hue = $hue;
@@ -55,95 +55,78 @@ public function __construct(int $hue, int $saturation, int $lightness, int $alph
5555
}
5656

5757
/**
58-
* @return int
58+
* @return float
5959
*/
60-
public function getHue(): int
60+
public function getHue(): float
6161
{
6262
return $this->hue;
6363
}
6464

6565
/**
66-
* @return int
66+
* @return float
6767
*/
68-
public function getSaturation(): int
68+
public function getSaturation(): float
6969
{
7070
return $this->saturation;
7171
}
7272

7373
/**
74-
* @return int
74+
* @return float
7575
*/
76-
public function getLightness(): int
76+
public function getLightness(): float
7777
{
7878
return $this->lightness;
7979
}
8080

8181
/**
82-
* @return int
82+
* @return float
8383
*/
84-
public function getAlpha(): int
84+
public function getAlpha(): float
8585
{
8686
return $this->alpha;
8787
}
8888

8989
/**
9090
* @return RgbaColor
91+
* @see http://en.wikipedia.org/wiki/HSL_color_space.
92+
* @see https://gist.github.com/mjackson/5311256
9193
*/
9294
public function asRgba(): RgbaColor
9395
{
94-
$S = $this->saturation / 100;
95-
$L = $this->lightness / 100;
96-
$H = $this->hue;
97-
98-
$C = (1 - abs((2 * $L) - 1)) * $S;
99-
$X = $C * (1 - abs((($H / 60) % 2) - 1));
100-
$m = $L - ($C / 2);
101-
102-
if ($S == 0) {
103-
$rgb = (int) round($L * 255);
104-
return new RgbaColor($rgb, $rgb, $rgb, $this->alpha);
96+
$h = $this->hue / 360;
97+
$l = $this->lightness / 100;
98+
$s = $this->saturation / 100;
99+
$a = $this->alpha;
100+
101+
if ($s == 0) {
102+
$rgb = $l * 255;
103+
return new RgbaColor($rgb, $rgb, $rgb, $this->alpha * 255);
105104
}
106105

107-
if ($this->hue < 0) {
108-
throw new \UnexpectedValueException('this should never be thrown');
109-
} elseif ($H < 60) {
110-
$r = $C;
111-
$g = $X;
112-
$b = 0;
113-
} elseif ($H < 120) {
114-
$r = $X;
115-
$g = $C;
116-
$b = 0;
117-
} elseif ($H < 180) {
118-
$r = 0;
119-
$g = $C;
120-
$b = $X;
121-
} elseif ($H < 240) {
122-
$r = 0;
123-
$g = $X;
124-
$b = $C;
125-
} elseif ($H < 300) {
126-
$r = $X;
127-
$g = 0;
128-
$b = $C;
129-
} elseif ($H < 360) {
130-
$r = $C;
131-
$g = 0;
132-
$b = $X;
133-
} else {
134-
throw new \UnexpectedValueException('this should never be thrown');
135-
}
106+
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
107+
$p = 2 * $l - $q;
136108

137-
$R = ($r + $m) * 255;
138-
$G = ($g + $m) * 255;
139-
$B = ($b + $m) * 255;
109+
$r = $this->hue2rgb($p, $q, $h + 1/3);
110+
$g = $this->hue2rgb($p, $q, $h);
111+
$b = $this->hue2rgb($p, $q, $h - 1/3);
140112

141-
return new RgbaColor(
142-
(int) round($R),
143-
(int) round($G),
144-
(int) round($B),
145-
$this->alpha
146-
);
113+
return new RgbaColor($r * 255, $g * 255, $b * 255, $a * 255);
114+
}
115+
116+
/**
117+
* @param float $p
118+
* @param float $q
119+
* @param float $t
120+
* @return float
121+
*/
122+
private function hue2rgb(float $p, float $q, float $t): float
123+
{
124+
if($t < 0) $t += 1;
125+
if($t > 1) $t -= 1;
126+
if($t < 1/6) return $p + ($q - $p) * 6 * $t;
127+
if($t < 1/2) return $q;
128+
if($t < 2/3) return $p + ($q - $p) * (2/3 - $t) * 6;
129+
return $p;
147130
}
148131

149132
/**
@@ -155,18 +138,39 @@ public function asHsla(): HslaColor
155138
}
156139

157140
/**
158-
* @param int $delta
141+
* @param ColorInterface $color
142+
* @param float $maxDist
143+
* @return bool
144+
*/
145+
public function isSimilarTo(ColorInterface $color, float $maxDist = 5): bool
146+
{
147+
$color = $color->asHsla();
148+
149+
$deltaH1 = abs($this->getHue() - $color->getHue());
150+
$deltH12 = 360 - $color->getHue() + $this->getHue();
151+
152+
return (
153+
min($deltaH1, $deltH12) < $maxDist
154+
&& abs($this->getSaturation() - $color->getSaturation()) < $maxDist
155+
&& abs($this->getLightness() - $color->getLightness()) < $maxDist
156+
&& abs($this->getAlpha() - $color->getAlpha()) < ($maxDist / 100)
157+
);
158+
}
159+
160+
/**
161+
* @param float $delta 0..100
159162
*
160163
* @return ColorInterface
161164
*/
162-
public function withAdjustedAlpha(int $delta): ColorInterface
165+
public function withAdjustedAlpha(float $delta): ColorInterface
163166
{
167+
$delta = $delta / 100;
164168
$alpha = $this->getAlpha() + $delta;
165169
if ($alpha < 0) {
166170
$alpha = 0;
167171
}
168-
if ($alpha > 255) {
169-
$alpha = 255;
172+
if ($alpha > 1) {
173+
$alpha = 1;
170174
}
171175

172176
return new self(

0 commit comments

Comments
 (0)