getRed() + self::LUMINANCE_WEIGHT_GREEN * $this->getGreen() + self::LUMINANCE_WEIGHT_BLUE * $this->getBlue(); } public function isLight(): 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 toMisuzu(Colour $colour): int { if($colour->shouldInherit()) return self::MSZ_INHERIT; return ($colour->getRed() << 16) | ($colour->getGreen() << 8) | $colour->getBlue(); } 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) && ColourNamed::isValidName($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; 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); $value[1] = (float)$value[1]; $value[2] = (float)$value[2]; 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] = round(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();