2023-01-02 03:23:48 +00:00
|
|
|
<?php
|
2023-01-02 03:24:41 +00:00
|
|
|
// Colour.php
|
|
|
|
// Created: 2023-01-02
|
|
|
|
// Updated: 2023-01-02
|
|
|
|
|
2023-01-02 03:23:48 +00:00
|
|
|
namespace Index\Colour;
|
|
|
|
|
|
|
|
use Stringable;
|
2023-01-02 22:22:37 +00:00
|
|
|
|
2023-01-02 03:23:48 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
private const READABILITY_THRESHOLD = 186.0;
|
2023-01-02 03:23:48 +00:00
|
|
|
private const LUMINANCE_WEIGHT_RED = .299;
|
|
|
|
private const LUMINANCE_WEIGHT_GREEN = .587;
|
|
|
|
private const LUMINANCE_WEIGHT_BLUE = .114;
|
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
// luminance shit might need further review
|
|
|
|
|
2023-01-02 03:23:48 +00:00
|
|
|
public function getLuminance(): float {
|
|
|
|
return self::LUMINANCE_WEIGHT_RED * $this->getRed()
|
|
|
|
+ self::LUMINANCE_WEIGHT_GREEN * $this->getGreen()
|
|
|
|
+ self::LUMINANCE_WEIGHT_BLUE * $this->getBlue();
|
|
|
|
}
|
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
public function isLight(): bool {
|
2023-01-02 03:23:48 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-01-02 22:37:10 +00:00
|
|
|
public static function toMisuzu(Colour $colour): int {
|
|
|
|
if($colour->shouldInherit())
|
|
|
|
return self::MSZ_INHERIT;
|
|
|
|
return ($colour->getRed() << 16) | ($colour->getGreen() << 8) | $colour->getBlue();
|
|
|
|
}
|
|
|
|
|
2023-01-02 03:23:48 +00:00
|
|
|
public static function parse(?string $value): Colour {
|
|
|
|
if($value === null)
|
|
|
|
return self::$none;
|
|
|
|
|
|
|
|
$value = strtolower(trim($value));
|
|
|
|
if($value === '' || $value === 'inherit')
|
|
|
|
return self::$none;
|
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
if(ctype_alpha($value) && ColourNamed::isValidName($value))
|
2023-01-02 03:23:48 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
for($i = 0; $i < $parts; ++$i)
|
|
|
|
$value[$i] = trim($value[$i]);
|
|
|
|
|
|
|
|
if(str_ends_with($value[1], '%'))
|
|
|
|
$value[1] = substr($value[1], 0, -1);
|
|
|
|
|
|
|
|
if(str_ends_with($value[2], '%'))
|
|
|
|
$value[2] = substr($value[2], 0, -1);
|
2023-01-02 03:23:48 +00:00
|
|
|
|
2023-01-02 22:22:37 +00:00
|
|
|
$value[1] = (float)$value[1];
|
|
|
|
$value[2] = (float)$value[2];
|
2023-01-02 03:23:48 +00:00
|
|
|
|
|
|
|
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')) {
|
2023-01-02 22:22:37 +00:00
|
|
|
$value[0] = round(rad2deg((float)substr($value[0], 0, -3)));
|
2023-01-02 03:23:48 +00:00
|
|
|
} 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();
|