Skip to content

Commit

Permalink
Merge pull request #26 from wsssoftware/colors
Browse files Browse the repository at this point in the history
Colors helper
  • Loading branch information
allanmcarvalho authored Nov 6, 2024
2 parents acca3d7 + d635c74 commit d953c62
Show file tree
Hide file tree
Showing 8 changed files with 535 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ To Laravel Toolkit be able to compile its tailwind you must add this line on tai
### [ACL](docs/ACL.md)
A minimalist implementation of an access control level

### [Colors](docs/COLORS.md)
A toolset of helpers for colors.

### [Flash](docs/FLASH.md)
Simple flash messages from backend to front end.

Expand Down
53 changes: 53 additions & 0 deletions docs/COLORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## Colors

A toolset of helpers for colors.

### Converting
You can use the following methods of `Facade` to convert between colors format:
```php
use LaravelToolkit\Facades\Colors;

[$r, $g, $b] = Colors::hexToRgb('#e4572e');
[$h, $s, $l] = Colors::hexToHsl('#e4572e');

$hex = Colors::hslToHex(14, 77, 54);
[$r, $g, $b] = Colors::hslToRgb(14, 77, 54);

$hex = Colors::rgbToHex(228, 87, 46);
[$h, $s, $l] = Colors::rgbToHsl(228, 87, 46);

```

### Random colors
If you want to generate a random color use:
```php
use LaravelToolkit\Facades\Colors;

$hex = Colors::randHex();
[$r, $g, $b] = Colors::randRgb();
[$h, $s, $l] = Colors::randHsl();

```

### Tailwind palette
If you have a base color and want to generate a palette:
```php
use LaravelToolkit\Facades\Colors;
use LaravelToolkit\Colors\ColorStep;
use LaravelToolkit\Colors\ColorFormat;

// from hex
$palette = Colors::palette('#e4572e');
// from rgb
$palette = Colors::palette(rgb: [228, 87, 46]);
// from hsl
$palette = Colors::palette(hsl: [14, 77, 54]);

// changing base color
$palette = Colors::palette('#e4572e', baseStep: ColorStep::STEP_400);
// choosing output format
$palette = Colors::palette('#e4572e', outputFormat: ColorFormat::RGB);
// with custom threshold
$palette = Colors::palette('#e4572e', thresholdLightest: 10, thresholdDarkest: 10);

```
10 changes: 10 additions & 0 deletions src/Colors/ColorFormat.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace LaravelToolkit\Colors;

enum ColorFormat
{
case HEX;
case HSL;
case RGB;
}
50 changes: 50 additions & 0 deletions src/Colors/ColorStep.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace LaravelToolkit\Colors;

enum ColorStep: int
{
case STEP_50 = 50;
case STEP_100 = 100;
case STEP_200 = 200;
case STEP_300 = 300;
case STEP_400 = 400;
case STEP_500 = 500;
case STEP_600 = 600;
case STEP_700 = 700;
case STEP_800 = 800;
case STEP_900 = 900;
case STEP_950 = 950;

/**
* @return \LaravelToolkit\Colors\ColorStep[]
*/
public function afterSteps(): array
{
$items = [];
foreach (self::cases() as $case) {
if ($case->value <= $this->value) {
continue;
}
$items[] = $case;
}

return $items;
}

/**
* @return \LaravelToolkit\Colors\ColorStep[]
*/
public function beforeSteps(): array
{
$items = [];
foreach (self::cases() as $case) {
if ($case === $this) {
break;
}
$items[] = $case;
}

return $items;
}
}
174 changes: 174 additions & 0 deletions src/Colors/Colors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace LaravelToolkit\Colors;

use Exception;

/**
* @see \LaravelToolkit\Facades\Colors
*/
class Colors
{
/**
* @return array{0: float, 1: float, 2: float}
*
* @throws \Exception
*/
public function hexToHsl(string $hex): array
{
return $this->rgbToHsl(...$this->hexToRgb($hex));
}

/**
* @return array{0: int, 1: int, 2: int}
*
* @throws \Exception
*/
public function hexToRgb(string $hex): array
{
$hex = ltrim($hex, '#');
if (strlen($hex) === 6) {
return [
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2)),
];
} elseif (strlen($hex) === 3) {
return [
hexdec(str_repeat(substr($hex, 0, 1), 2)),
hexdec(str_repeat(substr($hex, 1, 1), 2)),
hexdec(str_repeat(substr($hex, 2, 1), 2)),
];
} else {
throw new Exception('Invalid hex string');
}
}

public function hslToHex(int $h, int $s, int $l): string
{
return $this->rgbToHex(...$this->hslToRgb($h, $s, $l));
}

/**
* @return array{0: int, 1: int, 2: int}
*/
public function hslToRgb(int $h, int $s, int $l): array
{
$h /= 360;
$s /= 100;
$l /= 100;
if ($s == 0) {
$r = $g = $b = $l;
} else {
$q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
$p = 2 * $l - $q;

$r = $this->hueToRgb($p, $q, $h + 1 / 3);
$g = $this->hueToRgb($p, $q, $h);
$b = $this->hueToRgb($p, $q, $h - 1 / 3);
}

return [
intval(round($r * 255)),
intval(round($g * 255)),
intval(round($b * 255)),
];
}

