Файл: vendor/intervention/image/src/Colors/Rgb/Colorspace.php
Строк: 287
<?php
declare(strict_types=1);
namespace InterventionImageColorsRgb;
use InterventionImageColorsAbstractColorspace;
use InterventionImageColorsCmykColor as CmykColor;
use InterventionImageColorsHslColor as HslColor;
use InterventionImageColorsHsvColor as HsvColor;
use InterventionImageColorsOklabColor as OklabColor;
use InterventionImageColorsOklabColorspace as Oklab;
use InterventionImageColorsOklchColor as OklchColor;
use InterventionImageColorsRgbColor as RgbColor;
use InterventionImageColorsRgbDecodersHexColorDecoder;
use InterventionImageExceptionsColorException;
use InterventionImageExceptionsDriverException;
use InterventionImageExceptionsInvalidArgumentException;
use InterventionImageExceptionsNotSupportedException;
use InterventionImageInputHandler;
use InterventionImageInterfacesColorChannelInterface;
use InterventionImageInterfacesColorInterface;
use TypeError;
class Colorspace extends AbstractColorspace
{
/**
* Channel class names of colorspace.
*
* @var array<string>
*/
public static array $channels = [
ChannelsRed::class,
ChannelsGreen::class,
ChannelsBlue::class,
ChannelsAlpha::class,
];
/**
* {@inheritdoc}
*
* @see ColorspaceInterface::colorFromNormalized()
*
* @throws InvalidArgumentException
*/
public static function colorFromNormalized(array $normalized): RgbColor
{
if (!in_array(count($normalized), [3, 4])) {
throw new InvalidArgumentException('Number of color channels must be 3 or 4 for ' . static::class);
}
// add alpha value if missing
$normalized = count($normalized) === 3 ? array_pad($normalized, 4, 1) : $normalized;
return new Color(...array_map(
function (string $channel, null|float $normalized) {
try {
return $channel::fromNormalized($normalized);
} catch (TypeError $e) {
throw new InvalidArgumentException(
'Normalized color value must be in range 0 to 1',
previous: $e,
);
}
},
self::$channels,
$normalized,
));
}
/**
* {@inheritdoc}
*
* @see ColorspaceInterface::importColor()
*
* @throws ColorException
*/
public function importColor(ColorInterface $color): RgbColor
{
return match ($color::class) {
CmykColor::class => $this->importCmykColor($color),
HsvColor::class => $this->importHsvColor($color),
HslColor::class => $this->importHslColor($color),
OklabColor::class => $this->importOklabColor($color),
OklchColor::class => $this->importOklchColor($color),
NamedColor::class => $this->importNamedColor($color),
RgbColor::class => $color,
default => throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
),
};
}
/**
* @throws ColorException
*/
private function importCmykColor(CmykColor $color): RgbColor
{
try {
return new Color(
(int) (255 * (1 - $color->cyan()->normalized()) * (1 - $color->key()->normalized())),
(int) (255 * (1 - $color->magenta()->normalized()) * (1 - $color->key()->normalized())),
(int) (255 * (1 - $color->yellow()->normalized()) * (1 - $color->key()->normalized())),
$color->alpha()->normalized(),
);
} catch (InvalidArgumentException $e) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
previous: $e,
);
}
}
/**
* Import given HSV color to RGB color space.
*
* @throws ColorException
*/
private function importHsvColor(HsvColor $color): RgbColor
{
$chroma = $color->value()->normalized() * $color->saturation()->normalized();
$hue = $color->hue()->normalized() * 6;
$x = $chroma * (1 - abs(fmod($hue, 2) - 1));
// connect channel values
$values = match (true) {
$hue < 1 => [$chroma, $x, 0],
$hue < 2 => [$x, $chroma, 0],
$hue < 3 => [0, $chroma, $x],
$hue < 4 => [0, $x, $chroma],
$hue < 5 => [$x, 0, $chroma],
default => [$chroma, 0, $x],
};
// add to each value
$values = array_map(
fn(float|int $value): float => max(0.0, min(1.0, $value + $color->value()->normalized() - $chroma)),
$values,
);
$values[] = $color->alpha()->normalized(); // append alpha channel value
try {
return $this->colorFromNormalized($values);
} catch (InvalidArgumentException $e) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
previous: $e,
);
}
}
/**
* Import given HSL color to RGB color space.
*
* @throws ColorException
*/
private function importHslColor(HslColor $color): RgbColor
{
// normalized values of hsl channels
[$h, $s, $l] = array_map(
fn(ColorChannelInterface $channel): float => $channel->normalized(),
$color->channels(),
);
$c = (1 - abs(2 * $l - 1)) * $s;
$x = $c * (1 - abs(fmod($h * 6, 2) - 1));
$m = $l - $c / 2;
$values = match (true) {
$h < 1 / 6 => [$c, $x, 0],
$h < 2 / 6 => [$x, $c, 0],
$h < 3 / 6 => [0, $c, $x],
$h < 4 / 6 => [0, $x, $c],
$h < 5 / 6 => [$x, 0, $c],
default => [$c, 0, $x],
};
$values = array_map(fn(float|int $value): float => max(0.0, min(1.0, $value + $m)), $values);
$values[] = $color->alpha()->normalized(); // append alpha channel value
try {
$color = $this->colorFromNormalized($values);
} catch (InvalidArgumentException $e) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
previous: $e,
);
}
return $color;
}
/**
* Import given OKLAB color to RGB color space.
*
* @throws ColorException
*/
private function importOklabColor(OklabColor $color): RgbColor
{
$linearToRgb = function (float $c): float {
$c = max(0.0, min(1.0, $c));
if ($c <= 0.0031308) {
return 12.92 * $c;
}
return 1.055 * ($c ** (1 / 2.4)) - 0.055;
};
$l = $color->lightness()->value() + 0.3963377774 * $color->a()->value() + 0.2158037573 * $color->b()->value();
$m = $color->lightness()->value() - 0.1055613458 * $color->a()->value() - 0.0638541728 * $color->b()->value();
$s = $color->lightness()->value() - 0.0894841775 * $color->a()->value() - 1.2914855480 * $color->b()->value();
$l = $l ** 3;
$m = $m ** 3;
$s = $s ** 3;
$r = +4.0767416621 * $l - 3.3077115913 * $m + 0.2309699292 * $s;
$g = -1.2684380046 * $l + 2.6097574011 * $m - 0.3413193965 * $s;
$b = -0.0041960863 * $l - 0.7034186147 * $m + 1.7076147010 * $s;
$r = $linearToRgb($r);
$g = $linearToRgb($g);
$b = $linearToRgb($b);
try {
return new Color(
(int) round($r * 255),
(int) round($g * 255),
(int) round($b * 255),
$color->alpha()->normalized(),
);
} catch (InvalidArgumentException $e) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
previous: $e,
);
}
}
/**
* Import given OKLCH color to RGB color space.
*
* @throws ColorException
*/
private function importOklchColor(OklchColor $color): RgbColor
{
try {
$color = $color->toColorspace(Oklab::class);
} catch (InvalidArgumentException $e) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
previous: $e,
);
}
if (!$color instanceof OklabColor) {
throw new ColorException(
'Failed to import color ' . $color::class . ' to ' . $this::class,
);
}
return $this->importOklabColor($color);
}
/**
* Import given named color to RGB color space.
*
* @throws ColorException
*/
private function importNamedColor(NamedColor $color): RgbColor
{
try {
$output = InputHandler::usingDecoders([
HexColorDecoder::class,
])->handle($color->toHex());
} catch (InvalidArgumentException | NotSupportedException | DriverException $e) {
throw new ColorException('Failed to import named color to rgb color space', previous: $e);
}
return $output instanceof RgbColor
? $output
: throw new ColorException('Failed to import named color to rgb color space');
}
}