1<?php
2/**
3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14class ColorHsl
15{
16
17    const CANONICAL = "color";
18    private $hue;
19
20    private $saturation;
21    /**
22     * @var float|int
23     */
24    private $lightness;
25
26    /**
27     * ColorHsl constructor.
28     * @param $hue
29     * @param $saturation
30     * @param float|int $lightness
31     */
32    public function __construct($hue, $saturation, $lightness)
33    {
34        $this->hue = $hue;
35        $this->saturation = $saturation;
36        $this->lightness = $lightness;
37    }
38
39
40    public static function createFromChannels(float $hue, float $saturation, float $lightness): ColorHsl
41    {
42        return new ColorHsl($hue, $saturation, $lightness);
43    }
44
45    public function getLightness()
46    {
47        return $this->lightness;
48    }
49
50    public function getSaturation()
51    {
52        return $this->saturation;
53    }
54
55    public function getHue()
56    {
57        return $this->hue;
58    }
59
60    /**
61     * @throws ExceptionCombo
62     */
63    public function setLightness(int $int): ColorHsl
64    {
65        if ($int < 0 || $int > 100) {
66            throw new ExceptionCombo("Lightness should be between 0 and 100");
67        }
68        $this->lightness = $int;
69        return $this;
70    }
71
72    /**
73     * @return ColorRgb Reference:
74     *
75     * Reference:
76     * https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
77     * https://gist.github.com/brandonheyer/5254516
78     * @throws ExceptionCombo
79     */
80    function toRgb(): ColorRgb
81    {
82
83        $lightness = $this->lightness / 100;
84        $saturation = $this->saturation / 100;
85        $hue = $this->hue;
86
87        $chroma = (1 - abs(2 * $lightness - 1)) * $saturation;
88        $x = $chroma * (1 - abs(fmod(($hue / 60), 2) - 1));
89        $m = $lightness - ($chroma / 2);
90
91        if ($hue < 60) {
92            $red = $chroma;
93            $green = $x;
94            $blue = 0;
95        } else if ($hue < 120) {
96            $red = $x;
97            $green = $chroma;
98            $blue = 0;
99        } else if ($hue < 180) {
100            $red = 0;
101            $green = $chroma;
102            $blue = $x;
103        } else if ($hue < 240) {
104            $red = 0;
105            $green = $x;
106            $blue = $chroma;
107        } else if ($hue < 300) {
108            $red = $x;
109            $green = 0;
110            $blue = $chroma;
111        } else {
112            $red = $chroma;
113            $green = 0;
114            $blue = $x;
115        }
116
117        $red = ($red + $m) * 255;
118        $green = ($green + $m) * 255;
119        $blue = ($blue + $m) * 255;
120
121        /**
122         * To the closest integer
123         */
124        try {
125            return ColorRgb::createFromRgbChannels(
126                intval(round($red)),
127                intval(round($green)),
128                intval(round($blue))
129            );
130        } catch (ExceptionCombo $e) {
131            // should not happen but yeah, who knows
132            // and because there is no safe constructor, no safe default, we throw
133            $message = "Error while creating the rgb color from the hsl ($this)";
134            throw new ExceptionCombo($message, self::CANONICAL, 0, $e);
135        }
136
137    }
138
139    /**
140     * @throws ExceptionCombo
141     */
142    public function setSaturation(int $saturation): ColorHsl
143    {
144        if ($saturation < 0 || $saturation > 100) {
145            throw new ExceptionCombo("Saturation should be between 0 and 100");
146        }
147        $this->saturation = $saturation;
148        return $this;
149    }
150
151    public function toComplement(): ColorHsl
152    {
153        // Adjust Hue 180 degrees
154        $this->hue += ($this->hue > 180) ? -180 : 180;
155        return $this;
156    }
157
158    public function __toString()
159    {
160        return "hsl($this->hue deg, $this->saturation%, $this->lightness%)";
161    }
162
163    public function darken(int $lightness = 5): ColorHsl
164    {
165        if ($this->lightness - $lightness < 0) {
166            $this->lightness = 0;
167        }
168        $this->lightness -= $lightness;
169        return $this;
170    }
171
172    /**
173     * @throws ExceptionCombo
174     */
175    public function diff($color): array
176    {
177        if ($color instanceof ColorRgb) {
178            $color = $color->toHsl();
179        }
180
181        return [
182            "h" => round($this->getHue() - $color->getHue(), 2),
183            "s" => round($this->getSaturation() - $color->getSaturation(), 2),
184            "l" => round($this->getLightness() - $color->getLightness(), 2),
185        ];
186    }
187
188
189}
190