Skip to content

Commit f1ec1f1

Browse files
committed
feat: enhance textToColor with color interpolation and variation modes
1 parent 1b6a0fa commit f1ec1f1

File tree

4 files changed

+132
-11
lines changed

4 files changed

+132
-11
lines changed

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,27 @@ Generate a fixed color based on a string.
223223
/** @var HslColor $hslColor */
224224
$hslColor = colority()->textToColor("Hi, I'm Tomás");
225225
```
226+
227+
You can also pass a `fromColor`. In that case, only colors within the same color range as that color will be generated.
228+
229+
```php
230+
/** @var HslColor $hslColor - Only colors within the grayscale range. */
231+
$hslColor = colority()->textToColor("Hi, I'm Tomás", colority()->fromHex('#CCC'));
232+
```
233+
234+
On the other hand, if you pass both `fromColor` and `toColor`, the method performs a **linear interpolation** between the two colors. The same text will always map to the same position in the gradient between the colors, creating a deterministic color that falls somewhere on the line connecting the two provided colors.
235+
236+
```php
237+
/** @var HslColor $hslColor - Color interpolated between light green and dark teal */
238+
$hslColor = colority()->textToColor(
239+
"Hi, I'm Tomás",
240+
colority()->fromHex('#85d5a4'), // Light green (start point)
241+
colority()->fromHex('#165a59') // Dark teal (end point)
242+
);
243+
```
244+
245+
This interpolation approach is particularly useful for creating **consistent color themes** where you want all generated colors to harmonize within a specific palette range.
246+
226247
> **🧙 Advise**
227248
> Useful for generating a color associated with, for example, **a username, mail address, etc**, since a string will always return the same color.
228249
@@ -273,7 +294,7 @@ Colority::fromRgb(string|array<int> $rgbValue): RgbColor
273294

274295
Colority::fromHsl(string|array<float> $hslValue): HslColor
275296

276-
Colority::textToColor(string $text): HslColor
297+
Colority::textToColor(string $text, ?Color $fromColor = null, ?Color $toColor = null): HslColor
277298

278299
Colority::getSimilarColor(Color $color, int $hueRange = 30, int $saturationRange = 10, int $lightnessRange = 10): Color
279300
```

src/Services/ColorityManager.php

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,72 @@ public static function instance(): self
4242
return self::$instance;
4343
}
4444

45-
public function textToColor(string $text): HslColor
45+
/**
46+
* Generates a deterministic color from a text string.
47+
*
48+
* This method creates a consistent color representation of any given text by using
49+
* SHA256 hashing to ensure the same text always produces the same color.
50+
*
51+
* Behavior modes:
52+
* - If both `fromColor` and `toColor` are provided: interpolates between
53+
* the two colors using the text hash to determine the position in the
54+
* gradient (0-1)
55+
*
56+
* - If only `fromColor` is provided: generates color variations around the
57+
* base color with small random offsets (±30° hue, ±10% saturation/lightness)
58+
*
59+
* - If no colors are provided: generates a completely random color based on
60+
* the hash
61+
*
62+
* @param string $text The input text to convert to a color
63+
* @param Color|null $fromColor Optional base color for variations or interpolation start
64+
* @param Color|null $toColor Optional end color for interpolation
65+
* @return HslColor The generated color in HSL format
66+
*/
67+
public function textToColor(string $text, ?Color $fromColor = null, ?Color $toColor = null): HslColor
4668
{
4769
$hash = hash('sha256', $text);
4870

49-
// between 0 and 360deg
50-
$hue = hexdec(mb_substr($hash, 0, 8)) % 361;
51-
52-
// between 0 and 100%
53-
$saturation = hexdec(mb_substr($hash, 8, 8)) % 101;
54-
55-
// between 0 and 100%
56-
$lightness = hexdec(mb_substr($hash, 16, 8)) % 101;
71+
if ($fromColor instanceof Color && $toColor instanceof Color) {
72+
// Interpolar entre dos colores base
73+
$startHslColor = $fromColor->toHsl();
74+
$endHslColor = $toColor->toHsl();
75+
76+
[$startH, $startS, $startL] = $startHslColor->getArrayValueColor();
77+
[$endH, $endS, $endL] = $endHslColor->getArrayValueColor();
78+
79+
// Use the hash to determine the position in the gradient (0-1)
80+
$interpolationFactor = (hexdec(mb_substr($hash, 0, 8)) % 1000) / 1000;
81+
82+
// Interpolate each HSL component
83+
$hue = $startH + ($endH - $startH) * $interpolationFactor;
84+
$saturation = $startS + ($endS - $startS) * $interpolationFactor;
85+
$lightness = $startL + ($endL - $startL) * $interpolationFactor;
86+
87+
// Ensure the values are within valid ranges
88+
$hue = max(0, min(360, $hue));
89+
$saturation = max(0, min(100, $saturation));
90+
$lightness = max(0, min(100, $lightness));
91+
92+
} elseif ($fromColor instanceof Color) {
93+
// Use only the start color to generate variations within that palette
94+
$baseHslColor = $fromColor->toHsl();
95+
[$baseH, $baseS, $baseL] = $baseHslColor->getArrayValueColor();
96+
97+
// Generate small variations around the base color using the hash
98+
$hueVariation = (hexdec(mb_substr($hash, 0, 8)) % 61) - 30; // ±30 degrees
99+
$saturationVariation = (hexdec(mb_substr($hash, 8, 8)) % 21) - 10; // ±10%
100+
$lightnessVariation = (hexdec(mb_substr($hash, 16, 8)) % 21) - 10; // ±10%
101+
102+
$hue = max(0, min(360, $baseH + $hueVariation));
103+
$saturation = max(0, min(100, $baseS + $saturationVariation));
104+
$lightness = max(0, min(100, $baseL + $lightnessVariation));
105+
106+
} else {
107+
$hue = hexdec(mb_substr($hash, 0, 8)) % 361;
108+
$saturation = hexdec(mb_substr($hash, 8, 8)) % 101;
109+
$lightness = hexdec(mb_substr($hash, 16, 8)) % 101;
110+
}
57111

58112
return $this->fromHsl([$hue, $saturation, $lightness]);
59113
}

