refactor: susun semula struktur folder — Laravel source ke src/
This commit is contained in:
21
vendor/intervention/image/LICENSE
vendored
Normal file
21
vendor/intervention/image/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-present Oliver Vogel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
51
vendor/intervention/image/composer.json
vendored
Normal file
51
vendor/intervention/image/composer.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"description": "PHP Image Processing",
|
||||
"homepage": "https://image.intervention.io",
|
||||
"keywords": [
|
||||
"image",
|
||||
"gd",
|
||||
"imagick",
|
||||
"watermark",
|
||||
"thumbnail",
|
||||
"resize"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"squizlabs/php_codesniffer": "^4",
|
||||
"slevomat/coding-standard": "~8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-exif": "Recommended to be able to read EXIF data properly."
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
90
vendor/intervention/image/readme.md
vendored
Normal file
90
vendor/intervention/image/readme.md
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
# Intervention Image
|
||||
## PHP Image Processing
|
||||
|
||||
[](https://packagist.org/packages/intervention/image)
|
||||
[](https://github.com/Intervention/image/actions)
|
||||
[](https://packagist.org/packages/intervention/image/stats)
|
||||
[](https://ko-fi.com/interventionphp)
|
||||
|
||||
Intervention Image is a **PHP image processing library** that provides a simple
|
||||
and expressive way to create, edit, and compose images. It comes with a universal
|
||||
interface for the popular PHP image manipulation extensions. You can
|
||||
choose between the GD library, Imagick or libvips as the base layer for all operations.
|
||||
|
||||
- Fluent interface for common image editing tasks
|
||||
- Interchangeable driver architecture with support for **GD, Imagick and libvips**
|
||||
- Support for animated images with all drivers
|
||||
- Framework-agnostic
|
||||
|
||||
## Installation
|
||||
|
||||
Install this library using [Composer](https://getcomposer.org). Simply request the package with the following command:
|
||||
|
||||
```bash
|
||||
composer require intervention/image
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
Learn the [basics](https://image.intervention.io/v4/basics/instantiation/) on
|
||||
how to use Intervention Image and more with the [official documentation](https://image.intervention.io/v4/).
|
||||
|
||||
## Code Examples
|
||||
|
||||
```php
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
|
||||
use Intervention\Image\Alignment;
|
||||
use Intervention\Image\Color;
|
||||
use Intervention\Image\Format;
|
||||
|
||||
// create image manager instance using the preferred driver
|
||||
$manager = ImageManager::usingDriver(GdDriver::class);
|
||||
|
||||
// read image data from path
|
||||
$image = $manager->decodePath('images/example.webp');
|
||||
|
||||
// scale image by height
|
||||
$image->scale(height: 300);
|
||||
|
||||
// insert a watermark
|
||||
$image->insert('images/watermark.png', alignment: Alignment::BOTTOM_RIGHT);
|
||||
|
||||
// encode edited image
|
||||
$encoded = $image->encodeUsingFormat(Format::JPEG, quality: 65);
|
||||
|
||||
// save encoded image
|
||||
$encoded->save('images/example.jpg');
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
Before you begin with the installation make sure that your server environment
|
||||
supports the following requirements.
|
||||
|
||||
- PHP >= 8.3
|
||||
- Mbstring PHP Extension
|
||||
- Image Processing PHP Extension (GD, Imagick or libvips)
|
||||
|
||||
## Supported Image Libraries
|
||||
|
||||
Depending on your environment Intervention Image lets you choose between
|
||||
different image processing extensions.
|
||||
|
||||
- GD Library
|
||||
- Imagick PHP extension
|
||||
- [libvips](https://github.com/Intervention/image-driver-vips)
|
||||
|
||||
## Security
|
||||
|
||||
If you discover any security related issues, please email oliver@intervention.io directly.
|
||||
|
||||
## Authors
|
||||
|
||||
This library is developed and maintained by [Oliver Vogel](https://intervention.io)
|
||||
|
||||
Thanks to the community of [contributors](https://github.com/Intervention/image/graphs/contributors) who have helped to improve this project.
|
||||
|
||||
## License
|
||||
|
||||
Intervention Image is licensed under the [MIT License](LICENSE).
|
||||
238
vendor/intervention/image/src/Alignment.php
vendored
Normal file
238
vendor/intervention/image/src/Alignment.php
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Error;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
|
||||
enum Alignment: string
|
||||
{
|
||||
case TOP = 'top';
|
||||
case TOP_RIGHT = 'top-right';
|
||||
case RIGHT = 'right';
|
||||
case BOTTOM_RIGHT = 'bottom-right';
|
||||
case BOTTOM = 'bottom';
|
||||
case BOTTOM_LEFT = 'bottom-left';
|
||||
case LEFT = 'left';
|
||||
case TOP_LEFT = 'top-left';
|
||||
case CENTER = 'center';
|
||||
|
||||
/**
|
||||
* Create position from given identifier.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(string|self $identifier): self
|
||||
{
|
||||
if ($identifier instanceof self) {
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
try {
|
||||
$position = self::from(strtolower($identifier));
|
||||
} catch (Error) {
|
||||
$position = match (strtolower($identifier)) {
|
||||
'top-center',
|
||||
'top_center',
|
||||
'topcenter',
|
||||
'center-top',
|
||||
'center_top',
|
||||
'centertop',
|
||||
'top-middle',
|
||||
'top_middle',
|
||||
'topmiddle',
|
||||
'middle-top',
|
||||
'middle_top',
|
||||
'middletop' => self::TOP,
|
||||
|
||||
'top_right',
|
||||
'topright',
|
||||
'right-top',
|
||||
'right_top',
|
||||
'righttop' => self::TOP_RIGHT,
|
||||
|
||||
'right-center',
|
||||
'right_center',
|
||||
'rightcenter',
|
||||
'center-right',
|
||||
'center_right',
|
||||
'centerright',
|
||||
'right-middle',
|
||||
'right_middle',
|
||||
'rightmiddle',
|
||||
'middle-right',
|
||||
'middle_right',
|
||||
'middleright' => self::RIGHT,
|
||||
|
||||
'bottom_right',
|
||||
'bottomright',
|
||||
'right-bottom',
|
||||
'right_bottom',
|
||||
'rightbottom' => self::BOTTOM_RIGHT,
|
||||
|
||||
'bottom-center',
|
||||
'bottom_center',
|
||||
'bottomcenter',
|
||||
'center-bottom',
|
||||
'center_bottom',
|
||||
'centerbottom',
|
||||
'bottom-middle',
|
||||
'bottom_middle',
|
||||
'bottommiddle',
|
||||
'middle-bottom',
|
||||
'middle_bottom',
|
||||
'middlebottom' => self::BOTTOM,
|
||||
|
||||
'bottom_left',
|
||||
'bottomleft',
|
||||
'left-bottom',
|
||||
'left_bottom',
|
||||
'leftbottom' => self::BOTTOM_LEFT,
|
||||
|
||||
'left-center',
|
||||
'left_center',
|
||||
'leftcenter',
|
||||
'center-left',
|
||||
'center_left',
|
||||
'centerleft',
|
||||
'left-middle',
|
||||
'left_middle',
|
||||
'leftmiddle',
|
||||
'middle-left',
|
||||
'middle_left',
|
||||
'middleleft' => self::LEFT,
|
||||
|
||||
'top_left',
|
||||
'topleft',
|
||||
'left-top',
|
||||
'left_top',
|
||||
'lefttop' => self::TOP_LEFT,
|
||||
|
||||
'middle',
|
||||
'center-center',
|
||||
'center_center',
|
||||
'centercenter',
|
||||
'center-middle',
|
||||
'center_middle',
|
||||
'centermiddle',
|
||||
'middle-center',
|
||||
'middle_center',
|
||||
'middlecenter' => self::CENTER,
|
||||
|
||||
default => throw new InvalidArgumentException(
|
||||
'Unable to create ' . self::class . ' from "' . $identifier . '"',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create position from given identifier or return null on failure.
|
||||
*/
|
||||
public static function tryCreate(string|self $identifier): ?self
|
||||
{
|
||||
try {
|
||||
return self::create($identifier);
|
||||
} catch (InvalidArgumentException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current alignment by adjusting only the horizontal axis to the specified value.
|
||||
*/
|
||||
public function alignHorizontally(string|self $alignment): self
|
||||
{
|
||||
// handle "leftish" alignments
|
||||
if (in_array($alignment, [self::LEFT, self::BOTTOM_LEFT, self::TOP_LEFT])) {
|
||||
return match ($this) {
|
||||
self::TOP, self::TOP_RIGHT, self::TOP_LEFT => self::TOP_LEFT,
|
||||
self::BOTTOM, self::BOTTOM_RIGHT, self::BOTTOM_LEFT => self::BOTTOM_LEFT,
|
||||
self::CENTER, self::LEFT, self::RIGHT => self::LEFT,
|
||||
};
|
||||
}
|
||||
|
||||
// handle "rightish" alignments
|
||||
if (in_array($alignment, [self::RIGHT, self::TOP_RIGHT, self::BOTTOM_RIGHT])) {
|
||||
return match ($this) {
|
||||
self::TOP, self::TOP_RIGHT, self::TOP_LEFT => self::TOP_RIGHT,
|
||||
self::BOTTOM, self::BOTTOM_RIGHT, self::BOTTOM_LEFT => self::BOTTOM_RIGHT,
|
||||
self::CENTER, self::LEFT, self::RIGHT => self::RIGHT,
|
||||
};
|
||||
}
|
||||
|
||||
// handle centering
|
||||
if (in_array($alignment, [self::CENTER, self::TOP, self::BOTTOM])) {
|
||||
return match ($this) {
|
||||
self::TOP, self::TOP_RIGHT, self::TOP_LEFT => self::TOP,
|
||||
self::BOTTOM, self::BOTTOM_RIGHT, self::BOTTOM_LEFT => self::BOTTOM,
|
||||
self::CENTER, self::LEFT, self::RIGHT => self::CENTER,
|
||||
};
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current alignment by adjusting only the vertical axis to the specified value.
|
||||
*/
|
||||
public function alignVertically(string|self $alignment): self
|
||||
{
|
||||
// handle "bottomish" alignments
|
||||
if (in_array($alignment, [self::BOTTOM, self::BOTTOM_RIGHT, self::BOTTOM_LEFT])) {
|
||||
return match ($this) {
|
||||
self::LEFT, self::TOP_LEFT, self::BOTTOM_LEFT => self::BOTTOM_LEFT,
|
||||
self::RIGHT, self::TOP_RIGHT, self::BOTTOM_RIGHT => self::BOTTOM_RIGHT,
|
||||
self::CENTER, self::TOP, self::BOTTOM => self::BOTTOM,
|
||||
};
|
||||
}
|
||||
|
||||
// handle "topish" alignments
|
||||
if (in_array($alignment, [self::TOP, self::TOP_RIGHT, self::TOP_LEFT])) {
|
||||
return match ($this) {
|
||||
self::LEFT, self::TOP_LEFT, self::BOTTOM_LEFT => self::TOP_LEFT,
|
||||
self::RIGHT, self::TOP_RIGHT, self::BOTTOM_RIGHT => self::TOP_RIGHT,
|
||||
self::CENTER, self::TOP, self::BOTTOM => self::TOP,
|
||||
};
|
||||
}
|
||||
|
||||
// handle centering
|
||||
if (in_array($alignment, [self::CENTER, self::RIGHT, self::LEFT])) {
|
||||
return match ($this) {
|
||||
self::LEFT, self::TOP_LEFT, self::BOTTOM_LEFT => self::LEFT,
|
||||
self::RIGHT, self::TOP_RIGHT, self::BOTTOM_RIGHT => self::RIGHT,
|
||||
self::CENTER, self::TOP, self::BOTTOM => self::CENTER,
|
||||
};
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the horizontal alignment.
|
||||
*/
|
||||
public function horizontal(): self
|
||||
{
|
||||
return match ($this) {
|
||||
self::TOP, self::CENTER, self::BOTTOM => self::CENTER,
|
||||
self::RIGHT, self::TOP_RIGHT, self::BOTTOM_RIGHT => self::RIGHT,
|
||||
self::LEFT, self::TOP_LEFT, self::BOTTOM_LEFT => self::LEFT,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only the vertical alignment.
|
||||
*/
|
||||
public function vertical(): self
|
||||
{
|
||||
return match ($this) {
|
||||
self::CENTER, self::RIGHT, self::LEFT => self::CENTER,
|
||||
self::TOP, self::TOP_RIGHT, self::TOP_LEFT => self::TOP,
|
||||
self::BOTTOM, self::BOTTOM_RIGHT, self::BOTTOM_LEFT => self::BOTTOM,
|
||||
};
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Analyzers/ColorspaceAnalyzer.php
vendored
Normal file
12
vendor/intervention/image/src/Analyzers/ColorspaceAnalyzer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class ColorspaceAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Analyzers/HeightAnalyzer.php
vendored
Normal file
12
vendor/intervention/image/src/Analyzers/HeightAnalyzer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class HeightAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
//
|
||||
}
|
||||
18
vendor/intervention/image/src/Analyzers/PixelColorAnalyzer.php
vendored
Normal file
18
vendor/intervention/image/src/Analyzers/PixelColorAnalyzer.php
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class PixelColorAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
public function __construct(
|
||||
public int $x,
|
||||
public int $y,
|
||||
public int $frame = 0
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
||||
17
vendor/intervention/image/src/Analyzers/PixelColorsAnalyzer.php
vendored
Normal file
17
vendor/intervention/image/src/Analyzers/PixelColorsAnalyzer.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class PixelColorsAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
public function __construct(
|
||||
public int $x,
|
||||
public int $y
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Analyzers/ProfileAnalyzer.php
vendored
Normal file
12
vendor/intervention/image/src/Analyzers/ProfileAnalyzer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class ProfileAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Analyzers/ResolutionAnalyzer.php
vendored
Normal file
12
vendor/intervention/image/src/Analyzers/ResolutionAnalyzer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class ResolutionAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Analyzers/WidthAnalyzer.php
vendored
Normal file
12
vendor/intervention/image/src/Analyzers/WidthAnalyzer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Analyzers;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableAnalyzer;
|
||||
|
||||
class WidthAnalyzer extends SpecializableAnalyzer
|
||||
{
|
||||
//
|
||||
}
|
||||
174
vendor/intervention/image/src/AnimationFactory.php
vendored
Normal file
174
vendor/intervention/image/src/AnimationFactory.php
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Error;
|
||||
use Intervention\Gif\DisposalMethod;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\FilesystemException;
|
||||
use Intervention\Image\Interfaces\AnimationFactoryInterface;
|
||||
use Intervention\Image\Interfaces\DriverInterface;
|
||||
use Intervention\Image\Interfaces\FrameInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
class AnimationFactory implements AnimationFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Current frame number.
|
||||
*/
|
||||
protected int $currentFrameNumber = 0;
|
||||
|
||||
/**
|
||||
* Image sources of animation frames.
|
||||
*
|
||||
* @var array<mixed>
|
||||
*/
|
||||
protected array $sources = [];
|
||||
|
||||
/**
|
||||
* Frame delays of animation frames in seconds.
|
||||
*
|
||||
* @var array<float>
|
||||
*/
|
||||
protected array $delays = [];
|
||||
|
||||
/**
|
||||
* Frame processing call names.
|
||||
*
|
||||
* @var array<null|string>
|
||||
*/
|
||||
protected array $processingCalls = [];
|
||||
|
||||
/**
|
||||
* Frame processing arguments of calls.
|
||||
*
|
||||
* @var array<null|array<mixed>>
|
||||
*/
|
||||
protected array $processingArguments = [];
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $width,
|
||||
protected int $height,
|
||||
null|callable $animation = null,
|
||||
) {
|
||||
if (is_callable($animation)) {
|
||||
$animation($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnimationFactoryInterface::build()
|
||||
*/
|
||||
public static function build(
|
||||
int $width,
|
||||
int $height,
|
||||
callable $animation,
|
||||
DriverInterface $driver,
|
||||
): ImageInterface {
|
||||
return (new self($width, $height, $animation))->image($driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnimationFactoryInterface::add()
|
||||
*/
|
||||
public function add(mixed $source, float $delay = 1): AnimationFactoryInterface
|
||||
{
|
||||
$this->currentFrameNumber++;
|
||||
|
||||
$this->sources[$this->currentFrameNumber] = $source;
|
||||
$this->delays[$this->currentFrameNumber] = $delay;
|
||||
$this->processingCalls[$this->currentFrameNumber] = null;
|
||||
$this->processingArguments[$this->currentFrameNumber] = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnimationFactoryInterface::image()
|
||||
*/
|
||||
public function image(DriverInterface $driver): ImageInterface
|
||||
{
|
||||
if (count($this->sources) === 0) {
|
||||
return $driver->createImage($this->width, $this->height);
|
||||
}
|
||||
|
||||
$frames = array_map(
|
||||
$this->buildFrame(...),
|
||||
array_fill(0, count($this->sources), $driver),
|
||||
$this->sources,
|
||||
$this->delays,
|
||||
$this->processingCalls,
|
||||
$this->processingArguments,
|
||||
);
|
||||
|
||||
return new Image($driver, $driver->createCore($frames));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build frame from given image source and delay.
|
||||
*
|
||||
* @param null|array<mixed> $processingArguments
|
||||
*/
|
||||
private function buildFrame(
|
||||
DriverInterface $driver,
|
||||
mixed $source,
|
||||
float $delay,
|
||||
?string $processingCall = null,
|
||||
?array $processingArguments = null,
|
||||
): FrameInterface {
|
||||
try {
|
||||
// try to decode image source
|
||||
$image = $driver->decodeImage($source);
|
||||
} catch (DecoderException | FilesystemException) {
|
||||
// create empty image with colored background
|
||||
$image = $driver->createImage($this->width, $this->height)
|
||||
->fill($driver->decodeColor($source));
|
||||
}
|
||||
|
||||
// adjust size if necessary
|
||||
if ($image->width() !== $this->width || $image->height() !== $this->height) {
|
||||
$image->cover($this->width, $this->height);
|
||||
}
|
||||
|
||||
// apply processing call if available
|
||||
if ($processingCall !== null) {
|
||||
call_user_func_array([$image, $processingCall], $processingArguments);
|
||||
}
|
||||
|
||||
// return ready-made frame with all attributes
|
||||
return $image
|
||||
->core()
|
||||
->first()
|
||||
->setDelay($delay)
|
||||
->setDisposalMethod(DisposalMethod::BACKGROUND->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect processing calls on frame images.
|
||||
*
|
||||
* @param array<null|array<mixed>> $arguments
|
||||
* @throws Error
|
||||
*/
|
||||
public function __call(string $name, array $arguments): self
|
||||
{
|
||||
if (!method_exists(Image::class, $name)) {
|
||||
throw new Error('Call to undefined method ' . Image::class . '::' . $name . '()');
|
||||
}
|
||||
|
||||
$this->processingCalls[$this->currentFrameNumber] = $name;
|
||||
$this->processingArguments[$this->currentFrameNumber] = $arguments;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
234
vendor/intervention/image/src/Collection.php
vendored
Normal file
234
vendor/intervention/image/src/Collection.php
vendored
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Intervention\Image\Interfaces\CollectionInterface;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use Traversable;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* @implements IteratorAggregate<int|string, mixed>
|
||||
*/
|
||||
class Collection implements CollectionInterface, IteratorAggregate, Countable
|
||||
{
|
||||
/**
|
||||
* Create new collection object.
|
||||
*
|
||||
* @param array<int|string, mixed> $items
|
||||
*/
|
||||
public function __construct(protected array $items = [])
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Static constructor.
|
||||
*
|
||||
* @param array<int|string, mixed> $items
|
||||
* @return self<int|string, mixed>
|
||||
*/
|
||||
public static function create(array $items = []): self
|
||||
{
|
||||
return new self($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::has()
|
||||
*/
|
||||
public function has(int|string $key): bool
|
||||
{
|
||||
return array_key_exists($key, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::set()
|
||||
*/
|
||||
public function set(int|string $key, mixed $item): self
|
||||
{
|
||||
$this->items[$key] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns Iterator.
|
||||
*
|
||||
* @return Traversable<int|string, mixed>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::toArray()
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count items in collection.
|
||||
*
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append new item to collection.
|
||||
*
|
||||
* @return CollectionInterface<int|string, mixed>
|
||||
*/
|
||||
public function push(mixed $item): CollectionInterface
|
||||
{
|
||||
$this->items[] = $item;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first item in collection.
|
||||
*/
|
||||
public function first(): mixed
|
||||
{
|
||||
if (count($this->items) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last item in collection.
|
||||
*/
|
||||
public function last(): mixed
|
||||
{
|
||||
if (count($this->items) === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return end($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return item at given position starting at 0.
|
||||
*/
|
||||
public function at(int $key = 0, mixed $default = null): mixed
|
||||
{
|
||||
if ($this->count() === 0) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$positions = array_values($this->items);
|
||||
if (!array_key_exists($key, $positions)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $positions[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::get()
|
||||
*/
|
||||
public function get(int|string $query, mixed $default = null): mixed
|
||||
{
|
||||
if ($this->count() === 0) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_int($query) && array_key_exists($query, $this->items)) {
|
||||
return $this->items[$query];
|
||||
}
|
||||
|
||||
if (is_string($query) && !str_contains($query, '.')) {
|
||||
return array_key_exists($query, $this->items) ? $this->items[$query] : $default;
|
||||
}
|
||||
|
||||
$query = explode('.', (string) $query);
|
||||
|
||||
$result = $default;
|
||||
$items = $this->items;
|
||||
foreach ($query as $key) {
|
||||
if (!is_array($items) || !array_key_exists($key, $items)) {
|
||||
$result = $default;
|
||||
break;
|
||||
}
|
||||
|
||||
$result = $items[$key];
|
||||
$items = $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::map()
|
||||
*/
|
||||
public function map(callable $callback): self
|
||||
{
|
||||
|
||||
return new self(
|
||||
array_map(
|
||||
fn(mixed $item) => $callback($item),
|
||||
$this->items,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::map()
|
||||
*/
|
||||
public function filter(callable $callback): self
|
||||
{
|
||||
return new self(
|
||||
array_filter(
|
||||
$this->items,
|
||||
fn(mixed $item) => $callback($item),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::clear()
|
||||
*/
|
||||
public function clear(): CollectionInterface
|
||||
{
|
||||
$this->items = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::slice()
|
||||
*/
|
||||
public function slice(int $offset, ?int $length = null): CollectionInterface
|
||||
{
|
||||
$this->items = array_slice($this->items, $offset, $length);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
173
vendor/intervention/image/src/Color.php
vendored
Normal file
173
vendor/intervention/image/src/Color.php
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Cyan;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Key;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Magenta;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Yellow;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder as CmykStringColorDecoder;
|
||||
use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder as HslStringColorDecoder;
|
||||
use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder as HsvStringColorDecoder;
|
||||
use Intervention\Image\Colors\Oklab\Decoders\StringColorDecoder as OklabStringColorDecoder;
|
||||
use Intervention\Image\Colors\Oklch\Decoders\StringColorDecoder as OklchStringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Alpha as RgbAlpha;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Alpha as CmykAlpha;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Alpha as HslAlpha;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Hue as HslHue;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Luminance;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Saturation as HslSaturation;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Alpha as HsvAlpha;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Hue;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Saturation;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Value;
|
||||
use Intervention\Image\Colors\Oklab\Channels\A;
|
||||
use Intervention\Image\Colors\Oklab\Channels\Alpha as OklabAlpha;
|
||||
use Intervention\Image\Colors\Oklab\Channels\B;
|
||||
use Intervention\Image\Colors\Oklab\Channels\Lightness as OklabLightness;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Alpha as OklchAlpha;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Chroma;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Lightness as OklchLightness;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder as RgbHexColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\NamedColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\StringColorDecoder as RgbStringColorDecoder;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
|
||||
class Color
|
||||
{
|
||||
/**
|
||||
* Parse color from string value.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): ColorInterface
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
RgbStringColorDecoder::class,
|
||||
CmykStringColorDecoder::class,
|
||||
HsvStringColorDecoder::class,
|
||||
HslStringColorDecoder::class,
|
||||
OklabStringColorDecoder::class,
|
||||
OklchStringColorDecoder::class,
|
||||
NamedColorDecoder::class,
|
||||
RgbHexColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse RGB color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof ColorInterface) {
|
||||
throw new ColorException('Result must be instance of ' . self::class . ', got ' . $color::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new RGB color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function rgb(int|Red $r, int|Green $g, int|Blue $b, float|RgbAlpha $a = 1): RgbColor
|
||||
{
|
||||
return new RgbColor($r, $g, $b, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new CMYK color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function cmyk(
|
||||
int|Cyan $c,
|
||||
int|Magenta $m,
|
||||
int|Yellow $y,
|
||||
int|Key $k,
|
||||
float|CmykAlpha $a = 1,
|
||||
): CmykColor {
|
||||
return new CmykColor($c, $m, $y, $k, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HSL color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function hsl(int|HslHue $h, int|HslSaturation $s, int|Luminance $l, float|HslAlpha $a = 1): HslColor
|
||||
{
|
||||
return new HslColor($h, $s, $l, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new HSV color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function hsv(int|Hue $h, int|Saturation $s, int|Value $v, float|HsvAlpha $a = 1): HsvColor
|
||||
{
|
||||
return new HsvColor($h, $s, $v, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new OKLAB color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function oklab(
|
||||
float|OklabLightness $l,
|
||||
float|A $a,
|
||||
float|B $b,
|
||||
float|OklabAlpha $alpha = 1,
|
||||
): OklabColor {
|
||||
return new OklabColor($l, $a, $b, $alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new OKLCH color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function oklch(
|
||||
float|OklchLightness $l,
|
||||
float|Chroma $c,
|
||||
float|Hue $h,
|
||||
float|OklchAlpha $a = 1,
|
||||
): OklchColor {
|
||||
return new OklchColor($l, $c, $h, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create transparent RGB color.
|
||||
*/
|
||||
public static function transparent(): ColorInterface
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return new RgbColor(255, 255, 255, 0);
|
||||
}
|
||||
}
|
||||
215
vendor/intervention/image/src/Colors/AbstractColor.php
vendored
Normal file
215
vendor/intervention/image/src/Colors/AbstractColor.php
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Colors\Hsl\Channels\Luminance;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Saturation;
|
||||
use Intervention\Image\Colors\Hsl\Colorspace as HslColorspace;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
use ReflectionClass;
|
||||
use Stringable;
|
||||
|
||||
abstract class AbstractColor implements ColorInterface, Stringable
|
||||
{
|
||||
/**
|
||||
* Color channels.
|
||||
*
|
||||
* @var array<ColorChannelInterface>
|
||||
*/
|
||||
protected array $channels;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::channels()
|
||||
*/
|
||||
public function channels(): array
|
||||
{
|
||||
return $this->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::channel()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function channel(string $classname): ColorChannelInterface
|
||||
{
|
||||
$channels = array_filter(
|
||||
$this->channels(),
|
||||
fn(ColorChannelInterface $channel): bool => $channel::class === $classname,
|
||||
);
|
||||
|
||||
if (count($channels) === 0) {
|
||||
throw new InvalidArgumentException('Color channel ' . $classname . ' could not be found');
|
||||
}
|
||||
|
||||
return reset($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toColorspace()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function toColorspace(string|ColorspaceInterface $colorspace): ColorInterface
|
||||
{
|
||||
if (is_string($colorspace) && !class_exists($colorspace)) {
|
||||
throw new InvalidArgumentException('Unknown color space (' . $colorspace . ') as conversion target');
|
||||
}
|
||||
|
||||
$colorspace = is_string($colorspace) ? new $colorspace() : $colorspace;
|
||||
|
||||
if (!$colorspace instanceof ColorspaceInterface) {
|
||||
throw new InvalidArgumentException('Given color space must implement ' . ColorspaceInterface::class);
|
||||
}
|
||||
|
||||
return $colorspace->importColor($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isTransparent()
|
||||
*/
|
||||
public function isTransparent(): bool
|
||||
{
|
||||
return $this->alpha()->value() < $this->alpha()->max();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isClear()
|
||||
*/
|
||||
public function isClear(): bool
|
||||
{
|
||||
return floatval($this->alpha()->value()) === 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withTransparency()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function withTransparency(float $transparency): ColorInterface
|
||||
{
|
||||
$color = clone $this;
|
||||
|
||||
$color->channels = array_map(
|
||||
fn(ColorChannelInterface $channel): ColorChannelInterface =>
|
||||
$channel instanceof AlphaChannel ? $channel::fromNormalized($transparency) : $channel,
|
||||
$this->channels
|
||||
);
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withBrightness()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function withBrightness(int $level): ColorInterface
|
||||
{
|
||||
$hsl = clone $this->toColorspace(HslColorspace::class);
|
||||
$hsl->channel(Luminance::class)->scale($level);
|
||||
|
||||
return $hsl->toColorspace($this->colorspace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withSaturation()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function withSaturation(int $level): ColorInterface
|
||||
{
|
||||
$hsl = clone $this->toColorspace(HslColorspace::class);
|
||||
$hsl->channel(Saturation::class)->scale($level);
|
||||
|
||||
return $hsl->toColorspace($this->colorspace());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withInversion()
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function withInversion(): ColorInterface
|
||||
{
|
||||
try {
|
||||
$rgb = $this->toColorspace(RgbColorspace::class);
|
||||
} catch (InvalidArgumentException) {
|
||||
throw new ColorException('Failed to invert color');
|
||||
}
|
||||
|
||||
try {
|
||||
$inverted = new \Intervention\Image\Colors\Rgb\Color(
|
||||
255 - $rgb->channel(Red::class)->value(),
|
||||
255 - $rgb->channel(Green::class)->value(),
|
||||
255 - $rgb->channel(Blue::class)->value(),
|
||||
$rgb->alpha()->normalized(),
|
||||
);
|
||||
return $inverted->toColorspace($this->colorspace());
|
||||
} catch (InvalidArgumentException) {
|
||||
throw new ColorException('Failed to invert color');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show debug info for the current color.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return array_reduce($this->channels(), function (array $result, ColorChannelInterface $item) {
|
||||
$key = strtolower((new ReflectionClass($item))->getShortName());
|
||||
$result[$key] = $item->toString();
|
||||
return $result;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone color.
|
||||
*/
|
||||
public function __clone(): void
|
||||
{
|
||||
foreach ($this->channels as $key => $channel) {
|
||||
$this->channels[$key] = clone $channel;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::__toString()
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
||||
89
vendor/intervention/image/src/Colors/AbstractColorChannel.php
vendored
Normal file
89
vendor/intervention/image/src/Colors/AbstractColorChannel.php
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Stringable;
|
||||
|
||||
abstract class AbstractColorChannel implements ColorChannelInterface, Stringable
|
||||
{
|
||||
/**
|
||||
* Main color channel value.
|
||||
*/
|
||||
protected int|float $value;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::normalized()
|
||||
*/
|
||||
public function normalized(int $precision = 32): float
|
||||
{
|
||||
return round(($this->value() - $this->min()) / ($this->max() - $this->min()), $precision);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::scale()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function scale(int $percent): self
|
||||
{
|
||||
if ($percent === 0) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($percent < -100 || $percent > 100) {
|
||||
throw new InvalidArgumentException('Percentage value must be between -100 and 100');
|
||||
}
|
||||
|
||||
$normalized = $this->normalized();
|
||||
$base = $percent >= 0 ? (1 - $normalized) : $normalized;
|
||||
$scaled = min(1.0, max(0.0, $normalized + $base / 100 * $percent));
|
||||
$this->value = static::fromNormalized($scaled)->value();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw exception if the given value is not applicable for channel
|
||||
* otherwise the value is returned unchanged.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function validValueOrFail(int|float $value): mixed
|
||||
{
|
||||
if ($value < $this->min() || $value > $this->max()) {
|
||||
throw new InvalidArgumentException(
|
||||
'Color channel ' . $this::class . ' value must be in range ' . $this->min() . ' to ' . $this->max(),
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return (string) $this->value();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::__toString()
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
}
|
||||
27
vendor/intervention/image/src/Colors/AbstractColorspace.php
vendored
Normal file
27
vendor/intervention/image/src/Colors/AbstractColorspace.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
abstract class AbstractColorspace implements ColorspaceInterface
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static array $channels = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::channels()
|
||||
*/
|
||||
public static function channels(): array
|
||||
{
|
||||
return static::$channels;
|
||||
}
|
||||
}
|
||||
76
vendor/intervention/image/src/Colors/AlphaChannel.php
vendored
Normal file
76
vendor/intervention/image/src/Colors/AlphaChannel.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
|
||||
class AlphaChannel extends AbstractColorChannel
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
final public function __construct(float $value = 1)
|
||||
{
|
||||
if ($value < 0 || $value > 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Color channel value of ' . static::class . ' must be in range 0 to 1',
|
||||
);
|
||||
}
|
||||
|
||||
$this->value = (int) $this->validValueOrFail(intval(round($value * $this->max())));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::fromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromNormalized(float $normalized): self
|
||||
{
|
||||
return new static($normalized);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::value()
|
||||
*/
|
||||
public function value(): int
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return strval($this->normalized(2));
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Cmyk/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Cmyk/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Cmyk/Channels/Cyan.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Cmyk/Channels/Cyan.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Cyan extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Key.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Key.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||
|
||||
class Key extends Cyan
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Magenta.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Magenta.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||
|
||||
class Magenta extends Cyan
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Yellow.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Cmyk/Channels/Yellow.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||
|
||||
class Yellow extends Cyan
|
||||
{
|
||||
//
|
||||
}
|
||||
185
vendor/intervention/image/src/Colors/Cmyk/Color.php
vendored
Normal file
185
vendor/intervention/image/src/Colors/Cmyk/Color.php
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Cyan;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Key;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Magenta;
|
||||
use Intervention\Image\Colors\Cmyk\Channels\Yellow;
|
||||
use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int|Cyan $c, int|Magenta $m, int|Yellow $y, int|Key $k, float|Alpha $a = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_int($c) ? new Cyan($c) : $c,
|
||||
is_int($m) ? new Magenta($m) : $m,
|
||||
is_int($y) ? new Yellow($y) : $y,
|
||||
is_int($k) ? new Key($k) : $k,
|
||||
is_float($a) ? new Alpha($a) : $a,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(int|Cyan $c, int|Magenta $m, int|Yellow $y, int|Key $k, float|Alpha $a = 1): self
|
||||
{
|
||||
return new self($c, $m, $y, $k, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CMYK color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse CMYK color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return $this->toColorspace(Rgb::class)->toHex($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMYK cyan channel.
|
||||
*/
|
||||
public function cyan(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Cyan::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMYK magenta channel.
|
||||
*/
|
||||
public function magenta(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Magenta::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMYK yellow channel.
|
||||
*/
|
||||
public function yellow(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Yellow::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMYK key channel.
|
||||
*/
|
||||
public function key(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Key::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CMYK alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'cmyk(%d %d %d %d / %s)',
|
||||
$this->cyan()->value(),
|
||||
$this->magenta()->value(),
|
||||
$this->yellow()->value(),
|
||||
$this->key()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'cmyk(%d %d %d %d)',
|
||||
$this->cyan()->value(),
|
||||
$this->magenta()->value(),
|
||||
$this->yellow()->value(),
|
||||
$this->key()->value()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return 0 === array_sum([
|
||||
$this->cyan()->value(),
|
||||
$this->magenta()->value(),
|
||||
$this->yellow()->value(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
139
vendor/intervention/image/src/Colors/Cmyk/Colorspace.php
vendored
Normal file
139
vendor/intervention/image/src/Colors/Cmyk/Colorspace.php
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Channels\Cyan::class,
|
||||
Channels\Magenta::class,
|
||||
Channels\Yellow::class,
|
||||
Channels\Key::class,
|
||||
Channels\Alpha::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::colorFromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function colorFromNormalized(array $normalized): CmykColor
|
||||
{
|
||||
if (!in_array(count($normalized), [4, 5])) {
|
||||
throw new InvalidArgumentException('Number of color channels must be 4 or 5 for ' . static::class);
|
||||
}
|
||||
|
||||
// add alpha value if missing
|
||||
$normalized = count($normalized) === 4 ? array_pad($normalized, 5, 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): CmykColor
|
||||
{
|
||||
return match ($color::class) {
|
||||
OklchColor::class,
|
||||
OklabColor::class,
|
||||
HsvColor::class,
|
||||
NamedColor::class,
|
||||
HslColor::class => $this->importViaRgbColor($color),
|
||||
RgbColor::class => $this->importRgbColor($color),
|
||||
CmykColor::class => $color,
|
||||
default => throw new ColorException(
|
||||
'Unable to import color ' . $color::class . ' to ' . $this::class,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given RGB color to CMYK colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importRgbColor(RgbColor $color): CmykColor
|
||||
{
|
||||
$c = (255 - $color->red()->value()) / 255.0 * 100;
|
||||
$m = (255 - $color->green()->value()) / 255.0 * 100;
|
||||
$y = (255 - $color->blue()->value()) / 255.0 * 100;
|
||||
$k = intval(round(min([$c, $m, $y])));
|
||||
|
||||
$c = intval(round($c - $k));
|
||||
$m = intval(round($m - $k));
|
||||
$y = intval(round($y - $k));
|
||||
|
||||
try {
|
||||
return new CmykColor($c, $m, $y, $k, $color->alpha()->normalized());
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given color to CMYK colorspace by converting it to RGB first.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importViaRgbColor(NamedColor|OklabColor|OklchColor|HslColor|HsvColor $color): CmykColor
|
||||
{
|
||||
try {
|
||||
$color = $color->toColorspace(RgbColorspace::class);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof RgbColor) {
|
||||
throw new ColorException('Failed to import color ' . $color::class . ' to ' . $this::class);
|
||||
}
|
||||
|
||||
return $this->importRgbColor($color);
|
||||
}
|
||||
}
|
||||
57
vendor/intervention/image/src/Colors/Cmyk/Decoders/StringColorDecoder.php
vendored
Normal file
57
vendor/intervention/image/src/Colors/Cmyk/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Cmyk\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Cmyk\Color;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
private const string PATTERN =
|
||||
'/^cmyk ?\(' .
|
||||
'(?P<c>[0-9\.]+%?)((, ?)| )' .
|
||||
'(?P<m>[0-9\.]+%?)((, ?)| )' .
|
||||
'(?P<y>[0-9\.]+%?)((, ?)| )' .
|
||||
'(?P<k>[0-9\.]+%?)\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_starts_with(strtolower($input), 'cmyk')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode CMYK color strings
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, (string) $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid cmyk() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
$values = array_map(function (string $value): int {
|
||||
return intval(round(floatval(trim(str_replace('%', '', $value)))));
|
||||
}, [$matches['c'], $matches['m'], $matches['y'], $matches['k']]);
|
||||
|
||||
return new Color(...$values);
|
||||
}
|
||||
}
|
||||
46
vendor/intervention/image/src/Colors/FloatColorChannel.php
vendored
Normal file
46
vendor/intervention/image/src/Colors/FloatColorChannel.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
|
||||
abstract class FloatColorChannel extends AbstractColorChannel
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
final public function __construct(float $value)
|
||||
{
|
||||
$this->value = (float) $this->validValueOrFail($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::fromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromNormalized(float $normalized): self
|
||||
{
|
||||
if ($normalized < 0 || $normalized > 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Normalized color channel value must be between 0 to 1',
|
||||
);
|
||||
}
|
||||
|
||||
return new static(static::min() + $normalized * (static::max() - static::min()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::value()
|
||||
*/
|
||||
public function value(): float
|
||||
{
|
||||
return (float) $this->value;
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Hsl/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Hsl/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsl/Channels/Hue.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsl/Channels/Hue.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Hue extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 360;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsl/Channels/Luminance.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsl/Channels/Luminance.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Luminance extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsl/Channels/Saturation.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsl/Channels/Saturation.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Saturation extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
170
vendor/intervention/image/src/Colors/Hsl/Color.php
vendored
Normal file
170
vendor/intervention/image/src/Colors/Hsl/Color.php
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Hue;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Luminance;
|
||||
use Intervention\Image\Colors\Hsl\Channels\Saturation;
|
||||
use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new color object.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int|Hue $h, int|Saturation $s, int|Luminance $l, float|Alpha $a = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_int($h) ? new Hue($h) : $h,
|
||||
is_int($s) ? new Saturation($s) : $s,
|
||||
is_int($l) ? new Luminance($l) : $l,
|
||||
is_float($a) ? new Alpha($a) : $a,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(int|Hue $h, int|Saturation $s, int|Luminance $l, float|Alpha $a = 1): self
|
||||
{
|
||||
return new self($h, $s, $l, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HSL color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse HSL color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Hue channel
|
||||
*/
|
||||
public function hue(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Hue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Saturation channel.
|
||||
*/
|
||||
public function saturation(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Saturation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Luminance channel.
|
||||
*/
|
||||
public function luminance(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Luminance::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return $this->toColorspace(Rgb::class)->toHex($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'hsl(%d %d%% %d%% / %s)',
|
||||
$this->hue()->value(),
|
||||
$this->saturation()->value(),
|
||||
$this->luminance()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'hsl(%d %d%% %d%%)',
|
||||
$this->hue()->value(),
|
||||
$this->saturation()->value(),
|
||||
$this->luminance()->value()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return floatval($this->saturation()->value()) === 0.0;
|
||||
}
|
||||
}
|
||||
204
vendor/intervention/image/src/Colors/Hsl/Colorspace.php
vendored
Normal file
204
vendor/intervention/image/src/Colors/Hsl/Colorspace.php
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Channels\Hue::class,
|
||||
Channels\Saturation::class,
|
||||
Channels\Luminance::class,
|
||||
Channels\Alpha::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::colorFromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function colorFromNormalized(array $normalized): HslColor
|
||||
{
|
||||
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): HslColor
|
||||
{
|
||||
return match ($color::class) {
|
||||
HslColor::class => $color,
|
||||
OklchColor::class,
|
||||
OklabColor::class,
|
||||
NamedColor::class,
|
||||
CmykColor::class => $this->importViaRgbColor($color),
|
||||
RgbColor::class => $this->importRgbColor($color),
|
||||
HsvColor::class => $this->importHsvColor($color),
|
||||
default => throw new ColorException(
|
||||
'Unable to import color ' . $color::class . ' to ' . $this::class,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given RGB color to HSL colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importRgbColor(RgbColor $color): HslColor
|
||||
{
|
||||
// normalized values of rgb channels
|
||||
$values = array_map(
|
||||
fn(ColorChannelInterface $channel): float => $channel->normalized(),
|
||||
$color->channels(),
|
||||
);
|
||||
|
||||
// take only RGB
|
||||
$values = array_slice($values, 0, 3);
|
||||
|
||||
// calculate Luminance
|
||||
$min = min(...$values);
|
||||
$max = max(...$values);
|
||||
$luminance = ($max + $min) / 2;
|
||||
$delta = $max - $min;
|
||||
|
||||
// calculate saturation
|
||||
$saturation = $delta === 0.0 ? 0 : $delta / (1 - abs(2 * $luminance - 1));
|
||||
|
||||
// calculate hue
|
||||
[$r, $g, $b] = $values;
|
||||
$hue = match (true) {
|
||||
($delta === 0.0) => 0,
|
||||
($max === $r) => 60 * fmod((($g - $b) / $delta), 6),
|
||||
($max === $g) => 60 * ((($b - $r) / $delta) + 2),
|
||||
($max === $b) => 60 * ((($r - $g) / $delta) + 4),
|
||||
default => 0,
|
||||
};
|
||||
|
||||
$hue = (round($hue) + 360) % 360; // normalize hue
|
||||
|
||||
try {
|
||||
return new Color(
|
||||
intval(round($hue)),
|
||||
intval(round($saturation * 100)),
|
||||
intval(round($luminance * 100)),
|
||||
$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 HSL colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importHsvColor(HsvColor $color): HslColor
|
||||
{
|
||||
// normalized values of hsv channels
|
||||
[$h, $s, $v] = array_map(
|
||||
fn(ColorChannelInterface $channel): float => $channel->normalized(),
|
||||
$color->channels(),
|
||||
);
|
||||
|
||||
// calculate Luminance
|
||||
$luminance = (2.0 - $s) * $v / 2.0;
|
||||
|
||||
// calculate Saturation
|
||||
$saturation = match (true) {
|
||||
$luminance === 0.0 => $s,
|
||||
$luminance === 1.0 => 0,
|
||||
$luminance < .5 => $s * $v / ($luminance * 2),
|
||||
default => $s * $v / (2 - $luminance * 2),
|
||||
};
|
||||
|
||||
try {
|
||||
return new Color(
|
||||
intval(round($h * 360)),
|
||||
intval(round($saturation * 100)),
|
||||
intval(round($luminance * 100)),
|
||||
$color->alpha()->normalized(),
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given color to HSL color space by converting it to RGB first.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importViaRgbColor(NamedColor|CmykColor|OklabColor|OklchColor $color): HslColor
|
||||
{
|
||||
try {
|
||||
$color = $color->toColorspace(Rgb::class);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof RgbColor) {
|
||||
throw new ColorException('Failed to import color ' . $color::class . ' to ' . $this::class);
|
||||
}
|
||||
|
||||
return $this->importRgbColor($color);
|
||||
}
|
||||
}
|
||||
71
vendor/intervention/image/src/Colors/Hsl/Decoders/StringColorDecoder.php
vendored
Normal file
71
vendor/intervention/image/src/Colors/Hsl/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsl\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Hsl\Color;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern of HSL color syntax
|
||||
*/
|
||||
private const string PATTERN =
|
||||
'/^hsla? ?\( ?' .
|
||||
'(?P<h>[0-9\.]+)(?:deg)?((, ?)| )' .
|
||||
'(?P<s>[0-9\.]+%?)((, ?)| )' .
|
||||
'(?P<l>[0-9\.]+%?)' .
|
||||
'(?:(?:(?: ?\/ ?)|(?:[, ]) ?)' .
|
||||
'(?<a>(?:0\.[0-9]+)|1\.0|\.[0-9]+|[0-9]{1,3}%|1|0))?' .
|
||||
' ?\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_starts_with(strtolower($input), 'hsl')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hsl color strings.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid hsl() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
$values = array_map(fn(string $value): int => match (strpos($value, '%')) {
|
||||
false => intval(trim($value)),
|
||||
default => intval(trim(str_replace('%', '', $value))),
|
||||
}, [$matches['h'], $matches['s'], $matches['l']]);
|
||||
|
||||
// alpha value
|
||||
if (array_key_exists('a', $matches)) {
|
||||
$values[] = match (strpos($matches['a'], '%')) {
|
||||
false => floatval(trim($matches['a'])),
|
||||
default => floatval(trim(str_replace('%', '', $matches['a']))) / 100,
|
||||
};
|
||||
}
|
||||
|
||||
return new Color(...$values);
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Hsv/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Hsv/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsv/Channels/Hue.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsv/Channels/Hue.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Hue extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 360;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsv/Channels/Saturation.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsv/Channels/Saturation.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Saturation extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Hsv/Channels/Value.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Hsv/Channels/Value.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Value extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
168
vendor/intervention/image/src/Colors/Hsv/Color.php
vendored
Normal file
168
vendor/intervention/image/src/Colors/Hsv/Color.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Hue;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Saturation;
|
||||
use Intervention\Image\Colors\Hsv\Channels\Value;
|
||||
use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new color object.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int|Hue $h, int|Saturation $s, int|Value $v, float|Alpha $a = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_int($h) ? new Hue($h) : $h,
|
||||
is_int($s) ? new Saturation($s) : $s,
|
||||
is_int($v) ? new Value($v) : $v,
|
||||
is_float($a) ? new Alpha($a) : $a,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(int|Hue $h, int|Saturation $s, int|Value $v, float|Alpha $a = 1): self
|
||||
{
|
||||
return new self($h, $s, $v, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HSV color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse HSV color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Hue channel.
|
||||
*/
|
||||
public function hue(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Hue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Saturation channel.
|
||||
*/
|
||||
public function saturation(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Saturation::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Value channel.
|
||||
*/
|
||||
public function value(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Value::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return $this->toColorspace(Rgb::class)->toHex($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'hsv(%d %d%% %d%% / %s)',
|
||||
$this->hue()->value(),
|
||||
$this->saturation()->value(),
|
||||
$this->value()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'hsv(%d %d%% %d%%)',
|
||||
$this->hue()->value(),
|
||||
$this->saturation()->value(),
|
||||
$this->value()->value()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return floatval($this->saturation()->value()) === 0.0;
|
||||
}
|
||||
}
|
||||
199
vendor/intervention/image/src/Colors/Hsv/Colorspace.php
vendored
Normal file
199
vendor/intervention/image/src/Colors/Hsv/Colorspace.php
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Channels\Hue::class,
|
||||
Channels\Saturation::class,
|
||||
Channels\Value::class,
|
||||
Channels\Alpha::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::colorFromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function colorFromNormalized(array $normalized): HsvColor
|
||||
{
|
||||
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 InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function importColor(ColorInterface $color): HsvColor
|
||||
{
|
||||
return match ($color::class) {
|
||||
CmykColor::class,
|
||||
OklchColor::class,
|
||||
NamedColor::class,
|
||||
OklabColor::class => $this->importViaRgbColor($color),
|
||||
RgbColor::class => $this->importRgbColor($color),
|
||||
HslColor::class => $this->importHslColor($color),
|
||||
HsvColor::class => $color,
|
||||
default => throw new ColorException(
|
||||
'Unable to import color ' . $color::class . ' to ' . $this::class,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given RGB color to HSV colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importRgbColor(RgbColor $color): HsvColor
|
||||
{
|
||||
// normalized values of rgb channels
|
||||
$values = array_map(
|
||||
fn(ColorChannelInterface $channel): float => $channel->normalized(),
|
||||
$color->channels(),
|
||||
);
|
||||
|
||||
// take only RGB
|
||||
$values = array_slice($values, 0, 3);
|
||||
|
||||
// calculate chroma
|
||||
$min = min(...$values);
|
||||
$max = max(...$values);
|
||||
$chroma = $max - $min;
|
||||
|
||||
// calculate value
|
||||
$v = 100 * $max;
|
||||
|
||||
if ($chroma === 0.0) {
|
||||
// grayscale color
|
||||
try {
|
||||
return new Color(0, 0, intval(round($v)), $color->alpha()->normalized());
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate saturation
|
||||
$s = 100 * ($chroma / $max);
|
||||
|
||||
// calculate hue
|
||||
[$r, $g, $b] = $values;
|
||||
$h = match (true) {
|
||||
($r === $min) => 3 - (($g - $b) / $chroma),
|
||||
($b === $min) => 1 - (($r - $g) / $chroma),
|
||||
default => 5 - (($b - $r) / $chroma),
|
||||
} * 60;
|
||||
|
||||
try {
|
||||
return new Color(
|
||||
intval(round($h)),
|
||||
intval(round($s)),
|
||||
intval(round($v)),
|
||||
$color->alpha()->normalized(),
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given HSL color to HSV colorspace.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function importHslColor(ColorInterface $color): HsvColor
|
||||
{
|
||||
if (!$color instanceof HslColor) {
|
||||
throw new InvalidArgumentException('Color must be of type ' . HslColor::class);
|
||||
}
|
||||
|
||||
// normalized values of hsl channels
|
||||
[$h, $s, $l] = array_map(
|
||||
fn(ColorChannelInterface $channel): float => $channel->normalized(),
|
||||
$color->channels()
|
||||
);
|
||||
|
||||
$v = $l + $s * min($l, 1 - $l);
|
||||
$s = ($v === 0.0) ? 0 : 2 * (1 - $l / $v);
|
||||
|
||||
return $this->colorFromNormalized([$h, $s, $v, $color->alpha()->normalized()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given color to HSV color space by converting it to RGB first.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importViaRgbColor(NamedColor|CmykColor|OklchColor|OklabColor $color): HsvColor
|
||||
{
|
||||
try {
|
||||
$color = $color->toColorspace(RgbColorspace::class);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof RgbColor) {
|
||||
throw new ColorException('Failed to import color ' . $color::class . ' to ' . $this::class);
|
||||
}
|
||||
|
||||
return $this->importRgbColor($color);
|
||||
}
|
||||
}
|
||||
71
vendor/intervention/image/src/Colors/Hsv/Decoders/StringColorDecoder.php
vendored
Normal file
71
vendor/intervention/image/src/Colors/Hsv/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Hsv\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Hsv\Color;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern of hsv/b color syntax.
|
||||
*/
|
||||
private const string PATTERN =
|
||||
'/^hs(v|b) ?\( ?(' .
|
||||
'?P<h>[0-9\.]+)(?:deg)?((, ?)| )' .
|
||||
'(?P<s>[0-9\.]+%?)((, ?)| )' .
|
||||
'(?P<v>[0-9\.]+%?)' .
|
||||
'(?:(?:(?: ?\/ ?)|(?:[, ]) ?)' .
|
||||
'(?<a>(?:0\.[0-9]+)|1\.0|\.[0-9]+|[0-9]{1,3}%|1|0))?' .
|
||||
' ?\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/^hs(v|b)/i', $input) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hsv/hsb color strings.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid hsv() or hsb() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
$values = array_map(fn(string $value): int => match (strpos($value, '%')) {
|
||||
false => intval(trim($value)),
|
||||
default => intval(trim(str_replace('%', '', $value))),
|
||||
}, [$matches['h'], $matches['s'], $matches['v']]);
|
||||
|
||||
// alpha value
|
||||
if (array_key_exists('a', $matches)) {
|
||||
$values[] = match (strpos($matches['a'], '%')) {
|
||||
false => floatval(trim($matches['a'])),
|
||||
default => floatval(trim(str_replace('%', '', $matches['a']))) / 100,
|
||||
};
|
||||
}
|
||||
|
||||
return new Color(...$values);
|
||||
}
|
||||
}
|
||||
46
vendor/intervention/image/src/Colors/IntegerColorChannel.php
vendored
Normal file
46
vendor/intervention/image/src/Colors/IntegerColorChannel.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
|
||||
abstract class IntegerColorChannel extends AbstractColorChannel
|
||||
{
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
final public function __construct(int $value)
|
||||
{
|
||||
$this->value = (int) $this->validValueOrFail($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::fromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function fromNormalized(float $normalized): self
|
||||
{
|
||||
if ($normalized < 0 || $normalized > 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Normalized color channel value must be between 0 to 1',
|
||||
);
|
||||
}
|
||||
|
||||
return new static(intval(round($normalized * static::max())));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::value()
|
||||
*/
|
||||
public function value(): int
|
||||
{
|
||||
return (int) $this->value;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Oklab/Channels/A.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Oklab/Channels/A.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab\Channels;
|
||||
|
||||
use Intervention\Image\Colors\FloatColorChannel;
|
||||
|
||||
class A extends FloatColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return -0.4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 0.4;
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Oklab/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Oklab/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Oklab/Channels/B.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Oklab/Channels/B.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab\Channels;
|
||||
|
||||
class B extends A
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Oklab/Channels/Lightness.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Oklab/Channels/Lightness.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab\Channels;
|
||||
|
||||
use Intervention\Image\Colors\FloatColorChannel;
|
||||
|
||||
class Lightness extends FloatColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
168
vendor/intervention/image/src/Colors/Oklab/Color.php
vendored
Normal file
168
vendor/intervention/image/src/Colors/Oklab/Color.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Oklab\Channels\A;
|
||||
use Intervention\Image\Colors\Oklab\Channels\B;
|
||||
use Intervention\Image\Colors\Oklab\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Oklab\Channels\Lightness;
|
||||
use Intervention\Image\Colors\Oklab\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new color object.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(float|Lightness $l, float|A $a, float|B $b, float|Alpha $alpha = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_float($l) ? new Lightness($l) : $l,
|
||||
is_float($a) ? new A($a) : $a,
|
||||
is_float($b) ? new B($b) : $b,
|
||||
is_float($alpha) ? new Alpha($alpha) : $alpha,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(float|Lightness $l, float|A $a, float|B $b, float|Alpha $alpha = 1): self
|
||||
{
|
||||
return new self($l, $a, $b, $alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse OKLAB color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse OKLAB color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Lightness channel.
|
||||
*/
|
||||
public function lightness(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Lightness::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the a axis (green-red) channel.
|
||||
*/
|
||||
public function a(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(A::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the b axis (blue-yellow) channel.
|
||||
*/
|
||||
public function b(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(B::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return $this->toColorspace(Rgb::class)->toHex($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'oklab(%s %s %s / %s)',
|
||||
$this->lightness()->value(),
|
||||
$this->a()->value(),
|
||||
$this->b()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'oklab(%s %s %s)',
|
||||
$this->lightness()->value(),
|
||||
$this->a()->value(),
|
||||
$this->b()->value(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return $this->a()->value() === 0.0 && $this->b()->value() === 0.0;
|
||||
}
|
||||
}
|
||||
177
vendor/intervention/image/src/Colors/Oklab/Colorspace.php
vendored
Normal file
177
vendor/intervention/image/src/Colors/Oklab/Colorspace.php
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Channels\Lightness::class,
|
||||
Channels\A::class,
|
||||
Channels\B::class,
|
||||
Channels\Alpha::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::colorFromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function colorFromNormalized(array $normalized): OklabColor
|
||||
{
|
||||
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): OklabColor
|
||||
{
|
||||
return match ($color::class) {
|
||||
CmykColor::class,
|
||||
HsvColor::class,
|
||||
NamedColor::class,
|
||||
HslColor::class => $this->importViaRgbColor($color),
|
||||
RgbColor::class => $this->importRgbColor($color),
|
||||
OklchColor::class => $this->importOklchColor($color),
|
||||
OklabColor::class => $color,
|
||||
default => throw new ColorException(
|
||||
'Unable to import color ' . $color::class . ' to ' . $this::class,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given RGB color OKLAB colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importRgbColor(RgbColor $color): OklabColor
|
||||
{
|
||||
$cbrt = fn(float $x): float => $x < 0 ? -abs($x) ** (1 / 3) : $x ** (1 / 3);
|
||||
$rgbToLinear = fn(float $x): float => $x <= 0.04045 ? $x / 12.92 : (($x + 0.055) / 1.055) ** 2.4;
|
||||
|
||||
$r = $color->red()->normalized();
|
||||
$g = $color->green()->normalized();
|
||||
$b = $color->blue()->normalized();
|
||||
|
||||
$r = $rgbToLinear($r);
|
||||
$g = $rgbToLinear($g);
|
||||
$b = $rgbToLinear($b);
|
||||
|
||||
$l = 0.4122214708 * $r + 0.5363325363 * $g + 0.0514459929 * $b;
|
||||
$m = 0.2119034982 * $r + 0.6806995451 * $g + 0.1073969566 * $b;
|
||||
$s = 0.0883024619 * $r + 0.2817188376 * $g + 0.6299787005 * $b;
|
||||
|
||||
$l = $cbrt($l);
|
||||
$m = $cbrt($m);
|
||||
$s = $cbrt($s);
|
||||
|
||||
try {
|
||||
return new Color(
|
||||
0.2104542553 * $l + 0.7936177850 * $m - 0.0040720468 * $s,
|
||||
1.9779984951 * $l - 2.4285922050 * $m + 0.4505937099 * $s,
|
||||
0.0259040371 * $l + 0.7827717662 * $m - 0.8086757660 * $s,
|
||||
$color->alpha()->normalized(),
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given OKLCH color OKLAB colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importOklchColor(OklchColor $color): OklabColor
|
||||
{
|
||||
$hRad = deg2rad($color->hue()->value());
|
||||
|
||||
try {
|
||||
return new Color(
|
||||
$color->lightness()->value(),
|
||||
$color->chroma()->value() * cos($hRad),
|
||||
$color->chroma()->value() * sin($hRad),
|
||||
$color->alpha()->normalized(),
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given color to OKLAB color space by converting it to RGB first.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importViaRgbColor(NamedColor|CmykColor|HslColor|HsvColor $color): OklabColor
|
||||
{
|
||||
try {
|
||||
$color = $color->toColorspace(Rgb::class)->toColorspace($this::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 $color;
|
||||
}
|
||||
}
|
||||
93
vendor/intervention/image/src/Colors/Oklab/Decoders/StringColorDecoder.php
vendored
Normal file
93
vendor/intervention/image/src/Colors/Oklab/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklab\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Oklab\Color;
|
||||
use Intervention\Image\Colors\Oklab\Channels\Lightness;
|
||||
use Intervention\Image\Colors\Oklab\Channels\A;
|
||||
use Intervention\Image\Colors\Oklab\Channels\B;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern of oklab color syntax.
|
||||
*/
|
||||
private const string PATTERN =
|
||||
'/^oklab ?\( ?' .
|
||||
'(?P<l>(1|0|0?\.[0-9]+)|[0-9\.]+%)((, ?)|( ))' .
|
||||
'(?P<a>(-?0|-?0?\.[0-9\.]+)|(-?[0-9\.]+%))((, ?)|( ))' .
|
||||
'(?P<b>(-?0|-?0?\.[0-9\.]+)|(-?[0-9\.]+%))' .
|
||||
'(?:(?:(?: ?\/ ?)|(?:[, ]) ?)' .
|
||||
'(?<alpha>(?:0\.[0-9]+)|1\.0|\.[0-9]+|[0-9]{1,3}%|1|0))?' .
|
||||
' ?\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_starts_with(strtolower($input), 'oklab')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hsl color strings.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid oklab() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
$values = [
|
||||
$this->decodeChannelValue($matches['l'], Lightness::class),
|
||||
$this->decodeChannelValue($matches['a'], A::class),
|
||||
$this->decodeChannelValue($matches['b'], B::class),
|
||||
];
|
||||
|
||||
// alpha value
|
||||
if (array_key_exists('alpha', $matches)) {
|
||||
$values[] = $this->decodeAlphaChannelValue($matches['alpha']);
|
||||
}
|
||||
|
||||
return new Color(...$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode channel value.
|
||||
*/
|
||||
private function decodeChannelValue(string $value, string $channel): float
|
||||
{
|
||||
if (strpos($value, '%')) {
|
||||
return floatval(trim(str_replace('%', '', $value))) * $channel::max() / 100;
|
||||
}
|
||||
|
||||
return floatval(trim($value));
|
||||
}
|
||||
|
||||
private function decodeAlphaChannelValue(string $value): float
|
||||
{
|
||||
if (strpos($value, '%')) {
|
||||
return floatval(trim(str_replace('%', '', $value))) / 100;
|
||||
}
|
||||
|
||||
return floatval(trim($value));
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Oklch/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Oklch/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Oklch/Channels/Chroma.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Oklch/Channels/Chroma.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch\Channels;
|
||||
|
||||
use Intervention\Image\Colors\FloatColorChannel;
|
||||
|
||||
class Chroma extends FloatColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return -0.4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 0.4;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Oklch/Channels/Hue.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Oklch/Channels/Hue.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch\Channels;
|
||||
|
||||
use Intervention\Image\Colors\FloatColorChannel;
|
||||
|
||||
class Hue extends FloatColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 360;
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Oklch/Channels/Lightness.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Oklch/Channels/Lightness.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch\Channels;
|
||||
|
||||
use Intervention\Image\Colors\FloatColorChannel;
|
||||
|
||||
class Lightness extends FloatColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
168
vendor/intervention/image/src/Colors/Oklch/Color.php
vendored
Normal file
168
vendor/intervention/image/src/Colors/Oklch/Color.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Chroma;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Hue;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Lightness;
|
||||
use Intervention\Image\Colors\Oklch\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new color object.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(float|Lightness $l, float|Chroma $c, float|Hue $h, float|Alpha $a = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_float($l) ? new Lightness($l) : $l,
|
||||
is_float($c) ? new Chroma($c) : $c,
|
||||
is_float($h) ? new Hue($h) : $h,
|
||||
is_float($a) ? new Alpha($a) : $a,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(float|Lightness $l, float|Chroma $c, float|Hue $h, float|Alpha $a = 1): self
|
||||
{
|
||||
return new self($l, $c, $h, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse OKLCH color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse OKLCH color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Lightness channel.
|
||||
*/
|
||||
public function lightness(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Lightness::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the chroma channel.
|
||||
*/
|
||||
public function chroma(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Chroma::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hue channel.
|
||||
*/
|
||||
public function hue(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Hue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return $this->toColorspace(Rgb::class)->toHex($prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'oklch(%s %s %s / %s)',
|
||||
$this->lightness()->value(),
|
||||
$this->chroma()->value(),
|
||||
$this->hue()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'oklch(%s %s %s)',
|
||||
$this->lightness()->value(),
|
||||
$this->chroma()->value(),
|
||||
$this->hue()->value(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return $this->chroma()->value() === 0.0;
|
||||
}
|
||||
}
|
||||
164
vendor/intervention/image/src/Colors/Oklch/Colorspace.php
vendored
Normal file
164
vendor/intervention/image/src/Colors/Oklch/Colorspace.php
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklab\Colorspace as Oklab;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Chroma;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Hue;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Lightness;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Lightness::class,
|
||||
Chroma::class,
|
||||
Hue::class,
|
||||
Alpha::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorspaceInterface::colorFromNormalized()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function colorFromNormalized(array $normalized): OklchColor
|
||||
{
|
||||
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): OklchColor
|
||||
{
|
||||
return match ($color::class) {
|
||||
CmykColor::class,
|
||||
HsvColor::class,
|
||||
NamedColor::class,
|
||||
HslColor::class => $this->importViaRgbColor($color),
|
||||
OklabColor::class => $this->importOklabColor($color),
|
||||
RgbColor::class => $this->importRgbColor($color),
|
||||
OklchColor::class => $color,
|
||||
default => throw new ColorException(
|
||||
'Unable to import color ' . $color::class . ' to ' . $this::class,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given OKLAB color OKLCH colorspace.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importOklabColor(OklabColor $color): OklchColor
|
||||
{
|
||||
$a = $color->a()->value();
|
||||
$b = $color->b()->value();
|
||||
|
||||
$c = sqrt($a * $a + $b * $b);
|
||||
$h = rad2deg(atan2($b, $a));
|
||||
$h = $h < 0 ? $h + 360 : $h;
|
||||
|
||||
try {
|
||||
return new Color($color->lightness()->value(), $c, $h, $color->alpha()->normalized());
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import given RGB color to OKLCH color space.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importRgbColor(RgbColor $color): OklchColor
|
||||
{
|
||||
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 color to OKLCH color space by converting it to RGB first.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function importViaRgbColor(NamedColor|HslColor|HsvColor|CmykColor $color): OklchColor
|
||||
{
|
||||
try {
|
||||
$color = $color->toColorspace(Rgb::class)->toColorspace($this::class);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorException(
|
||||
'Failed to import color ' . $color::class . ' to ' . $this::class,
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof OklchColor) {
|
||||
throw new ColorException('Failed to import color ' . $color::class . ' to ' . $this::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
}
|
||||
93
vendor/intervention/image/src/Colors/Oklch/Decoders/StringColorDecoder.php
vendored
Normal file
93
vendor/intervention/image/src/Colors/Oklch/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Oklch\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Oklch\Channels\Chroma;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Hue;
|
||||
use Intervention\Image\Colors\Oklch\Channels\Lightness;
|
||||
use Intervention\Image\Colors\Oklch\Color;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern for oklch color syntax.
|
||||
*/
|
||||
protected const string PATTERN =
|
||||
'/^oklch ?\( ?' .
|
||||
'(?P<l>(1|0|0?\.[0-9]+)|[0-9\.]+%)((, ?)|( ))' .
|
||||
'(?P<c>(-?0|-?0?\.[0-9\.]+)|(-?[0-9\.]+%))((, ?)|( ))' .
|
||||
'(?P<h>[0-9\.]+)' .
|
||||
'(?:(?:(?: ?\/ ?)|(?:[, ]) ?)' .
|
||||
'(?<a>(?:0\.[0-9]+)|1\.0|\.[0-9]+|[0-9]{1,3}%|1|0))?' .
|
||||
' ?\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!str_starts_with(strtolower($input), 'oklch')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hsl color string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid oklch() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
$values = [
|
||||
$this->decodeChannelValue($matches['l'], Lightness::class),
|
||||
$this->decodeChannelValue($matches['c'], Chroma::class),
|
||||
$this->decodeChannelValue($matches['h'], Hue::class),
|
||||
];
|
||||
|
||||
// alpha value
|
||||
if (array_key_exists('a', $matches)) {
|
||||
$values[] = $this->decodeAlphaChannelValue($matches['a']);
|
||||
}
|
||||
|
||||
return new Color(...$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode channel value.
|
||||
*/
|
||||
private function decodeChannelValue(string $value, string $channel): float
|
||||
{
|
||||
if (strpos($value, '%')) {
|
||||
return floatval(trim(str_replace('%', '', $value))) * $channel::max() / 100;
|
||||
}
|
||||
|
||||
return floatval(trim($value));
|
||||
}
|
||||
|
||||
private function decodeAlphaChannelValue(string $value): float
|
||||
{
|
||||
if (strpos($value, '%')) {
|
||||
return floatval(trim(str_replace('%', '', $value))) / 100;
|
||||
}
|
||||
|
||||
return floatval(trim($value));
|
||||
}
|
||||
}
|
||||
21
vendor/intervention/image/src/Colors/Profile.php
vendored
Normal file
21
vendor/intervention/image/src/Colors/Profile.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors;
|
||||
|
||||
use Intervention\Image\File;
|
||||
use Intervention\Image\Interfaces\ProfileInterface;
|
||||
|
||||
class Profile extends File implements ProfileInterface
|
||||
{
|
||||
/**
|
||||
* Create color profile instance from given path in file system.
|
||||
*/
|
||||
public static function fromPath(string $path): self
|
||||
{
|
||||
$stream = fopen(self::readableFilePathOrFail($path), 'r');
|
||||
|
||||
return new self($stream);
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Colors/Rgb/Channels/Alpha.php
vendored
Normal file
12
vendor/intervention/image/src/Colors/Rgb/Channels/Alpha.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||
|
||||
use Intervention\Image\Colors\AlphaChannel;
|
||||
|
||||
class Alpha extends AlphaChannel
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Rgb/Channels/Blue.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Rgb/Channels/Blue.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||
|
||||
class Blue extends Red
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/image/src/Colors/Rgb/Channels/Green.php
vendored
Normal file
10
vendor/intervention/image/src/Colors/Rgb/Channels/Green.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||
|
||||
class Green extends Red
|
||||
{
|
||||
//
|
||||
}
|
||||
30
vendor/intervention/image/src/Colors/Rgb/Channels/Red.php
vendored
Normal file
30
vendor/intervention/image/src/Colors/Rgb/Channels/Red.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||
|
||||
use Intervention\Image\Colors\IntegerColorChannel;
|
||||
|
||||
class Red extends IntegerColorChannel
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::min()
|
||||
*/
|
||||
public static function min(): float
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorChannelInterface::max()
|
||||
*/
|
||||
public static function max(): float
|
||||
{
|
||||
return 255;
|
||||
}
|
||||
}
|
||||
189
vendor/intervention/image/src/Colors/Rgb/Color.php
vendored
Normal file
189
vendor/intervention/image/src/Colors/Rgb/Color.php
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColor;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\NamedColorDecoder;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\StringColorDecoder;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
|
||||
class Color extends AbstractColor
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int|Red $r, int|Green $g, int|Blue $b, float|Alpha $a = 1)
|
||||
{
|
||||
$this->channels = [
|
||||
is_int($r) ? new Red($r) : $r,
|
||||
is_int($g) ? new Green($g) : $g,
|
||||
is_int($b) ? new Blue($b) : $b,
|
||||
is_float($a) ? new Alpha($a) : $a,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::create()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(int|Red $r, int|Green $g, int|Blue $b, float|Alpha $a = 1): self
|
||||
{
|
||||
return new self($r, $g, $b, $a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse RGB color from string.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public static function parse(string $input): self
|
||||
{
|
||||
try {
|
||||
$color = InputHandler::usingDecoders([
|
||||
StringColorDecoder::class,
|
||||
NamedColorDecoder::class,
|
||||
HexColorDecoder::class,
|
||||
])->handle($input);
|
||||
} catch (NotSupportedException | DriverException $e) {
|
||||
throw new InvalidArgumentException(
|
||||
'Unable to parse RGB color from input "' . $input . '"',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$color instanceof self) {
|
||||
throw new ColorException('Result must be instance of ' . self::class);
|
||||
}
|
||||
|
||||
return $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RGB red color channel.
|
||||
*/
|
||||
public function red(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Red::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RGB green color channel.
|
||||
*/
|
||||
public function green(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Green::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RGB blue color channel.
|
||||
*/
|
||||
public function blue(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Blue::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the colors alpha channel.
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
/** @throws void */
|
||||
return $this->channel(Alpha::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'%s%02x%02x%02x%02x',
|
||||
$prefix ? '#' : '',
|
||||
$this->red()->value(),
|
||||
$this->green()->value(),
|
||||
$this->blue()->value(),
|
||||
$this->alpha()->value()
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s%02x%02x%02x',
|
||||
$prefix ? '#' : '',
|
||||
$this->red()->value(),
|
||||
$this->green()->value(),
|
||||
$this->blue()->value()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
if ($this->isTransparent()) {
|
||||
return sprintf(
|
||||
'rgb(%d %d %d / %s)',
|
||||
$this->red()->value(),
|
||||
$this->green()->value(),
|
||||
$this->blue()->value(),
|
||||
$this->alpha()->toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'rgb(%d %d %d)',
|
||||
$this->red()->value(),
|
||||
$this->green()->value(),
|
||||
$this->blue()->value()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
$values = [$this->red()->value(), $this->green()->value(), $this->blue()->value()];
|
||||
|
||||
return count(array_unique($values, SORT_REGULAR)) === 1;
|
||||
}
|
||||
}
|
||||
286
vendor/intervention/image/src/Colors/Rgb/Colorspace.php
vendored
Normal file
286
vendor/intervention/image/src/Colors/Rgb/Colorspace.php
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb;
|
||||
|
||||
use Intervention\Image\Colors\AbstractColorspace;
|
||||
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||
use Intervention\Image\Colors\Oklab\Color as OklabColor;
|
||||
use Intervention\Image\Colors\Oklab\Colorspace as Oklab;
|
||||
use Intervention\Image\Colors\Oklch\Color as OklchColor;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use TypeError;
|
||||
|
||||
class Colorspace extends AbstractColorspace
|
||||
{
|
||||
/**
|
||||
* Channel class names of colorspace.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
public static array $channels = [
|
||||
Channels\Red::class,
|
||||
Channels\Green::class,
|
||||
Channels\Blue::class,
|
||||
Channels\Alpha::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');
|
||||
}
|
||||
}
|
||||
78
vendor/intervention/image/src/Colors/Rgb/Decoders/HexColorDecoder.php
vendored
Normal file
78
vendor/intervention/image/src/Colors/Rgb/Decoders/HexColorDecoder.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\ColorDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class HexColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern of hexadecimal color syntax.
|
||||
*/
|
||||
protected const string PATTERN = '/^#?(?P<hex>[a-f\d]{3}(?:[a-f\d]?|(?:[a-f\d]{3}(?:[a-f\d]{2})?)?)\b)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($input, '#')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// matching max. length & only hexadecimal
|
||||
if (strlen($input) <= 8 && preg_match('/^[a-f\d]+$/i', $input) === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return preg_match(static::PATTERN, $input) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode hexadecimal rgb colors with and without transparency.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorDecoderException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(static::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Hex color has an invalid format');
|
||||
}
|
||||
|
||||
// split into hex chunks
|
||||
$values = match (strlen($matches['hex'])) {
|
||||
3, 4 => str_split($matches['hex']),
|
||||
6, 8 => str_split($matches['hex'], 2),
|
||||
default => throw new InvalidArgumentException('Hex color has an incorrect length'),
|
||||
};
|
||||
|
||||
// convert to decimal
|
||||
$values = array_map(function (string $value): int {
|
||||
return match (strlen($value)) {
|
||||
1 => (int) hexdec($value . $value),
|
||||
2 => (int) hexdec($value),
|
||||
default => throw new ColorDecoderException('Failed to decode hex color'),
|
||||
};
|
||||
}, $values);
|
||||
|
||||
// normalize
|
||||
$values = count($values) === 3 ? array_pad($values, 4, 255) : $values;
|
||||
$values = array_map(fn(int $value): float => $value / 255, $values);
|
||||
|
||||
return Rgb::colorFromNormalized($values);
|
||||
}
|
||||
}
|
||||
39
vendor/intervention/image/src/Colors/Rgb/Decoders/NamedColorDecoder.php
vendored
Normal file
39
vendor/intervention/image/src/Colors/Rgb/Decoders/NamedColorDecoder.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\NamedColor;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class NamedColorDecoder extends HexColorDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->stringToColorName($input) === null ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode html color names.
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
return parent::decode($this->stringToColorName($input)?->toHex());
|
||||
}
|
||||
|
||||
private function stringToColorName(string $input): ?NamedColor
|
||||
{
|
||||
return NamedColor::tryFrom(strtolower($input));
|
||||
}
|
||||
}
|
||||
78
vendor/intervention/image/src/Colors/Rgb/Decoders/StringColorDecoder.php
vendored
Normal file
78
vendor/intervention/image/src/Colors/Rgb/Decoders/StringColorDecoder.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Color;
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\ColorDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
|
||||
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
/**
|
||||
* Regex pattern of rgb color syntax.
|
||||
*/
|
||||
private const string PATTERN =
|
||||
'/^s?rgba? ?\( ?' .
|
||||
'(?P<r>[0-9]{1,3})([, ]) ?' .
|
||||
'(?P<g>[0-9]{1,3})\2 ?' .
|
||||
'(?<b>[0-9]{1,3})' .
|
||||
'(?:(?:(?: ?\/ ?)|(?:[, ]) ?)' .
|
||||
'(?<a>(?:0\.[0-9]+)|1\.0|\.[0-9]+|[0-9]{1,3}%|1|0))?' .
|
||||
' ?\)$/i';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
if (!is_string($input)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preg_match('/^s?rgb/i', $input) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode rgb color strings.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorDecoderException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (preg_match(self::PATTERN, $input, $matches) !== 1) {
|
||||
throw new InvalidArgumentException('Invalid rgb() color syntax "' . $input . '"');
|
||||
}
|
||||
|
||||
// rgb values
|
||||
$values = array_map(fn(string $value): int => match (strpos($value, '%')) {
|
||||
false => intval(trim($value)),
|
||||
default => intval(round(floatval(trim(str_replace('%', '', $value))) / 100 * 255)),
|
||||
}, [$matches['r'], $matches['g'], $matches['b']]);
|
||||
|
||||
// alpha value
|
||||
if (array_key_exists('a', $matches)) {
|
||||
$values[] = match (strpos($matches['a'], '%')) {
|
||||
false => floatval(trim($matches['a'])),
|
||||
default => floatval(trim(str_replace('%', '', $matches['a']))) / 100,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
return new Color(...$values);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new ColorDecoderException('Failed to decode RGB color string', previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
502
vendor/intervention/image/src/Colors/Rgb/NamedColor.php
vendored
Normal file
502
vendor/intervention/image/src/Colors/Rgb/NamedColor.php
vendored
Normal file
@@ -0,0 +1,502 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Colors\Rgb;
|
||||
|
||||
use Error;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\ColorException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
use TypeError;
|
||||
use ValueError;
|
||||
|
||||
enum NamedColor: string implements ColorInterface
|
||||
{
|
||||
case ALICEBLUE = 'aliceblue';
|
||||
case ANTIQUEWHITE = 'antiquewhite';
|
||||
case AQUA = 'aqua';
|
||||
case AQUAMARINE = 'aquamarine';
|
||||
case AZURE = 'azure';
|
||||
case BEIGE = 'beige';
|
||||
case BISQUE = 'bisque';
|
||||
case BLACK = 'black';
|
||||
case BLANCHEDALMOND = 'blanchedalmond';
|
||||
case BLUE = 'blue';
|
||||
case BLUEVIOLET = 'blueviolet';
|
||||
case BROWN = 'brown';
|
||||
case BURLYWOOD = 'burlywood';
|
||||
case CADETBLUE = 'cadetblue';
|
||||
case CHARTREUSE = 'chartreuse';
|
||||
case CHOCOLATE = 'chocolate';
|
||||
case CORAL = 'coral';
|
||||
case CORNFLOWERBLUE = 'cornflowerblue';
|
||||
case CORNSILK = 'cornsilk';
|
||||
case CRIMSON = 'crimson';
|
||||
case CYAN = 'cyan';
|
||||
case DARKBLUE = 'darkblue';
|
||||
case DARKCYAN = 'darkcyan';
|
||||
case DARKGRAY = 'darkgray';
|
||||
case DARKGREEN = 'darkgreen';
|
||||
case DARKKHAKI = 'darkkhaki';
|
||||
case DARKMAGENTA = 'darkmagenta';
|
||||
case DARKOLIVEGREEN = 'darkolivegreen';
|
||||
case DARKORANGE = 'darkorange';
|
||||
case DARKORCHID = 'darkorchid';
|
||||
case DARKRED = 'darkred';
|
||||
case DARKSALMON = 'darksalmon';
|
||||
case DARKSEAGREEN = 'darkseagreen';
|
||||
case DARKSLATEBLUE = 'darkslateblue';
|
||||
case DARKSLATEGRAY = 'darkslategray';
|
||||
case DARKTURQUOISE = 'darkturquoise';
|
||||
case DARKVIOLET = 'darkviolet';
|
||||
case DEEPPINK = 'deeppink';
|
||||
case DEEPSKYBLUE = 'deepskyblue';
|
||||
case DIMGRAY = 'dimgray';
|
||||
case DODGERBLUE = 'dodgerblue';
|
||||
case FIREBRICK = 'firebrick';
|
||||
case FLORALWHITE = 'floralwhite';
|
||||
case FORESTGREEN = 'forestgreen';
|
||||
case FUCHSIA = 'fuchsia';
|
||||
case GAINSBORO = 'gainsboro';
|
||||
case GHOSTWHITE = 'ghostwhite';
|
||||
case GOLD = 'gold';
|
||||
case GOLDENROD = 'goldenrod';
|
||||
case GRAY = 'gray';
|
||||
case GREEN = 'green';
|
||||
case GREENYELLOW = 'greenyellow';
|
||||
case HONEYDEW = 'honeydew';
|
||||
case HOTPINK = 'hotpink';
|
||||
case INDIANRED = 'indianred';
|
||||
case INDIGO = 'indigo';
|
||||
case IVORY = 'ivory';
|
||||
case KHAKI = 'khaki';
|
||||
case LAVENDER = 'lavender';
|
||||
case LAVENDERBLUSH = 'lavenderblush';
|
||||
case LAWNGREEN = 'lawngreen';
|
||||
case LEMONCHIFFON = 'lemonchiffon';
|
||||
case LIGHTBLUE = 'lightblue';
|
||||
case LIGHTCORAL = 'lightcoral';
|
||||
case LIGHTCYAN = 'lightcyan';
|
||||
case LIGHTGOLDENRODYELLOW = 'lightgoldenrodyellow';
|
||||
case LIGHTGRAY = 'lightgray';
|
||||
case LIGHTGREEN = 'lightgreen';
|
||||
case LIGHTPINK = 'lightpink';
|
||||
case LIGHTSALMON = 'lightsalmon';
|
||||
case LIGHTSEAGREEN = 'lightseagreen';
|
||||
case LIGHTSKYBLUE = 'lightskyblue';
|
||||
case LIGHTSLATEGRAY = 'lightslategray';
|
||||
case LIGHTSTEELBLUE = 'lightsteelblue';
|
||||
case LIGHTYELLOW = 'lightyellow';
|
||||
case LIME = 'lime';
|
||||
case LIMEGREEN = 'limegreen';
|
||||
case LINEN = 'linen';
|
||||
case MAGENTA = 'magenta';
|
||||
case MAROON = 'maroon';
|
||||
case MEDIUMAQUAMARINE = 'mediumaquamarine';
|
||||
case MEDIUMBLUE = 'mediumblue';
|
||||
case MEDIUMORCHID = 'mediumorchid';
|
||||
case MEDIUMPURPLE = 'mediumpurple';
|
||||
case MEDIUMSEAGREEN = 'mediumseagreen';
|
||||
case MEDIUMSLATEBLUE = 'mediumslateblue';
|
||||
case MEDIUMSPRINGGREEN = 'mediumspringgreen';
|
||||
case MEDIUMTURQUOISE = 'mediumturquoise';
|
||||
case MEDIUMVIOLETRED = 'mediumvioletred';
|
||||
case MIDNIGHTBLUE = 'midnightblue';
|
||||
case MINTCREAM = 'mintcream';
|
||||
case MISTYROSE = 'mistyrose';
|
||||
case MOCCASIN = 'moccasin';
|
||||
case NAVAJOWHITE = 'navajowhite';
|
||||
case NAVY = 'navy';
|
||||
case OLDLACE = 'oldlace';
|
||||
case OLIVE = 'olive';
|
||||
case OLIVEDRAB = 'olivedrab';
|
||||
case ORANGE = 'orange';
|
||||
case ORANGERED = 'orangered';
|
||||
case ORCHID = 'orchid';
|
||||
case PALEGOLDENROD = 'palegoldenrod';
|
||||
case PALEGREEN = 'palegreen';
|
||||
case PALETURQUOISE = 'paleturquoise';
|
||||
case PALEVIOLETRED = 'palevioletred';
|
||||
case PAPAYAWHIP = 'papayawhip';
|
||||
case PEACHPUFF = 'peachpuff';
|
||||
case PERU = 'peru';
|
||||
case PINK = 'pink';
|
||||
case PLUM = 'plum';
|
||||
case POWDERBLUE = 'powderblue';
|
||||
case PURPLE = 'purple';
|
||||
case RED = 'red';
|
||||
case ROSYBROWN = 'rosybrown';
|
||||
case ROYALBLUE = 'royalblue';
|
||||
case SADDLEBROWN = 'saddlebrown';
|
||||
case SALMON = 'salmon';
|
||||
case SANDYBROWN = 'sandybrown';
|
||||
case SEAGREEN = 'seagreen';
|
||||
case SEASHELL = 'seashell';
|
||||
case SIENNA = 'sienna';
|
||||
case SILVER = 'silver';
|
||||
case SKYBLUE = 'skyblue';
|
||||
case SLATEBLUE = 'slateblue';
|
||||
case SLATEGRAY = 'slategray';
|
||||
case SNOW = 'snow';
|
||||
case SPRINGGREEN = 'springgreen';
|
||||
case STEELBLUE = 'steelblue';
|
||||
case TAN = 'tan';
|
||||
case TEAL = 'teal';
|
||||
case THISTLE = 'thistle';
|
||||
case TOMATO = 'tomato';
|
||||
case TURQUOISE = 'turquoise';
|
||||
case VIOLET = 'violet';
|
||||
case WHEAT = 'wheat';
|
||||
case WHITE = 'white';
|
||||
case WHITESMOKE = 'whitesmoke';
|
||||
case YELLOW = 'yellow';
|
||||
case YELLOWGREEN = 'yellowgreen';
|
||||
|
||||
/**
|
||||
* Create new named color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function create(string $input): ColorInterface
|
||||
{
|
||||
try {
|
||||
return self::from(strtolower($input));
|
||||
} catch (TypeError | ValueError) {
|
||||
throw new InvalidArgumentException('Failed to create color from input "' . $input . '"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new named color or return null of creation fails.
|
||||
*/
|
||||
public static function tryCreate(string $input): ?ColorInterface
|
||||
{
|
||||
try {
|
||||
return self::from(strtolower($input));
|
||||
} catch (Error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Rgb();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toHex()
|
||||
*/
|
||||
public function toHex(bool $prefix = false): string
|
||||
{
|
||||
return ($prefix ? '#' : '') . match ($this) {
|
||||
self::ALICEBLUE => 'f0f8ff',
|
||||
self::ANTIQUEWHITE => 'faebd7',
|
||||
self::AQUA => '00ffff',
|
||||
self::AQUAMARINE => '7fffd4',
|
||||
self::AZURE => 'f0ffff',
|
||||
self::BEIGE => 'f5f5dc',
|
||||
self::BISQUE => 'ffe4c4',
|
||||
self::BLACK => '000000',
|
||||
self::BLANCHEDALMOND => 'ffebcd',
|
||||
self::BLUE => '0000ff',
|
||||
self::BLUEVIOLET => '8a2be2',
|
||||
self::BROWN => 'a52a2a',
|
||||
self::BURLYWOOD => 'deb887',
|
||||
self::CADETBLUE => '5f9ea0',
|
||||
self::CHARTREUSE => '7fff00',
|
||||
self::CHOCOLATE => 'd2691e',
|
||||
self::CORAL => 'ff7f50',
|
||||
self::CORNFLOWERBLUE => '6495ed',
|
||||
self::CORNSILK => 'fff8dc',
|
||||
self::CRIMSON => 'dc143c',
|
||||
self::CYAN => '00ffff',
|
||||
self::DARKBLUE => '00008b',
|
||||
self::DARKCYAN => '008b8b',
|
||||
self::DARKGRAY => 'a9a9a9',
|
||||
self::DARKGREEN => '006400',
|
||||
self::DARKKHAKI => 'bdb76b',
|
||||
self::DARKMAGENTA => '8b008b',
|
||||
self::DARKOLIVEGREEN => '556b2f',
|
||||
self::DARKORANGE => 'ff8c00',
|
||||
self::DARKORCHID => '9932cc',
|
||||
self::DARKRED => '8b0000',
|
||||
self::DARKSALMON => 'e9967a',
|
||||
self::DARKSEAGREEN => '8fbc8f',
|
||||
self::DARKSLATEBLUE => '483d8b',
|
||||
self::DARKSLATEGRAY => '2f4f4f',
|
||||
self::DARKTURQUOISE => '00ced1',
|
||||
self::DARKVIOLET => '9400d3',
|
||||
self::DEEPPINK => 'ff1493',
|
||||
self::DEEPSKYBLUE => '00bfff',
|
||||
self::DIMGRAY => '696969',
|
||||
self::DODGERBLUE => '1e90ff',
|
||||
self::FIREBRICK => 'b22222',
|
||||
self::FLORALWHITE => 'fffaf0',
|
||||
self::FORESTGREEN => '228b22',
|
||||
self::FUCHSIA => 'ff00ff',
|
||||
self::GAINSBORO => 'dcdcdc',
|
||||
self::GHOSTWHITE => 'f8f8ff',
|
||||
self::GOLD => 'ffd700',
|
||||
self::GOLDENROD => 'daa520',
|
||||
self::GRAY => '808080',
|
||||
self::GREEN => '008000',
|
||||
self::GREENYELLOW => 'adff2f',
|
||||
self::HONEYDEW => 'f0fff0',
|
||||
self::HOTPINK => 'ff69b4',
|
||||
self::INDIANRED => 'cd5c5c',
|
||||
self::INDIGO => '4b0082',
|
||||
self::IVORY => 'fffff0',
|
||||
self::KHAKI => 'f0e68c',
|
||||
self::LAVENDER => 'e6e6fa',
|
||||
self::LAVENDERBLUSH => 'fff0f5',
|
||||
self::LAWNGREEN => '7cfc00',
|
||||
self::LEMONCHIFFON => 'fffacd',
|
||||
self::LIGHTBLUE => 'add8e6',
|
||||
self::LIGHTCORAL => 'f08080',
|
||||
self::LIGHTCYAN => 'e0ffff',
|
||||
self::LIGHTGOLDENRODYELLOW => 'fafad2',
|
||||
self::LIGHTGRAY => 'd3d3d3',
|
||||
self::LIGHTGREEN => '90ee90',
|
||||
self::LIGHTPINK => 'ffb6c1',
|
||||
self::LIGHTSALMON => 'ffa07a',
|
||||
self::LIGHTSEAGREEN => '20b2aa',
|
||||
self::LIGHTSKYBLUE => '87cefa',
|
||||
self::LIGHTSLATEGRAY => '778899',
|
||||
self::LIGHTSTEELBLUE => 'b0c4de',
|
||||
self::LIGHTYELLOW => 'ffffe0',
|
||||
self::LIME => '00ff00',
|
||||
self::LIMEGREEN => '32cd32',
|
||||
self::LINEN => 'faf0e6',
|
||||
self::MAGENTA => 'ff00ff',
|
||||
self::MAROON => '800000',
|
||||
self::MEDIUMAQUAMARINE => '66cdaa',
|
||||
self::MEDIUMBLUE => '0000cd',
|
||||
self::MEDIUMORCHID => 'ba55d3',
|
||||
self::MEDIUMPURPLE => '9370db',
|
||||
self::MEDIUMSEAGREEN => '3cb371',
|
||||
self::MEDIUMSLATEBLUE => '7b68ee',
|
||||
self::MEDIUMSPRINGGREEN => '00fa9a',
|
||||
self::MEDIUMTURQUOISE => '48d1cc',
|
||||
self::MEDIUMVIOLETRED => 'c71585',
|
||||
self::MIDNIGHTBLUE => '191970',
|
||||
self::MINTCREAM => 'f5fffa',
|
||||
self::MISTYROSE => 'ffe4e1',
|
||||
self::MOCCASIN => 'ffe4b5',
|
||||
self::NAVAJOWHITE => 'ffdead',
|
||||
self::NAVY => '000080',
|
||||
self::OLDLACE => 'fdf5e6',
|
||||
self::OLIVE => '808000',
|
||||
self::OLIVEDRAB => '6b8e23',
|
||||
self::ORANGE => 'ffa500',
|
||||
self::ORANGERED => 'ff4500',
|
||||
self::ORCHID => 'da70d6',
|
||||
self::PALEGOLDENROD => 'eee8aa',
|
||||
self::PALEGREEN => '98fb98',
|
||||
self::PALETURQUOISE => 'afeeee',
|
||||
self::PALEVIOLETRED => 'db7093',
|
||||
self::PAPAYAWHIP => 'ffefd5',
|
||||
self::PEACHPUFF => 'ffdab9',
|
||||
self::PERU => 'cd853f',
|
||||
self::PINK => 'ffc0cb',
|
||||
self::PLUM => 'dda0dd',
|
||||
self::POWDERBLUE => 'b0e0e6',
|
||||
self::PURPLE => '800080',
|
||||
self::RED => 'ff0000',
|
||||
self::ROSYBROWN => 'bc8f8f',
|
||||
self::ROYALBLUE => '4169e1',
|
||||
self::SADDLEBROWN => '8b4513',
|
||||
self::SALMON => 'fa8072',
|
||||
self::SANDYBROWN => 'f4a460',
|
||||
self::SEAGREEN => '2e8b57',
|
||||
self::SEASHELL => 'fff5ee',
|
||||
self::SIENNA => 'a0522d',
|
||||
self::SILVER => 'c0c0c0',
|
||||
self::SKYBLUE => '87ceeb',
|
||||
self::SLATEBLUE => '6a5acd',
|
||||
self::SLATEGRAY => '708090',
|
||||
self::SNOW => 'fffafa',
|
||||
self::SPRINGGREEN => '00ff7f',
|
||||
self::STEELBLUE => '4682b4',
|
||||
self::TAN => 'd2b48c',
|
||||
self::TEAL => '008080',
|
||||
self::THISTLE => 'd8bfd8',
|
||||
self::TOMATO => 'ff6347',
|
||||
self::TURQUOISE => '40e0d0',
|
||||
self::VIOLET => 'ee82ee',
|
||||
self::WHEAT => 'f5deb3',
|
||||
self::WHITE => 'ffffff',
|
||||
self::WHITESMOKE => 'f5f5f5',
|
||||
self::YELLOW => 'ffff00',
|
||||
self::YELLOWGREEN => '9acd32',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::channels()
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function channels(): array
|
||||
{
|
||||
return $this->toRgbColor()->channels();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::channel()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function channel(string $classname): ColorChannelInterface
|
||||
{
|
||||
return $this->toRgbColor()->channel($classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::alpha()
|
||||
*/
|
||||
public function alpha(): ColorChannelInterface
|
||||
{
|
||||
// @phpstan-ignore missingType.checkedException
|
||||
return new Alpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::toColorspace()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function toColorspace(string|ColorspaceInterface $colorspace): ColorInterface
|
||||
{
|
||||
return $this->toRgbColor()->toColorspace($colorspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isGrayscale()
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function isGrayscale(): bool
|
||||
{
|
||||
return $this->toRgbColor()->isGrayscale();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isTransparent()
|
||||
*/
|
||||
public function isTransparent(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::isClear()
|
||||
*/
|
||||
public function isClear(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withTransparency()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function withTransparency(float $transparency): ColorInterface
|
||||
{
|
||||
return $this->toRgbColor()->withTransparency($transparency);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withBrightness()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function withBrightness(int $level): ColorInterface
|
||||
{
|
||||
return $this->toRgbColor()->withBrightness($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withSaturation()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function withSaturation(int $level): ColorInterface
|
||||
{
|
||||
return $this->toRgbColor()->withSaturation($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorInterface::withInversion()
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
public function withInversion(): ColorInterface
|
||||
{
|
||||
return $this->toRgbColor()->withInversion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert current named color to rgb color object.
|
||||
*
|
||||
* @throws ColorException
|
||||
*/
|
||||
private function toRgbColor(): RgbColor
|
||||
{
|
||||
$color = $this->colorspace()->importColor($this);
|
||||
|
||||
return $color instanceof RgbColor
|
||||
? $color
|
||||
: throw new ColorException('Failed to convert named color to rgb object');
|
||||
}
|
||||
}
|
||||
69
vendor/intervention/image/src/Config.php
vendored
Normal file
69
vendor/intervention/image/src/Config.php
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* Create config object instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public bool $autoOrientation = true,
|
||||
public bool $decodeAnimation = true,
|
||||
public string|ColorInterface $backgroundColor = 'ffffff',
|
||||
public bool $strip = false,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Set values of given config options.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setOptions(mixed ...$options): self
|
||||
{
|
||||
foreach ($this->prepareOptions($options) as $name => $value) {
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new InvalidArgumentException('Property ' . $name . ' does not exists for ' . $this::class);
|
||||
}
|
||||
|
||||
$this->{$name} = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method makes it possible to call self::setOptions() with a single
|
||||
* array instead of named parameters.
|
||||
*
|
||||
* @param array<mixed> $options
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function prepareOptions(array $options): array
|
||||
{
|
||||
if ($options === []) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
if (count($options) > 1) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
if (!array_key_exists(0, $options)) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
if (!is_array($options[0])) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
return $options[0];
|
||||
}
|
||||
}
|
||||
286
vendor/intervention/image/src/DataUri.php
vendored
Normal file
286
vendor/intervention/image/src/DataUri.php
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\DataUriInterface;
|
||||
use Stringable;
|
||||
|
||||
class DataUri implements DataUriInterface
|
||||
{
|
||||
/**
|
||||
* Pattern of data uri scheme.
|
||||
*/
|
||||
protected const string PATTERN = "/^data:(?P<mediaType>\w+\/[-+.\w]+)?" .
|
||||
"(?P<parameters>(;[-\w]+=[-\w]+)*)(?P<base64>;base64)?,(?P<data>.*)/";
|
||||
|
||||
/**
|
||||
* Media type of data uri output.
|
||||
*/
|
||||
protected ?string $mediaType = null;
|
||||
|
||||
/**
|
||||
* Parameters of data uri output.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $parameters = [];
|
||||
|
||||
/**
|
||||
* Create new data uri instance.
|
||||
*
|
||||
* @param array<string, string> $parameters
|
||||
*/
|
||||
public function __construct(
|
||||
protected string|Stringable $data = '',
|
||||
null|string|MediaType $mediaType = null,
|
||||
array $parameters = [],
|
||||
protected bool $base64 = true
|
||||
) {
|
||||
$this->setMediaType($mediaType);
|
||||
$this->setParameters($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::create()
|
||||
*/
|
||||
public static function create(
|
||||
string $data,
|
||||
null|string|MediaType $mediaType = null,
|
||||
array $parameters = [],
|
||||
bool $base64 = true
|
||||
): self {
|
||||
return new self(
|
||||
data: $data,
|
||||
mediaType: $mediaType,
|
||||
parameters: $parameters,
|
||||
base64: $base64,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::parse()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function parse(string|Stringable $dataUriScheme): self
|
||||
{
|
||||
$result = preg_match(self::PATTERN, (string) $dataUriScheme, $matches);
|
||||
|
||||
if ($result === false || $result === 0) {
|
||||
throw new InvalidArgumentException('Invalid data uri scheme');
|
||||
}
|
||||
|
||||
$isBase64Encoded = $matches['base64'] !== '';
|
||||
|
||||
$datauri = new self(
|
||||
data: $isBase64Encoded ? base64_decode($matches['data'], strict: true) : rawurldecode($matches['data']),
|
||||
mediaType: $matches['mediaType'],
|
||||
base64: $isBase64Encoded,
|
||||
);
|
||||
|
||||
if ($matches['parameters'] !== '') {
|
||||
$parameters = explode(';', $matches['parameters']);
|
||||
$parameters = array_filter($parameters, fn(string $value): bool => $value !== '');
|
||||
$parameters = array_map(fn(string $value): array => explode('=', $value), $parameters);
|
||||
foreach ($parameters as $parameter) {
|
||||
$datauri->setParameter(...$parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return $datauri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::data()
|
||||
*/
|
||||
public function data(): string
|
||||
{
|
||||
return (string) $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::setData()
|
||||
*/
|
||||
public function setData(string|Stringable $data): self
|
||||
{
|
||||
$this->data = (string) $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::mediaType()
|
||||
*/
|
||||
public function mediaType(): ?string
|
||||
{
|
||||
return $this->mediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::setMediaType()
|
||||
*/
|
||||
public function setMediaType(null|string|MediaType $mediaType): self
|
||||
{
|
||||
$this->mediaType = $mediaType instanceof MediaType ? $mediaType->value : $mediaType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::parameters()
|
||||
*/
|
||||
public function parameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::setParameters()
|
||||
*/
|
||||
public function setParameters(array $parameters): self
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::appendParameters()
|
||||
*/
|
||||
public function appendParameters(array $parameters): self
|
||||
{
|
||||
foreach ($parameters as $key => $value) {
|
||||
$this->setParameter((string) $key, (string) $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::parameter()
|
||||
*/
|
||||
public function parameter(string $key): ?string
|
||||
{
|
||||
return $this->parameters[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::setParameter()
|
||||
*/
|
||||
public function setParameter(string $key, string $value): self
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::charset()
|
||||
*/
|
||||
public function charset(): ?string
|
||||
{
|
||||
return $this->parameter('charset');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::setCharset()
|
||||
*/
|
||||
public function setCharset(string $charset): self
|
||||
{
|
||||
$this->setParameter('charset', $charset);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data for output.
|
||||
*/
|
||||
private function encodedData(): string
|
||||
{
|
||||
return $this->base64 === true ? (string) base64_encode($this->data) : rawurlencode((string) $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare all set parameters for output.
|
||||
*/
|
||||
private function encodedParameters(): string
|
||||
{
|
||||
if (count($this->parameters) === 0 && $this->base64 === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parameters = array_map(function (mixed $key, mixed $value) {
|
||||
return $key . '=' . $value;
|
||||
}, array_keys($this->parameters), $this->parameters);
|
||||
|
||||
$parameterString = count($parameters) > 0 ? ';' . implode(';', $parameters) : '';
|
||||
|
||||
if ($this->base64 === true) {
|
||||
$parameterString .= ';base64';
|
||||
}
|
||||
|
||||
return $parameterString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DataUriInterface::toString()
|
||||
*/
|
||||
public function toString(): string
|
||||
{
|
||||
return 'data:' . $this->mediaType() . $this->encodedParameters() . ',' . $this->encodedData();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see Stringable::__toString()
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show debug info for the current data uri scheme.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return [
|
||||
'mediaType' => $this->mediaType,
|
||||
'size' => strlen($this->data),
|
||||
];
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/Base64ImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/Base64ImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class Base64ImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/BinaryImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/BinaryImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class BinaryImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
38
vendor/intervention/image/src/Decoders/ColorObjectDecoder.php
vendored
Normal file
38
vendor/intervention/image/src/Decoders/ColorObjectDecoder.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
class ColorObjectDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $input instanceof ColorInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function decode(mixed $input): ColorInterface
|
||||
{
|
||||
if (!$input instanceof ColorInterface) {
|
||||
throw new InvalidArgumentException('Color object must be of type ' . ColorInterface::class);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/DataUriImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/DataUriImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class DataUriImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/EncodedImageObjectDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/EncodedImageObjectDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class EncodedImageObjectDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/FilePathImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/FilePathImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class FilePathImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
32
vendor/intervention/image/src/Decoders/ImageObjectDecoder.php
vendored
Normal file
32
vendor/intervention/image/src/Decoders/ImageObjectDecoder.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractDecoder;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
class ImageObjectDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $input instanceof ImageInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface|ColorInterface
|
||||
{
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/NativeObjectDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/NativeObjectDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class NativeObjectDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/SplFileInfoImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/SplFileInfoImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class SplFileInfoImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/image/src/Decoders/StreamImageDecoder.php
vendored
Normal file
12
vendor/intervention/image/src/Decoders/StreamImageDecoder.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
|
||||
class StreamImageDecoder extends SpecializableDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
11
vendor/intervention/image/src/Direction.php
vendored
Normal file
11
vendor/intervention/image/src/Direction.php
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image;
|
||||
|
||||
enum Direction
|
||||
{
|
||||
case HORIZONTAL;
|
||||
case VERTICAL;
|
||||
}
|
||||
97
vendor/intervention/image/src/Drivers/AbstractDecoder.php
vendored
Normal file
97
vendor/intervention/image/src/Drivers/AbstractDecoder.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Exception;
|
||||
use Intervention\Image\Collection;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\RuntimeException;
|
||||
use Intervention\Image\Interfaces\CollectionInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Traits\CanBuildStream;
|
||||
use Intervention\Image\Traits\CanParseFilePath;
|
||||
use Stringable;
|
||||
use Throwable;
|
||||
|
||||
abstract class AbstractDecoder implements DecoderInterface
|
||||
{
|
||||
use CanBuildStream;
|
||||
use CanParseFilePath;
|
||||
|
||||
/**
|
||||
* Determine if the given input is GIF data format.
|
||||
*/
|
||||
protected function isGifFormat(string $input): bool
|
||||
{
|
||||
return str_starts_with($input, 'GIF87a') || str_starts_with($input, 'GIF89a');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and return EXIF data from given input which can be a file path
|
||||
* or a stream stream resource.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DecoderException
|
||||
* @return CollectionInterface<string, mixed>
|
||||
*/
|
||||
protected function extractExifData(string $input): CollectionInterface
|
||||
{
|
||||
if (!function_exists('exif_read_data')) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
try {
|
||||
// source might be file path
|
||||
$source = self::readableFilePathOrFail($input);
|
||||
} catch (Throwable) {
|
||||
try {
|
||||
// source might be stream resource
|
||||
$source = self::buildStreamOrFail($input);
|
||||
} catch (RuntimeException) {
|
||||
return new Collection();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// extract exif data
|
||||
$data = @exif_read_data($source, null, true);
|
||||
if (is_resource($source)) {
|
||||
fclose($source);
|
||||
}
|
||||
} catch (Exception) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
return new Collection(is_array($data) ? $data : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes given base64 encoded data.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeBase64Data(mixed $input): string
|
||||
{
|
||||
if (!is_string($input) && !$input instanceof Stringable) {
|
||||
throw new InvalidArgumentException(
|
||||
'Base64-encoded data must be either of type string or instance of Stringable',
|
||||
);
|
||||
}
|
||||
|
||||
$decoded = base64_decode((string) $input, true);
|
||||
|
||||
if ($decoded === false) {
|
||||
throw new DecoderException('Input is not valid Base64-encoded data');
|
||||
}
|
||||
|
||||
if (base64_encode($decoded) !== str_replace(["\n", "\r"], '', (string) $input)) {
|
||||
throw new DecoderException('Input is not valid Base64-encoded data');
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
}
|
||||
199
vendor/intervention/image/src/Drivers/AbstractDriver.php
vendored
Normal file
199
vendor/intervention/image/src/Drivers/AbstractDriver.php
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Intervention\Image\Config;
|
||||
use Intervention\Image\Exceptions\ColorDecoderException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\MissingDependencyException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\InputHandler;
|
||||
use Intervention\Image\Interfaces\AnalyzerInterface;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\DriverInterface;
|
||||
use Intervention\Image\Interfaces\EncoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
use Intervention\Image\Interfaces\SpecializableInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
|
||||
abstract class AbstractDriver implements DriverInterface
|
||||
{
|
||||
/**
|
||||
* @throws MissingDependencyException
|
||||
*/
|
||||
public function __construct(protected Config $config = new Config())
|
||||
{
|
||||
$this->checkHealth();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::config()
|
||||
*/
|
||||
public function config(): Config
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::decodeImage()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public function decodeImage(mixed $input, ?array $decoders = null): ImageInterface
|
||||
{
|
||||
$decoders = $decoders === null ? InputHandler::IMAGE_DECODERS : $decoders;
|
||||
|
||||
if (count($decoders) === 0) {
|
||||
throw new InvalidArgumentException('No decoders in array');
|
||||
}
|
||||
|
||||
try {
|
||||
$result = InputHandler::usingDecoders($decoders, $this)->handle($input);
|
||||
} catch (NotSupportedException) {
|
||||
$type = is_object($input) ? $input::class : gettype($input);
|
||||
throw new InvalidArgumentException('Unsupported image source type "' . $type . '"');
|
||||
}
|
||||
|
||||
if (!$result instanceof ImageInterface) {
|
||||
throw new ImageDecoderException('Result must be instance of ' . ImageInterface::class);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::decodeColor()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ColorDecoderException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public function decodeColor(mixed $input, ?array $decoders = null): ColorInterface
|
||||
{
|
||||
$decoders = $decoders === null ? InputHandler::COLOR_DECODERS : $decoders;
|
||||
|
||||
if (count($decoders) === 0) {
|
||||
throw new InvalidArgumentException('No decoders in array');
|
||||
}
|
||||
|
||||
try {
|
||||
$result = InputHandler::usingDecoders($decoders, $this)->handle($input);
|
||||
} catch (NotSupportedException) {
|
||||
throw new ColorDecoderException('Unknown color format');
|
||||
}
|
||||
|
||||
if (!$result instanceof ColorInterface) {
|
||||
throw new ColorDecoderException('Result must be instance of ' . ColorInterface::class);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::specializeModifier()
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function specializeModifier(ModifierInterface $modifier): ModifierInterface
|
||||
{
|
||||
return $this->specialize($modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::specializeAnalyzer()
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function specializeAnalyzer(AnalyzerInterface $analyzer): AnalyzerInterface
|
||||
{
|
||||
return $this->specialize($analyzer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::specializeEncoder()
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function specializeEncoder(EncoderInterface $encoder): EncoderInterface
|
||||
{
|
||||
return $this->specialize($encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::specializeDecoder()
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function specializeDecoder(DecoderInterface $decoder): DecoderInterface
|
||||
{
|
||||
return $this->specialize($decoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
private function specialize(
|
||||
ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface $object
|
||||
): ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface {
|
||||
// return object directly if no specializing is possible
|
||||
if (!$object instanceof SpecializableInterface) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
// return directly and only attach driver if object is already specialized
|
||||
if ($object instanceof SpecializedInterface) {
|
||||
$object->setDriver($this);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
// resolve classname for specializable object
|
||||
$objectShortname = substr($object::class, (int) strrpos($object::class, '\\') + 1);
|
||||
|
||||
$specializedClassname = implode("\\", [
|
||||
substr($this::class, 0, (int) strrpos($this::class, '\\')), // driver's namespace
|
||||
match (true) {
|
||||
$object instanceof ModifierInterface => 'Modifiers',
|
||||
$object instanceof AnalyzerInterface => 'Analyzers',
|
||||
$object instanceof EncoderInterface => 'Encoders',
|
||||
$object instanceof DecoderInterface => 'Decoders',
|
||||
},
|
||||
$objectShortname,
|
||||
]);
|
||||
|
||||
// fail if driver specialized classname does not exists
|
||||
if (!class_exists($specializedClassname)) {
|
||||
throw new NotSupportedException(
|
||||
"Class '" . $objectShortname . "' is not supported by " . $this->id() . " driver"
|
||||
);
|
||||
}
|
||||
|
||||
// create a driver specialized object with the specializable properties of generic object
|
||||
$specialized = new $specializedClassname(...$object->specializationArguments());
|
||||
|
||||
// attach driver
|
||||
return $specialized->setDriver($this);
|
||||
}
|
||||
}
|
||||
78
vendor/intervention/image/src/Drivers/AbstractEncoder.php
vendored
Normal file
78
vendor/intervention/image/src/Drivers/AbstractEncoder.php
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Intervention\Image\EncodedImage;
|
||||
use Intervention\Image\Exceptions\LogicException;
|
||||
use Intervention\Image\Exceptions\StreamException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\EncodedImageInterface;
|
||||
use Intervention\Image\Interfaces\EncoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
use Intervention\Image\Traits\CanBuildStream;
|
||||
|
||||
abstract class AbstractEncoder implements EncoderInterface
|
||||
{
|
||||
use CanBuildStream;
|
||||
|
||||
/**
|
||||
* Default encoding quality.
|
||||
*/
|
||||
public const int DEFAULT_QUALITY = 75;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see EncoderInterface::encode()
|
||||
*
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function encode(ImageInterface $image): EncodedImageInterface
|
||||
{
|
||||
if ($this instanceof SpecializedInterface) {
|
||||
throw new LogicException(
|
||||
"Specialized class '" . static::class . "' must override encode()"
|
||||
);
|
||||
}
|
||||
|
||||
return $image->encode($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build new stream, run callback with it and return result as encoded image.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws StreamException
|
||||
*/
|
||||
protected function createEncodedImage(callable $callback, ?string $mediaType = null): EncodedImage
|
||||
{
|
||||
$stream = self::buildStreamOrFail();
|
||||
$callback($stream);
|
||||
|
||||
return is_string($mediaType) ? new EncodedImage($stream, $mediaType) : new EncodedImage($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see EncoderInterface::setOptions()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setOptions(mixed ...$options): self
|
||||
{
|
||||
foreach ($options as $key => $value) {
|
||||
if (!property_exists($this, (string) $key)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Option $' . $key . ' does not exist on ' . $this::class,
|
||||
);
|
||||
}
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
174
vendor/intervention/image/src/Drivers/AbstractFontProcessor.php
vendored
Normal file
174
vendor/intervention/image/src/Drivers/AbstractFontProcessor.php
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Intervention\Image\Alignment;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||
use Intervention\Image\Interfaces\PointInterface;
|
||||
use Intervention\Image\Size;
|
||||
use Intervention\Image\Typography\Line;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
abstract class AbstractFontProcessor implements FontProcessorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::textBlock()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock
|
||||
{
|
||||
$lines = $this->wrapTextBlock(new TextBlock($text), $font);
|
||||
$pivot = $this->buildPivot($lines, $font, $position);
|
||||
|
||||
$leading = $this->leading($font);
|
||||
$blockWidth = $this->boxSize((string) $lines->longestLine(), $font)->width();
|
||||
|
||||
$x = $pivot->x();
|
||||
$y = $font->hasFile() ? $pivot->y() + $this->capHeight($font) : $pivot->y();
|
||||
$xAdjustment = 0;
|
||||
|
||||
// adjust line positions according to alignment
|
||||
$horizontalAlignment = $font->alignmentHorizontal();
|
||||
foreach ($lines as $line) {
|
||||
$lineBoxSize = $this->boxSize((string) $line, $font);
|
||||
$lineWidth = $lineBoxSize->width() + $lineBoxSize->pivot()->x();
|
||||
$xAdjustment = $horizontalAlignment === Alignment::LEFT ? 0 : $blockWidth - $lineWidth;
|
||||
$xAdjustment = $horizontalAlignment === Alignment::RIGHT ? intval(round($xAdjustment)) : $xAdjustment;
|
||||
$xAdjustment = $horizontalAlignment === Alignment::CENTER ? intval(round($xAdjustment / 2)) : $xAdjustment;
|
||||
$position = new Point($x + $xAdjustment, $y);
|
||||
$position->rotate($font->angle(), $pivot);
|
||||
$line->setPosition($position);
|
||||
$y += $leading;
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::nativeFontSize()
|
||||
*/
|
||||
public function nativeFontSize(FontInterface $font): float
|
||||
{
|
||||
return $font->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::typographicalSize()
|
||||
*/
|
||||
public function typographicalSize(FontInterface $font): int
|
||||
{
|
||||
return $this->boxSize('Hy', $font)->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::capHeight()
|
||||
*/
|
||||
public function capHeight(FontInterface $font): int
|
||||
{
|
||||
return $this->boxSize('T', $font)->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::leading()
|
||||
*/
|
||||
public function leading(FontInterface $font): int
|
||||
{
|
||||
return intval(round($this->typographicalSize($font) * $font->lineHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reformat a text block by wrapping each line before the given maximum width.
|
||||
*/
|
||||
protected function wrapTextBlock(TextBlock $block, FontInterface $font): TextBlock
|
||||
{
|
||||
$newLines = [];
|
||||
foreach ($block as $line) {
|
||||
foreach ($this->wrapLine($line, $font) as $newLine) {
|
||||
$newLines[] = $newLine;
|
||||
}
|
||||
}
|
||||
|
||||
return $block->setLines($newLines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a line exceeds the given maximum width and wrap it if necessary.
|
||||
* The output will be an array of formatted lines that are all within the
|
||||
* maximum width.
|
||||
*
|
||||
* @return array<Line>
|
||||
*/
|
||||
protected function wrapLine(Line $line, FontInterface $font): array
|
||||
{
|
||||
// no wrap width - no wrapping
|
||||
if (is_null($font->wrapWidth())) {
|
||||
return [$line];
|
||||
}
|
||||
|
||||
$wrapped = [];
|
||||
$formattedLine = new Line();
|
||||
|
||||
foreach ($line as $word) {
|
||||
// calculate width of newly formatted line
|
||||
$lineWidth = $this->boxSize(match ($formattedLine->count()) {
|
||||
0 => $word,
|
||||
default => $formattedLine . ' ' . $word,
|
||||
}, $font)->width();
|
||||
|
||||
// decide if word fits on current line or a new line must be created
|
||||
if ($line->count() === 1 || $lineWidth <= $font->wrapWidth()) {
|
||||
$formattedLine->add($word);
|
||||
} else {
|
||||
if ($formattedLine->count() !== 0) {
|
||||
$wrapped[] = $formattedLine;
|
||||
}
|
||||
$formattedLine = new Line($word);
|
||||
}
|
||||
}
|
||||
|
||||
$wrapped[] = $formattedLine;
|
||||
|
||||
return $wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pivot point of textblock according to the font settings and based on given position.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface
|
||||
{
|
||||
// bounding box
|
||||
$box = Polygon::fromSize(new Size(
|
||||
$this->boxSize((string) $block->longestLine(), $font)->width(),
|
||||
$this->leading($font) * ($block->count() - 1) + $this->capHeight($font)
|
||||
));
|
||||
|
||||
// set position
|
||||
$box->setPivot($position);
|
||||
|
||||
// alignment
|
||||
$box->alignHorizontally($font->alignmentHorizontal());
|
||||
$box->alignVertically($font->alignmentVertical());
|
||||
$box->rotate($font->angle());
|
||||
|
||||
return $box->last();
|
||||
}
|
||||
}
|
||||
25
vendor/intervention/image/src/Drivers/AbstractFrame.php
vendored
Normal file
25
vendor/intervention/image/src/Drivers/AbstractFrame.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Intervention\Image\Interfaces\FrameInterface;
|
||||
|
||||
abstract class AbstractFrame implements FrameInterface
|
||||
{
|
||||
/**
|
||||
* Show debug info for the current image.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return [
|
||||
'delay' => $this->delay(),
|
||||
'left' => $this->offsetLeft(),
|
||||
'top' => $this->offsetTop(),
|
||||
'disposalMethod' => $this->disposalMethod(),
|
||||
];
|
||||
}
|
||||
}
|
||||
23
vendor/intervention/image/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
vendored
Normal file
23
vendor/intervention/image/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\ColorspaceAnalyzer as GenericColorspaceAnalyzer;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
|
||||
class ColorspaceAnalyzer extends GenericColorspaceAnalyzer implements SpecializedInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
return new Colorspace();
|
||||
}
|
||||
}
|
||||
22
vendor/intervention/image/src/Drivers/Gd/Analyzers/HeightAnalyzer.php
vendored
Normal file
22
vendor/intervention/image/src/Drivers/Gd/Analyzers/HeightAnalyzer.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\HeightAnalyzer as GenericHeightAnalyzer;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
|
||||
class HeightAnalyzer extends GenericHeightAnalyzer implements SpecializedInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
return imagesy($image->core()->native());
|
||||
}
|
||||
}
|
||||
61
vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
vendored
Normal file
61
vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\PixelColorAnalyzer as GenericPixelColorAnalyzer;
|
||||
use Intervention\Image\Exceptions\AnalyzerException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\StateException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\FrameInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
use ValueError;
|
||||
|
||||
class PixelColorAnalyzer extends GenericPixelColorAnalyzer implements SpecializedInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws AnalyzerException
|
||||
* @throws StateException
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
$colorProcessor = $this->driver()->colorProcessor($image);
|
||||
|
||||
return $this->colorAt($colorProcessor, $image->core()->frame($this->frame));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException
|
||||
* @throws AnalyzerException
|
||||
*/
|
||||
protected function colorAt(ColorProcessorInterface $processor, FrameInterface $frame): ColorInterface
|
||||
{
|
||||
$gd = $frame->native();
|
||||
$index = @imagecolorat($gd, $this->x, $this->y);
|
||||
|
||||
if (!is_int($index)) {
|
||||
throw new InvalidArgumentException(
|
||||
'The specified position (' . $this->x . ', ' . $this->y . ') is not within the image area',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$index = imagecolorsforindex($gd, $index);
|
||||
} catch (ValueError) {
|
||||
throw new AnalyzerException(
|
||||
'The specified index is outside of the range',
|
||||
);
|
||||
}
|
||||
|
||||
return $processor->import($index);
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
vendored
Normal file
30
vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Collection;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
class PixelColorsAnalyzer extends PixelColorAnalyzer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
$colors = new Collection();
|
||||
$colorProcessor = $this->driver()->colorProcessor($image);
|
||||
|
||||
foreach ($image as $frame) {
|
||||
$colors->push(
|
||||
parent::colorAt($colorProcessor, $frame)
|
||||
);
|
||||
}
|
||||
|
||||
return $colors;
|
||||
}
|
||||
}
|
||||
219
vendor/intervention/image/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
vendored
Normal file
219
vendor/intervention/image/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\ResolutionAnalyzer as GenericResolutionAnalyzer;
|
||||
use Intervention\Image\Exceptions\AnalyzerException;
|
||||
use Intervention\Image\Exceptions\StreamException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\MissingDependencyException;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\OriginInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
use Intervention\Image\Resolution;
|
||||
use Intervention\Image\Traits\CanBuildStream;
|
||||
use Throwable;
|
||||
|
||||
class ResolutionAnalyzer extends GenericResolutionAnalyzer implements SpecializedInterface
|
||||
{
|
||||
use CanBuildStream;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws AnalyzerException
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
$result = imageresolution($image->core()->native());
|
||||
|
||||
if (!is_array($result)) {
|
||||
throw new AnalyzerException('Failed to read image resolution');
|
||||
}
|
||||
|
||||
// GD returns 96x96 as resolution by default even if the image has no resolution.
|
||||
// This is problematic because it is impossible to tell whether the image
|
||||
// really has this resolution or whether it just corresponds to the default value.
|
||||
//
|
||||
// If GD's default resolution is returned here and the resolution is still unchanged
|
||||
// we will make an attempt to find the resolution from origin.
|
||||
if ($this->isGdDefaultResolution($result) && $image->core()->meta()->get('resolutionChanged') !== true) {
|
||||
try {
|
||||
$alternativeResoltion = $this->readResolutionFromOrigin($image->origin());
|
||||
} catch (Throwable) {
|
||||
$alternativeResoltion = [96, 96];
|
||||
}
|
||||
|
||||
$result = $alternativeResoltion !== $result ? $alternativeResoltion : $result;
|
||||
}
|
||||
|
||||
return new Resolution(...$result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|float> $resolution
|
||||
*/
|
||||
private function isGdDefaultResolution(array $resolution): bool
|
||||
{
|
||||
return intval($resolution[0] ?? 0) === 96 && intval($resolution[1] ?? 0) === 96;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AnalyzerException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws StreamException
|
||||
* @return array<float>
|
||||
*/
|
||||
private function readResolutionFromOrigin(OriginInterface $origin): array
|
||||
{
|
||||
$handle = self::buildStreamOrFail(file_get_contents($origin->filePath()));
|
||||
|
||||
try {
|
||||
try {
|
||||
return $this->resolutionFromJfifHeader($handle);
|
||||
} catch (Throwable) {
|
||||
# code ...
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->resolutionFromExifHeader($handle);
|
||||
} catch (Throwable) {
|
||||
# code ...
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->resolutionFromPngPhys($handle);
|
||||
} catch (Throwable) {
|
||||
# code ...
|
||||
}
|
||||
|
||||
throw new AnalyzerException('Unable to read resolution from path');
|
||||
} finally {
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @throws AnalyzerException
|
||||
* @return array<float>
|
||||
*/
|
||||
private function resolutionFromJfifHeader($handle): array
|
||||
{
|
||||
// read first 20 bytes
|
||||
rewind($handle);
|
||||
$header = fread($handle, 20);
|
||||
|
||||
// find the JFIF segment
|
||||
$offset = strpos($header, 'JFIF');
|
||||
if ($offset === false) {
|
||||
throw new AnalyzerException('Unable to read JFIF header');
|
||||
}
|
||||
|
||||
// read bytes at known offsets relative to JFIF
|
||||
$units = ord($header[$offset + 7]);
|
||||
$x = unpack('n', substr($header, $offset + 8, 2))[1];
|
||||
$y = unpack('n', substr($header, $offset + 10, 2))[1];
|
||||
|
||||
if ($units === 2) { // unit is dots per cm → convert to DPI
|
||||
return [round($x * 2.54), round($y * 2.54)];
|
||||
}
|
||||
|
||||
return [$x, $y]; // unit is DPI or no unit
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @throws MissingDependencyException
|
||||
* @throws AnalyzerException
|
||||
* @return array<float>
|
||||
*/
|
||||
private function resolutionFromExifHeader($handle): array
|
||||
{
|
||||
if (!function_exists('exif_read_data')) {
|
||||
throw new MissingDependencyException('Unable to read exif data');
|
||||
}
|
||||
|
||||
rewind($handle);
|
||||
$data = @exif_read_data($handle, null, true);
|
||||
|
||||
if ($data === false) {
|
||||
throw new AnalyzerException('Unable to read exif data');
|
||||
}
|
||||
|
||||
if (isset($data['XResolution']) && isset($data['YResolution'])) {
|
||||
$resolution = [$data['XResolution'], $data['YResolution']];
|
||||
}
|
||||
|
||||
if (isset($data['IFD0']) && isset($data['IFD0']['XResolution']) && isset($data['IFD0']['YResolution'])) {
|
||||
$resolution = [$data['IFD0']['XResolution'], $data['IFD0']['YResolution']];
|
||||
}
|
||||
|
||||
if (!isset($resolution)) {
|
||||
throw new AnalyzerException('Unable to read exif data');
|
||||
}
|
||||
|
||||
return array_map(function (mixed $value): int|float {
|
||||
if (strpos($value, '/') === false) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$values = array_map(fn(string $value): int => intval($value), explode('/', $value));
|
||||
|
||||
if ($values[1] === 0) {
|
||||
throw new AnalyzerException('Unable to read exif data, division by zero');
|
||||
}
|
||||
|
||||
return $values[0] / $values[1];
|
||||
}, $resolution);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $handle
|
||||
* @throws AnalyzerException
|
||||
* @return array<float>
|
||||
*/
|
||||
private function resolutionFromPngPhys($handle): array
|
||||
{
|
||||
rewind($handle);
|
||||
$signature = fread($handle, 8);
|
||||
|
||||
// no PNG content
|
||||
if ($signature !== "\x89PNG\x0D\x0A\x1A\x0A") {
|
||||
throw new AnalyzerException('Input must be PNG format');
|
||||
}
|
||||
|
||||
$marker = '';
|
||||
|
||||
while (!feof($handle)) {
|
||||
$marker = strlen($marker) < 4 ? $marker . fread($handle, 1) : substr($marker, 1) . fread($handle, 1);
|
||||
|
||||
// find pHYs chunk
|
||||
if ($marker === 'pHYs') {
|
||||
// find length
|
||||
fseek($handle, -8, SEEK_CUR);
|
||||
$length = fread($handle, 4);
|
||||
$length = unpack('N', $length)[1];
|
||||
fseek($handle, 4, SEEK_CUR);
|
||||
|
||||
// read data
|
||||
$data = fread($handle, $length);
|
||||
|
||||
$x = unpack('N', substr($data, 0, 4))[1];
|
||||
$y = unpack('N', substr($data, 4, 4))[1];
|
||||
|
||||
return [
|
||||
round($x * .0254),
|
||||
round($y * .0254),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [0, 0];
|
||||
}
|
||||
}
|
||||
22
vendor/intervention/image/src/Drivers/Gd/Analyzers/WidthAnalyzer.php
vendored
Normal file
22
vendor/intervention/image/src/Drivers/Gd/Analyzers/WidthAnalyzer.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||
|
||||
use Intervention\Image\Analyzers\WidthAnalyzer as GenericWidthAnalyzer;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
|
||||
class WidthAnalyzer extends GenericWidthAnalyzer implements SpecializedInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AnalyzerInterface::analyze()
|
||||
*/
|
||||
public function analyze(ImageInterface $image): mixed
|
||||
{
|
||||
return imagesx($image->core()->native());
|
||||
}
|
||||
}
|
||||
100
vendor/intervention/image/src/Drivers/Gd/Cloner.php
vendored
Normal file
100
vendor/intervention/image/src/Drivers/Gd/Cloner.php
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd;
|
||||
|
||||
use GdImage;
|
||||
use Intervention\Image\Colors\Rgb\Color;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\SizeInterface;
|
||||
use Intervention\Image\Size;
|
||||
|
||||
class Cloner
|
||||
{
|
||||
/**
|
||||
* Create a clone of the given GdImage
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function clone(GdImage $gd): GdImage
|
||||
{
|
||||
// create empty canvas with same size
|
||||
$clone = static::cloneEmpty($gd);
|
||||
|
||||
// transfer actual image to clone
|
||||
imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd));
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an "empty" clone of the given GdImage
|
||||
*
|
||||
* This only retains the basic data without transferring the actual image.
|
||||
* It is optionally possible to change the size of the result and set a
|
||||
* background color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function cloneEmpty(
|
||||
GdImage $gd,
|
||||
?SizeInterface $size = null,
|
||||
Color $background = new Color(255, 255, 255, 0)
|
||||
): GdImage {
|
||||
// define size
|
||||
$size = $size ?: new Size(imagesx($gd), imagesy($gd));
|
||||
|
||||
if ($size->width() < 1 || $size->height() < 1) {
|
||||
throw new InvalidArgumentException('Invalid image size');
|
||||
}
|
||||
|
||||
// create new gd image with same size or new given size
|
||||
$clone = imagecreatetruecolor($size->width(), $size->height());
|
||||
if ($clone === false) {
|
||||
throw new DriverException('Failed to create new image while cloning');
|
||||
}
|
||||
|
||||
// copy resolution to clone
|
||||
$resolution = imageresolution($gd);
|
||||
if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) {
|
||||
imageresolution($clone, $resolution[0], $resolution[1]);
|
||||
}
|
||||
|
||||
// fill with background
|
||||
$processor = new ColorProcessor();
|
||||
|
||||
imagefill($clone, 0, 0, $processor->export($background));
|
||||
imagealphablending($clone, true);
|
||||
imagesavealpha($clone, true);
|
||||
|
||||
// set background image as transparent if alpha channel value if color is below .5
|
||||
// comes into effect when the end format only supports binary transparency (like GIF)
|
||||
if ($background->alpha()->value() < .5) {
|
||||
imagecolortransparent($clone, $processor->export($background));
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a clone of an GdImage that is positioned on the specified background color.
|
||||
* Possible transparent areas are mixed with this color.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public static function cloneBlended(GdImage $gd, Color $background): GdImage
|
||||
{
|
||||
// create empty canvas with same size
|
||||
$clone = static::cloneEmpty($gd, background: $background);
|
||||
|
||||
// transfer actual image to clone
|
||||
imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd));
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
156
vendor/intervention/image/src/Drivers/Gd/ColorProcessor.php
vendored
Normal file
156
vendor/intervention/image/src/Drivers/Gd/ColorProcessor.php
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Colors\Rgb\Channels\Alpha;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||
use Intervention\Image\Colors\Rgb\Color;
|
||||
use Intervention\Image\Colors\Rgb\Colorspace as Rgb;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\RuntimeException;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
use Intervention\Image\Traits\CanConvertRange;
|
||||
|
||||
class ColorProcessor implements ColorProcessorInterface
|
||||
{
|
||||
use CanConvertRange;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorProcessorInterface::colorspace()
|
||||
*/
|
||||
public function colorspace(): ColorspaceInterface
|
||||
{
|
||||
return new Rgb();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorProcessorInterface::export()
|
||||
*
|
||||
* @throws DriverException
|
||||
*/
|
||||
public function export(ColorInterface $color): int
|
||||
{
|
||||
// convert color to colorspace
|
||||
$color = $color->toColorspace($this->colorspace());
|
||||
|
||||
// gd only supports rgb so the channels can be accessed directly
|
||||
$r = $color->channel(Red::class)->value();
|
||||
$g = $color->channel(Green::class)->value();
|
||||
$b = $color->channel(Blue::class)->value();
|
||||
$a = $color->channel(Alpha::class)->value();
|
||||
|
||||
try {
|
||||
// convert alpha value to gd alpha
|
||||
// ([opaque]1-0[transparent]) to ([opaque]0-127[transparent])
|
||||
$a = (int) round(self::convertRange($a, Alpha::min(), Alpha::max(), 127, 0));
|
||||
} catch (RuntimeException $e) {
|
||||
throw new DriverException('Failed to export color', previous: $e);
|
||||
}
|
||||
|
||||
return ($a << 24) + ($r << 16) + ($g << 8) + $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ColorProcessorInterface::import()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
*/
|
||||
public function import(mixed $color): ColorInterface
|
||||
{
|
||||
if (!is_int($color) && !is_array($color)) {
|
||||
throw new InvalidArgumentException('GD driver can only decode colors in integer or array format');
|
||||
}
|
||||
|
||||
if (is_array($color)) {
|
||||
// array conversion
|
||||
if (!$this->isValidArrayColor($color)) {
|
||||
throw new InvalidArgumentException(
|
||||
'GD driver can only decode array color format array{red: int, green: int, blue: int, alpha: int}',
|
||||
);
|
||||
}
|
||||
|
||||
$r = $color['red'];
|
||||
$g = $color['green'];
|
||||
$b = $color['blue'];
|
||||
$a = $color['alpha'];
|
||||
} else {
|
||||
// integer conversion
|
||||
$a = ($color >> 24) & 0xFF;
|
||||
$r = ($color >> 16) & 0xFF;
|
||||
$g = ($color >> 8) & 0xFF;
|
||||
$b = $color & 0xFF;
|
||||
}
|
||||
|
||||
try {
|
||||
// convert gd apha integer to intervention alpha integer
|
||||
// ([opaque]0-127[transparent]) to ([opaque]1-0[transparent])
|
||||
$a = self::convertRange($a, 127, 0, 0, 1);
|
||||
} catch (RuntimeException $e) {
|
||||
throw new DriverException('Failed to import color', previous: $e);
|
||||
}
|
||||
|
||||
try {
|
||||
return new Color($r, $g, $b, $a);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DriverException('Failed to import color', previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given array is valid color format
|
||||
* array{red: int, green: int, blue: int, alpha: int}
|
||||
* i.e. result of imagecolorsforindex()
|
||||
*
|
||||
* @param array<mixed> $color
|
||||
*/
|
||||
private function isValidArrayColor(array $color): bool
|
||||
{
|
||||
if (!array_key_exists('red', $color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists('green', $color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists('blue', $color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!array_key_exists('alpha', $color)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_int($color['red'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_int($color['green'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_int($color['blue'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_int($color['alpha'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
145
vendor/intervention/image/src/Drivers/Gd/Core.php
vendored
Normal file
145
vendor/intervention/image/src/Drivers/Gd/Core.php
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Collection;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Interfaces\CollectionInterface;
|
||||
use Intervention\Image\Interfaces\CoreInterface;
|
||||
use Intervention\Image\Interfaces\FrameInterface;
|
||||
|
||||
class Core extends Collection implements CoreInterface
|
||||
{
|
||||
protected int $loops = 0;
|
||||
protected CollectionInterface $meta;
|
||||
|
||||
/**
|
||||
* Create new core
|
||||
*
|
||||
* @param array<int|string, FrameInterface> $items
|
||||
*/
|
||||
public function __construct(array $items = [])
|
||||
{
|
||||
parent::__construct($items);
|
||||
|
||||
$this->meta = new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::add()
|
||||
*/
|
||||
public function add(FrameInterface $frame): CoreInterface
|
||||
{
|
||||
$this->push($frame);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::native()
|
||||
*/
|
||||
public function native(): mixed
|
||||
{
|
||||
return $this->first()->native();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::setNative()
|
||||
*/
|
||||
public function setNative(mixed $native): CoreInterface
|
||||
{
|
||||
$this->clear()->push(new Frame($native));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::frame()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function frame(int $position): FrameInterface
|
||||
{
|
||||
$frame = $this->at($position);
|
||||
|
||||
if ($frame === null || $position < 0 || $position > $this->count()) {
|
||||
throw new InvalidArgumentException('Frame #' . $position . ' could not be found in the image');
|
||||
}
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::loops()
|
||||
*/
|
||||
public function loops(): int
|
||||
{
|
||||
return $this->loops;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::setLoops()
|
||||
*/
|
||||
public function setLoops(int $loops): CoreInterface
|
||||
{
|
||||
$this->loops = $loops;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::first()
|
||||
*/
|
||||
public function first(): FrameInterface
|
||||
{
|
||||
return parent::first();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CollectionInterface::last()
|
||||
*/
|
||||
public function last(): FrameInterface
|
||||
{
|
||||
return parent::last();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see CoreInterface::meta()
|
||||
*/
|
||||
public function meta(): CollectionInterface
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone instance
|
||||
*/
|
||||
public function __clone(): void
|
||||
{
|
||||
$this->meta = clone $this->meta;
|
||||
|
||||
foreach ($this->items as $key => $frame) {
|
||||
$this->items[$key] = clone $frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
vendor/intervention/image/src/Drivers/Gd/Decoders/AbstractDecoder.php
vendored
Normal file
93
vendor/intervention/image/src/Drivers/Gd/Decoders/AbstractDecoder.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Drivers\SpecializableDecoder;
|
||||
use Intervention\Image\Exceptions\DirectoryNotFoundException;
|
||||
use Intervention\Image\Exceptions\FileNotFoundException;
|
||||
use Intervention\Image\Exceptions\FileNotReadableException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\Interfaces\SpecializedInterface;
|
||||
use Intervention\Image\MediaType;
|
||||
use Intervention\Image\Traits\CanParseFilePath;
|
||||
use TypeError;
|
||||
use ValueError;
|
||||
|
||||
abstract class AbstractDecoder extends SpecializableDecoder implements SpecializedInterface
|
||||
{
|
||||
use CanParseFilePath;
|
||||
|
||||
/**
|
||||
* Return media (mime) type of the file at given file path
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws NotSupportedException
|
||||
* @throws DirectoryNotFoundException
|
||||
* @throws FileNotFoundException
|
||||
* @throws FileNotReadableException
|
||||
*/
|
||||
protected function mediaTypeByFilePath(string $filepath): MediaType
|
||||
{
|
||||
$filepath = self::readableFilePathOrFail($filepath);
|
||||
|
||||
if (function_exists('finfo_file') && function_exists('finfo_open')) {
|
||||
$mediaType = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $filepath);
|
||||
if (is_string($mediaType)) {
|
||||
try {
|
||||
return MediaType::from($mediaType);
|
||||
} catch (ValueError | TypeError) {
|
||||
throw new NotSupportedException('Unsupported media type (MIME) ' . $mediaType . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info = @getimagesize($filepath);
|
||||
|
||||
if (!is_array($info)) {
|
||||
throw new ImageDecoderException('Failed to read media (MIME) type from data in file path');
|
||||
}
|
||||
|
||||
try {
|
||||
return MediaType::from($info['mime']);
|
||||
} catch (ValueError | TypeError) {
|
||||
throw new NotSupportedException('Unsupported media type (MIME) ' . $info['mime'] . '.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return media (mime) type of the given image data
|
||||
*
|
||||
* @throws ImageDecoderException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
protected function mediaTypeByBinary(string $data): MediaType
|
||||
{
|
||||
if (function_exists('finfo_buffer') && function_exists('finfo_open')) {
|
||||
$mediaType = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data);
|
||||
if (is_string($mediaType)) {
|
||||
try {
|
||||
return MediaType::from($mediaType);
|
||||
} catch (ValueError | TypeError) {
|
||||
throw new NotSupportedException('Unsupported media type (MIME) ' . $mediaType . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info = @getimagesizefromstring($data);
|
||||
|
||||
if (!is_array($info)) {
|
||||
throw new ImageDecoderException('Failed to read media (MIME) type from binary data');
|
||||
}
|
||||
|
||||
try {
|
||||
return MediaType::from($info['mime']);
|
||||
} catch (ValueError | TypeError) {
|
||||
throw new NotSupportedException('Unsupported media type (MIME) ' . $info['mime'] . '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
46
vendor/intervention/image/src/Drivers/Gd/Decoders/Base64ImageDecoder.php
vendored
Normal file
46
vendor/intervention/image/src/Drivers/Gd/Decoders/Base64ImageDecoder.php
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Traits\CanDetectImageSources;
|
||||
|
||||
class Base64ImageDecoder extends BinaryImageDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDetectImageSources;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $this->couldBeBase64Data($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface
|
||||
{
|
||||
try {
|
||||
$data = $this->decodeBase64Data($input);
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException('Unable to Base64-decode image from string');
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::decode($data);
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException('Base64-encoded data contains unsupported image type');
|
||||
}
|
||||
}
|
||||
}
|
||||
99
vendor/intervention/image/src/Drivers/Gd/Decoders/BinaryImageDecoder.php
vendored
Normal file
99
vendor/intervention/image/src/Drivers/Gd/Decoders/BinaryImageDecoder.php
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\Exceptions\StateException;
|
||||
use Intervention\Image\Format;
|
||||
use Intervention\Image\Modifiers\OrientModifier;
|
||||
use Intervention\Image\Traits\CanDetectImageSources;
|
||||
use Stringable;
|
||||
|
||||
class BinaryImageDecoder extends NativeObjectDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDetectImageSources;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $this->couldBeBinaryData($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws DriverException
|
||||
* @throws StateException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface
|
||||
{
|
||||
if (!is_string($input) && !$input instanceof Stringable) {
|
||||
throw new InvalidArgumentException(
|
||||
'Image source must be binary data of type string or instance of ' . Stringable::class,
|
||||
);
|
||||
}
|
||||
|
||||
$input = (string) $input;
|
||||
|
||||
if ($input === '') {
|
||||
throw new InvalidArgumentException('Unable to decode binary data from empty string');
|
||||
}
|
||||
|
||||
return $this->isGifFormat($input) ? $this->decodeGif($input) : $this->decodeBinary($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from given binary data
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws DriverException
|
||||
* @throws StateException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
private function decodeBinary(string $input): ImageInterface
|
||||
{
|
||||
$gd = @imagecreatefromstring($input);
|
||||
|
||||
if ($gd === false) {
|
||||
throw new ImageDecoderException('Failed to decode unsupported image format from binary data');
|
||||
}
|
||||
|
||||
// create image instance
|
||||
$image = parent::decode($gd);
|
||||
|
||||
// get media type
|
||||
$mediaType = $this->mediaTypeByBinary($input);
|
||||
|
||||
// extract & set exif data for appropriate formats
|
||||
if (in_array($mediaType->format(), [Format::JPEG, Format::TIFF])) {
|
||||
$image->setExif($this->extractExifData($input));
|
||||
}
|
||||
|
||||
// set mediaType on origin
|
||||
$image->origin()->setMediaType($mediaType);
|
||||
|
||||
// adjust image orientation
|
||||
if ($this->driver()->config()->autoOrientation) {
|
||||
$image->modify(new OrientModifier());
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
65
vendor/intervention/image/src/Drivers/Gd/Decoders/DataUriImageDecoder.php
vendored
Normal file
65
vendor/intervention/image/src/Drivers/Gd/Decoders/DataUriImageDecoder.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\DataUri;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\Exceptions\StateException;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Traits\CanDetectImageSources;
|
||||
|
||||
class DataUriImageDecoder extends BinaryImageDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDetectImageSources;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $this->couldBeDataUrl($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws DriverException
|
||||
* @throws ImageDecoderException
|
||||
* @throws StateException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface
|
||||
{
|
||||
if ($input instanceof DataUri) {
|
||||
try {
|
||||
return parent::decode($input->data());
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException('Data Uri contains unsupported image type');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_string($input)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Image source must be data uri scheme of type string or ' . DataUri::class,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::decode(DataUri::parse($input)->data());
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException('Data Uri contains unsupported image type');
|
||||
}
|
||||
}
|
||||
}
|
||||
52
vendor/intervention/image/src/Drivers/Gd/Decoders/EncodedImageObjectDecoder.php
vendored
Normal file
52
vendor/intervention/image/src/Drivers/Gd/Decoders/EncodedImageObjectDecoder.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\EncodedImage;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\NotSupportedException;
|
||||
use Intervention\Image\Exceptions\StateException;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\EncodedImageInterface;
|
||||
|
||||
class EncodedImageObjectDecoder extends BinaryImageDecoder
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $input instanceof EncodedImageInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws DriverException
|
||||
* @throws StateException
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface
|
||||
{
|
||||
if (!$input instanceof EncodedImageInterface) {
|
||||
throw new InvalidArgumentException('Image source must be of type ' . EncodedImage::class);
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::decode($input->toString());
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException(EncodedImage::class . ' contains unsupported image type');
|
||||
}
|
||||
}
|
||||
}
|
||||
119
vendor/intervention/image/src/Drivers/Gd/Decoders/FilePathImageDecoder.php
vendored
Normal file
119
vendor/intervention/image/src/Drivers/Gd/Decoders/FilePathImageDecoder.php
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\DirectoryNotFoundException;
|
||||
use Intervention\Image\Exceptions\DriverException;
|
||||
use Intervention\Image\Exceptions\FileNotFoundException;
|
||||
use Intervention\Image\Exceptions\FileNotReadableException;
|
||||
use Intervention\Image\Exceptions\ImageDecoderException;
|
||||
use Intervention\Image\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Image\Exceptions\StateException;
|
||||
use Intervention\Image\Format;
|
||||
use Intervention\Image\Interfaces\DecoderInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\MediaType;
|
||||
use Intervention\Image\Modifiers\OrientModifier;
|
||||
use Intervention\Image\Traits\CanDetectImageSources;
|
||||
use Throwable;
|
||||
|
||||
class FilePathImageDecoder extends NativeObjectDecoder implements DecoderInterface
|
||||
{
|
||||
use CanDetectImageSources;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::supports()
|
||||
*/
|
||||
public function supports(mixed $input): bool
|
||||
{
|
||||
return $this->couldBeFilePath($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DecoderInterface::decode()
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws DriverException
|
||||
* @throws StateException
|
||||
* @throws FileNotFoundException
|
||||
* @throws FileNotReadableException
|
||||
* @throws DirectoryNotFoundException
|
||||
*/
|
||||
public function decode(mixed $input): ImageInterface
|
||||
{
|
||||
// make sure path is valid
|
||||
$path = self::readableFilePathOrFail($input);
|
||||
|
||||
try {
|
||||
// detect media (mime) type
|
||||
$mediaType = $this->mediaTypeByFilePath($path);
|
||||
} catch (Throwable) {
|
||||
throw new ImageDecoderException('File contains unsupported image format');
|
||||
}
|
||||
|
||||
$image = match ($mediaType->format()) {
|
||||
// gif files might be animated and therefore cannot
|
||||
// be handled by the standard GD decoder.
|
||||
Format::GIF => $this->decodeGif($path),
|
||||
default => $this->decodeDefault($path, $mediaType),
|
||||
};
|
||||
|
||||
// set file path & mediaType on origin
|
||||
$image->origin()->setFilePath($path);
|
||||
$image->origin()->setMediaType($mediaType);
|
||||
|
||||
// extract exif for the appropriate formats
|
||||
if ($mediaType->format() === Format::JPEG) {
|
||||
$image->setExif($this->extractExifData($path));
|
||||
}
|
||||
|
||||
// adjust image orientation
|
||||
if ($this->driver()->config()->autoOrientation) {
|
||||
$image->modify(new OrientModifier());
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to decode data from file path as given image format
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws ImageDecoderException
|
||||
* @throws StateException
|
||||
* @throws DriverException
|
||||
*/
|
||||
private function decodeDefault(string $path, MediaType $mediaType): ImageInterface
|
||||
{
|
||||
$gdImage = match ($mediaType->format()) {
|
||||
Format::JPEG => @imagecreatefromjpeg($path),
|
||||
Format::WEBP => @imagecreatefromwebp($path),
|
||||
Format::PNG => @imagecreatefrompng($path),
|
||||
Format::AVIF => @imagecreatefromavif($path),
|
||||
Format::BMP => @imagecreatefrombmp($path),
|
||||
default => throw new ImageDecoderException('File contains unsupported image format'),
|
||||
};
|
||||
|
||||
if ($gdImage === false) {
|
||||
throw new ImageDecoderException(
|
||||
'Failed to decode data from file "' . $path . '" as image format "' . $mediaType->value . '"',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return parent::decode($gdImage);
|
||||
} catch (DecoderException) {
|
||||
throw new ImageDecoderException(
|
||||
'Failed to decode data from file "' . $path . '" as image format "' . $mediaType->value . '"',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user