index/src/Colour/Colour.php

176 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();