src/Support/Facades/Colority.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* @method static HexColor fromHex(string $hexValue)
1616
* @method static RgbColor fromRgb(string|array<int> $rgbValue)
1717
* @method static HslColor fromHsl(string|array<float> $hslValue)
18-
* @method static HslColor textToColor(string $text)
18+
* @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
2020
*/
2121
final class Colority

tests/Services/ColorityManagerTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,52 @@
4040
['Colority', 'hsl(315deg,73%,64%)'],
4141
]);
4242

43+
test('textToColor with fromColor and toColor interpolates between colors', function (): void {
44+
$instance = ColorityManager::instance();
45+
46+
$fromColor = new HslColor('hsl(0deg,100%,50%)'); // Red
47+
$toColor = new HslColor('hsl(240deg,100%,50%)'); // Blue
48+
49+
$result = $instance->textToColor('test', $fromColor, $toColor);
50+
51+
expect($result)->toBeInstanceOf(HslColor::class);
52+
53+
// The result should be deterministic for the same input
54+
$result2 = $instance->textToColor('test', $fromColor, $toColor);
55+
expect($result->getValueColorWithMeasureUnits())->toBe($result2->getValueColorWithMeasureUnits());
56+
57+
// Different texts should produce different results
58+
$result3 = $instance->textToColor('different text', $fromColor, $toColor);
59+
expect($result->getValueColorWithMeasureUnits())->not()->toBe($result3->getValueColorWithMeasureUnits());
60+
});
61+
62+
test('textToColor with fromColor generates variations around base color', function (): void {
63+
$instance = ColorityManager::instance();
64+
65+
$baseColor = new HslColor('hsl(120deg,60%,40%)'); // Green base
66+
67+
$result = $instance->textToColor('test', $baseColor);
68+
69+
expect($result)->toBeInstanceOf(HslColor::class);
70+
71+
// The result should be deterministic for the same input
72+
$result2 = $instance->textToColor('test', $baseColor);
73+
expect($result->getValueColorWithMeasureUnits())->toBe($result2->getValueColorWithMeasureUnits());
74+
75+
// Different texts should produce different variations
76+
$result3 = $instance->textToColor('different text', $baseColor);
77+
expect($result->getValueColorWithMeasureUnits())->not()->toBe($result3->getValueColorWithMeasureUnits());
78+
79+
// The result should be reasonably close to the base color
80+
[$resultH, $resultS, $resultL] = $result->getArrayValueColor();
81+
[$baseH, $baseS, $baseL] = $baseColor->getArrayValueColor();
82+
83+
// Verify variations are within expected ranges (±30° hue, ±10% saturation/lightness)
84+
expect(abs($resultH - $baseH))->toBeLessThanOrEqual(30);
85+
expect(abs($resultS - $baseS))->toBeLessThanOrEqual(10);
86+
expect(abs($resultL - $baseL))->toBeLessThanOrEqual(10);
87+
});
88+
4389
test('getSimilarColor', function (): void {
4490
$instance = ColorityManager::instance();
4591

0 commit comments

Comments
 (0)