protected function hueToRgb(float $p, float $q, float $t): float
{
$t = match (true) {
$t < 0 => $t + 1,
$t > 1 => $t - 1,
default => $t,
};

return match (true) {
$t < 1 / 6 => $p + ($q - $p) * 6 * $t,
$t < 1 / 2 => $q,
$t < 2 / 3 => $p + ($q - $p) * (2 / 3 - $t) * 6,
default => $p,
};
}

/**
* @throws \Exception
*/
public function palette(
?string $hex = null,
?array $rgb = null,
?array $hsl = null,
ColorStep $baseStep = ColorStep::STEP_500,
ColorFormat $outputFormat = ColorFormat::HEX,
float $thresholdLightest = 5,
float $thresholdDarkest = 6,
): array {
[$h, $s, $l] = match (true) {
! empty($hex) => $this->hexToHsl($hex),
! empty($rgb) => $this->rgbToHsl(...$rgb),
! empty($hsl) => $hsl,
default => throw new Exception('You must provide one color format.'),
};

return (new PaletteGenerator($h, $s, $l, $baseStep, $outputFormat, $thresholdLightest, $thresholdDarkest))();
}

public function randHex(): string
{
return $this->rgbToHex(...$this->randRgb());
}

public function randHsl(): array
{
return $this->rgbToHsl(...$this->randRgb());
}

public function randRgb(): array
{
return [
rand(0, 255),
rand(0, 255),
rand(0, 255),
];
}

public function rgbToHex(int $r, int $g, int $b): string
{
return sprintf('#%02X%02X%02X', $r, $g, $b);
}

/**
* @return array{0: float, 1: float, 2: float}
*/
public function rgbToHsl(int $r, int $g, int $b): array
{
$r /= 255;
$g /= 255;
$b /= 255;

$max = max($r, $g, $b);
$min = min($r, $g, $b);

$h = $s = 0;
$l = ($max + $min) / 2;

if ($max !== $min) {
$delta = $max - $min;
$s = $l > 0.5 ? $delta / (2.0 - $max - $min) : $delta / ($max + $min);

$h = match (true) {
$max == $r => ($g - $b) / $delta + ($g < $b ? 6 : 0),
$max == $g => ($b - $r) / $delta + 2,
default => ($r - $g) / $delta + 4
};

$h /= 6;
}

return [
intval(round($h * 360)),
intval(round($s * 100)),
intval(round($l * 100)),
];
}
}
58 changes: 58 additions & 0 deletions src/Colors/PaletteGenerator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace LaravelToolkit\Colors;

use LaravelToolkit\Facades\Colors;

class PaletteGenerator
{
public function __construct(
protected readonly int $h,
protected readonly int $s,
protected readonly int $l,
protected readonly ColorStep $baseStep,
protected readonly ColorFormat $outputFormat,
protected readonly float $thresholdLightest,
protected readonly float $thresholdDarkest,
) {
//
}

public function __invoke(): array
{
$palette = collect();

$lighterSteps = $this->baseStep->beforeSteps();
$lighterRangeStep = (100 - $this->thresholdLightest - $this->l) / count($lighterSteps);
foreach ($lighterSteps as $index => $step) {
$level = $step->value;
$baseLevel = $this->baseStep->value;
$palette->put($level, [
$this->h,
round(max(0, $this->s - ($this->s * (($baseLevel - $level) / $baseLevel) * 0.5))),
round($this->l + ($lighterRangeStep * (count($lighterSteps) - $index))),
]);
}

$palette->put($this->baseStep->value, [$this->h, $this->s, $this->l]);

$darkerSteps = $this->baseStep->afterSteps();
$darkerRangeStep = ($this->l - $this->thresholdDarkest) / count($darkerSteps);
foreach ($darkerSteps as $index => $step) {
$level = $step->value;
$baseLevel = $this->baseStep->value;
$palette->put($step->value, [
$this->h,
round(min(100, $this->s + ($this->s * (($level - $baseLevel) / $baseLevel) * 0.4))),
round($this->l - ($darkerRangeStep * ($index + 1))),
]);
}

return $palette
->map(fn (array $hsl) => match ($this->outputFormat) {
ColorFormat::HEX => Colors::hslToHex(...$hsl),
ColorFormat::RGB => Colors::hslToRgb(...$hsl),
default => $hsl
})->toArray();
}
}
29 changes: 29 additions & 0 deletions src/Facades/Colors.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace LaravelToolkit\Facades;

use Illuminate\Support\Facades\Facade;
use LaravelToolkit\Colors\ColorFormat;
use LaravelToolkit\Colors\ColorStep;

/**
* @method static int[] hexToHsl(string $hex)
* @method static int[] hexToRgb(string $hex)
* @method static string hslToHex(int $h, int $s, int $l)
* @method static array hslToRgb(int $h, int $s, int $l)
* @method static array palette(null|string $hex = null, null|array $rgb = null, null|array $hsl = null, ColorStep $baseStep = ColorStep::STEP_500, ColorFormat $outputFormat = ColorFormat::HEX, float $thresholdLightest = 5, float $thresholdDarkest = 6)
* @method static string randHex()
* @method static array randHsl()
* @method static array randRgb()
* @method static string rgbToHex(int $r, int $g, int $b)
* @method static int[] rgbToHsl(int $r, int $g, int $b)
*
* @see \LaravelToolkit\Colors\Colors
*/
class Colors extends Facade
{
protected static function getFacadeAccessor(): string
{
return \LaravelToolkit\Colors\Colors::class;
}
}
Loading

0 comments on commit d953c62

Please sign in to comment.