Skip to content

Commit 873f0ac

Browse files
authored
feat: support gradient generation
Functionality that addresses the need described in #5
1 parent 48379b2 commit 873f0ac

File tree

4 files changed

+178
-0
lines changed

4 files changed

+178
-0
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,23 @@ $hexColor = colority()->fromHex('#51B389');
260260
$similarHexColor = colority()->getSimilarColor($hexColor);
261261
```
262262

263+
#### gradient
264+
265+
Generates a gradient of colors between multiple colors.
266+
267+
```php
268+
/** @var array<HexColor> $gradient */
269+
$gradient = colority()->gradient(
270+
colors: [
271+
colority()->fromHex('#ff0000'), // Red
272+
colority()->fromHex('#00ff00'), // Green
273+
colority()->fromHex('#0000ff') // Blue
274+
],
275+
steps: 10
276+
);
277+
```
278+
279+
263280
### Ways of using Colority
264281
You can use Colority either with the aliases `colority()`
265282
```php
@@ -297,6 +314,8 @@ Colority::fromHsl(string|array<float> $hslValue): HslColor
297314
Colority::textToColor(string $text, ?Color $fromColor = null, ?Color $toColor = null): HslColor
298315

299316
Colority::getSimilarColor(Color $color, int $hueRange = 30, int $saturationRange = 10, int $lightnessRange = 10): Color
317+
318+
Colority::gradient(array<Color> $colors, int $steps = 5): array<HexColor>
300319
```
301320

302321
### `Color`

src/Services/ColorityManager.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,64 @@ public function textToColor(string $text, ?Color $fromColor = null, ?Color $toCo
112112
return $this->fromHsl([$hue, $saturation, $lightness]);
113113
}
114114

115+
/**
116+
* Generates a gradient of colors between multiple colors.
117+
*
118+
* @param array<Color> $colors The array of colors to create the gradient from
119+
* @param int $steps The number of steps in the gradient
120+
* @return array<HexColor> An array of HexColor objects representing the gradient
121+
*/
122+
public function gradient(array $colors, int $steps = 5): array
123+
{
124+
if ($colors === []) {
125+
return [];
126+
}
127+
128+
if ($steps < 2 || count($colors) === 1) {
129+
return [$colors[0]->toHsl()->toHex()];
130+
}
131+
132+
$gradientColors = [];
133+
$totalSegments = count($colors) - 1;
134+
135+
for ($i = 0; $i < $steps; $i++) {
136+
// Calculate global progress (0 to 1)
137+
$t = $i / ($steps - 1);
138+
139+
// Calculate which segment we are in
140+
$segment = (int) floor($t * $totalSegments);
141+
142+
// Handle the last point explicitly to avoid out of bounds
143+
if ($segment >= $totalSegments) {
144+
$segment = $totalSegments - 1;
145+
}
146+
147+
// Calculate local progress within the segment (0 to 1)
148+
$segmentStart = $segment / $totalSegments;
149+
$segmentEnd = ($segment + 1) / $totalSegments;
150+
$localT = ($t - $segmentStart) / ($segmentEnd - $segmentStart);
151+
152+
$startHsl = $colors[$segment]->toHsl();
153+
$endHsl = $colors[$segment + 1]->toHsl();
154+
155+
[$startH, $startS, $startL] = $startHsl->getArrayValueColor();
156+
[$endH, $endS, $endL] = $endHsl->getArrayValueColor();
157+
158+
$hue = $startH + ($endH - $startH) * $localT;
159+
$saturation = $startS + ($endS - $startS) * $localT;
160+
$lightness = $startL + ($endL - $startL) * $localT;
161+
162+
// Ensure values are within valid ranges and rounded to avoid precision issues
163+
$hue = round(max(0, min(360, $hue)), 2);
164+
$saturation = round(max(0, min(100, $saturation)), 2);
165+
$lightness = round(max(0, min(100, $lightness)), 2);
166+
167+
$gradientColors[] = $this->fromHsl([$hue, $saturation, $lightness])->toHex();
168+
}
169+
170+
return $gradientColors;
171+
}
172+
115173
public function getSimilarColor(Color $color, int $hueRange = 30, int $saturationRange = 10, int $lightnessRange = 10): Color
116174
{
117175
[$baseH, $baseS, $baseL] = $color->toHsl()->getArrayValueColor();

src/Support/Facades/Colority.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* @method static HslColor fromHsl(string|array<float> $hslValue)
1818
* @method static HslColor textToColor(string $text, ?Color $fromColor = null, ?Color $toColor = null)
1919
* @method static getSimilarColor(Color $color, int $hueRange = 30, int $saturationRange = 10, int $lightnessRange = 10): Color
20+
* @method static array<HexColor> gradient(array<Color> $colors, int $steps = 5): array
2021
*/
2122
final class Colority
2223
{

tests/Services/ColorityManagerTest.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,103 @@
186186
expect($colority->parse($hslColor))->toBeNull();
187187

188188
})->with(['xxx(0,0,0)', 't,o,m', '(-255,-255,-255)', '0,0,0,0', 'hsl(0,0,0,0)']);
189+
190+
/**
191+
* Gradient
192+
*/
193+
it('generates a gradient between two colors', function (): void {
194+
$colority = ColorityManager::instance();
195+
196+
$start = $colority->fromHex('#000000');
197+
198+
$end = $colority->fromHex('#ffffff');
199+
200+
$gradient = $colority->gradient([$start, $end], 3);
201+
202+
expect($gradient)->toHaveCount(3);
203+
204+
// Start color should be black
205+
expect(strtolower($gradient[0]->toHex()->getValueColor()))->toBe('#000000');
206+
207+
// Middle color should be gray
208+
// HSL interpolation of black (0,0,0) and white (0,0,100) -> (0,0,50) -> #808080
209+
expect(strtolower($gradient[1]->toHex()->getValueColor()))->toBe('#808080');
210+
211+
// End color should be white
212+
expect(strtolower($gradient[2]->toHex()->getValueColor()))->toBe('#ffffff');
213+
});
214+
215+
it('generates a gradient with default steps', function (): void {
216+
$colority = ColorityManager::instance();
217+
218+
$start = $colority->fromHex('#ff0000');
219+
220+
$end = $colority->fromHex('#0000ff');
221+
222+
$gradient = $colority->gradient([$start, $end]);
223+
224+
expect($gradient)->toHaveCount(5);
225+
});
226+
227+
it('handles single step gradient request', function (): void {
228+
$colority = ColorityManager::instance();
229+
230+
$start = $colority->fromHex('#ff0000');
231+
232+
$end = $colority->fromHex('#0000ff');
233+
234+
$gradient = $colority->gradient([$start, $end], 1);
235+
236+
expect($gradient)->toHaveCount(1);
237+
238+
expect(strtolower($gradient[0]->toHex()->getValueColor()))->toBe('#ff0000');
239+
});
240+
241+
it('generates a gradient through multiple colors', function (): void {
242+
243+
$colority = ColorityManager::instance();
244+
245+
$red = $colority->fromHex('#ff0000');
246+
247+
$green = $colority->fromHex('#00ff00');
248+
249+
$blue = $colority->fromHex('#0000ff');
250+
251+
// 5 steps: Red -> (Red-Green) -> Green -> (Green-Blue) -> Blue
252+
$gradient = $colority->gradient([$red, $green, $blue], 5);
253+
254+
expect($gradient)->toHaveCount(5);
255+
256+
expect($gradient)->each->toBeInstanceOf(HexColor::class);
257+
258+
$hexes = array_map(fn ($c) => strtolower($c->toHex()->getValueColor()), $gradient);
259+
260+
// Check start
261+
expect($hexes[0])->toBe('#ff0000');
262+
263+
// Check middle
264+
expect($hexes[2])->toBe('#00ff00');
265+
266+
// Check end
267+
expect($hexes[4])->toBe('#0000ff');
268+
});
269+
270+
it('handles empty array of colors', function (): void {
271+
$colority = ColorityManager::instance();
272+
273+
$gradient = $colority->gradient([]);
274+
275+
expect($gradient)->toBeEmpty();
276+
});
277+
278+
it('handles single color array', function (): void {
279+
$colority = ColorityManager::instance();
280+
281+
$red = $colority->fromHex('#ff0000');
282+
283+
$gradient = $colority->gradient([$red], 5);
284+
285+
expect($gradient)->toHaveCount(1);
286+
287+
expect(strtolower($gradient[0]->toHex()->getValueColor()))->toBe('#ff0000');
288+
});

0 commit comments

Comments
 (0)