177 lines
5.6 KiB
PHP
177 lines
5.6 KiB
PHP
|
<?php
|
||
|
namespace Index\Colour;
|
||
|
|
||
|
use Stringable;
|
||
|
|
||
|
abstract class Colour implements Stringable {
|
||
|
public abstract function getRed(): int;
|
||
|
public abstract function getGreen(): int;
|
||
|
public abstract function getBlue(): int;
|
||
|
public abstract function getAlpha(): float;
|
||
|
|
||
|
public abstract function shouldInherit(): bool;
|
||
|
|
||
|
public abstract function __toString(): string;
|
||
|
|
||
|
private const READABILITY_THRESHOLD = 186;
|
||
|
private const LUMINANCE_WEIGHT_RED = .299;
|
||
|
private const LUMINANCE_WEIGHT_GREEN = .587;
|
||
|
private const LUMINANCE_WEIGHT_BLUE = .114;
|
||
|
|
||
|
public function getLuminance(): float {
|
||
|
return self::LUMINANCE_WEIGHT_RED * $this->getRed()
|
||
|
+ self::LUMINANCE_WEIGHT_GREEN * $this->getGreen()
|
||
|
+ self::LUMINANCE_WEIGHT_BLUE * $this->getBlue();
|
||
|
}
|
||
|
|
||
|
public function isBright(): bool {
|
||
|
return $this->getLuminance() > self::READABILITY_THRESHOLD;
|
||
|
}
|
||
|
|
||
|
public function isDark(): bool {
|
||
|
return $this->getLuminance() <= self::READABILITY_THRESHOLD;
|
||
|
}
|
||
|
|
||
|
private static ColourNull $none;
|
||
|
|
||
|
public static function none(): Colour {
|
||
|
return self::$none;
|
||
|
}
|
||
|
|
||
|
private const MSZ_INHERIT = 0x40000000;
|
||
|
|
||
|
public static function fromMisuzu(int $raw): Colour {
|
||
|
if($raw & self::MSZ_INHERIT)
|
||
|
return self::$none;
|
||
|
return ColourRGB::fromRawRGB($raw);
|
||
|
}
|
||
|
|
||
|
public static function parse(?string $value): Colour {
|
||
|
if($value === null)
|
||
|
return self::$none;
|
||
|
|
||
|
$value = strtolower(trim($value));
|
||
|
if($value === '' || $value === 'inherit')
|
||
|
return self::$none;
|
||
|
|
||
|
if(ctype_alpha($value))
|
||
|
return new ColourNamed($value);
|
||
|
|
||
|
if($value[0] === '#') {
|
||
|
$value = substr($value, 1);
|
||
|
|
||
|
if(ctype_xdigit($value)) {
|
||
|
$length = strlen($value);
|
||
|
|
||
|
if($length === 3) {
|
||
|
$value = $value[0] . $value[0] . $value[1] . $value[1] . $value[2] . $value[2];
|
||
|
$length *= 2;
|
||
|
} elseif($length === 4) {
|
||
|
$value = $value[0] . $value[0] . $value[1] . $value[1] . $value[2] . $value[2] . $value[3] . $value[3];
|
||
|
$length *= 2;
|
||
|
}
|
||
|
|
||
|
if($length === 6)
|
||
|
return ColourRGB::fromRawRGB(hexdec($value));
|
||
|
if($length === 8)
|
||
|
return ColourRGB::fromRawRGBA(hexdec($value));
|
||
|
}
|
||
|
|
||
|
return self::$none;
|
||
|
}
|
||
|
|
||
|
if(str_starts_with($value, 'rgb(') || str_starts_with($value, 'rgba(')) {
|
||
|
$open = strpos($value, '(');
|
||
|
if($open === false)
|
||
|
return self::$none;
|
||
|
|
||
|
$close = strpos($value, ')', $open);
|
||
|
if($close === false)
|
||
|
return self::$none;
|
||
|
|
||
|
$open += 1;
|
||
|
$value = substr($value, $open, $close - $open);
|
||
|
|
||
|
if(strpos($value, ',') === false) {
|
||
|
// todo: support comma-less syntax
|
||
|
return self::$none;
|
||
|
} else {
|
||
|
$value = explode(',', $value, 4);
|
||
|
$parts = count($value);
|
||
|
if($parts !== 3 && $parts !== 4)
|
||
|
return self::$none;
|
||
|
|
||
|
$value[0] = (int)trim($value[0]);
|
||
|
$value[1] = (int)trim($value[1]);
|
||
|
$value[2] = (int)trim($value[2]);
|
||
|
$value[3] = (float)trim($value[3] ?? '1');
|
||
|
}
|
||
|
|
||
|
return new ColourRGB(...$value);
|
||
|
}
|
||
|
|
||
|
if(str_starts_with($value, 'hsl(') || str_starts_with($value, 'hsla(')) {
|
||
|
$open = strpos($value, '(');
|
||
|
if($open === false)
|
||
|
return self::$none;
|
||
|
|
||
|
$close = strpos($value, ')', $open);
|
||
|
if($close === false)
|
||
|
return self::$none;
|
||
|
|
||
|
$open += 1;
|
||
|
$value = substr($value, $open, $close - $open);
|
||
|
|
||
|
if(strpos($value, ',') === false) {
|
||
|
// todo: support comma-less syntax
|
||
|
return self::$none;
|
||
|
} else {
|
||
|
$value = explode(',', $value, 4);
|
||
|
$parts = count($value);
|
||
|
if($parts !== 3 && $parts !== 4)
|
||
|
return self::$none;
|
||
|
|
||
|
if(!str_ends_with($value[1], '%') || !str_ends_with($value[2], '%'))
|
||
|
return self::$none;
|
||
|
|
||
|
$value[1] = (float)substr($value[1], 0, -1);
|
||
|
$value[2] = (float)substr($value[2], 0, -1);
|
||
|
|
||
|
if($value[1] < 0 || $value[1] > 100 || $value[2] < 0 || $value[2] > 100)
|
||
|
return self::$none;
|
||
|
|
||
|
$value[1] /= 100.0;
|
||
|
$value[2] /= 100.0;
|
||
|
|
||
|
if(ctype_digit($value[0])) {
|
||
|
$value[0] = (float)$value[0];
|
||
|
} else {
|
||
|
if(str_ends_with($value[0], 'deg')) {
|
||
|
$value[0] = (float)substr($value[0], 0, -3);
|
||
|
} elseif(str_ends_with($value[0], 'grad')) {
|
||
|
$value[0] = 0.9 * (float)substr($value[0], 0, -4);
|
||
|
} elseif(str_ends_with($value[0], 'rad')) {
|
||
|
$value[0] = rad2deg((float)substr($value[0], 0, -3));
|
||
|
} elseif(str_ends_with($value[0], 'turn')) {
|
||
|
$value[0] = 360.0 * ((float)substr($value[0], 0, -4));
|
||
|
} else {
|
||
|
return self::$none;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$value[3] = (float)trim($value[3] ?? '1');
|
||
|
}
|
||
|
|
||
|
return new ColourHSL(...$value);
|
||
|
}
|
||
|
|
||
|
return self::$none;
|
||
|
}
|
||
|
|
||
|
public static function init(): void {
|
||
|
self::$none = new ColourNull;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Colour::init();
|