refactor: susun semula struktur folder — Laravel source ke src/
This commit is contained in:
129
vendor/intervention/gif/AGENTS.md
vendored
Normal file
129
vendor/intervention/gif/AGENTS.md
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
# Intervention Gif Agent Guide
|
||||
|
||||
This document provides a guide for software engineering agents working on the Intervention Gif codebase.
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
**intervention/gif** -- Native PHP GIF encoder/decoder library. Parses and generates
|
||||
GIF binary data. Part of the [Intervention Image](https://github.com/Intervention/image) ecosystem.
|
||||
|
||||
- **Language:** PHP 8.3+ | **Namespace:** `Intervention\Gif`
|
||||
|
||||
### 1.1 Architecture
|
||||
|
||||
The codebase mirrors the GIF89a binary specification. Each GIF format block has three
|
||||
corresponding classes:
|
||||
|
||||
- `src/Blocks/<Name>.php` -- Data model (value object)
|
||||
- `src/Decoders/<Name>Decoder.php` -- Binary stream reader
|
||||
- `src/Encoders/<Name>Encoder.php` -- Binary stream writer
|
||||
|
||||
Encoder/decoder resolution is convention-based via `CanDecode`/`CanEncode` traits.
|
||||
All block models extend `AbstractEntity` (which implements `Stringable`).
|
||||
|
||||
## 2. Development Environment
|
||||
|
||||
The project uses Composer to manage dependencies. These dependencies are installed automatically when using the Docker test runners.
|
||||
|
||||
## 3. Build, Lint, and Test Commands
|
||||
|
||||
The following commands are used to ensure code quality and correctness.
|
||||
|
||||
### 3.1. Testing (PHPUnit)
|
||||
|
||||
The project uses PHPUnit for unit and feature testing.
|
||||
|
||||
- **Run all tests:**
|
||||
```bash
|
||||
docker compose run --rm tests
|
||||
```
|
||||
|
||||
- **Run a single test file:**
|
||||
To run a specific test file, provide the path to the file.
|
||||
```bash
|
||||
docker compose run --rm tests tests/Unit/BuilderTest.php
|
||||
```
|
||||
|
||||
- **Run a single test method:**
|
||||
Use the `--filter` option to run a specific test method by its name.
|
||||
```bash
|
||||
docker compose run --rm tests tests/Unit/BuilderTest.php --filter testMethodName
|
||||
```
|
||||
|
||||
- **Check test coverage:**
|
||||
```bash
|
||||
docker compose run --rm coverage
|
||||
```
|
||||
|
||||
### 3.2. Static Analysis (PHPStan)
|
||||
|
||||
PHPStan is used for static analysis to find potential bugs.
|
||||
|
||||
- **Run static analysis:**
|
||||
```bash
|
||||
docker compose run --rm analysis
|
||||
```
|
||||
|
||||
### 3.3. Coding Standards (PHP CodeSniffer)
|
||||
|
||||
The project adheres to the PSR-12 coding standard with additional rules. PHP CodeSniffer is used to enforce these standards.
|
||||
|
||||
- **Check for coding standard violations:**
|
||||
```bash
|
||||
docker compose run --rm standards
|
||||
```
|
||||
|
||||
## 4. Code Style and Conventions
|
||||
|
||||
Consistency is key. Adhere to the following guidelines when writing code.
|
||||
|
||||
### 4.1. Formatting
|
||||
|
||||
- **PSR-12:** The primary coding standard is PSR-12.
|
||||
- **Indentation:** Use 4 spaces for indentation, not tabs.
|
||||
- **Line Endings:** Use Unix-style line endings (LF).
|
||||
- **Strict Types:** All PHP files must start with `declare(strict_types=1);`.
|
||||
- **Class Structure:** Follow the ordering defined in `phpcs.xml`:
|
||||
1. `uses`
|
||||
2. `enum cases`
|
||||
3. `constants`
|
||||
4. `static properties`
|
||||
5. `properties`
|
||||
6. `constructor`
|
||||
7. `static constructors`
|
||||
8. `methods`
|
||||
9. `magic methods`
|
||||
|
||||
### 4.2. Naming Conventions
|
||||
|
||||
- **Classes:** `PascalCase`.
|
||||
- **Methods:** `camelCase`.
|
||||
- **Variables:** `camelCase`.
|
||||
- **Constants:** `UPPER_CASE` with underscore separators.
|
||||
- **File Names:** File names must match the class name they contain (e.g., `MyClass.php` for `class MyClass`).
|
||||
|
||||
### 4.3. Imports
|
||||
|
||||
- **One class per `use` statement:** Do not group multiple classes in a single `use` statement.
|
||||
- **No leading backslash:** `use` statements must not start with a backslash.
|
||||
- **Order:** `use` statements should be ordered alphabetically. Unused imports must be removed.
|
||||
|
||||
### 4.4. Types and Type Hinting
|
||||
|
||||
- **Strict Typing:** All code should be strictly typed.
|
||||
- **Parameter Types:** All method parameters must have a type hint.
|
||||
- **Return Types:** All methods must have a return type hint.
|
||||
- **Property Types:** All class properties must have a type hint.
|
||||
- **Nullable Types:** Use nullable types (`?TypeName`) when a `null` value is explicitly allowed.
|
||||
|
||||
### 4.5. Error Handling
|
||||
|
||||
- Exceptions should be used for error handling.
|
||||
- When catching exceptions, be as specific as possible. Avoid catching generic `\Exception` or `\Throwable`.
|
||||
- Exception messages should be clear and descriptive.
|
||||
|
||||
### 4.6. PHPDoc (DocBlocks)
|
||||
|
||||
- PHPDoc blocks are required for all classes, properties, and methods.
|
||||
- Follow the annotation order defined in `phpcs.xml`.
|
||||
- Use DocBlocks to provide context and explain complex logic. Do not restate the obvious from the code signature.
|
||||
21
vendor/intervention/gif/LICENSE
vendored
Normal file
21
vendor/intervention/gif/LICENSE
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020-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.
|
||||
100
vendor/intervention/gif/README.md
vendored
Normal file
100
vendor/intervention/gif/README.md
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
# Intervention GIF
|
||||
## Native PHP GIF Encoder/Decoder
|
||||
|
||||
[](https://packagist.org/packages/intervention/gif)
|
||||

|
||||
[](https://packagist.org/packages/intervention/gif/stats)
|
||||
[](https://ko-fi.com/interventionphp)
|
||||
|
||||
Intervention GIF is a PHP encoder and decoder for the GIF image format that
|
||||
does not depend on any image processing extension.
|
||||
|
||||
Only the special `Splitter::class` class divides the data stream of an animated
|
||||
GIF into individual `GDImage` objects for each frame and is therefore dependent
|
||||
on the GD library.
|
||||
|
||||
The library is the main component of [Intervention
|
||||
Image](https://github.com/Intervention/image) for processing animated GIF files
|
||||
with the GD library, but also works independently.
|
||||
|
||||
## Installation
|
||||
|
||||
You can easily install this package using [Composer](https://getcomposer.org).
|
||||
Just request the package with the following command:
|
||||
|
||||
```bash
|
||||
composer require intervention/gif
|
||||
```
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Decoding
|
||||
|
||||
```php
|
||||
use Intervention\Gif\Decoder;
|
||||
|
||||
// Decode filepath to Intervention\Gif\GifDataStream::class
|
||||
$gif = Decoder::decode('images/animation.gif');
|
||||
|
||||
// Decoder can also handle binary content directly
|
||||
$gif = Decoder::decode($contents);
|
||||
```
|
||||
|
||||
### Encoding
|
||||
|
||||
Use the Builder class to create a new GIF image.
|
||||
|
||||
```php
|
||||
use Intervention\Gif\Builder;
|
||||
|
||||
// create new gif canvas
|
||||
$gif = Builder::canvas(width: 32, height: 32);
|
||||
|
||||
// add animation frames to canvas
|
||||
$delay = .25; // delay in seconds after next frame is displayed
|
||||
$left = 0; // position offset (left)
|
||||
$top = 0; // position offset (top)
|
||||
|
||||
// add animation frames with optional delay in seconds
|
||||
// and optional position offset for each frame
|
||||
$gif->addFrame('images/frame01.gif', $delay, $left, $top);
|
||||
$gif->addFrame('images/frame02.gif', $delay, $left);
|
||||
$gif->addFrame('images/frame03.gif', $delay);
|
||||
$gif->addFrame('images/frame04.gif');
|
||||
|
||||
// set loop count; 0 for infinite looping
|
||||
$gif->setLoops(12);
|
||||
|
||||
// encode
|
||||
$data = $gif->encode();
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP >= 8.3
|
||||
|
||||
## Development & Testing
|
||||
|
||||
With this package comes a Docker docker test runners. To build this container
|
||||
you have to have Docker installed on your system. You can run all tests with
|
||||
this command.
|
||||
|
||||
```bash
|
||||
docker-compose run --rm --build tests
|
||||
```
|
||||
|
||||
Run the static analyzer on the code base.
|
||||
|
||||
```bash
|
||||
docker-compose run --rm --build analysis
|
||||
```
|
||||
|
||||
## Authors
|
||||
|
||||
This library is developed and maintained by [Oliver Vogel](https://intervention.io)
|
||||
|
||||
Thanks to the community of [contributors](https://github.com/Intervention/gif/graphs/contributors) who have helped to improve this project.
|
||||
|
||||
## License
|
||||
|
||||
Intervention GIF is licensed under the [MIT License](LICENSE).
|
||||
44
vendor/intervention/gif/composer.json
vendored
Normal file
44
vendor/intervention/gif/composer.json
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"description": "PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"image",
|
||||
"gd",
|
||||
"gif",
|
||||
"animation"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"squizlabs/php_codesniffer": "^4",
|
||||
"slevomat/coding-standard": "~8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
6
vendor/intervention/gif/entrypoint.sh
vendored
Normal file
6
vendor/intervention/gif/entrypoint.sh
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
composer install --quiet
|
||||
|
||||
exec "$@"
|
||||
27
vendor/intervention/gif/phpunit.xml.dist
vendored
Normal file
27
vendor/intervention/gif/phpunit.xml.dist
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticProperties="false"
|
||||
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
colors="true"
|
||||
displayDetailsOnTestsThatTriggerNotices="true"
|
||||
displayDetailsOnTestsThatTriggerDeprecations="true"
|
||||
displayDetailsOnPhpunitDeprecations="true"
|
||||
displayDetailsOnTestsThatTriggerWarnings="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false">
|
||||
<testsuites>
|
||||
<testsuite name="Unit Tests">
|
||||
<directory suffix=".php">./tests/Unit/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
42
vendor/intervention/gif/src/AbstractEntity.php
vendored
Normal file
42
vendor/intervention/gif/src/AbstractEntity.php
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
use Intervention\Gif\Traits\CanDecode;
|
||||
use Intervention\Gif\Traits\CanEncode;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use Stringable;
|
||||
|
||||
abstract class AbstractEntity implements Stringable
|
||||
{
|
||||
use CanEncode;
|
||||
use CanDecode;
|
||||
|
||||
public const TERMINATOR = "\x00";
|
||||
|
||||
/**
|
||||
* Get short classname of current instance.
|
||||
*/
|
||||
public static function shortClassname(): ?string
|
||||
{
|
||||
try {
|
||||
return (new ReflectionClass(static::class))->getShortName();
|
||||
} catch (ReflectionException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast object to string.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->encode();
|
||||
}
|
||||
}
|
||||
10
vendor/intervention/gif/src/AbstractExtension.php
vendored
Normal file
10
vendor/intervention/gif/src/AbstractExtension.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
abstract class AbstractExtension extends AbstractEntity
|
||||
{
|
||||
public const MARKER = "\x21";
|
||||
}
|
||||
97
vendor/intervention/gif/src/Blocks/ApplicationExtension.php
vendored
Normal file
97
vendor/intervention/gif/src/Blocks/ApplicationExtension.php
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
use Intervention\Gif\Exceptions\StateException;
|
||||
|
||||
class ApplicationExtension extends AbstractExtension
|
||||
{
|
||||
public const LABEL = "\xFF";
|
||||
|
||||
/**
|
||||
* Application Identifier & Auth Code.
|
||||
*/
|
||||
protected string $application = '';
|
||||
|
||||
/**
|
||||
* Data Sub Blocks.
|
||||
*
|
||||
* @var array<DataSubBlock>
|
||||
*/
|
||||
protected array $blocks = [];
|
||||
|
||||
/**
|
||||
* Get size of block.
|
||||
*/
|
||||
public function blockSize(): int
|
||||
{
|
||||
return strlen($this->application);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set application name.
|
||||
*/
|
||||
public function setApplication(string $value): self
|
||||
{
|
||||
$this->application = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get application name.
|
||||
*/
|
||||
public function application(): string
|
||||
{
|
||||
return $this->application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add block to application extension.
|
||||
*/
|
||||
public function addBlock(DataSubBlock $block): self
|
||||
{
|
||||
$this->blocks[] = $block;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data sub blocks of instance.
|
||||
*
|
||||
* @param array<DataSubBlock> $blocks
|
||||
*/
|
||||
public function setBlocks(array $blocks): self
|
||||
{
|
||||
$this->blocks = $blocks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blocks of ApplicationExtension.
|
||||
*
|
||||
* @return array<DataSubBlock>
|
||||
*/
|
||||
public function blocks(): array
|
||||
{
|
||||
return $this->blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first block of ApplicationExtension.
|
||||
*
|
||||
* @throws StateException
|
||||
*/
|
||||
public function firstBlock(): DataSubBlock
|
||||
{
|
||||
if (!array_key_exists(0, $this->blocks)) {
|
||||
throw new StateException('Failed to retrieve data sub block');
|
||||
}
|
||||
|
||||
return $this->blocks[0];
|
||||
}
|
||||
}
|
||||
96
vendor/intervention/gif/src/Blocks/Color.php
vendored
Normal file
96
vendor/intervention/gif/src/Blocks/Color.php
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class Color extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $r = 0,
|
||||
protected int $g = 0,
|
||||
protected int $b = 0
|
||||
) {
|
||||
if ($r < 0 || $r > 255) {
|
||||
throw new InvalidArgumentException('Color channel red must be in range 0 to 255');
|
||||
}
|
||||
|
||||
if ($g < 0 || $g > 255) {
|
||||
throw new InvalidArgumentException('Color channel green must be in range 0 to 255');
|
||||
}
|
||||
|
||||
if ($b < 0 || $b > 255) {
|
||||
throw new InvalidArgumentException('Color channel blue must be in range 0 to 255');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get red value.
|
||||
*/
|
||||
public function red(): int
|
||||
{
|
||||
return $this->r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set red value.
|
||||
*/
|
||||
public function setRed(int $value): self
|
||||
{
|
||||
$this->r = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get green value.
|
||||
*/
|
||||
public function green(): int
|
||||
{
|
||||
return $this->g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set green value.
|
||||
*/
|
||||
public function setGreen(int $value): self
|
||||
{
|
||||
$this->g = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blue value.
|
||||
*/
|
||||
public function blue(): int
|
||||
{
|
||||
return $this->b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set blue value.
|
||||
*/
|
||||
public function setBlue(int $value): self
|
||||
{
|
||||
$this->b = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return hash value of current color.
|
||||
*/
|
||||
public function hash(): string
|
||||
{
|
||||
return md5(strval($this->r . $this->g . $this->b));
|
||||
}
|
||||
}
|
||||
123
vendor/intervention/gif/src/Blocks/ColorTable.php
vendored
Normal file
123
vendor/intervention/gif/src/Blocks/ColorTable.php
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ColorTable extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @param array<Color> $colors
|
||||
*/
|
||||
public function __construct(protected array $colors = [])
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of current colors.
|
||||
*
|
||||
* @return array<Color>
|
||||
*/
|
||||
public function colors(): array
|
||||
{
|
||||
return array_values($this->colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add color to table.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addRgb(int $r, int $g, int $b): self
|
||||
{
|
||||
$this->addColor(new Color($r, $g, $b));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add color to table.
|
||||
*/
|
||||
public function addColor(Color $color): self
|
||||
{
|
||||
$this->colors[] = $color;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset colors to array of color objects.
|
||||
*
|
||||
* @param array<Color> $colors
|
||||
*/
|
||||
public function setColors(array $colors): self
|
||||
{
|
||||
$this->empty();
|
||||
foreach ($colors as $color) {
|
||||
$this->addColor($color);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count colors of current instance.
|
||||
*/
|
||||
public function countColors(): int
|
||||
{
|
||||
return count($this->colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any colors are present on the current table
|
||||
*/
|
||||
public function hasColors(): bool
|
||||
{
|
||||
return $this->countColors() >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty color table.
|
||||
*/
|
||||
public function empty(): self
|
||||
{
|
||||
$this->colors = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of color table in logical screen descriptor.
|
||||
*/
|
||||
public function logicalSize(): int
|
||||
{
|
||||
return match ($this->countColors()) {
|
||||
4 => 1,
|
||||
8 => 2,
|
||||
16 => 3,
|
||||
32 => 4,
|
||||
64 => 5,
|
||||
128 => 6,
|
||||
256 => 7,
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of bytes contained by the current table.
|
||||
*/
|
||||
public function byteSize(): int
|
||||
{
|
||||
if (!$this->hasColors()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 3 * pow(2, $this->logicalSize() + 1);
|
||||
}
|
||||
}
|
||||
47
vendor/intervention/gif/src/Blocks/CommentExtension.php
vendored
Normal file
47
vendor/intervention/gif/src/Blocks/CommentExtension.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
|
||||
class CommentExtension extends AbstractExtension
|
||||
{
|
||||
public const LABEL = "\xFE";
|
||||
|
||||
/**
|
||||
* Comment blocks.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $comments = [];
|
||||
|
||||
/**
|
||||
* Get all or one comment.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function comments(): array
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one comment by key.
|
||||
*/
|
||||
public function comment(int $key): mixed
|
||||
{
|
||||
return $this->comments[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set comment text.
|
||||
*/
|
||||
public function addComment(string $value): self
|
||||
{
|
||||
$this->comments[] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
41
vendor/intervention/gif/src/Blocks/DataSubBlock.php
vendored
Normal file
41
vendor/intervention/gif/src/Blocks/DataSubBlock.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class DataSubBlock extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(protected string $value)
|
||||
{
|
||||
if ($this->size() > 255) {
|
||||
throw new InvalidArgumentException(
|
||||
'Data Sub-Block can not have a block size larger than 255 bytes'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size of current block.
|
||||
*/
|
||||
public function size(): int
|
||||
{
|
||||
return strlen($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block value.
|
||||
*/
|
||||
public function value(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
254
vendor/intervention/gif/src/Blocks/FrameBlock.php
vendored
Normal file
254
vendor/intervention/gif/src/Blocks/FrameBlock.php
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
|
||||
/**
|
||||
* The GIF files that can be found on the Internet come in a wide variety
|
||||
* of forms. Some strictly adhere to the original specification, others do
|
||||
* not and differ in the actual sequence of blocks or their number.
|
||||
*
|
||||
* For this reason, this libary has this (kind of "virtual") FrameBlock,
|
||||
* which can contain all possible blocks in different order that occur in
|
||||
* a GIF animation.
|
||||
*
|
||||
* - Image Description
|
||||
* - Local Color Table
|
||||
* - Image Data Block
|
||||
* - Plain Text Extension
|
||||
* - Application Extension
|
||||
* - Comment Extension
|
||||
*
|
||||
* The TableBasedImage block, which is a chain of ImageDescriptor, (Local
|
||||
* Color Table) and ImageData, is used as a marker for terminating a
|
||||
* FrameBlock.
|
||||
*
|
||||
* So far I have only seen GIF files that follow this scheme. However, there are
|
||||
* examples which have one (or more) comment extensions added before the end. So
|
||||
* there can be additional "global comments" that are not part of the FrameBlock
|
||||
* and are appended to the GifDataStream afterwards.
|
||||
*/
|
||||
class FrameBlock extends AbstractEntity
|
||||
{
|
||||
protected ?GraphicControlExtension $graphicControlExtension = null;
|
||||
|
||||
protected ?ColorTable $colorTable = null;
|
||||
|
||||
protected ?PlainTextExtension $plainTextExtension = null;
|
||||
|
||||
/**
|
||||
* @var array<ApplicationExtension> $applicationExtensions
|
||||
*/
|
||||
protected array $applicationExtensions = [];
|
||||
|
||||
/**
|
||||
* @var array<CommentExtension> $commentExtensions
|
||||
*/
|
||||
protected array $commentExtensions = [];
|
||||
|
||||
public function __construct(
|
||||
protected ImageDescriptor $imageDescriptor = new ImageDescriptor(),
|
||||
protected ImageData $imageData = new ImageData()
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entity to block.
|
||||
*/
|
||||
public function addEntity(AbstractEntity $entity): self
|
||||
{
|
||||
return match (true) {
|
||||
$entity instanceof TableBasedImage => $this->setTableBasedImage($entity),
|
||||
$entity instanceof GraphicControlExtension => $this->setGraphicControlExtension($entity),
|
||||
$entity instanceof ImageDescriptor => $this->setImageDescriptor($entity),
|
||||
$entity instanceof ColorTable => $this->setColorTable($entity),
|
||||
$entity instanceof ImageData => $this->setImageData($entity),
|
||||
$entity instanceof PlainTextExtension => $this->setPlainTextExtension($entity),
|
||||
$entity instanceof NetscapeApplicationExtension,
|
||||
$entity instanceof ApplicationExtension => $this->addApplicationExtension($entity),
|
||||
$entity instanceof CommentExtension => $this->addCommentExtension($entity),
|
||||
default => $this,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return application extensions of current frame block.
|
||||
*
|
||||
* @return array<ApplicationExtension>
|
||||
*/
|
||||
public function applicationExtensions(): array
|
||||
{
|
||||
return $this->applicationExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return comment extensions of current frame block.
|
||||
*
|
||||
* @return array<CommentExtension>
|
||||
*/
|
||||
public function commentExtensions(): array
|
||||
{
|
||||
return $this->commentExtensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the graphic control extension.
|
||||
*/
|
||||
public function setGraphicControlExtension(GraphicControlExtension $extension): self
|
||||
{
|
||||
$this->graphicControlExtension = $extension;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the graphic control extension of the current frame block.
|
||||
*/
|
||||
public function graphicControlExtension(): ?GraphicControlExtension
|
||||
{
|
||||
return $this->graphicControlExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image descriptor.
|
||||
*/
|
||||
public function setImageDescriptor(ImageDescriptor $descriptor): self
|
||||
{
|
||||
$this->imageDescriptor = $descriptor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image descriptor of the frame block.
|
||||
*/
|
||||
public function imageDescriptor(): ImageDescriptor
|
||||
{
|
||||
return $this->imageDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color table of the current frame block.
|
||||
*/
|
||||
public function setColorTable(ColorTable $table): self
|
||||
{
|
||||
$this->colorTable = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color table.
|
||||
*/
|
||||
public function colorTable(): ?ColorTable
|
||||
{
|
||||
return $this->colorTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if frame block has color table.
|
||||
*/
|
||||
public function hasColorTable(): bool
|
||||
{
|
||||
return !is_null($this->colorTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image data of frame block.
|
||||
*/
|
||||
public function setImageData(ImageData $data): self
|
||||
{
|
||||
$this->imageData = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image data of current frame block.
|
||||
*/
|
||||
public function imageData(): ImageData
|
||||
{
|
||||
return $this->imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set plain text extension.
|
||||
*/
|
||||
public function setPlainTextExtension(PlainTextExtension $extension): self
|
||||
{
|
||||
$this->plainTextExtension = $extension;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plain text extension.
|
||||
*/
|
||||
public function plainTextExtension(): ?PlainTextExtension
|
||||
{
|
||||
return $this->plainTextExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given application extension to the current frame block.
|
||||
*/
|
||||
public function addApplicationExtension(ApplicationExtension $extension): self
|
||||
{
|
||||
$this->applicationExtensions[] = $extension;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all application extensions from the current frame block.
|
||||
*/
|
||||
public function clearApplicationExtensions(): self
|
||||
{
|
||||
$this->applicationExtensions = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given comment extension to the current frame block
|
||||
*/
|
||||
public function addCommentExtension(CommentExtension $extension): self
|
||||
{
|
||||
$this->commentExtensions[] = $extension;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return netscape extension of the frame block if available.
|
||||
*/
|
||||
public function netscapeExtension(): ?NetscapeApplicationExtension
|
||||
{
|
||||
$extensions = array_filter(
|
||||
$this->applicationExtensions,
|
||||
fn(ApplicationExtension $extension): bool => $extension instanceof NetscapeApplicationExtension,
|
||||
);
|
||||
|
||||
return count($extensions) > 0 ? reset($extensions) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table based image of the current frame block.
|
||||
*/
|
||||
public function setTableBasedImage(TableBasedImage $tableBasedImage): self
|
||||
{
|
||||
$this->setImageDescriptor($tableBasedImage->imageDescriptor());
|
||||
|
||||
$colorTable = $tableBasedImage->colorTable();
|
||||
if ($colorTable !== null) {
|
||||
$this->setColorTable($colorTable);
|
||||
}
|
||||
|
||||
$this->setImageData($tableBasedImage->imageData());
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
129
vendor/intervention/gif/src/Blocks/GraphicControlExtension.php
vendored
Normal file
129
vendor/intervention/gif/src/Blocks/GraphicControlExtension.php
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
use Intervention\Gif\DisposalMethod;
|
||||
|
||||
class GraphicControlExtension extends AbstractExtension
|
||||
{
|
||||
public const LABEL = "\xF9";
|
||||
public const BLOCKSIZE = "\x04";
|
||||
|
||||
/**
|
||||
* Existance flag of transparent color.
|
||||
*/
|
||||
protected bool $transparentColorExistance = false;
|
||||
|
||||
/**
|
||||
* Transparent color index.
|
||||
*/
|
||||
protected int $transparentColorIndex = 0;
|
||||
|
||||
/**
|
||||
* User input flag.
|
||||
*/
|
||||
protected bool $userInput = false;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $delay = 0,
|
||||
protected DisposalMethod $disposalMethod = DisposalMethod::UNDEFINED,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Set delay time (1/100 second).
|
||||
*/
|
||||
public function setDelay(int $value): self
|
||||
{
|
||||
$this->delay = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return delay time (1/100 second).
|
||||
*/
|
||||
public function delay(): int
|
||||
{
|
||||
return $this->delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set disposal method.
|
||||
*/
|
||||
public function setDisposalMethod(DisposalMethod $method): self
|
||||
{
|
||||
$this->disposalMethod = $method;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disposal method.
|
||||
*/
|
||||
public function disposalMethod(): DisposalMethod
|
||||
{
|
||||
return $this->disposalMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transparent color index.
|
||||
*/
|
||||
public function transparentColorIndex(): int
|
||||
{
|
||||
return $this->transparentColorIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transparent color index.
|
||||
*/
|
||||
public function setTransparentColorIndex(int $index): self
|
||||
{
|
||||
$this->transparentColorIndex = $index;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current transparent color existance.
|
||||
*/
|
||||
public function transparentColorExistance(): bool
|
||||
{
|
||||
return $this->transparentColorExistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set existance flag of transparent color.
|
||||
*/
|
||||
public function setTransparentColorExistance(bool $existance = true): self
|
||||
{
|
||||
$this->transparentColorExistance = $existance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user input flag.
|
||||
*/
|
||||
public function userInput(): bool
|
||||
{
|
||||
return $this->userInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user input flag.
|
||||
*/
|
||||
public function setUserInput(bool $value = true): self
|
||||
{
|
||||
$this->userInput = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
38
vendor/intervention/gif/src/Blocks/Header.php
vendored
Normal file
38
vendor/intervention/gif/src/Blocks/Header.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
|
||||
class Header extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Header signature.
|
||||
*/
|
||||
public const SIGNATURE = 'GIF';
|
||||
|
||||
/**
|
||||
* Current GIF version.
|
||||
*/
|
||||
protected string $version = '89a';
|
||||
|
||||
/**
|
||||
* Set GIF version.
|
||||
*/
|
||||
public function setVersion(string $value): self
|
||||
{
|
||||
$this->version = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current version.
|
||||
*/
|
||||
public function version(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
}
|
||||
68
vendor/intervention/gif/src/Blocks/ImageData.php
vendored
Normal file
68
vendor/intervention/gif/src/Blocks/ImageData.php
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
|
||||
class ImageData extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* LZW min. code size.
|
||||
*/
|
||||
protected int $lzwMinCodeSize;
|
||||
|
||||
/**
|
||||
* Sub blocks.
|
||||
*
|
||||
* @var array<DataSubBlock>
|
||||
*/
|
||||
protected array $blocks = [];
|
||||
|
||||
/**
|
||||
* Get LZW min. code size.
|
||||
*/
|
||||
public function lzwMinCodeSize(): int
|
||||
{
|
||||
return $this->lzwMinCodeSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lzw min. code size.
|
||||
*/
|
||||
public function setLzwMinCodeSize(int $size): self
|
||||
{
|
||||
$this->lzwMinCodeSize = $size;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current data sub blocks.
|
||||
*
|
||||
* @return array<DataSubBlock>
|
||||
*/
|
||||
public function blocks(): array
|
||||
{
|
||||
return $this->blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Addd sub block.
|
||||
*/
|
||||
public function addBlock(DataSubBlock $block): self
|
||||
{
|
||||
$this->blocks[] = $block;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if data sub blocks are present.
|
||||
*/
|
||||
public function hasBlocks(): bool
|
||||
{
|
||||
return count($this->blocks) >= 1;
|
||||
}
|
||||
}
|
||||
205
vendor/intervention/gif/src/Blocks/ImageDescriptor.php
vendored
Normal file
205
vendor/intervention/gif/src/Blocks/ImageDescriptor.php
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ImageDescriptor extends AbstractEntity
|
||||
{
|
||||
public const SEPARATOR = "\x2C";
|
||||
|
||||
/**
|
||||
* Width of frame.
|
||||
*/
|
||||
protected int $width = 0;
|
||||
|
||||
/**
|
||||
* Height of frame.
|
||||
*/
|
||||
protected int $height = 0;
|
||||
|
||||
/**
|
||||
* Left position of frame.
|
||||
*/
|
||||
protected int $left = 0;
|
||||
|
||||
/**
|
||||
* Top position of frame.
|
||||
*/
|
||||
protected int $top = 0;
|
||||
|
||||
/**
|
||||
* Determine if frame is interlaced.
|
||||
*/
|
||||
protected bool $interlaced = false;
|
||||
|
||||
/**
|
||||
* Local color table flag.
|
||||
*/
|
||||
protected bool $localColorTableExistance = false;
|
||||
|
||||
/**
|
||||
* Sort flag of local color table.
|
||||
*/
|
||||
protected bool $localColorTableSorted = false;
|
||||
|
||||
/**
|
||||
* Size of local color table.
|
||||
*/
|
||||
protected int $localColorTableSize = 0;
|
||||
|
||||
/**
|
||||
* Get current width.
|
||||
*/
|
||||
public function width(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current width.
|
||||
*/
|
||||
public function height(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Top.
|
||||
*/
|
||||
public function top(): int
|
||||
{
|
||||
return $this->top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current Left.
|
||||
*/
|
||||
public function left(): int
|
||||
{
|
||||
return $this->left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set size of current instance.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setSize(int $width, int $height): self
|
||||
{
|
||||
if ($width <= 0) {
|
||||
throw new InvalidArgumentException('Width in ' . $this::class . ' must be larger than 0');
|
||||
}
|
||||
|
||||
if ($height <= 0) {
|
||||
throw new InvalidArgumentException('Height in ' . $this::class . ' must be larger than 0');
|
||||
}
|
||||
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set position of current instance.
|
||||
*/
|
||||
public function setPosition(int $left, int $top): self
|
||||
{
|
||||
$this->left = $left;
|
||||
$this->top = $top;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if frame is interlaced.
|
||||
*/
|
||||
public function isInterlaced(): bool
|
||||
{
|
||||
return $this->interlaced;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or unset interlaced value.
|
||||
*/
|
||||
public function setInterlaced(bool $value = true): self
|
||||
{
|
||||
$this->interlaced = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if local color table is present.
|
||||
*/
|
||||
public function localColorTableExistance(): bool
|
||||
{
|
||||
return $this->localColorTableExistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for localColorTableExistance.
|
||||
*/
|
||||
public function hasLocalColorTable(): bool
|
||||
{
|
||||
return $this->localColorTableExistance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set local color table flag.
|
||||
*/
|
||||
public function setLocalColorTableExistance(bool $existance = true): self
|
||||
{
|
||||
$this->localColorTableExistance = $existance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local color table sorted flag.
|
||||
*/
|
||||
public function localColorTableSorted(): bool
|
||||
{
|
||||
return $this->localColorTableSorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set local color table sorted flag.
|
||||
*/
|
||||
public function setLocalColorTableSorted(bool $sorted = true): self
|
||||
{
|
||||
$this->localColorTableSorted = $sorted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of local color table.
|
||||
*/
|
||||
public function localColorTableSize(): int
|
||||
{
|
||||
return $this->localColorTableSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get byte size of global color table.
|
||||
*/
|
||||
public function localColorTableByteSize(): int
|
||||
{
|
||||
return 3 * pow(2, $this->localColorTableSize() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set size of local color table.
|
||||
*/
|
||||
public function setLocalColorTableSize(int $size): self
|
||||
{
|
||||
$this->localColorTableSize = $size;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
212
vendor/intervention/gif/src/Blocks/LogicalScreenDescriptor.php
vendored
Normal file
212
vendor/intervention/gif/src/Blocks/LogicalScreenDescriptor.php
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class LogicalScreenDescriptor extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Width.
|
||||
*/
|
||||
protected int $width;
|
||||
|
||||
/**
|
||||
* Height.
|
||||
*/
|
||||
protected int $height;
|
||||
|
||||
/**
|
||||
* Global color table flag.
|
||||
*/
|
||||
protected bool $globalColorTableExistance = false;
|
||||
|
||||
/**
|
||||
* Sort flag of global color table.
|
||||
*/
|
||||
protected bool $globalColorTableSorted = false;
|
||||
|
||||
/**
|
||||
* Size of global color table.
|
||||
*/
|
||||
protected int $globalColorTableSize = 0;
|
||||
|
||||
/**
|
||||
* Background color index.
|
||||
*/
|
||||
protected int $backgroundColorIndex = 0;
|
||||
|
||||
/**
|
||||
* Color resolution.
|
||||
*/
|
||||
protected int $bitsPerPixel = 8;
|
||||
|
||||
/**
|
||||
* Pixel aspect ration.
|
||||
*/
|
||||
protected int $pixelAspectRatio = 0;
|
||||
|
||||
/**
|
||||
* Set size.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setSize(int $width, int $height): self
|
||||
{
|
||||
if ($width <= 0) {
|
||||
throw new InvalidArgumentException('Width in ' . $this::class . ' must be larger than 0');
|
||||
}
|
||||
|
||||
if ($height <= 0) {
|
||||
throw new InvalidArgumentException('Height in ' . $this::class . ' must be larger than 0');
|
||||
}
|
||||
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get width of current instance.
|
||||
*/
|
||||
public function width(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get height of current instance.
|
||||
*/
|
||||
public function height(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if global color table is present.
|
||||
*/
|
||||
public function globalColorTableExistance(): bool
|
||||
{
|
||||
return $this->globalColorTableExistance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias of globalColorTableExistance.
|
||||
*/
|
||||
public function hasGlobalColorTable(): bool
|
||||
{
|
||||
return $this->globalColorTableExistance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global color table flag.
|
||||
*/
|
||||
public function setGlobalColorTableExistance(bool $existance = true): self
|
||||
{
|
||||
$this->globalColorTableExistance = $existance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global color table sorted flag.
|
||||
*/
|
||||
public function globalColorTableSorted(): bool
|
||||
{
|
||||
return $this->globalColorTableSorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global color table sorted flag.
|
||||
*/
|
||||
public function setGlobalColorTableSorted(bool $sorted = true): self
|
||||
{
|
||||
$this->globalColorTableSorted = $sorted;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get size of global color table.
|
||||
*/
|
||||
public function globalColorTableSize(): int
|
||||
{
|
||||
return $this->globalColorTableSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get byte size of global color table.
|
||||
*/
|
||||
public function globalColorTableByteSize(): int
|
||||
{
|
||||
return 3 * pow(2, $this->globalColorTableSize() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set size of global color table.
|
||||
*/
|
||||
public function setGlobalColorTableSize(int $size): self
|
||||
{
|
||||
$this->globalColorTableSize = $size;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background color index.
|
||||
*/
|
||||
public function backgroundColorIndex(): int
|
||||
{
|
||||
return $this->backgroundColorIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color index.
|
||||
*/
|
||||
public function setBackgroundColorIndex(int $index): self
|
||||
{
|
||||
$this->backgroundColorIndex = $index;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current pixel aspect ration.
|
||||
*/
|
||||
public function pixelAspectRatio(): int
|
||||
{
|
||||
return $this->pixelAspectRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set pixel aspect ratio.
|
||||
*/
|
||||
public function setPixelAspectRatio(int $ratio): self
|
||||
{
|
||||
$this->pixelAspectRatio = $ratio;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color resolution.
|
||||
*/
|
||||
public function bitsPerPixel(): int
|
||||
{
|
||||
return $this->bitsPerPixel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color resolution.
|
||||
*/
|
||||
public function setBitsPerPixel(int $value): self
|
||||
{
|
||||
$this->bitsPerPixel = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
66
vendor/intervention/gif/src/Blocks/NetscapeApplicationExtension.php
vendored
Normal file
66
vendor/intervention/gif/src/Blocks/NetscapeApplicationExtension.php
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Gif\Exceptions\StateException;
|
||||
|
||||
class NetscapeApplicationExtension extends ApplicationExtension
|
||||
{
|
||||
public const IDENTIFIER = "NETSCAPE";
|
||||
public const AUTH_CODE = "2.0";
|
||||
public const SUB_BLOCK_PREFIX = "\x01";
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
try {
|
||||
$this->setApplication(self::IDENTIFIER . self::AUTH_CODE);
|
||||
$this->setBlocks([new DataSubBlock(self::SUB_BLOCK_PREFIX . "\x00\x00")]);
|
||||
} catch (InvalidArgumentException) {
|
||||
// ignore exception because of hard coded input
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of loops.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function loops(): int
|
||||
{
|
||||
try {
|
||||
$unpacked = unpack('v*', substr($this->firstBlock()->value(), 1));
|
||||
} catch (StateException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode loop count of netscape extension',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to calculate loop count');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set number of loops.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setLoops(int $loops): self
|
||||
{
|
||||
$this->setBlocks([
|
||||
new DataSubBlock(self::SUB_BLOCK_PREFIX . pack('v*', $loops))
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
59
vendor/intervention/gif/src/Blocks/PlainTextExtension.php
vendored
Normal file
59
vendor/intervention/gif/src/Blocks/PlainTextExtension.php
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
|
||||
class PlainTextExtension extends AbstractExtension
|
||||
{
|
||||
public const LABEL = "\x01";
|
||||
|
||||
/**
|
||||
* Array of text.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $text = [];
|
||||
|
||||
/**
|
||||
* Get current text.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function text(): array
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add text.
|
||||
*/
|
||||
public function addText(string $text): self
|
||||
{
|
||||
$this->text[] = $text;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set text array of extension.
|
||||
*
|
||||
* @param array<string> $text
|
||||
*/
|
||||
public function setText(array $text): self
|
||||
{
|
||||
$this->text = $text;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any text is present.
|
||||
*/
|
||||
public function hasText(): bool
|
||||
{
|
||||
return $this->text !== [];
|
||||
}
|
||||
}
|
||||
68
vendor/intervention/gif/src/Blocks/TableBasedImage.php
vendored
Normal file
68
vendor/intervention/gif/src/Blocks/TableBasedImage.php
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
|
||||
class TableBasedImage extends AbstractEntity
|
||||
{
|
||||
protected ImageDescriptor $imageDescriptor;
|
||||
protected ?ColorTable $colorTable = null;
|
||||
protected ImageData $imageData;
|
||||
|
||||
/**
|
||||
* Get image descriptor.
|
||||
*/
|
||||
public function imageDescriptor(): ImageDescriptor
|
||||
{
|
||||
return $this->imageDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image descriptor for current instance.
|
||||
*/
|
||||
public function setImageDescriptor(ImageDescriptor $descriptor): self
|
||||
{
|
||||
$this->imageDescriptor = $descriptor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image data.
|
||||
*/
|
||||
public function imageData(): ImageData
|
||||
{
|
||||
return $this->imageData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image data for current instance.
|
||||
*/
|
||||
public function setImageData(ImageData $data): self
|
||||
{
|
||||
$this->imageData = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current color table or null of table based based image has none.
|
||||
*/
|
||||
public function colorTable(): ?ColorTable
|
||||
{
|
||||
return $this->colorTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set color table.
|
||||
*/
|
||||
public function setColorTable(ColorTable $table): self
|
||||
{
|
||||
$this->colorTable = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
12
vendor/intervention/gif/src/Blocks/Trailer.php
vendored
Normal file
12
vendor/intervention/gif/src/Blocks/Trailer.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Blocks;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
|
||||
class Trailer extends AbstractEntity
|
||||
{
|
||||
public const MARKER = "\x3b";
|
||||
}
|
||||
233
vendor/intervention/gif/src/Builder.php
vendored
Normal file
233
vendor/intervention/gif/src/Builder.php
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Blocks\GraphicControlExtension;
|
||||
use Intervention\Gif\Blocks\ImageDescriptor;
|
||||
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
|
||||
use Intervention\Gif\Blocks\TableBasedImage;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
use Intervention\Gif\Exceptions\StreamException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Gif\Exceptions\StateException;
|
||||
use Intervention\Gif\Traits\CanHandleFiles;
|
||||
|
||||
class Builder
|
||||
{
|
||||
use CanHandleFiles;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(protected GifDataStream $gif = new GifDataStream())
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new canvas.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function canvas(int $width, int $height): self
|
||||
{
|
||||
return (new self())->setSize($width, $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GifDataStream object we're currently building.
|
||||
*/
|
||||
public function gifDataStream(): GifDataStream
|
||||
{
|
||||
return $this->gif;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set canvas size of gif.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setSize(int $width, int $height): self
|
||||
{
|
||||
$this->gif->logicalScreenDescriptor()->setSize($width, $height);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set loop count.
|
||||
*
|
||||
* @throws StateException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setLoops(int $loops): self
|
||||
{
|
||||
if ($loops < 0) {
|
||||
throw new InvalidArgumentException('The loop count must be equal to or greater than 0');
|
||||
}
|
||||
|
||||
if ($this->gif->frames() === []) {
|
||||
throw new StateException('Add at least one frame before setting the loop count');
|
||||
}
|
||||
|
||||
// with one single loop the netscape extension must be removed otherwise the
|
||||
// gif is looped twice because the first repetition always takes place
|
||||
if ($loops === 1) {
|
||||
$this->gif->firstFrame()?->clearApplicationExtensions();
|
||||
return $this;
|
||||
}
|
||||
|
||||
// make sure a netscape extension is present to store the loop count
|
||||
if ($this->gif->firstFrame()?->netscapeExtension() === null) {
|
||||
$this->gif->firstFrame()?->addApplicationExtension(
|
||||
new NetscapeApplicationExtension()
|
||||
);
|
||||
}
|
||||
|
||||
// the loop count is reduced by one because what is referred to here as
|
||||
// the “loop count” actually means repetitions in GIF format, and thus
|
||||
// the first repetition always takes place. A loop count of 0 howerver
|
||||
// means infinite repetitions and remains unaltered.
|
||||
$loops = $loops === 0 ? $loops : $loops - 1;
|
||||
|
||||
// add loop count to netscape extension on first frame
|
||||
$this->gif->firstFrame()?->netscapeExtension()?->setLoops($loops);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new animation frame from given source which can be path to a file or GIF image data.
|
||||
*
|
||||
* @throws DecoderException
|
||||
* @throws StreamException
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function addFrame(
|
||||
mixed $source,
|
||||
float $delay = 0,
|
||||
int $left = 0,
|
||||
int $top = 0,
|
||||
bool $interlaced = false
|
||||
): self {
|
||||
$frame = new FrameBlock();
|
||||
$source = Decoder::decode($source);
|
||||
|
||||
// store delay
|
||||
$frame->setGraphicControlExtension(
|
||||
$this->buildGraphicControlExtension(
|
||||
$source,
|
||||
intval($delay * 100)
|
||||
)
|
||||
);
|
||||
|
||||
// store image
|
||||
$frame->setTableBasedImage(
|
||||
$this->buildTableBasedImage($source, $left, $top, $interlaced)
|
||||
);
|
||||
|
||||
// add frame
|
||||
$this->gif->addFrame($frame);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build new graphic control extension with given delay & disposal method
|
||||
*/
|
||||
protected function buildGraphicControlExtension(
|
||||
GifDataStream $source,
|
||||
int $delay,
|
||||
DisposalMethod $disposalMethod = DisposalMethod::BACKGROUND
|
||||
): GraphicControlExtension {
|
||||
// create extension
|
||||
$extension = new GraphicControlExtension($delay, $disposalMethod);
|
||||
|
||||
// set transparency index
|
||||
$control = $source->firstFrame()?->graphicControlExtension();
|
||||
if ($control !== null && $control->transparentColorExistance()) {
|
||||
$extension->setTransparentColorExistance();
|
||||
$extension->setTransparentColorIndex(
|
||||
$control->transparentColorIndex()
|
||||
);
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build table based image object from given source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function buildTableBasedImage(
|
||||
GifDataStream $source,
|
||||
int $left,
|
||||
int $top,
|
||||
bool $interlaced
|
||||
): TableBasedImage {
|
||||
$block = new TableBasedImage();
|
||||
$block->setImageDescriptor(new ImageDescriptor());
|
||||
|
||||
// set global color table from source as local color table
|
||||
$block->imageDescriptor()->setLocalColorTableExistance();
|
||||
$globalColorTable = $source->globalColorTable();
|
||||
|
||||
if ($globalColorTable === null) {
|
||||
throw new DecoderException(
|
||||
'Failed to build table based image. Unable to find global color table in gif data stream',
|
||||
);
|
||||
}
|
||||
|
||||
$block->setColorTable($globalColorTable);
|
||||
|
||||
$block->imageDescriptor()->setLocalColorTableSorted(
|
||||
$source->logicalScreenDescriptor()->globalColorTableSorted()
|
||||
);
|
||||
|
||||
try {
|
||||
$block->imageDescriptor()->setLocalColorTableSize(
|
||||
$source->logicalScreenDescriptor()->globalColorTableSize()
|
||||
);
|
||||
|
||||
$block->imageDescriptor()->setSize(
|
||||
$source->logicalScreenDescriptor()->width(),
|
||||
$source->logicalScreenDescriptor()->height()
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode image source',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
// set position
|
||||
$block->imageDescriptor()->setPosition($left, $top);
|
||||
|
||||
// set interlaced flag
|
||||
$block->imageDescriptor()->setInterlaced($interlaced);
|
||||
|
||||
// add image data from source
|
||||
$block->setImageData(
|
||||
$source->firstFrame()?->imageData() ?: throw new DecoderException(
|
||||
'Failed to build table based image. Unable to find image data',
|
||||
)
|
||||
);
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the current build.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return $this->gif->encode();
|
||||
}
|
||||
}
|
||||
50
vendor/intervention/gif/src/Decoder.php
vendored
Normal file
50
vendor/intervention/gif/src/Decoder.php
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\StreamException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Gif\Traits\CanHandleFiles;
|
||||
|
||||
class Decoder
|
||||
{
|
||||
use CanHandleFiles;
|
||||
|
||||
/**
|
||||
* Decode given input.
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws StreamException
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public static function decode(mixed $input): GifDataStream
|
||||
{
|
||||
$stream = match (true) {
|
||||
self::isFilePath($input) => self::streamFromFilePath($input),
|
||||
is_string($input) => self::streamFromData($input),
|
||||
self::isStream($input) => $input,
|
||||
default => throw new InvalidArgumentException(
|
||||
'Decoder input must be either file path, stream resource or binary data'
|
||||
)
|
||||
};
|
||||
|
||||
$result = rewind($stream);
|
||||
|
||||
if ($result === false) {
|
||||
throw new StreamException('Failed to rewind stream');
|
||||
}
|
||||
|
||||
return GifDataStream::decode($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if input is stream resource.
|
||||
*/
|
||||
private static function isStream(mixed $input): bool
|
||||
{
|
||||
return is_resource($input) && get_resource_type($input) === 'stream';
|
||||
}
|
||||
}
|
||||
168
vendor/intervention/gif/src/Decoders/AbstractDecoder.php
vendored
Normal file
168
vendor/intervention/gif/src/Decoders/AbstractDecoder.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
abstract class AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*/
|
||||
abstract public function decode(): AbstractEntity;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(protected mixed $stream, protected ?int $length = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Set source to decode.
|
||||
*/
|
||||
public function setStream(mixed $stream): self
|
||||
{
|
||||
$this->stream = $stream;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read given number of bytes and move stream position.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function nextBytesOrFail(int $length): string
|
||||
{
|
||||
if ($length < 1) {
|
||||
throw new DecoderException('The length of the next byte chain must be at least one byte');
|
||||
}
|
||||
|
||||
$bytes = fread($this->stream, $length);
|
||||
if ($bytes === false || strlen($bytes) !== $length) {
|
||||
throw new DecoderException('Unexpected end of file');
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read given number of bytes and move stream position back to previous position.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function viewNextBytesOrFail(int $length): string
|
||||
{
|
||||
$bytes = $this->nextBytesOrFail($length);
|
||||
$this->moveStreamPosition($length * -1);
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read next byte and move stream position back to previous position.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function viewNextByteOrFail(): string
|
||||
{
|
||||
return $this->viewNextBytesOrFail(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all remaining bytes from stream.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function remainingBytes(): string
|
||||
{
|
||||
$contents = stream_get_contents($this->stream);
|
||||
|
||||
if ($contents === false) {
|
||||
throw new DecoderException('Failed to read remaining bytes from stream');
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next byte in stream and move stream position.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function nextByteOrFail(): string
|
||||
{
|
||||
return $this->nextBytesOrFail(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move stream position by given offset.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function moveStreamPosition(int $offset): self
|
||||
{
|
||||
$result = fseek($this->stream, $offset, SEEK_CUR);
|
||||
|
||||
if ($result !== 0) {
|
||||
throw new DecoderException('Failed to move stream position by offset ' . $offset);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode multi byte value.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeMultiByte(string $bytes): int
|
||||
{
|
||||
$unpacked = unpack('v*', $bytes);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode given bytes');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set length.
|
||||
*/
|
||||
public function setLength(int $length): self
|
||||
{
|
||||
$this->length = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get length.
|
||||
*/
|
||||
public function length(): ?int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current stream position.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function position(): int
|
||||
{
|
||||
$position = ftell($this->stream);
|
||||
|
||||
if ($position === false) {
|
||||
throw new DecoderException('Failed to read current position from stream');
|
||||
}
|
||||
|
||||
return $position;
|
||||
}
|
||||
}
|
||||
48
vendor/intervention/gif/src/Decoders/AbstractPackedBitDecoder.php
vendored
Normal file
48
vendor/intervention/gif/src/Decoders/AbstractPackedBitDecoder.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
abstract class AbstractPackedBitDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode packed byte.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodePackedByte(string $byte): int
|
||||
{
|
||||
$unpacked = unpack('C', $byte);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode packed info block size');
|
||||
}
|
||||
|
||||
return intval($unpacked[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if packed bit is set.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function hasPackedBit(string $byte, int $num): bool
|
||||
{
|
||||
return (bool) $this->packedBits($byte)[$num];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get packed bits.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function packedBits(string $byte, int $start = 0, int $length = 8): string
|
||||
{
|
||||
$bits = str_pad(decbin($this->decodePackedByte($byte)), 8, '0', STR_PAD_LEFT);
|
||||
|
||||
return substr($bits, $start, $length);
|
||||
}
|
||||
}
|
||||
87
vendor/intervention/gif/src/Decoders/ApplicationExtensionDecoder.php
vendored
Normal file
87
vendor/intervention/gif/src/Decoders/ApplicationExtensionDecoder.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ApplicationExtension;
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ApplicationExtensionDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): ApplicationExtension
|
||||
{
|
||||
$result = new ApplicationExtension();
|
||||
|
||||
$this->nextByteOrFail(); // marker
|
||||
$this->nextByteOrFail(); // label
|
||||
$blocksize = $this->decodeBlockSize($this->nextByteOrFail());
|
||||
$application = $this->nextBytesOrFail($blocksize);
|
||||
|
||||
if ($application === NetscapeApplicationExtension::IDENTIFIER . NetscapeApplicationExtension::AUTH_CODE) {
|
||||
$result = new NetscapeApplicationExtension();
|
||||
|
||||
// skip length
|
||||
$this->nextByteOrFail();
|
||||
|
||||
try {
|
||||
$result->setBlocks([
|
||||
new DataSubBlock($this->nextBytesOrFail(3))
|
||||
]);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode image data sub block of image data',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
// skip terminator
|
||||
$this->nextByteOrFail();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result->setApplication($application);
|
||||
|
||||
// decode data sub blocks
|
||||
$blocksize = $this->decodeBlockSize($this->nextByteOrFail());
|
||||
while ($blocksize > 0) {
|
||||
try {
|
||||
$result->addBlock(new DataSubBlock($this->nextBytesOrFail($blocksize)));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode image data sub block of image data',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
|
||||
$blocksize = $this->decodeBlockSize($this->nextByteOrFail());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode block size of ApplicationExtension from given byte.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeBlockSize(string $byte): int
|
||||
{
|
||||
$unpacked = @unpack('C', $byte);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode block size of application extension');
|
||||
}
|
||||
|
||||
return intval($unpacked[1]);
|
||||
}
|
||||
}
|
||||
48
vendor/intervention/gif/src/Decoders/ColorDecoder.php
vendored
Normal file
48
vendor/intervention/gif/src/Decoders/ColorDecoder.php
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Color;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ColorDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source to Color.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): Color
|
||||
{
|
||||
try {
|
||||
return new Color(
|
||||
$this->decodeColorValue($this->nextByteOrFail()),
|
||||
$this->decodeColorValue($this->nextByteOrFail()),
|
||||
$this->decodeColorValue($this->nextByteOrFail()),
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode color channel values',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode color value from source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeColorValue(string $byte): int
|
||||
{
|
||||
$unpacked = unpack('C', $byte);
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode color value');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
}
|
||||
29
vendor/intervention/gif/src/Decoders/ColorTableDecoder.php
vendored
Normal file
29
vendor/intervention/gif/src/Decoders/ColorTableDecoder.php
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Color;
|
||||
use Intervention\Gif\Blocks\ColorTable;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class ColorTableDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode given string to ColorTable.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): ColorTable
|
||||
{
|
||||
$table = new ColorTable();
|
||||
$length = $this->length() !== null ? $this->length() : 0;
|
||||
|
||||
for ($i = 0; $i < ($length / 3); $i++) {
|
||||
$table->addColor(Color::decode($this->stream));
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
}
|
||||
65
vendor/intervention/gif/src/Decoders/CommentExtensionDecoder.php
vendored
Normal file
65
vendor/intervention/gif/src/Decoders/CommentExtensionDecoder.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class CommentExtensionDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): CommentExtension
|
||||
{
|
||||
$this->nextBytesOrFail(2); // skip marker & label
|
||||
|
||||
$extension = new CommentExtension();
|
||||
foreach ($this->decodeComments() as $comment) {
|
||||
$extension->addComment($comment);
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode comment from current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
* @return array<string>
|
||||
*/
|
||||
protected function decodeComments(): array
|
||||
{
|
||||
$comments = [];
|
||||
|
||||
do {
|
||||
$byte = $this->nextByteOrFail();
|
||||
$size = $this->decodeBlocksize($byte);
|
||||
if ($size > 0) {
|
||||
$comments[] = $this->nextBytesOrFail($size);
|
||||
}
|
||||
} while ($byte !== CommentExtension::TERMINATOR);
|
||||
|
||||
return $comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode blocksize of following comment.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeBlocksize(string $byte): int
|
||||
{
|
||||
$unpacked = @unpack('C', $byte);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode block size of comment extension');
|
||||
}
|
||||
|
||||
return intval($unpacked[1]);
|
||||
}
|
||||
}
|
||||
38
vendor/intervention/gif/src/Decoders/DataSubBlockDecoder.php
vendored
Normal file
38
vendor/intervention/gif/src/Decoders/DataSubBlockDecoder.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class DataSubBlockDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): DataSubBlock
|
||||
{
|
||||
$char = $this->nextByteOrFail();
|
||||
$unpacked = unpack('C', $char);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode data sub block');
|
||||
}
|
||||
|
||||
$size = (int) $unpacked[1];
|
||||
|
||||
try {
|
||||
return new DataSubBlock($this->nextBytesOrFail($size));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode image data sub block of image data',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
vendor/intervention/gif/src/Decoders/FrameBlockDecoder.php
vendored
Normal file
52
vendor/intervention/gif/src/Decoders/FrameBlockDecoder.php
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
use Intervention\Gif\Blocks\ApplicationExtension;
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Blocks\GraphicControlExtension;
|
||||
use Intervention\Gif\Blocks\ImageDescriptor;
|
||||
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
|
||||
use Intervention\Gif\Blocks\PlainTextExtension;
|
||||
use Intervention\Gif\Blocks\TableBasedImage;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class FrameBlockDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode FrameBlock.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): FrameBlock
|
||||
{
|
||||
$frame = new FrameBlock();
|
||||
|
||||
do {
|
||||
$block = match ($this->viewNextBytesOrFail(2)) {
|
||||
AbstractExtension::MARKER . GraphicControlExtension::LABEL
|
||||
=> GraphicControlExtension::decode($this->stream),
|
||||
AbstractExtension::MARKER . NetscapeApplicationExtension::LABEL
|
||||
=> NetscapeApplicationExtension::decode($this->stream),
|
||||
AbstractExtension::MARKER . ApplicationExtension::LABEL
|
||||
=> ApplicationExtension::decode($this->stream),
|
||||
AbstractExtension::MARKER . PlainTextExtension::LABEL
|
||||
=> PlainTextExtension::decode($this->stream),
|
||||
AbstractExtension::MARKER . CommentExtension::LABEL
|
||||
=> CommentExtension::decode($this->stream),
|
||||
default => match ($this->viewNextByteOrFail()) {
|
||||
ImageDescriptor::SEPARATOR => TableBasedImage::decode($this->stream),
|
||||
default => throw new DecoderException('Failed to decode data block'),
|
||||
}
|
||||
};
|
||||
|
||||
$frame->addEntity($block);
|
||||
} while (!($block instanceof TableBasedImage));
|
||||
|
||||
return $frame;
|
||||
}
|
||||
}
|
||||
58
vendor/intervention/gif/src/Decoders/GifDataStreamDecoder.php
vendored
Normal file
58
vendor/intervention/gif/src/Decoders/GifDataStreamDecoder.php
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\AbstractExtension;
|
||||
use Intervention\Gif\Blocks\ColorTable;
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Blocks\Header;
|
||||
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
|
||||
use Intervention\Gif\Blocks\Trailer;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\GifDataStream;
|
||||
|
||||
class GifDataStreamDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source to GifDataStream.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): GifDataStream
|
||||
{
|
||||
$gif = new GifDataStream();
|
||||
|
||||
$gif->setHeader(
|
||||
Header::decode($this->stream),
|
||||
);
|
||||
|
||||
$gif->setLogicalScreenDescriptor(
|
||||
LogicalScreenDescriptor::decode($this->stream),
|
||||
);
|
||||
|
||||
if ($gif->logicalScreenDescriptor()->hasGlobalColorTable()) {
|
||||
$length = $gif->logicalScreenDescriptor()->globalColorTableByteSize();
|
||||
$gif->setGlobalColorTable(
|
||||
ColorTable::decode($this->stream, $length)
|
||||
);
|
||||
}
|
||||
|
||||
while ($this->viewNextByteOrFail() !== Trailer::MARKER) {
|
||||
match ($this->viewNextBytesOrFail(2)) {
|
||||
// handle trailing "global" comment blocks which are not part of "FrameBlock"
|
||||
AbstractExtension::MARKER . CommentExtension::LABEL
|
||||
=> $gif->addComment(
|
||||
CommentExtension::decode($this->stream)
|
||||
),
|
||||
default => $gif->addFrame(
|
||||
FrameBlock::decode($this->stream)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return $gif;
|
||||
}
|
||||
}
|
||||
117
vendor/intervention/gif/src/Decoders/GraphicControlExtensionDecoder.php
vendored
Normal file
117
vendor/intervention/gif/src/Decoders/GraphicControlExtensionDecoder.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\GraphicControlExtension;
|
||||
use Intervention\Gif\DisposalMethod;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use TypeError;
|
||||
use ValueError;
|
||||
|
||||
class GraphicControlExtensionDecoder extends AbstractPackedBitDecoder
|
||||
{
|
||||
/**
|
||||
* Decode given string to current instance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): GraphicControlExtension
|
||||
{
|
||||
$result = new GraphicControlExtension();
|
||||
|
||||
// bytes 1-3
|
||||
$this->nextBytesOrFail(3); // skip marker, label & bytesize
|
||||
|
||||
// byte #4
|
||||
$packedField = $this->nextByteOrFail();
|
||||
$result->setDisposalMethod($this->decodeDisposalMethod($packedField));
|
||||
$result->setUserInput($this->decodeUserInput($packedField));
|
||||
$result->setTransparentColorExistance($this->decodeTransparentColorExistance($packedField));
|
||||
|
||||
// bytes 5-6
|
||||
$result->setDelay($this->decodeDelay($this->nextBytesOrFail(2)));
|
||||
|
||||
// byte #7
|
||||
$result->setTransparentColorIndex($this->decodeTransparentColorIndex(
|
||||
$this->nextByteOrFail()
|
||||
));
|
||||
|
||||
// byte #8 (terminator)
|
||||
$this->nextByteOrFail();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode disposal method
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected function decodeDisposalMethod(string $byte): DisposalMethod
|
||||
{
|
||||
try {
|
||||
return DisposalMethod::from(
|
||||
intval(bindec($this->packedBits($byte, 3, 3)))
|
||||
);
|
||||
} catch (TypeError | ValueError $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode disposal method in graphic control extension',
|
||||
previous: $e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode user input flag.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeUserInput(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode transparent color existance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeTransparentColorExistance(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode delay value.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeDelay(string $bytes): int
|
||||
{
|
||||
$unpacked = unpack('v*', $bytes);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode animation delay in graphic control extension');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode transparent color index.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeTransparentColorIndex(string $byte): int
|
||||
{
|
||||
$unpacked = unpack('C', $byte);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode transparent color index in graphic control extension');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
}
|
||||
40
vendor/intervention/gif/src/Decoders/HeaderDecoder.php
vendored
Normal file
40
vendor/intervention/gif/src/Decoders/HeaderDecoder.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Header;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class HeaderDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): Header
|
||||
{
|
||||
$header = new Header();
|
||||
$header->setVersion($this->decodeVersion());
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode version string.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeVersion(): string
|
||||
{
|
||||
$parsed = (bool) preg_match("/^GIF(?P<version>[0-9]{2}[a-z])$/", $this->nextBytesOrFail(6), $matches);
|
||||
|
||||
if ($parsed === false) {
|
||||
throw new DecoderException('Failed to parse GIF file header');
|
||||
}
|
||||
|
||||
return $matches['version'];
|
||||
}
|
||||
}
|
||||
57
vendor/intervention/gif/src/Decoders/ImageDataDecoder.php
vendored
Normal file
57
vendor/intervention/gif/src/Decoders/ImageDataDecoder.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Blocks\ImageData;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ImageDataDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): ImageData
|
||||
{
|
||||
$data = new ImageData();
|
||||
|
||||
// LZW min. code size
|
||||
$char = $this->nextByteOrFail();
|
||||
$unpacked = unpack('C', $char);
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode lzw min. code size of image data');
|
||||
}
|
||||
|
||||
$data->setLzwMinCodeSize(intval($unpacked[1]));
|
||||
|
||||
do {
|
||||
// decode sub blocks
|
||||
$char = $this->nextByteOrFail();
|
||||
$unpacked = unpack('C', $char);
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode image data sub block of image data');
|
||||
}
|
||||
|
||||
$size = intval($unpacked[1]);
|
||||
|
||||
if ($size > 0) {
|
||||
try {
|
||||
$data->addBlock(new DataSubBlock($this->nextBytesOrFail($size)));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException(
|
||||
'Failed to decode image data sub block of image data',
|
||||
previous: $e
|
||||
);
|
||||
}
|
||||
}
|
||||
} while ($char !== AbstractEntity::TERMINATOR);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
98
vendor/intervention/gif/src/Decoders/ImageDescriptorDecoder.php
vendored
Normal file
98
vendor/intervention/gif/src/Decoders/ImageDescriptorDecoder.php
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ImageDescriptor;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class ImageDescriptorDecoder extends AbstractPackedBitDecoder
|
||||
{
|
||||
/**
|
||||
* Decode given string to current instance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): ImageDescriptor
|
||||
{
|
||||
$descriptor = new ImageDescriptor();
|
||||
|
||||
$this->nextByteOrFail(); // skip separator
|
||||
|
||||
$descriptor->setPosition(
|
||||
$this->decodeMultiByte($this->nextBytesOrFail(2)),
|
||||
$this->decodeMultiByte($this->nextBytesOrFail(2))
|
||||
);
|
||||
|
||||
try {
|
||||
$descriptor->setSize(
|
||||
$this->decodeMultiByte($this->nextBytesOrFail(2)),
|
||||
$this->decodeMultiByte($this->nextBytesOrFail(2))
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException('Failed to decode image size of image descriptor', previous: $e);
|
||||
}
|
||||
|
||||
$packedField = $this->nextByteOrFail();
|
||||
|
||||
$descriptor->setLocalColorTableExistance(
|
||||
$this->decodeLocalColorTableExistance($packedField)
|
||||
);
|
||||
|
||||
$descriptor->setLocalColorTableSorted(
|
||||
$this->decodeLocalColorTableSorted($packedField)
|
||||
);
|
||||
|
||||
$descriptor->setLocalColorTableSize(
|
||||
$this->decodeLocalColorTableSize($packedField)
|
||||
);
|
||||
|
||||
$descriptor->setInterlaced(
|
||||
$this->decodeInterlaced($packedField)
|
||||
);
|
||||
|
||||
return $descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode local color table existance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeLocalColorTableExistance(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode local color table sort method.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeLocalColorTableSorted(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode local color table size.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeLocalColorTableSize(string $byte): int
|
||||
{
|
||||
return (int) bindec($this->packedBits($byte, 5, 3));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode interlaced flag.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeInterlaced(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 1);
|
||||
}
|
||||
}
|
||||
167
vendor/intervention/gif/src/Decoders/LogicalScreenDescriptorDecoder.php
vendored
Normal file
167
vendor/intervention/gif/src/Decoders/LogicalScreenDescriptorDecoder.php
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
|
||||
class LogicalScreenDescriptorDecoder extends AbstractPackedBitDecoder
|
||||
{
|
||||
/**
|
||||
* Decode given string to current instance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): LogicalScreenDescriptor
|
||||
{
|
||||
$logicalScreenDescriptor = new LogicalScreenDescriptor();
|
||||
|
||||
// bytes 1-4
|
||||
try {
|
||||
$logicalScreenDescriptor->setSize(
|
||||
$this->decodeWidth($this->nextBytesOrFail(2)),
|
||||
$this->decodeHeight($this->nextBytesOrFail(2))
|
||||
);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new DecoderException('Failed to decode image size of logical screen descriptor', previous: $e);
|
||||
}
|
||||
|
||||
// byte 5
|
||||
$packedField = $this->nextByteOrFail();
|
||||
|
||||
$logicalScreenDescriptor->setGlobalColorTableExistance(
|
||||
$this->decodeGlobalColorTableExistance($packedField)
|
||||
);
|
||||
|
||||
$logicalScreenDescriptor->setBitsPerPixel(
|
||||
$this->decodeBitsPerPixel($packedField)
|
||||
);
|
||||
|
||||
$logicalScreenDescriptor->setGlobalColorTableSorted(
|
||||
$this->decodeGlobalColorTableSorted($packedField)
|
||||
);
|
||||
|
||||
$logicalScreenDescriptor->setGlobalColorTableSize(
|
||||
$this->decodeGlobalColorTableSize($packedField)
|
||||
);
|
||||
|
||||
// byte 6
|
||||
$logicalScreenDescriptor->setBackgroundColorIndex(
|
||||
$this->decodeBackgroundColorIndex($this->nextByteOrFail())
|
||||
);
|
||||
|
||||
// byte 7
|
||||
$logicalScreenDescriptor->setPixelAspectRatio(
|
||||
$this->decodePixelAspectRatio($this->nextByteOrFail())
|
||||
);
|
||||
|
||||
return $logicalScreenDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode width.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeWidth(string $source): int
|
||||
{
|
||||
$unpacked = unpack('v*', $source);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode width in logical screen descriptor');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode height.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeHeight(string $source): int
|
||||
{
|
||||
$unpacked = unpack('v*', $source);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode height in logical screen descriptor');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode existance of global color table.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeGlobalColorTableExistance(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode color resolution in bits per pixel.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeBitsPerPixel(string $byte): int
|
||||
{
|
||||
return intval(bindec($this->packedBits($byte, 1, 3))) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode global color table sorted status.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeGlobalColorTableSorted(string $byte): bool
|
||||
{
|
||||
return $this->hasPackedBit($byte, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode size of global color table.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeGlobalColorTableSize(string $byte): int
|
||||
{
|
||||
return intval(bindec($this->packedBits($byte, 5, 3)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode background color index.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodeBackgroundColorIndex(string $source): int
|
||||
{
|
||||
$unpacked = unpack('C', $source);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode background color index in logical screen descriptor');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode pixel aspect ratio.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function decodePixelAspectRatio(string $source): int
|
||||
{
|
||||
$unpacked = unpack('C', $source);
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode pixel aspect ratio in logical screen descriptor');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
}
|
||||
10
vendor/intervention/gif/src/Decoders/NetscapeApplicationExtensionDecoder.php
vendored
Normal file
10
vendor/intervention/gif/src/Decoders/NetscapeApplicationExtensionDecoder.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
class NetscapeApplicationExtensionDecoder extends ApplicationExtensionDecoder
|
||||
{
|
||||
//
|
||||
}
|
||||
75
vendor/intervention/gif/src/Decoders/PlainTextExtensionDecoder.php
vendored
Normal file
75
vendor/intervention/gif/src/Decoders/PlainTextExtensionDecoder.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\PlainTextExtension;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class PlainTextExtensionDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode current source.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): PlainTextExtension
|
||||
{
|
||||
$extension = new PlainTextExtension();
|
||||
|
||||
// skip marker & label
|
||||
$this->nextBytesOrFail(2);
|
||||
|
||||
// skip info block
|
||||
$this->nextBytesOrFail($this->infoBlockSize());
|
||||
|
||||
// text blocks
|
||||
$extension->setText($this->decodeTextBlocks());
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of bytes in header block.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
private function infoBlockSize(): int
|
||||
{
|
||||
$unpacked = unpack('C', $this->nextByteOrFail());
|
||||
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode info block size of plain text extension');
|
||||
}
|
||||
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode text sub blocks.
|
||||
*
|
||||
* @throws DecoderException
|
||||
* @return array<string>
|
||||
*/
|
||||
private function decodeTextBlocks(): array
|
||||
{
|
||||
$blocks = [];
|
||||
|
||||
do {
|
||||
$char = $this->nextByteOrFail();
|
||||
$unpacked = unpack('C', $char);
|
||||
if ($unpacked === false || !array_key_exists(1, $unpacked)) {
|
||||
throw new DecoderException('Failed to decode text blocks in plain text extension');
|
||||
}
|
||||
|
||||
$size = (int) $unpacked[1];
|
||||
|
||||
if ($size > 0) {
|
||||
$blocks[] = $this->nextBytesOrFail($size);
|
||||
}
|
||||
} while ($char !== PlainTextExtension::TERMINATOR);
|
||||
|
||||
return $blocks;
|
||||
}
|
||||
}
|
||||
41
vendor/intervention/gif/src/Decoders/TableBasedImageDecoder.php
vendored
Normal file
41
vendor/intervention/gif/src/Decoders/TableBasedImageDecoder.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Decoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ColorTable;
|
||||
use Intervention\Gif\Blocks\ImageData;
|
||||
use Intervention\Gif\Blocks\ImageDescriptor;
|
||||
use Intervention\Gif\Blocks\TableBasedImage;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
class TableBasedImageDecoder extends AbstractDecoder
|
||||
{
|
||||
/**
|
||||
* Decode TableBasedImage.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public function decode(): TableBasedImage
|
||||
{
|
||||
$block = new TableBasedImage();
|
||||
|
||||
$block->setImageDescriptor(ImageDescriptor::decode($this->stream));
|
||||
|
||||
if ($block->imageDescriptor()->hasLocalColorTable()) {
|
||||
$block->setColorTable(
|
||||
ColorTable::decode(
|
||||
$this->stream,
|
||||
$block->imageDescriptor()->localColorTableByteSize()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$block->setImageData(
|
||||
ImageData::decode($this->stream)
|
||||
);
|
||||
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
13
vendor/intervention/gif/src/DisposalMethod.php
vendored
Normal file
13
vendor/intervention/gif/src/DisposalMethod.php
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
enum DisposalMethod: int
|
||||
{
|
||||
case UNDEFINED = 0;
|
||||
case NONE = 1; // overlay each frame in sequence
|
||||
case BACKGROUND = 2; // clear to background (as indicated by the logical screen descriptor)
|
||||
case PREVIOUS = 3; // restore the canvas to its previous state
|
||||
}
|
||||
21
vendor/intervention/gif/src/Encoders/AbstractEncoder.php
vendored
Normal file
21
vendor/intervention/gif/src/Encoders/AbstractEncoder.php
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
abstract class AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
abstract public function encode(): string;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(protected mixed $entity)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
37
vendor/intervention/gif/src/Encoders/ApplicationExtensionEncoder.php
vendored
Normal file
37
vendor/intervention/gif/src/Encoders/ApplicationExtensionEncoder.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ApplicationExtension;
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
|
||||
class ApplicationExtensionEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new decoder instance.
|
||||
*/
|
||||
public function __construct(ApplicationExtension $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
ApplicationExtension::MARKER,
|
||||
ApplicationExtension::LABEL,
|
||||
pack('C', $this->entity->blockSize()),
|
||||
$this->entity->application(),
|
||||
implode('', array_map(fn(DataSubBlock $block): string => $block->encode(), $this->entity->blocks())),
|
||||
ApplicationExtension::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
}
|
||||
38
vendor/intervention/gif/src/Encoders/ColorEncoder.php
vendored
Normal file
38
vendor/intervention/gif/src/Encoders/ColorEncoder.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Color;
|
||||
|
||||
class ColorEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(Color $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
$this->encodeColorValue($this->entity->red()),
|
||||
$this->encodeColorValue($this->entity->green()),
|
||||
$this->encodeColorValue($this->entity->blue()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode color value.
|
||||
*/
|
||||
private function encodeColorValue(int $value): string
|
||||
{
|
||||
return pack('C', $value);
|
||||
}
|
||||
}
|
||||
33
vendor/intervention/gif/src/Encoders/ColorTableEncoder.php
vendored
Normal file
33
vendor/intervention/gif/src/Encoders/ColorTableEncoder.php
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Color;
|
||||
use Intervention\Gif\Blocks\ColorTable;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
|
||||
class ColorTableEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(ColorTable $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', array_map(
|
||||
fn(Color $color): string => $color->encode(),
|
||||
$this->entity->colors(),
|
||||
));
|
||||
}
|
||||
}
|
||||
41
vendor/intervention/gif/src/Encoders/CommentExtensionEncoder.php
vendored
Normal file
41
vendor/intervention/gif/src/Encoders/CommentExtensionEncoder.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
|
||||
class CommentExtensionEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new decoder instance.
|
||||
*/
|
||||
public function __construct(CommentExtension $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
CommentExtension::MARKER,
|
||||
CommentExtension::LABEL,
|
||||
$this->encodeComments(),
|
||||
CommentExtension::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode comment blocks.
|
||||
*/
|
||||
private function encodeComments(): string
|
||||
{
|
||||
return implode('', array_map(function (string $comment): string {
|
||||
return pack('C', strlen($comment)) . $comment;
|
||||
}, $this->entity->comments()));
|
||||
}
|
||||
}
|
||||
26
vendor/intervention/gif/src/Encoders/DataSubBlockEncoder.php
vendored
Normal file
26
vendor/intervention/gif/src/Encoders/DataSubBlockEncoder.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
|
||||
class DataSubBlockEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(DataSubBlock $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return pack('C', $this->entity->size()) . $this->entity->value();
|
||||
}
|
||||
}
|
||||
49
vendor/intervention/gif/src/Encoders/FrameBlockEncoder.php
vendored
Normal file
49
vendor/intervention/gif/src/Encoders/FrameBlockEncoder.php
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ApplicationExtension;
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
|
||||
class FrameBlockEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new decoder instance.
|
||||
*/
|
||||
public function __construct(FrameBlock $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
$graphicControlExtension = $this->entity->graphicControlExtension();
|
||||
$colorTable = $this->entity->colorTable();
|
||||
$plainTextExtension = $this->entity->plainTextExtension();
|
||||
|
||||
return implode('', [
|
||||
implode('', array_map(
|
||||
fn(ApplicationExtension $extension): string => $extension->encode(),
|
||||
$this->entity->applicationExtensions(),
|
||||
)),
|
||||
implode('', array_map(
|
||||
fn(CommentExtension $extension): string => $extension->encode(),
|
||||
$this->entity->commentExtensions(),
|
||||
)),
|
||||
$plainTextExtension ? $plainTextExtension->encode() : '',
|
||||
$graphicControlExtension ? $graphicControlExtension->encode() : '',
|
||||
$this->entity->imageDescriptor()->encode(),
|
||||
$colorTable ? $colorTable->encode() : '',
|
||||
$this->entity->imageData()->encode(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
76
vendor/intervention/gif/src/Encoders/GifDataStreamEncoder.php
vendored
Normal file
76
vendor/intervention/gif/src/Encoders/GifDataStreamEncoder.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
use Intervention\Gif\GifDataStream;
|
||||
|
||||
class GifDataStreamEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(GifDataStream $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
$this->entity->header()->encode(),
|
||||
$this->entity->logicalScreenDescriptor()->encode(),
|
||||
$this->maybeEncodeGlobalColorTable(),
|
||||
$this->encodeFrames(),
|
||||
$this->encodeComments(),
|
||||
$this->entity->trailer()->encode(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode global color table if present in gif data.
|
||||
*/
|
||||
private function maybeEncodeGlobalColorTable(): string
|
||||
{
|
||||
if (!$this->entity->hasGlobalColorTable()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->entity->globalColorTable()->encode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode data blocks of source.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
private function encodeFrames(): string
|
||||
{
|
||||
return implode('', array_map(
|
||||
fn(FrameBlock $frame): string => $frame->encode(),
|
||||
$this->entity->frames(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode comment extension blocks of source.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
private function encodeComments(): string
|
||||
{
|
||||
return implode('', array_map(
|
||||
fn(CommentExtension $commentExtension): string => $commentExtension->encode(),
|
||||
$this->entity->comments()
|
||||
));
|
||||
}
|
||||
}
|
||||
63
vendor/intervention/gif/src/Encoders/GraphicControlExtensionEncoder.php
vendored
Normal file
63
vendor/intervention/gif/src/Encoders/GraphicControlExtensionEncoder.php
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\GraphicControlExtension;
|
||||
|
||||
class GraphicControlExtensionEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(GraphicControlExtension $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
GraphicControlExtension::MARKER,
|
||||
GraphicControlExtension::LABEL,
|
||||
GraphicControlExtension::BLOCKSIZE,
|
||||
$this->encodePackedField(),
|
||||
$this->encodeDelay(),
|
||||
$this->encodeTransparentColorIndex(),
|
||||
GraphicControlExtension::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode delay time.
|
||||
*/
|
||||
private function encodeDelay(): string
|
||||
{
|
||||
return pack('v*', $this->entity->delay());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode transparent color index.
|
||||
*/
|
||||
private function encodeTransparentColorIndex(): string
|
||||
{
|
||||
return pack('C', $this->entity->transparentColorIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packed field.
|
||||
*/
|
||||
private function encodePackedField(): string
|
||||
{
|
||||
return pack('C', bindec(implode('', [
|
||||
str_pad('0', 3, '0', STR_PAD_LEFT),
|
||||
str_pad(decbin($this->entity->disposalMethod()->value), 3, '0', STR_PAD_LEFT),
|
||||
(int) $this->entity->userInput(),
|
||||
(int) $this->entity->transparentColorExistance(),
|
||||
])));
|
||||
}
|
||||
}
|
||||
26
vendor/intervention/gif/src/Encoders/HeaderEncoder.php
vendored
Normal file
26
vendor/intervention/gif/src/Encoders/HeaderEncoder.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Header;
|
||||
|
||||
class HeaderEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(Header $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return Header::SIGNATURE . $this->entity->version();
|
||||
}
|
||||
}
|
||||
44
vendor/intervention/gif/src/Encoders/ImageDataEncoder.php
vendored
Normal file
44
vendor/intervention/gif/src/Encoders/ImageDataEncoder.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\AbstractEntity;
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Blocks\ImageData;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
use Intervention\Gif\Exceptions\StateException;
|
||||
|
||||
class ImageDataEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(ImageData $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
* @throws StateException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
if (!$this->entity->hasBlocks()) {
|
||||
throw new StateException('No data blocks in image data');
|
||||
}
|
||||
|
||||
return implode('', [
|
||||
pack('C', $this->entity->lzwMinCodeSize()),
|
||||
implode('', array_map(
|
||||
fn(DataSubBlock $block): string => $block->encode(),
|
||||
$this->entity->blocks(),
|
||||
)),
|
||||
AbstractEntity::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
}
|
||||
95
vendor/intervention/gif/src/Encoders/ImageDescriptorEncoder.php
vendored
Normal file
95
vendor/intervention/gif/src/Encoders/ImageDescriptorEncoder.php
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ImageDescriptor;
|
||||
|
||||
class ImageDescriptorEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(ImageDescriptor $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
ImageDescriptor::SEPARATOR,
|
||||
$this->encodeLeft(),
|
||||
$this->encodeTop(),
|
||||
$this->encodeWidth(),
|
||||
$this->encodeHeight(),
|
||||
$this->encodePackedField(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode left value.
|
||||
*/
|
||||
private function encodeLeft(): string
|
||||
{
|
||||
return pack('v*', $this->entity->left());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode top value.
|
||||
*/
|
||||
private function encodeTop(): string
|
||||
{
|
||||
return pack('v*', $this->entity->top());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode width value.
|
||||
*/
|
||||
private function encodeWidth(): string
|
||||
{
|
||||
return pack('v*', $this->entity->width());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode height value.
|
||||
*/
|
||||
private function encodeHeight(): string
|
||||
{
|
||||
return pack('v*', $this->entity->height());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode size of local color table.
|
||||
*/
|
||||
private function encodeLocalColorTableSize(): string
|
||||
{
|
||||
return str_pad(decbin($this->entity->localColorTableSize()), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode reserved field.
|
||||
*/
|
||||
private function encodeReservedField(): string
|
||||
{
|
||||
return str_pad('0', 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packed field.
|
||||
*/
|
||||
private function encodePackedField(): string
|
||||
{
|
||||
return pack('C', bindec(implode('', [
|
||||
(int) $this->entity->localColorTableExistance(),
|
||||
(int) $this->entity->isInterlaced(),
|
||||
(int) $this->entity->localColorTableSorted(),
|
||||
$this->encodeReservedField(),
|
||||
$this->encodeLocalColorTableSize(),
|
||||
])));
|
||||
}
|
||||
}
|
||||
93
vendor/intervention/gif/src/Encoders/LogicalScreenDescriptorEncoder.php
vendored
Normal file
93
vendor/intervention/gif/src/Encoders/LogicalScreenDescriptorEncoder.php
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
|
||||
|
||||
class LogicalScreenDescriptorEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(LogicalScreenDescriptor $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
$this->encodeWidth(),
|
||||
$this->encodeHeight(),
|
||||
$this->encodePackedField(),
|
||||
$this->encodeBackgroundColorIndex(),
|
||||
$this->encodePixelAspectRatio(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode width of current instance.
|
||||
*/
|
||||
private function encodeWidth(): string
|
||||
{
|
||||
return pack('v*', $this->entity->width());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode height of current instance.
|
||||
*/
|
||||
private function encodeHeight(): string
|
||||
{
|
||||
return pack('v*', $this->entity->height());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode background color index of global color table.
|
||||
*/
|
||||
private function encodeBackgroundColorIndex(): string
|
||||
{
|
||||
return pack('C', $this->entity->backgroundColorIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode pixel aspect ratio.
|
||||
*/
|
||||
private function encodePixelAspectRatio(): string
|
||||
{
|
||||
return pack('C', $this->entity->pixelAspectRatio());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return color resolution for encoding.
|
||||
*/
|
||||
private function encodeColorResolution(): string
|
||||
{
|
||||
return str_pad(decbin($this->entity->bitsPerPixel() - 1), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode size of global color table.
|
||||
*/
|
||||
private function encodeGlobalColorTableSize(): string
|
||||
{
|
||||
return str_pad(decbin($this->entity->globalColorTableSize()), 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode packed field of current instance.
|
||||
*/
|
||||
private function encodePackedField(): string
|
||||
{
|
||||
return pack('C', bindec(implode('', [
|
||||
(int) $this->entity->globalColorTableExistance(),
|
||||
$this->encodeColorResolution(),
|
||||
(int) $this->entity->globalColorTableSorted(),
|
||||
$this->encodeGlobalColorTableSize(),
|
||||
])));
|
||||
}
|
||||
}
|
||||
38
vendor/intervention/gif/src/Encoders/NetscapeApplicationExtensionEncoder.php
vendored
Normal file
38
vendor/intervention/gif/src/Encoders/NetscapeApplicationExtensionEncoder.php
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\ApplicationExtension;
|
||||
use Intervention\Gif\Blocks\DataSubBlock;
|
||||
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
|
||||
class NetscapeApplicationExtensionEncoder extends ApplicationExtensionEncoder
|
||||
{
|
||||
/**
|
||||
* Create new decoder instance.
|
||||
*/
|
||||
public function __construct(NetscapeApplicationExtension $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current source.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
ApplicationExtension::MARKER,
|
||||
ApplicationExtension::LABEL,
|
||||
pack('C', $this->entity->blockSize()),
|
||||
$this->entity->application(),
|
||||
implode('', array_map(fn(DataSubBlock $block): string => $block->encode(), $this->entity->blocks())),
|
||||
ApplicationExtension::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
}
|
||||
55
vendor/intervention/gif/src/Encoders/PlainTextExtensionEncoder.php
vendored
Normal file
55
vendor/intervention/gif/src/Encoders/PlainTextExtensionEncoder.php
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\PlainTextExtension;
|
||||
|
||||
class PlainTextExtensionEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(PlainTextExtension $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
if (!$this->entity->hasText()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode('', [
|
||||
PlainTextExtension::MARKER,
|
||||
PlainTextExtension::LABEL,
|
||||
$this->encodeHead(),
|
||||
$this->encodeTexts(),
|
||||
PlainTextExtension::TERMINATOR,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode head block.
|
||||
*/
|
||||
private function encodeHead(): string
|
||||
{
|
||||
return "\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode text chunks.
|
||||
*/
|
||||
private function encodeTexts(): string
|
||||
{
|
||||
return implode('', array_map(
|
||||
fn(string $text): string => pack('C', strlen($text)) . $text,
|
||||
$this->entity->text(),
|
||||
));
|
||||
}
|
||||
}
|
||||
30
vendor/intervention/gif/src/Encoders/TableBasedImageEncoder.php
vendored
Normal file
30
vendor/intervention/gif/src/Encoders/TableBasedImageEncoder.php
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\TableBasedImage;
|
||||
|
||||
class TableBasedImageEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(TableBasedImage $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return implode('', [
|
||||
$this->entity->imageDescriptor()->encode(),
|
||||
$this->entity->colorTable() ? $this->entity->colorTable()->encode() : '',
|
||||
$this->entity->imageData()->encode(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
26
vendor/intervention/gif/src/Encoders/TrailerEncoder.php
vendored
Normal file
26
vendor/intervention/gif/src/Encoders/TrailerEncoder.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Encoders;
|
||||
|
||||
use Intervention\Gif\Blocks\Trailer;
|
||||
|
||||
class TrailerEncoder extends AbstractEncoder
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*/
|
||||
public function __construct(Trailer $entity)
|
||||
{
|
||||
parent::__construct($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode current entity.
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return Trailer::MARKER;
|
||||
}
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/ArgumentException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/ArgumentException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class ArgumentException extends LogicException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/CoreException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/CoreException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class CoreException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/DecoderException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/DecoderException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class DecoderException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/EncoderException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/EncoderException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class EncoderException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/FilesystemException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/FilesystemException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class FilesystemException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
12
vendor/intervention/gif/src/Exceptions/GifException.php
vendored
Normal file
12
vendor/intervention/gif/src/Exceptions/GifException.php
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class GifException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/InvalidArgumentException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/InvalidArgumentException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class InvalidArgumentException extends ArgumentException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/LogicException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/LogicException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class LogicException extends GifException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/RuntimeException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/RuntimeException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class RuntimeException extends GifException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/SplitterException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/SplitterException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class SplitterException extends RuntimeException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/StateException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/StateException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class StateException extends LogicException
|
||||
{
|
||||
//
|
||||
}
|
||||
10
vendor/intervention/gif/src/Exceptions/StreamException.php
vendored
Normal file
10
vendor/intervention/gif/src/Exceptions/StreamException.php
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Exceptions;
|
||||
|
||||
class StreamException extends FilesystemException
|
||||
{
|
||||
//
|
||||
}
|
||||
197
vendor/intervention/gif/src/GifDataStream.php
vendored
Normal file
197
vendor/intervention/gif/src/GifDataStream.php
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
use Intervention\Gif\Blocks\ColorTable;
|
||||
use Intervention\Gif\Blocks\CommentExtension;
|
||||
use Intervention\Gif\Blocks\FrameBlock;
|
||||
use Intervention\Gif\Blocks\Header;
|
||||
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
|
||||
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
|
||||
use Intervention\Gif\Blocks\Trailer;
|
||||
|
||||
class GifDataStream extends AbstractEntity
|
||||
{
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @param array<FrameBlock> $frames
|
||||
* @param array<CommentExtension> $comments
|
||||
*/
|
||||
public function __construct(
|
||||
protected Header $header = new Header(),
|
||||
protected LogicalScreenDescriptor $logicalScreenDescriptor = new LogicalScreenDescriptor(),
|
||||
protected ?ColorTable $globalColorTable = null,
|
||||
protected array $frames = [],
|
||||
protected array $comments = []
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header.
|
||||
*/
|
||||
public function header(): Header
|
||||
{
|
||||
return $this->header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header.
|
||||
*/
|
||||
public function setHeader(Header $header): self
|
||||
{
|
||||
$this->header = $header;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logical screen descriptor.
|
||||
*/
|
||||
public function logicalScreenDescriptor(): LogicalScreenDescriptor
|
||||
{
|
||||
return $this->logicalScreenDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set logical screen descriptor.
|
||||
*/
|
||||
public function setLogicalScreenDescriptor(LogicalScreenDescriptor $descriptor): self
|
||||
{
|
||||
$this->logicalScreenDescriptor = $descriptor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return global color table if available else null.
|
||||
*/
|
||||
public function globalColorTable(): ?ColorTable
|
||||
{
|
||||
return $this->globalColorTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global color table.
|
||||
*/
|
||||
public function setGlobalColorTable(ColorTable $table): self
|
||||
{
|
||||
$this->globalColorTable = $table;
|
||||
$this->logicalScreenDescriptor->setGlobalColorTableExistance(true);
|
||||
$this->logicalScreenDescriptor->setGlobalColorTableSize(
|
||||
$table->logicalSize()
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main graphic control extension.
|
||||
*/
|
||||
public function mainApplicationExtension(): ?NetscapeApplicationExtension
|
||||
{
|
||||
foreach ($this->frames as $frame) {
|
||||
$extension = $frame->netscapeExtension();
|
||||
if ($extension !== null) {
|
||||
return $extension;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of frames.
|
||||
*
|
||||
* @return array<FrameBlock>
|
||||
*/
|
||||
public function frames(): array
|
||||
{
|
||||
return $this->frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of "global" comments.
|
||||
*
|
||||
* @return array<CommentExtension>
|
||||
*/
|
||||
public function comments(): array
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return first frame.
|
||||
*/
|
||||
public function firstFrame(): ?FrameBlock
|
||||
{
|
||||
if ($this->frames === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!array_key_exists(0, $this->frames)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->frames[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add frame.
|
||||
*/
|
||||
public function addFrame(FrameBlock $frame): self
|
||||
{
|
||||
$this->frames[] = $frame;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add comment extension.
|
||||
*/
|
||||
public function addComment(CommentExtension $comment): self
|
||||
{
|
||||
$this->comments[] = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current data.
|
||||
*
|
||||
* @param array<FrameBlock> $frames
|
||||
*/
|
||||
public function setFrames(array $frames): self
|
||||
{
|
||||
$this->frames = $frames;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trailer.
|
||||
*/
|
||||
public function trailer(): Trailer
|
||||
{
|
||||
return new Trailer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if gif is animated.
|
||||
*/
|
||||
public function isAnimated(): bool
|
||||
{
|
||||
return count($this->frames()) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if global color table is set.
|
||||
*/
|
||||
public function hasGlobalColorTable(): bool
|
||||
{
|
||||
return !is_null($this->globalColorTable);
|
||||
}
|
||||
}
|
||||
394
vendor/intervention/gif/src/Splitter.php
vendored
Normal file
394
vendor/intervention/gif/src/Splitter.php
vendored
Normal file
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif;
|
||||
|
||||
use ArrayIterator;
|
||||
use GdImage;
|
||||
use Intervention\Gif\Exceptions\CoreException;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
use Intervention\Gif\Exceptions\StreamException;
|
||||
use Intervention\Gif\Exceptions\InvalidArgumentException;
|
||||
use Intervention\Gif\Exceptions\SplitterException;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* @implements IteratorAggregate<GifDataStream|GdImage>
|
||||
*/
|
||||
class Splitter implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Single frames resolved from main GifDataStream.
|
||||
*
|
||||
* @var array<GifDataStream|GdImage>
|
||||
*/
|
||||
protected array $frames = [];
|
||||
|
||||
/**
|
||||
* Delays of each frame resolved from main GifDataStream.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
protected array $delays = [];
|
||||
|
||||
/**
|
||||
* Loop count of main GifDataStream.
|
||||
*/
|
||||
protected int $loops;
|
||||
|
||||
/**
|
||||
* Create new instance.
|
||||
*
|
||||
* @throws SplitterException
|
||||
*/
|
||||
public function __construct(protected GifDataStream $gif)
|
||||
{
|
||||
try {
|
||||
$this->loops = $gif->mainApplicationExtension()?->loops() ?: 0;
|
||||
} catch (DecoderException $e) {
|
||||
throw new SplitterException('Failed to create instance from ' . GifDataStream::class, previous: $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create splitter instance from gif data stream object.
|
||||
*
|
||||
* @throws SplitterException
|
||||
*/
|
||||
public static function create(GifDataStream $stream): self
|
||||
{
|
||||
return new self($stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create splitter instance from raw binary gif image data.
|
||||
*
|
||||
* @throws SplitterException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws StreamException
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public static function decode(mixed $input): self
|
||||
{
|
||||
return new self(Decoder::decode($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over the frames and pass each frame to a closure.
|
||||
*/
|
||||
public function each(callable $callback): self
|
||||
{
|
||||
array_map($callback, $this->frames, $this->delays);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set stream of instance.
|
||||
*/
|
||||
public function setStream(GifDataStream $stream): self
|
||||
{
|
||||
$this->gif = $stream;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build iterator.
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->frames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frames.
|
||||
*
|
||||
* @return array<GifDataStream|GdImage>
|
||||
*/
|
||||
public function frames(): array
|
||||
{
|
||||
return $this->frames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get delays.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public function delays(): array
|
||||
{
|
||||
return $this->delays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get loop count of currently handled gif data.
|
||||
*/
|
||||
public function loops(): int
|
||||
{
|
||||
return $this->loops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split current stream into array of seperate gif data stream objects for each frame.
|
||||
*
|
||||
* @throws SplitterException
|
||||
*/
|
||||
public function split(): self
|
||||
{
|
||||
$this->frames = [];
|
||||
|
||||
foreach ($this->gif->frames() as $frame) {
|
||||
// create separate stream for each frame
|
||||
try {
|
||||
$gif = Builder::canvas(
|
||||
$this->gif->logicalScreenDescriptor()->width(),
|
||||
$this->gif->logicalScreenDescriptor()->height()
|
||||
)->gifDataStream();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
throw new SplitterException('Failed to create separate stream resource for each frame', previous: $e);
|
||||
}
|
||||
|
||||
// check if working stream has global color table
|
||||
$table = $this->gif->globalColorTable();
|
||||
if ($table !== null) {
|
||||
$gif->setGlobalColorTable($table);
|
||||
$gif->logicalScreenDescriptor()->setGlobalColorTableExistance(true);
|
||||
|
||||
$gif->logicalScreenDescriptor()->setGlobalColorTableSorted(
|
||||
$this->gif->logicalScreenDescriptor()->globalColorTableSorted()
|
||||
);
|
||||
|
||||
$gif->logicalScreenDescriptor()->setGlobalColorTableSize(
|
||||
$this->gif->logicalScreenDescriptor()->globalColorTableSize()
|
||||
);
|
||||
|
||||
$gif->logicalScreenDescriptor()->setBackgroundColorIndex(
|
||||
$this->gif->logicalScreenDescriptor()->backgroundColorIndex()
|
||||
);
|
||||
|
||||
$gif->logicalScreenDescriptor()->setPixelAspectRatio(
|
||||
$this->gif->logicalScreenDescriptor()->pixelAspectRatio()
|
||||
);
|
||||
|
||||
$gif->logicalScreenDescriptor()->setBitsPerPixel(
|
||||
$this->gif->logicalScreenDescriptor()->bitsPerPixel()
|
||||
);
|
||||
}
|
||||
|
||||
// copy original frame
|
||||
$gif->addFrame($frame);
|
||||
|
||||
$this->frames[] = $gif;
|
||||
$this->delays[] = match (is_object($frame->graphicControlExtension())) {
|
||||
true => $frame->graphicControlExtension()->delay(),
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform current frames to an a rray of transparency flattened GdImage objects for each frame.
|
||||
*
|
||||
* @throws SplitterException
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function flatten(): self
|
||||
{
|
||||
$frames = $this->unprocessedFramesOrFail();
|
||||
$gdImages = $this->extractFrames();
|
||||
|
||||
// non-animated gif files don't need to be flattened
|
||||
// just replace frames with extracted
|
||||
if (count($frames) === 1) {
|
||||
$this->frames = $gdImages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// get main image size
|
||||
$width = imagesx($gdImages[0]);
|
||||
$height = imagesy($gdImages[0]);
|
||||
$transparent = imagecolortransparent($gdImages[0]);
|
||||
|
||||
foreach ($gdImages as $key => $gdImage) {
|
||||
// get meta data of frame
|
||||
$gif = $frames[$key];
|
||||
$descriptor = $gif->firstFrame()?->imageDescriptor();
|
||||
$offsetX = $descriptor?->left() ?: 0;
|
||||
$offsetY = $descriptor?->top() ?: 0;
|
||||
$w = $descriptor?->width() ?: 0;
|
||||
$h = $descriptor?->height() ?: 0;
|
||||
|
||||
if (in_array($this->disposalMethod($gif), [DisposalMethod::NONE, DisposalMethod::PREVIOUS])) {
|
||||
if ($key >= 1) {
|
||||
// create normalized gd image
|
||||
$canvas = imagecreatetruecolor($width, $height);
|
||||
|
||||
if ($canvas === false) {
|
||||
throw new CoreException('Failed to create new image instance for animation frame #' . $key);
|
||||
}
|
||||
|
||||
if (imagecolortransparent($gdImage) !== -1) {
|
||||
$transparent = imagecolortransparent($gdImage);
|
||||
} else {
|
||||
$transparent = imagecolorallocatealpha($gdImage, 255, 0, 255, 127);
|
||||
}
|
||||
|
||||
if (!is_int($transparent)) {
|
||||
throw new CoreException(
|
||||
'Failed to allocate transparent color in animation frame #' . $key,
|
||||
);
|
||||
}
|
||||
|
||||
// fill with transparent
|
||||
imagefill($canvas, 0, 0, $transparent);
|
||||
imagecolortransparent($canvas, $transparent);
|
||||
imagealphablending($canvas, true);
|
||||
|
||||
// insert last as base
|
||||
imagecopy(
|
||||
$canvas,
|
||||
$gdImages[$key - 1],
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
|
||||
// insert gd image
|
||||
imagecopy(
|
||||
$canvas,
|
||||
$gdImage,
|
||||
$offsetX,
|
||||
$offsetY,
|
||||
0,
|
||||
0,
|
||||
$w,
|
||||
$h
|
||||
);
|
||||
} else {
|
||||
imagealphablending($gdImage, true);
|
||||
$canvas = $gdImage;
|
||||
}
|
||||
} else {
|
||||
// create normalized gd image
|
||||
$canvas = imagecreatetruecolor($width, $height);
|
||||
if ($canvas === false) {
|
||||
throw new CoreException('Failed to create new image instance for animation frame #' . $key);
|
||||
}
|
||||
|
||||
if (imagecolortransparent($gdImage) !== -1) {
|
||||
$transparent = imagecolortransparent($gdImage);
|
||||
} else {
|
||||
$transparent = imagecolorallocatealpha($gdImage, 255, 0, 255, 127);
|
||||
}
|
||||
|
||||
if (!is_int($transparent)) {
|
||||
throw new CoreException('Animation frames cannot be converted into GdImage objects');
|
||||
}
|
||||
|
||||
// fill with transparent
|
||||
imagefill($canvas, 0, 0, $transparent);
|
||||
imagecolortransparent($canvas, $transparent);
|
||||
imagealphablending($canvas, true);
|
||||
|
||||
// insert frame gd image
|
||||
imagecopy(
|
||||
$canvas,
|
||||
$gdImage,
|
||||
$offsetX,
|
||||
$offsetY,
|
||||
0,
|
||||
0,
|
||||
$w,
|
||||
$h
|
||||
);
|
||||
}
|
||||
|
||||
$gdImages[$key] = $canvas;
|
||||
}
|
||||
|
||||
$this->frames = $gdImages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of GdImage objects for each frame.
|
||||
*
|
||||
* @throws CoreException
|
||||
* @throws SplitterException
|
||||
* @return array<GdImage>
|
||||
*/
|
||||
private function extractFrames(): array
|
||||
{
|
||||
$gdImages = [];
|
||||
|
||||
foreach ($this->unprocessedFramesOrFail() as $frame) {
|
||||
try {
|
||||
$gdImage = imagecreatefromstring($frame->encode());
|
||||
} catch (EncoderException) {
|
||||
throw new CoreException('Failed to extract animation frame to GdImage object');
|
||||
}
|
||||
|
||||
if ($gdImage === false) {
|
||||
throw new CoreException('Failed to extract animation frame to GdImage object');
|
||||
}
|
||||
|
||||
imagepalettetotruecolor($gdImage);
|
||||
imagesavealpha($gdImage, true);
|
||||
|
||||
$gdImages[] = $gdImage;
|
||||
}
|
||||
|
||||
return $gdImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return disposal method of given gif data stream.
|
||||
*
|
||||
* @throws SplitterException
|
||||
*/
|
||||
private function disposalMethod(GifDataStream $gif): DisposalMethod
|
||||
{
|
||||
$disposalMethod = $gif->firstFrame()?->graphicControlExtension()?->disposalMethod();
|
||||
|
||||
return $disposalMethod ?: throw new SplitterException('Failed to find disposal method in gif data stream');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of unprocessed frames or throw exception if frames are already processed.
|
||||
*
|
||||
* @throws SplitterException
|
||||
* @return array<GifDataStream>
|
||||
*/
|
||||
private function unprocessedFramesOrFail(): array
|
||||
{
|
||||
if (count($this->frames) === 0) {
|
||||
throw new SplitterException('No frames available. Run ' . $this::class . '::split() first');
|
||||
}
|
||||
|
||||
// if any frame is instanceof GdImage, frame was already flattened
|
||||
$processed = count(array_filter(
|
||||
$this->frames,
|
||||
fn(GdImage|GifDataStream $frame): bool => $frame instanceof GdImage,
|
||||
)) > 0;
|
||||
|
||||
if ($processed) {
|
||||
throw new SplitterException('Frames have already been flattened');
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$this->frames,
|
||||
fn(GifDataStream|GdImage $frame): bool => $frame instanceof GifDataStream,
|
||||
);
|
||||
}
|
||||
}
|
||||
43
vendor/intervention/gif/src/Traits/CanDecode.php
vendored
Normal file
43
vendor/intervention/gif/src/Traits/CanDecode.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Traits;
|
||||
|
||||
use Intervention\Gif\Decoders\AbstractDecoder;
|
||||
use Intervention\Gif\Exceptions\DecoderException;
|
||||
|
||||
trait CanDecode
|
||||
{
|
||||
/**
|
||||
* Decode current instance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
public static function decode(mixed $source, ?int $length = null): mixed
|
||||
{
|
||||
return self::decoder($source, $length)->decode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decoder for current instance.
|
||||
*
|
||||
* @throws DecoderException
|
||||
*/
|
||||
protected static function decoder(mixed $source, ?int $length = null): AbstractDecoder
|
||||
{
|
||||
$classname = sprintf('Intervention\Gif\Decoders\%sDecoder', self::shortClassname());
|
||||
|
||||
if (!class_exists($classname)) {
|
||||
throw new DecoderException('Decoder for "' . static::class . '" not found');
|
||||
}
|
||||
|
||||
$decoder = new $classname($source, $length);
|
||||
|
||||
if (!($decoder instanceof AbstractDecoder)) {
|
||||
throw new DecoderException('Decoder for "' . static::class . '" not found');
|
||||
}
|
||||
|
||||
return $decoder;
|
||||
}
|
||||
}
|
||||
43
vendor/intervention/gif/src/Traits/CanEncode.php
vendored
Normal file
43
vendor/intervention/gif/src/Traits/CanEncode.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Traits;
|
||||
|
||||
use Intervention\Gif\Encoders\AbstractEncoder;
|
||||
use Intervention\Gif\Exceptions\EncoderException;
|
||||
|
||||
trait CanEncode
|
||||
{
|
||||
/**
|
||||
* Encode current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
public function encode(): string
|
||||
{
|
||||
return $this->encoder()->encode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encoder object for current entity.
|
||||
*
|
||||
* @throws EncoderException
|
||||
*/
|
||||
protected function encoder(): AbstractEncoder
|
||||
{
|
||||
$classname = sprintf('Intervention\Gif\Encoders\%sEncoder', self::shortClassname());
|
||||
|
||||
if (!class_exists($classname)) {
|
||||
throw new EncoderException('Encoder for "' . $this::class . '" not found');
|
||||
}
|
||||
|
||||
$encoder = new $classname($this);
|
||||
|
||||
if (!($encoder instanceof AbstractEncoder)) {
|
||||
throw new EncoderException('Encoder for "' . $this::class . '" not found');
|
||||
}
|
||||
|
||||
return $encoder;
|
||||
}
|
||||
}
|
||||
70
vendor/intervention/gif/src/Traits/CanHandleFiles.php
vendored
Normal file
70
vendor/intervention/gif/src/Traits/CanHandleFiles.php
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Gif\Traits;
|
||||
|
||||
use Intervention\Gif\Exceptions\StreamException;
|
||||
|
||||
trait CanHandleFiles
|
||||
{
|
||||
/**
|
||||
* Determines if input is file path.
|
||||
*/
|
||||
private static function isFilePath(mixed $input): bool
|
||||
{
|
||||
return is_string($input) && !self::hasNullBytes($input) && @is_file($input) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if given string contains null bytes.
|
||||
*/
|
||||
private static function hasNullBytes(string $string): bool
|
||||
{
|
||||
return str_contains($string, chr(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create stream resource from given gif image data.
|
||||
*
|
||||
* @throws StreamException
|
||||
*/
|
||||
private static function streamFromData(string $data): mixed
|
||||
{
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
|
||||
if ($stream === false) {
|
||||
throw new StreamException('Failed to create tempory stream resource');
|
||||
}
|
||||
|
||||
$result = fwrite($stream, $data);
|
||||
if ($result === false) {
|
||||
fclose($stream);
|
||||
throw new StreamException('Failed to write tempory stream resource');
|
||||
}
|
||||
|
||||
$result = rewind($stream);
|
||||
if ($result === false) {
|
||||
fclose($stream);
|
||||
throw new StreamException('Failed to rewind tempory stream resource');
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create stream resource from given file path.
|
||||
*
|
||||
* @throws StreamException
|
||||
*/
|
||||
private static function streamFromFilePath(string $path): mixed
|
||||
{
|
||||
$stream = fopen($path, 'rb');
|
||||
|
||||
if ($stream === false) {
|
||||
throw new StreamException('Failed to create stream resource from path');
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
}
|
||||
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
|
||||
{
|
||||
//
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user