mirror of
https://github.com/Intervention/image.git
synced 2025-01-17 20:28:21 +01:00
Merge branch 'next'
This commit is contained in:
commit
4c1938f0d3
7
.gitattributes
vendored
7
.gitattributes
vendored
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
/.github export-ignore
|
/.github export-ignore
|
||||||
/tests export-ignore
|
/tests export-ignore
|
||||||
|
/.github export-ignore
|
||||||
/.gitattributes export-ignore
|
/.gitattributes export-ignore
|
||||||
/.gitignore export-ignore
|
/.gitignore export-ignore
|
||||||
/.travis.yml export-ignore
|
|
||||||
/phpunit.xml export-ignore
|
/phpunit.xml export-ignore
|
||||||
/README.md export-ignore
|
/phpunit.xml.dist export-ignore
|
||||||
|
/readme.md export-ignore
|
||||||
|
/docker-compose.yml export-ignore
|
||||||
|
/Dockerfile export-ignore
|
||||||
|
102
.github/workflows/run-tests.yml
vendored
Normal file
102
.github/workflows/run-tests.yml
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
name: Tests
|
||||||
|
|
||||||
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: [ '8.1', '8.2', '8.3' ]
|
||||||
|
imagemagick: [ '6.9.12-55', '7.1.0-40' ]
|
||||||
|
imagick: [ '3.7.0' ]
|
||||||
|
stability: [ prefer-stable ]
|
||||||
|
|
||||||
|
name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - ImageMagick ${{ matrix.imagemagick }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout project
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: mbstring, gd
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Prepare environment for Imagemagick
|
||||||
|
run: |
|
||||||
|
sudo apt-get -y remove imagemagick imagemagick-6-common libmagic-dev
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libjpeg-dev libgif-dev libtiff-dev libpng-dev libwebp-dev libavif-dev libheif-dev
|
||||||
|
sudo apt-get install -y libmagickwand-dev
|
||||||
|
|
||||||
|
- name: Cache ImageMagick
|
||||||
|
uses: actions/cache@v2
|
||||||
|
id: cache-imagemagick
|
||||||
|
with:
|
||||||
|
path: /home/runner/im/imagemagick-${{ matrix.imagemagick }}
|
||||||
|
key: ${{ runner.os }}-ImageMagick-${{ matrix.imagemagick }}-${{ hashFiles('**/composer.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-ImageMagick-${{ matrix.imagemagick }}-
|
||||||
|
|
||||||
|
- name: Check ImageMagick cache exists
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
id: cache-imagemagick-exists
|
||||||
|
with:
|
||||||
|
files: /home/runner/im/imagemagick-${{ matrix.imagemagick }}
|
||||||
|
|
||||||
|
- name: Install ImageMagick
|
||||||
|
if: ( steps.cache-imagemagick.outputs.cache-hit != 'true' || steps.cache-imagemagick-exists.outputs.files_exists != 'true' )
|
||||||
|
run: |
|
||||||
|
curl -o /tmp/ImageMagick.tar.xz -sL https://imagemagick.org/archive/releases/ImageMagick-${{ matrix.imagemagick }}.tar.xz
|
||||||
|
(
|
||||||
|
cd /tmp || exit 1
|
||||||
|
tar xf ImageMagick.tar.xz
|
||||||
|
cd ImageMagick-${{ matrix.imagemagick }}
|
||||||
|
sudo ./configure --prefix=/home/runner/im/imagemagick-${{ matrix.imagemagick }}
|
||||||
|
sudo make -j$(nproc)
|
||||||
|
sudo make install
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: Install PHP ImageMagick extension
|
||||||
|
run: |
|
||||||
|
curl -o /tmp/imagick.tgz -sL http://pecl.php.net/get/imagick-${{ matrix.imagick }}.tgz
|
||||||
|
(
|
||||||
|
cd /tmp || exit 1
|
||||||
|
tar -xzf imagick.tgz
|
||||||
|
cd imagick-${{ matrix.imagick }}
|
||||||
|
phpize
|
||||||
|
sudo ./configure --with-imagick=/home/runner/im/imagemagick-${{ matrix.imagemagick }}
|
||||||
|
sudo make -j$(nproc)
|
||||||
|
sudo make install
|
||||||
|
)
|
||||||
|
sudo bash -c 'echo "extension=imagick.so" >> /etc/php/${{ matrix.php }}/cli/php.ini'
|
||||||
|
php --ri imagick;
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache composer dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ matrix.stability }}-${{ hashFiles('**/composer.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ matrix.stability }}-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
|
||||||
|
|
||||||
|
- name: Which Imagick Version
|
||||||
|
run: php -r 'var_dump(Imagick::getVersion());'
|
||||||
|
|
||||||
|
- name: Supported Imagick Formats
|
||||||
|
run: php -r 'var_dump(Imagick::queryFormats());'
|
||||||
|
|
||||||
|
- name: Execute tests
|
||||||
|
run: vendor/bin/phpunit --no-coverage
|
||||||
|
|
||||||
|
- name: Run analyzer
|
||||||
|
run: vendor/bin/phpstan analyze --level=4 ./src
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
.DS_Store
|
|
||||||
composer.lock
|
|
||||||
vendor/
|
|
||||||
dev/
|
|
||||||
.idea/
|
.idea/
|
||||||
|
build/
|
||||||
|
vendor/
|
||||||
|
.DS_Store
|
||||||
|
.phpunit.result.cache
|
||||||
|
composer.lock
|
||||||
|
phpunit.xml
|
||||||
|
36
.travis.yml
36
.travis.yml
@ -1,36 +0,0 @@
|
|||||||
language: php
|
|
||||||
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- dist: trusty
|
|
||||||
php: 5.4
|
|
||||||
- dist: trusty
|
|
||||||
php: 5.5
|
|
||||||
- dist: xenial
|
|
||||||
php: 5.6
|
|
||||||
- dist: xenial
|
|
||||||
php: 7.0
|
|
||||||
- dist: xenial
|
|
||||||
php: 7.1
|
|
||||||
- dist: xenial
|
|
||||||
php: 7.2
|
|
||||||
- dist: xenial
|
|
||||||
php: 7.3
|
|
||||||
- dist: xenial
|
|
||||||
php: 7.4
|
|
||||||
- dist: xenial
|
|
||||||
php: nightly
|
|
||||||
- dist: xenial
|
|
||||||
php: hhvm
|
|
||||||
allow_failures:
|
|
||||||
- php: nightly
|
|
||||||
- php: hhvm
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- printf "\n" | pecl install imagick
|
|
||||||
- composer self-update || true
|
|
||||||
- composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader
|
|
||||||
|
|
||||||
script: vendor/bin/phpunit
|
|
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
FROM php:8-cli
|
||||||
|
|
||||||
|
RUN apt update \
|
||||||
|
&& apt install -y \
|
||||||
|
libpng-dev \
|
||||||
|
libicu-dev \
|
||||||
|
libavif-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libzip-dev \
|
||||||
|
zip \
|
||||||
|
zlib1g-dev \
|
||||||
|
locales \
|
||||||
|
locales-all \
|
||||||
|
libmagickwand-dev \
|
||||||
|
libwebp-dev \
|
||||||
|
&& pecl install imagick \
|
||||||
|
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp --with-avif \
|
||||||
|
&& docker-php-ext-enable imagick \
|
||||||
|
&& docker-php-ext-install \
|
||||||
|
intl \
|
||||||
|
opcache \
|
||||||
|
pdo \
|
||||||
|
pdo_pgsql \
|
||||||
|
pdo_mysql \
|
||||||
|
pgsql \
|
||||||
|
fileinfo \
|
||||||
|
mysqli \
|
||||||
|
gd \
|
||||||
|
bcmath \
|
||||||
|
exif \
|
||||||
|
zip \
|
||||||
|
&& apt-get clean
|
||||||
|
|
||||||
|
# install composer
|
||||||
|
#
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
4
LICENSE
4
LICENSE
@ -1,9 +1,9 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Oliver Vogel
|
Copyright (c) 2023 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:
|
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 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.
|
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.
|
||||||
|
55
README.md
55
README.md
@ -1,55 +0,0 @@
|
|||||||
# Intervention Image
|
|
||||||
|
|
||||||
Intervention Image is a **PHP image handling and manipulation** library providing an easier and expressive way to create, edit, and compose images. The package includes ServiceProviders and Facades for easy **Laravel** integration.
|
|
||||||
|
|
||||||
[![Latest Version](https://img.shields.io/packagist/v/intervention/image.svg)](https://packagist.org/packages/intervention/image)
|
|
||||||
[![Build Status](https://travis-ci.org/Intervention/image.png?branch=master)](https://travis-ci.org/Intervention/image)
|
|
||||||
[![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/image.svg)](https://packagist.org/packages/intervention/image/stats)
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- PHP >=5.4
|
|
||||||
- Fileinfo Extension
|
|
||||||
|
|
||||||
## Supported Image Libraries
|
|
||||||
|
|
||||||
- GD Library (>=2.0)
|
|
||||||
- Imagick PHP extension (>=6.5.7)
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
- [Installation](https://image.intervention.io/v2/introduction/installation)
|
|
||||||
- [Laravel Framework Integration](https://image.intervention.io/v2/introduction/installation#integration-in-laravel)
|
|
||||||
- [Basic Usage](https://image.intervention.io/v2/usage/overview#basic-usage)
|
|
||||||
|
|
||||||
## Code Examples
|
|
||||||
|
|
||||||
```php
|
|
||||||
// open an image file
|
|
||||||
$img = Image::make('public/foo.jpg');
|
|
||||||
|
|
||||||
// resize image instance
|
|
||||||
$img->resize(320, 240);
|
|
||||||
|
|
||||||
// insert a watermark
|
|
||||||
$img->insert('public/watermark.png');
|
|
||||||
|
|
||||||
// save image in desired format
|
|
||||||
$img->save('public/bar.jpg');
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to the [official documentation](http://image.intervention.io/) to learn more about Intervention Image.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions to the Intervention Image library are welcome. Please note the following guidelines before submitting your pull request.
|
|
||||||
|
|
||||||
- Follow [PSR-2](http://www.php-fig.org/psr/psr-2/) coding standards.
|
|
||||||
- Write tests for new functions and added features
|
|
||||||
- API calls should work consistently with both GD and Imagick drivers
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Intervention Image is licensed under the [MIT License](http://opensource.org/licenses/MIT).
|
|
||||||
|
|
||||||
Copyright 2017 [Oliver Vogel](http://olivervogel.com/)
|
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "intervention/image",
|
"name": "intervention/image",
|
||||||
"description": "Image handling and manipulation library with support for Laravel integration",
|
"description": "PHP image manipulation",
|
||||||
"homepage": "http://image.intervention.io/",
|
"homepage": "https://image.intervention.io/",
|
||||||
"keywords": ["image", "gd", "imagick", "laravel", "watermark", "thumbnail"],
|
"keywords": ["image", "gd", "imagick", "watermark", "thumbnail", "resize"],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
@ -12,36 +12,22 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4.0",
|
"php": "^8.1",
|
||||||
"ext-fileinfo": "*",
|
"intervention/gif": "^3"
|
||||||
"guzzlehttp/psr7": "~1.1 || ^2.0"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15",
|
"phpunit/phpunit": "^9",
|
||||||
"mockery/mockery": "~0.9.2"
|
"mockery/mockery": "^1.6",
|
||||||
},
|
"phpstan/phpstan": "^1"
|
||||||
"suggest": {
|
|
||||||
"ext-gd": "to use GD library based image processing.",
|
|
||||||
"ext-imagick": "to use Imagick based image processing.",
|
|
||||||
"intervention/imagecache": "Caching extension for the Intervention Image library"
|
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Intervention\\Image\\": "src/Intervention/Image"
|
"Intervention\\Image\\": "src"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extra": {
|
"autoload-dev": {
|
||||||
"branch-alias": {
|
"psr-4": {
|
||||||
"dev-master": "2.4-dev"
|
"Intervention\\Image\\Tests\\": "tests"
|
||||||
},
|
|
||||||
"laravel": {
|
|
||||||
"providers": [
|
|
||||||
"Intervention\\Image\\ImageServiceProvider"
|
|
||||||
],
|
|
||||||
"aliases": {
|
|
||||||
"Image": "Intervention\\Image\\Facades\\Image"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"minimum-stability": "stable"
|
|
||||||
}
|
}
|
||||||
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
tests:
|
||||||
|
build: ./
|
||||||
|
working_dir: /project
|
||||||
|
command: bash -c "composer install && ./vendor/bin/phpunit -vvv"
|
||||||
|
volumes:
|
||||||
|
- ./:/project
|
||||||
|
analysis:
|
||||||
|
build: ./
|
||||||
|
working_dir: /project
|
||||||
|
command: bash -c "composer install && ./vendor/bin/phpstan analyze --level=4 ./src"
|
||||||
|
volumes:
|
||||||
|
- ./:/project
|
24
phpunit.xml
24
phpunit.xml
@ -1,18 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit backupGlobals="false"
|
<phpunit backupGlobals="false"
|
||||||
backupStaticAttributes="false"
|
backupStaticAttributes="false"
|
||||||
bootstrap="vendor/autoload.php"
|
bootstrap="vendor/autoload.php"
|
||||||
colors="true"
|
colors="true"
|
||||||
convertErrorsToExceptions="true"
|
convertErrorsToExceptions="true"
|
||||||
convertNoticesToExceptions="true"
|
convertNoticesToExceptions="true"
|
||||||
convertWarningsToExceptions="true"
|
convertWarningsToExceptions="true"
|
||||||
processIsolation="false"
|
processIsolation="false"
|
||||||
stopOnFailure="false"
|
stopOnFailure="false"
|
||||||
syntaxCheck="false"
|
beStrictAboutTestsThatDoNotTestAnything="false"
|
||||||
>
|
>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="Package Test Suite">
|
<testsuite name="Package Test Suite">
|
||||||
<directory suffix=".php">./tests/</directory>
|
<directory suffix=".php">./tests/</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage processUncoveredFiles="true">
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
</phpunit>
|
</phpunit>
|
||||||
|
23
phpunit.xml.dist
Normal file
23
phpunit.xml.dist
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit backupGlobals="false"
|
||||||
|
backupStaticAttributes="false"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
convertErrorsToExceptions="true"
|
||||||
|
convertNoticesToExceptions="true"
|
||||||
|
convertWarningsToExceptions="true"
|
||||||
|
processIsolation="false"
|
||||||
|
stopOnFailure="false"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Package Test Suite">
|
||||||
|
<directory suffix=".php">./tests/</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
|
||||||
|
<coverage processUncoveredFiles="true">
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">src</directory>
|
||||||
|
</include>
|
||||||
|
</coverage>
|
||||||
|
</phpunit>
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"providers": [
|
|
||||||
"Intervention\\Image\\ImageServiceProvider"
|
|
||||||
],
|
|
||||||
"aliases": [
|
|
||||||
{
|
|
||||||
"alias": "Image",
|
|
||||||
"facade": "Intervention\\Image\\Facades\\Image"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
86
readme.md
Normal file
86
readme.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Intervention Image
|
||||||
|
## PHP Image Manipulation
|
||||||
|
|
||||||
|
[![Latest Version](https://img.shields.io/packagist/v/intervention/image.svg)](https://packagist.org/packages/intervention/image)
|
||||||
|
[![Build Status](https://github.com/Intervention/image/actions/workflows/run-tests.yml/badge.svg)](https://github.com/Intervention/image/actions)
|
||||||
|
[![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/image.svg)](https://packagist.org/packages/intervention/image/stats)
|
||||||
|
|
||||||
|
Intervention Image is a **image handling and manipulation library written in
|
||||||
|
PHP** providing an easy and expressive way to create, edit, and compose
|
||||||
|
images. GD library or Imagick can be selected as the base layer for all
|
||||||
|
operations.
|
||||||
|
|
||||||
|
- Simple interface for common image manipulation tasks
|
||||||
|
- Interchangable driver architecture
|
||||||
|
- Support for animated images
|
||||||
|
- Framework-agnostic
|
||||||
|
- PSR-12 compliant
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install this package easily with [Composer](https://getcomposer.org/). Just require the package with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require intervention/image
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Learn the [basics](https://image.intervention.io/v3/basics/instantiation/) on
|
||||||
|
how to use Intervention Image and more with the [official
|
||||||
|
documentation](https://image.intervention.io/v3/).
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
```php
|
||||||
|
// create image manager with desired driver
|
||||||
|
$manager = new ImageManager(
|
||||||
|
new Intervention\Image\Drivers\Gd\Driver()
|
||||||
|
);
|
||||||
|
|
||||||
|
// open an image file
|
||||||
|
$image = $manager->read('images/example.gif');
|
||||||
|
|
||||||
|
// resize image instance
|
||||||
|
$image->resize(height: 300);
|
||||||
|
|
||||||
|
// insert a watermark
|
||||||
|
$image->place('images/watermark.png');
|
||||||
|
|
||||||
|
// encode edited image
|
||||||
|
$encoded = $image->toJpg();
|
||||||
|
|
||||||
|
// save encoded image
|
||||||
|
$encoded->save('images/example.jpg');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- PHP >= 8.1
|
||||||
|
|
||||||
|
## Supported Image Libraries
|
||||||
|
|
||||||
|
- GD Library
|
||||||
|
- Imagick PHP extension
|
||||||
|
|
||||||
|
## Development & Testing
|
||||||
|
|
||||||
|
With this package comes a Docker image to build a test suite and analysis
|
||||||
|
container. 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Intervention Image is licensed under the [MIT License](http://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
Copyright 2020 [Oliver Vogel](http://intervention.io/)
|
19
src/Analyzers/AbstractAnalyzer.php
Normal file
19
src/Analyzers/AbstractAnalyzer.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\AnalyzerInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
abstract class AbstractAnalyzer implements AnalyzerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see AnalyzerInterface::analyze()
|
||||||
|
*/
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return $image->analyze($this);
|
||||||
|
}
|
||||||
|
}
|
7
src/Analyzers/ColorspaceAnalyzer.php
Normal file
7
src/Analyzers/ColorspaceAnalyzer.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class ColorspaceAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
}
|
7
src/Analyzers/HeightAnalyzer.php
Normal file
7
src/Analyzers/HeightAnalyzer.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class HeightAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
}
|
13
src/Analyzers/PixelColorAnalyzer.php
Normal file
13
src/Analyzers/PixelColorAnalyzer.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class PixelColorAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $x,
|
||||||
|
public int $y,
|
||||||
|
public int $frame_key = 0
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
12
src/Analyzers/PixelColorsAnalyzer.php
Normal file
12
src/Analyzers/PixelColorsAnalyzer.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class PixelColorsAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $x,
|
||||||
|
public int $y
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
7
src/Analyzers/ProfileAnalyzer.php
Normal file
7
src/Analyzers/ProfileAnalyzer.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class ProfileAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
}
|
7
src/Analyzers/ResolutionAnalyzer.php
Normal file
7
src/Analyzers/ResolutionAnalyzer.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class ResolutionAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
}
|
7
src/Analyzers/WidthAnalyzer.php
Normal file
7
src/Analyzers/WidthAnalyzer.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Analyzers;
|
||||||
|
|
||||||
|
class WidthAnalyzer extends AbstractAnalyzer
|
||||||
|
{
|
||||||
|
}
|
194
src/Collection.php
Normal file
194
src/Collection.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\RuntimeException;
|
||||||
|
use Intervention\Image\Interfaces\CollectionInterface;
|
||||||
|
use ArrayIterator;
|
||||||
|
use Countable;
|
||||||
|
use Traversable;
|
||||||
|
use IteratorAggregate;
|
||||||
|
|
||||||
|
class Collection implements CollectionInterface, IteratorAggregate, Countable
|
||||||
|
{
|
||||||
|
public function __construct(protected array $items = [])
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static constructor
|
||||||
|
*
|
||||||
|
* @param array $items
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function create(array $items = []): self
|
||||||
|
{
|
||||||
|
return new self($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(int|string $key): bool
|
||||||
|
{
|
||||||
|
return array_key_exists($key, $this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Iterator
|
||||||
|
*
|
||||||
|
* @return Traversable
|
||||||
|
*/
|
||||||
|
public function getIterator(): Traversable
|
||||||
|
{
|
||||||
|
return new ArrayIterator($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count items in collection
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return count($this->items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append new item to collection
|
||||||
|
*
|
||||||
|
* @param mixed $item
|
||||||
|
* @return CollectionInterface
|
||||||
|
*/
|
||||||
|
public function push($item): CollectionInterface
|
||||||
|
{
|
||||||
|
$this->items[] = $item;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return first item in collection
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function first(): mixed
|
||||||
|
{
|
||||||
|
if ($item = reset($this->items)) {
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns last item in collection
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function last(): mixed
|
||||||
|
{
|
||||||
|
if ($item = end($this->items)) {
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return item at given position starting at 0
|
||||||
|
*
|
||||||
|
* @param integer $key
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getAtPosition(int $key = 0, $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];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(int|string $query, $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) && strpos($query, '.') === false) {
|
||||||
|
return array_key_exists($query, $this->items) ? $this->items[$query] : $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = explode('.', $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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function map(callable $callback): self
|
||||||
|
{
|
||||||
|
$items = array_map(function ($item) use ($callback) {
|
||||||
|
return $callback($item);
|
||||||
|
}, $this->items);
|
||||||
|
|
||||||
|
return new self($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run callback on each item of the collection an remove it if it does not return true
|
||||||
|
*
|
||||||
|
* @param callable $callback
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function filter(callable $callback): self
|
||||||
|
{
|
||||||
|
$items = array_filter($this->items, function ($item) use ($callback) {
|
||||||
|
return $callback($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new self($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushEach(array $data, ?callable $callback = null): CollectionInterface
|
||||||
|
{
|
||||||
|
if (! is_iterable($data)) {
|
||||||
|
throw new RuntimeException('Unable to iterate given data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data as $item) {
|
||||||
|
$this->push(is_callable($callback) ? $callback($item) : $item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function empty(): CollectionInterface
|
||||||
|
{
|
||||||
|
$this->items = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
78
src/Colors/AbstractColor.php
Normal file
78
src/Colors/AbstractColor.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\ColorException;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
abstract class AbstractColor implements ColorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Color channels
|
||||||
|
*/
|
||||||
|
protected array $channels;
|
||||||
|
|
||||||
|
public function channels(): array
|
||||||
|
{
|
||||||
|
return $this->channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function channel(string $classname): ColorChannelInterface
|
||||||
|
{
|
||||||
|
$channels = array_filter($this->channels(), function (ColorChannelInterface $channel) use ($classname) {
|
||||||
|
return get_class($channel) == $classname;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (count($channels) == 0) {
|
||||||
|
throw new ColorException('Channel ' . $classname . ' could not be found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return reset($channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function normalize(): array
|
||||||
|
{
|
||||||
|
return array_map(function (ColorChannelInterface $channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $this->channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::toArray()
|
||||||
|
*/
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return array_map(function (ColorChannelInterface $channel) {
|
||||||
|
return $channel->value();
|
||||||
|
}, $this->channels());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::convertTo()
|
||||||
|
*/
|
||||||
|
public function convertTo(string|ColorspaceInterface $colorspace): ColorInterface
|
||||||
|
{
|
||||||
|
$colorspace = match (true) {
|
||||||
|
is_object($colorspace) => $colorspace,
|
||||||
|
default => new $colorspace(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return $colorspace->importColor($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::__toString()
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->toString();
|
||||||
|
}
|
||||||
|
}
|
91
src/Colors/AbstractColorChannel.php
Normal file
91
src/Colors/AbstractColorChannel.php
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\ColorException;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
|
||||||
|
abstract class AbstractColorChannel implements ColorChannelInterface
|
||||||
|
{
|
||||||
|
protected int $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::__construct()
|
||||||
|
*/
|
||||||
|
public function __construct(int $value = null, float $normalized = null)
|
||||||
|
{
|
||||||
|
$this->value = $this->validate(
|
||||||
|
match (true) {
|
||||||
|
is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())),
|
||||||
|
is_numeric($value) && is_null($normalized) => $value,
|
||||||
|
default => throw new ColorException('Color channels must either have a value or a normalized value')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of value()
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function toInt(): int
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::value()
|
||||||
|
*/
|
||||||
|
public function value(): int
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::normalize()
|
||||||
|
*/
|
||||||
|
public function normalize($precision = 32): float
|
||||||
|
{
|
||||||
|
return round(($this->value() - $this->min()) / ($this->max() - $this->min()), $precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::validate()
|
||||||
|
*/
|
||||||
|
public function validate(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if ($value < $this->min() || $value > $this->max()) {
|
||||||
|
throw new ColorException('Color channel 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();
|
||||||
|
}
|
||||||
|
}
|
18
src/Colors/Cmyk/Channels/Cyan.php
Normal file
18
src/Colors/Cmyk/Channels/Cyan.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Cyan extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
8
src/Colors/Cmyk/Channels/Key.php
Normal file
8
src/Colors/Cmyk/Channels/Key.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||||
|
|
||||||
|
class Key extends Cyan
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
8
src/Colors/Cmyk/Channels/Magenta.php
Normal file
8
src/Colors/Cmyk/Channels/Magenta.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||||
|
|
||||||
|
class Magenta extends Cyan
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
8
src/Colors/Cmyk/Channels/Yellow.php
Normal file
8
src/Colors/Cmyk/Channels/Yellow.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk\Channels;
|
||||||
|
|
||||||
|
class Yellow extends Cyan
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
86
src/Colors/Cmyk/Color.php
Normal file
86
src/Colors/Cmyk/Color.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColor;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Channels\Cyan;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Channels\Magenta;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Channels\Yellow;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Channels\Key;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Drivers\AbstractInputHandler;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Color extends AbstractColor
|
||||||
|
{
|
||||||
|
public function __construct(int $c, int $m, int $y, int $k)
|
||||||
|
{
|
||||||
|
$this->channels = [
|
||||||
|
new Cyan($c),
|
||||||
|
new Magenta($m),
|
||||||
|
new Yellow($y),
|
||||||
|
new Key($k),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create(mixed $input): ColorInterface
|
||||||
|
{
|
||||||
|
return (new class ([
|
||||||
|
Decoders\StringColorDecoder::class,
|
||||||
|
]) extends AbstractInputHandler
|
||||||
|
{
|
||||||
|
})->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorspace(): ColorspaceInterface
|
||||||
|
{
|
||||||
|
return new Colorspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toHex(string $prefix = ''): string
|
||||||
|
{
|
||||||
|
return $this->convertTo(RgbColorspace::class)->toHex($prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cyan(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Cyan::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function magenta(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Magenta::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function yellow(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Yellow::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Key::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'cmyk(%d%%, %d%%, %d%%, %d%%)',
|
||||||
|
$this->cyan()->value(),
|
||||||
|
$this->magenta()->value(),
|
||||||
|
$this->yellow()->value(),
|
||||||
|
$this->key()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isGreyscale(): bool
|
||||||
|
{
|
||||||
|
return 0 === array_sum([
|
||||||
|
$this->cyan()->value(),
|
||||||
|
$this->magenta()->value(),
|
||||||
|
$this->yellow()->value(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
64
src/Colors/Cmyk/Colorspace.php
Normal file
64
src/Colors/Cmyk/Colorspace.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||||
|
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||||
|
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Colorspace implements ColorspaceInterface
|
||||||
|
{
|
||||||
|
public static $channels = [
|
||||||
|
Channels\Cyan::class,
|
||||||
|
Channels\Magenta::class,
|
||||||
|
Channels\Yellow::class,
|
||||||
|
Channels\Key::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::createColor()
|
||||||
|
*/
|
||||||
|
public function colorFromNormalized(array $normalized): ColorInterface
|
||||||
|
{
|
||||||
|
$values = array_map(function ($classname, $value_normalized) {
|
||||||
|
return (new $classname(normalized: $value_normalized))->value();
|
||||||
|
}, self::$channels, $normalized);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::importColor()
|
||||||
|
*/
|
||||||
|
public function importColor(ColorInterface $color): ColorInterface
|
||||||
|
{
|
||||||
|
return match (get_class($color)) {
|
||||||
|
RgbColor::class => $this->importRgbColor($color),
|
||||||
|
HsvColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
|
||||||
|
HslColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
|
||||||
|
default => $color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importRgbColor(RgbColor $color): CmykColor
|
||||||
|
{
|
||||||
|
$c = (255 - $color->red()->value()) / 255.0 * 100;
|
||||||
|
$m = (255 - $color->green()->value()) / 255.0 * 100;
|
||||||
|
$y = (255 - $color->blue()->value()) / 255.0 * 100;
|
||||||
|
$k = intval(round(min([$c, $m, $y])));
|
||||||
|
|
||||||
|
$c = intval(round($c - $k));
|
||||||
|
$m = intval(round($m - $k));
|
||||||
|
$y = intval(round($y - $k));
|
||||||
|
|
||||||
|
return new CmykColor($c, $m, $y, $k);
|
||||||
|
}
|
||||||
|
}
|
37
src/Colors/Cmyk/Decoders/StringColorDecoder.php
Normal file
37
src/Colors/Cmyk/Decoders/StringColorDecoder.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Cmyk\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Cmyk\Color;
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decode CMYK color strings
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/^cmyk\((?P<c>[0-9\.]+%?), ?(?P<m>[0-9\.]+%?), ?(?P<y>[0-9\.]+%?), ?(?P<k>[0-9\.]+%?)\)$/i';
|
||||||
|
if (preg_match($pattern, $input, $matches) != 1) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = array_map(function ($value) {
|
||||||
|
return intval(round(floatval(trim(str_replace('%', '', $value)))));
|
||||||
|
}, [$matches['c'], $matches['m'], $matches['y'], $matches['k']]);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsl/Channels/Hue.php
Normal file
28
src/Colors/Hsl/Channels/Hue.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Hue extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 360;
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsl/Channels/Luminance.php
Normal file
28
src/Colors/Hsl/Channels/Luminance.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Luminance extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsl/Channels/Saturation.php
Normal file
28
src/Colors/Hsl/Channels/Saturation.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Saturation extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
104
src/Colors/Hsl/Color.php
Normal file
104
src/Colors/Hsl/Color.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColor;
|
||||||
|
use Intervention\Image\Colors\Hsl\Channels\Hue;
|
||||||
|
use Intervention\Image\Colors\Hsl\Channels\Luminance;
|
||||||
|
use Intervention\Image\Colors\Hsl\Channels\Saturation;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Drivers\AbstractInputHandler;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Color extends AbstractColor
|
||||||
|
{
|
||||||
|
public function __construct(int $h, int $s, int $l)
|
||||||
|
{
|
||||||
|
$this->channels = [
|
||||||
|
new Hue($h),
|
||||||
|
new Saturation($s),
|
||||||
|
new Luminance($l),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorspace(): ColorspaceInterface
|
||||||
|
{
|
||||||
|
return new Colorspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::create()
|
||||||
|
*/
|
||||||
|
public static function create(mixed $input): ColorInterface
|
||||||
|
{
|
||||||
|
return (new class ([
|
||||||
|
Decoders\StringColorDecoder::class,
|
||||||
|
]) extends AbstractInputHandler
|
||||||
|
{
|
||||||
|
})->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Hue channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function hue(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Hue::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Saturation channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function saturation(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Saturation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Luminance channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function luminance(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Luminance::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toHex(string $prefix = ''): string
|
||||||
|
{
|
||||||
|
return $this->convertTo(RgbColorspace::class)->toHex($prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::toString()
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'hsl(%d, %d%%, %d%%)',
|
||||||
|
$this->hue()->value(),
|
||||||
|
$this->saturation()->value(),
|
||||||
|
$this->luminance()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::isGreyscale()
|
||||||
|
*/
|
||||||
|
public function isGreyscale(): bool
|
||||||
|
{
|
||||||
|
return $this->saturation()->value() == 0;
|
||||||
|
}
|
||||||
|
}
|
109
src/Colors/Hsl/Colorspace.php
Normal file
109
src/Colors/Hsl/Colorspace.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||||
|
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||||
|
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Colorspace implements ColorspaceInterface
|
||||||
|
{
|
||||||
|
public static $channels = [
|
||||||
|
Channels\Hue::class,
|
||||||
|
Channels\Saturation::class,
|
||||||
|
Channels\Luminance::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::colorFromNormalized()
|
||||||
|
*/
|
||||||
|
public function colorFromNormalized(array $normalized): ColorInterface
|
||||||
|
{
|
||||||
|
$values = array_map(function ($classname, $value_normalized) {
|
||||||
|
return (new $classname(normalized: $value_normalized))->value();
|
||||||
|
}, self::$channels, $normalized);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importColor(ColorInterface $color): ColorInterface
|
||||||
|
{
|
||||||
|
return match (get_class($color)) {
|
||||||
|
CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
|
||||||
|
RgbColor::class => $this->importRgbColor($color),
|
||||||
|
HsvColor::class => $this->importHsvColor($color),
|
||||||
|
default => $color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importRgbColor(RgbColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
// normalized values of rgb channels
|
||||||
|
$values = array_map(function ($channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $color->channels());
|
||||||
|
|
||||||
|
// take only RGB
|
||||||
|
$values = array_slice($values, 0, 3);
|
||||||
|
|
||||||
|
// calculate Luminance
|
||||||
|
$min = min(...$values);
|
||||||
|
$max = max(...$values);
|
||||||
|
$luminance = ($max + $min) / 2;
|
||||||
|
$delta = $max - $min;
|
||||||
|
|
||||||
|
// calculate saturation
|
||||||
|
$saturation = match (true) {
|
||||||
|
$delta == 0 => 0,
|
||||||
|
default => $delta / (1 - abs(2 * $luminance - 1)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// calculate hue
|
||||||
|
list($r, $g, $b) = $values;
|
||||||
|
$hue = match (true) {
|
||||||
|
($delta == 0) => 0,
|
||||||
|
($max == $r) => 60 * fmod((($g - $b) / $delta), 6),
|
||||||
|
($max == $g) => 60 * ((($b - $r) / $delta) + 2),
|
||||||
|
($max == $b) => 60 * ((($r - $g) / $delta) + 4),
|
||||||
|
default => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
$hue = ($hue + 360) % 360; // normalize hue
|
||||||
|
|
||||||
|
return new Color(
|
||||||
|
intval(round($hue)),
|
||||||
|
intval(round($saturation * 100)),
|
||||||
|
intval(round($luminance * 100)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importHsvColor(HsvColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
// normalized values of hsv channels
|
||||||
|
list($h, $s, $v) = array_map(function ($channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $color->channels());
|
||||||
|
|
||||||
|
// calculate Luminance
|
||||||
|
$luminance = (2 - $s) * $v / 2;
|
||||||
|
|
||||||
|
// calculate Saturation
|
||||||
|
$saturation = match (true) {
|
||||||
|
$luminance == 0 => $s,
|
||||||
|
$luminance == 1 => 0,
|
||||||
|
$luminance < .5 => $s * $v / ($luminance * 2),
|
||||||
|
default => $s * $v / (2 - $luminance * 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Color(
|
||||||
|
intval(round($h * 360)),
|
||||||
|
intval(round($saturation * 100)),
|
||||||
|
intval(round($luminance * 100)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
40
src/Colors/Hsl/Decoders/StringColorDecoder.php
Normal file
40
src/Colors/Hsl/Decoders/StringColorDecoder.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsl\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Hsl\Color;
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decode hsl color strings
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/^hsl\((?P<h>[0-9\.]+), ?(?P<s>[0-9\.]+%?), ?(?P<l>[0-9\.]+%?)?\)$/i';
|
||||||
|
if (preg_match($pattern, $input, $matches) != 1) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = array_map(function ($value) {
|
||||||
|
return match (strpos($value, '%')) {
|
||||||
|
false => intval(trim($value)),
|
||||||
|
default => intval(trim(str_replace('%', '', $value))),
|
||||||
|
};
|
||||||
|
}, [$matches['h'], $matches['s'], $matches['l']]);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsv/Channels/Hue.php
Normal file
28
src/Colors/Hsv/Channels/Hue.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Hue extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 360;
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsv/Channels/Saturation.php
Normal file
28
src/Colors/Hsv/Channels/Saturation.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Saturation extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
28
src/Colors/Hsv/Channels/Value.php
Normal file
28
src/Colors/Hsv/Channels/Value.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Value extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
}
|
104
src/Colors/Hsv/Color.php
Normal file
104
src/Colors/Hsv/Color.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColor;
|
||||||
|
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\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Drivers\AbstractInputHandler;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Color extends AbstractColor
|
||||||
|
{
|
||||||
|
public function __construct(int $h, int $s, int $v)
|
||||||
|
{
|
||||||
|
$this->channels = [
|
||||||
|
new Hue($h),
|
||||||
|
new Saturation($s),
|
||||||
|
new Value($v),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorspace(): ColorspaceInterface
|
||||||
|
{
|
||||||
|
return new Colorspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::create()
|
||||||
|
*/
|
||||||
|
public static function create(mixed $input): ColorInterface
|
||||||
|
{
|
||||||
|
return (new class ([
|
||||||
|
Decoders\StringColorDecoder::class,
|
||||||
|
]) extends AbstractInputHandler
|
||||||
|
{
|
||||||
|
})->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Hue channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function hue(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Hue::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Saturation channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function saturation(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Saturation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Value channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function value(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Value::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toHex(string $prefix = ''): string
|
||||||
|
{
|
||||||
|
return $this->convertTo(RgbColorspace::class)->toHex($prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::toString()
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'hsv(%d, %d%%, %d%%)',
|
||||||
|
$this->hue()->value(),
|
||||||
|
$this->saturation()->value(),
|
||||||
|
$this->value()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::isGreyscale()
|
||||||
|
*/
|
||||||
|
public function isGreyscale(): bool
|
||||||
|
{
|
||||||
|
return $this->saturation()->value() == 0;
|
||||||
|
}
|
||||||
|
}
|
102
src/Colors/Hsv/Colorspace.php
Normal file
102
src/Colors/Hsv/Colorspace.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||||
|
use Intervention\Image\Colors\Rgb\Color as RgbColor;
|
||||||
|
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Colorspace implements ColorspaceInterface
|
||||||
|
{
|
||||||
|
public static $channels = [
|
||||||
|
Channels\Hue::class,
|
||||||
|
Channels\Saturation::class,
|
||||||
|
Channels\Value::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::colorFromNormalized()
|
||||||
|
*/
|
||||||
|
public function colorFromNormalized(array $normalized): ColorInterface
|
||||||
|
{
|
||||||
|
$values = array_map(function ($classname, $value_normalized) {
|
||||||
|
return (new $classname(normalized: $value_normalized))->value();
|
||||||
|
}, self::$channels, $normalized);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::importColor()
|
||||||
|
*/
|
||||||
|
public function importColor(ColorInterface $color): ColorInterface
|
||||||
|
{
|
||||||
|
return match (get_class($color)) {
|
||||||
|
CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
|
||||||
|
RgbColor::class => $this->importRgbColor($color),
|
||||||
|
HslColor::class => $this->importHslColor($color),
|
||||||
|
default => $color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importRgbColor(RgbColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
// normalized values of rgb channels
|
||||||
|
$values = array_map(function ($channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $color->channels());
|
||||||
|
|
||||||
|
// take only RGB
|
||||||
|
$values = array_slice($values, 0, 3);
|
||||||
|
|
||||||
|
// calculate chroma
|
||||||
|
$min = min(...$values);
|
||||||
|
$max = max(...$values);
|
||||||
|
$chroma = $max - $min;
|
||||||
|
|
||||||
|
// calculate value
|
||||||
|
$v = 100 * $max;
|
||||||
|
|
||||||
|
if ($chroma == 0) {
|
||||||
|
// greyscale color
|
||||||
|
return new Color(0, 0, intval(round($v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate saturation
|
||||||
|
$s = 100 * ($chroma / $max);
|
||||||
|
|
||||||
|
// calculate hue
|
||||||
|
list($r, $g, $b) = $values;
|
||||||
|
$h = match (true) {
|
||||||
|
($r == $min) => 3 - (($g - $b) / $chroma),
|
||||||
|
($b == $min) => 1 - (($r - $g) / $chroma),
|
||||||
|
default => 5 - (($b - $r) / $chroma),
|
||||||
|
} * 60;
|
||||||
|
|
||||||
|
return new Color(
|
||||||
|
intval(round($h)),
|
||||||
|
intval(round($s)),
|
||||||
|
intval(round($v))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importHslColor(HslColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
// normalized values of hsl channels
|
||||||
|
list($h, $s, $l) = array_map(function ($channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $color->channels());
|
||||||
|
|
||||||
|
$v = $l + $s * min($l, 1 - $l);
|
||||||
|
$s = ($v == 0) ? 0 : 2 * (1 - $l / $v);
|
||||||
|
|
||||||
|
return $this->colorFromNormalized([$h, $s, $v]);
|
||||||
|
}
|
||||||
|
}
|
40
src/Colors/Hsv/Decoders/StringColorDecoder.php
Normal file
40
src/Colors/Hsv/Decoders/StringColorDecoder.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Hsv\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Hsv\Color;
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decode hsv/hsb color strings
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/^hs(v|b)\((?P<h>[0-9\.]+), ?(?P<s>[0-9\.]+%?), ?(?P<v>[0-9\.]+%?)?\)$/i';
|
||||||
|
if (preg_match($pattern, $input, $matches) != 1) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = array_map(function ($value) {
|
||||||
|
return match (strpos($value, '%')) {
|
||||||
|
false => intval(trim($value)),
|
||||||
|
default => intval(trim(str_replace('%', '', $value))),
|
||||||
|
};
|
||||||
|
}, [$matches['h'], $matches['s'], $matches['v']]);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
}
|
10
src/Colors/Profile.php
Normal file
10
src/Colors/Profile.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors;
|
||||||
|
|
||||||
|
use Intervention\Image\File;
|
||||||
|
use Intervention\Image\Interfaces\ProfileInterface;
|
||||||
|
|
||||||
|
class Profile extends File implements ProfileInterface
|
||||||
|
{
|
||||||
|
}
|
11
src/Colors/Rgb/Channels/Alpha.php
Normal file
11
src/Colors/Rgb/Channels/Alpha.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||||
|
|
||||||
|
class Alpha extends Red
|
||||||
|
{
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
return strval(round($this->normalize(), 6));
|
||||||
|
}
|
||||||
|
}
|
8
src/Colors/Rgb/Channels/Blue.php
Normal file
8
src/Colors/Rgb/Channels/Blue.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||||
|
|
||||||
|
class Blue extends Red
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
8
src/Colors/Rgb/Channels/Green.php
Normal file
8
src/Colors/Rgb/Channels/Green.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||||
|
|
||||||
|
class Green extends Red
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
28
src/Colors/Rgb/Channels/Red.php
Normal file
28
src/Colors/Rgb/Channels/Red.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Channels;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColorChannel;
|
||||||
|
|
||||||
|
class Red extends AbstractColorChannel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::min()
|
||||||
|
*/
|
||||||
|
public function min(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorChannelInterface::max()
|
||||||
|
*/
|
||||||
|
public function max(): int
|
||||||
|
{
|
||||||
|
return 255;
|
||||||
|
}
|
||||||
|
}
|
166
src/Colors/Rgb/Color.php
Normal file
166
src/Colors/Rgb/Color.php
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\AbstractColor;
|
||||||
|
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\Channels\Alpha;
|
||||||
|
use Intervention\Image\Drivers\AbstractInputHandler;
|
||||||
|
use Intervention\Image\Interfaces\ColorChannelInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Color extends AbstractColor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create new instance
|
||||||
|
*
|
||||||
|
* @param int $r
|
||||||
|
* @param int $g
|
||||||
|
* @param int $b
|
||||||
|
* @param int $a
|
||||||
|
* @return ColorInterface
|
||||||
|
*/
|
||||||
|
public function __construct(int $r, int $g, int $b, int $a = 255)
|
||||||
|
{
|
||||||
|
$this->channels = [
|
||||||
|
new Red($r),
|
||||||
|
new Green($g),
|
||||||
|
new Blue($b),
|
||||||
|
new Alpha($a),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorspace(): ColorspaceInterface
|
||||||
|
{
|
||||||
|
return new Colorspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::create()
|
||||||
|
*/
|
||||||
|
public static function create(mixed $input): ColorInterface
|
||||||
|
{
|
||||||
|
return (new class ([
|
||||||
|
Decoders\HexColorDecoder::class,
|
||||||
|
Decoders\StringColorDecoder::class,
|
||||||
|
Decoders\TransparentColorDecoder::class,
|
||||||
|
Decoders\HtmlColornameDecoder::class,
|
||||||
|
]) extends AbstractInputHandler
|
||||||
|
{
|
||||||
|
})->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the RGB red color channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function red(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Red::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the RGB green color channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function green(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Green::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the RGB blue color channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function blue(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Blue::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the colors alpha channel
|
||||||
|
*
|
||||||
|
* @return ColorChannelInterface
|
||||||
|
*/
|
||||||
|
public function alpha(): ColorChannelInterface
|
||||||
|
{
|
||||||
|
return $this->channel(Alpha::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::toHex()
|
||||||
|
*/
|
||||||
|
public function toHex(string $prefix = ''): string
|
||||||
|
{
|
||||||
|
if ($this->isFullyOpaque()) {
|
||||||
|
return sprintf(
|
||||||
|
'%s%02x%02x%02x',
|
||||||
|
$prefix,
|
||||||
|
$this->red()->value(),
|
||||||
|
$this->green()->value(),
|
||||||
|
$this->blue()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'%s%02x%02x%02x%02x',
|
||||||
|
$prefix,
|
||||||
|
$this->red()->value(),
|
||||||
|
$this->green()->value(),
|
||||||
|
$this->blue()->value(),
|
||||||
|
$this->alpha()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFullyOpaque(): bool
|
||||||
|
{
|
||||||
|
return $this->alpha()->value() === 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::toString()
|
||||||
|
*/
|
||||||
|
public function toString(): string
|
||||||
|
{
|
||||||
|
if ($this->isFullyOpaque()) {
|
||||||
|
return sprintf(
|
||||||
|
'rgb(%d, %d, %d)',
|
||||||
|
$this->red()->value(),
|
||||||
|
$this->green()->value(),
|
||||||
|
$this->blue()->value()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'rgba(%d, %d, %d, %.1F)',
|
||||||
|
$this->red()->value(),
|
||||||
|
$this->green()->value(),
|
||||||
|
$this->blue()->value(),
|
||||||
|
$this->alpha()->normalize(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorInterface::isGreyscale()
|
||||||
|
*/
|
||||||
|
public function isGreyscale(): bool
|
||||||
|
{
|
||||||
|
$values = [$this->red()->value(), $this->green()->value(), $this->blue()->value()];
|
||||||
|
|
||||||
|
return count(array_unique($values, SORT_REGULAR)) === 1;
|
||||||
|
}
|
||||||
|
}
|
112
src/Colors/Rgb/Colorspace.php
Normal file
112
src/Colors/Rgb/Colorspace.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Hsv\Color as HsvColor;
|
||||||
|
use Intervention\Image\Colors\Hsl\Color as HslColor;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class Colorspace implements ColorspaceInterface
|
||||||
|
{
|
||||||
|
public static $channels = [
|
||||||
|
Channels\Red::class,
|
||||||
|
Channels\Green::class,
|
||||||
|
Channels\Blue::class,
|
||||||
|
Channels\Alpha::class
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::colorFromNormalized()
|
||||||
|
*/
|
||||||
|
public function colorFromNormalized(array $normalized): ColorInterface
|
||||||
|
{
|
||||||
|
$values = array_map(function ($classname, $value_normalized) {
|
||||||
|
return (new $classname(normalized: $value_normalized))->value();
|
||||||
|
}, self::$channels, $normalized);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see ColorspaceInterface::importColor()
|
||||||
|
*/
|
||||||
|
public function importColor(ColorInterface $color): ColorInterface
|
||||||
|
{
|
||||||
|
return match (get_class($color)) {
|
||||||
|
CmykColor::class => $this->importCmykColor($color),
|
||||||
|
HsvColor::class => $this->importHsvColor($color),
|
||||||
|
HslColor::class => $this->importHslColor($color),
|
||||||
|
default => $color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importCmykColor(CmykColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
return new Color(
|
||||||
|
(int) (255 * (1 - $color->cyan()->normalize()) * (1 - $color->key()->normalize())),
|
||||||
|
(int) (255 * (1 - $color->magenta()->normalize()) * (1 - $color->key()->normalize())),
|
||||||
|
(int) (255 * (1 - $color->yellow()->normalize()) * (1 - $color->key()->normalize())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importHsvColor(HsvColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
$chroma = $color->value()->normalize() * $color->saturation()->normalize();
|
||||||
|
$hue = $color->hue()->normalize() * 6;
|
||||||
|
$x = $chroma * (1 - abs(fmod($hue, 2) - 1));
|
||||||
|
|
||||||
|
// connect channel values
|
||||||
|
$values = match (true) {
|
||||||
|
$hue < 1 => [$chroma, $x, 0],
|
||||||
|
$hue < 2 => [$x, $chroma, 0],
|
||||||
|
$hue < 3 => [0, $chroma, $x],
|
||||||
|
$hue < 4 => [0, $x, $chroma],
|
||||||
|
$hue < 5 => [$x, 0, $chroma],
|
||||||
|
default => [$chroma, 0, $x],
|
||||||
|
};
|
||||||
|
|
||||||
|
// add to each value
|
||||||
|
$values = array_map(function ($value) use ($color, $chroma) {
|
||||||
|
return $value + ($color->value()->normalize() - $chroma);
|
||||||
|
}, $values);
|
||||||
|
|
||||||
|
array_push($values, 1); // append alpha channel value
|
||||||
|
|
||||||
|
return $this->colorFromNormalized($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function importHslColor(HslColor $color): ColorInterface
|
||||||
|
{
|
||||||
|
// normalized values of hsl channels
|
||||||
|
list($h, $s, $l) = array_map(function ($channel) {
|
||||||
|
return $channel->normalize();
|
||||||
|
}, $color->channels());
|
||||||
|
|
||||||
|
$c = (1 - abs(2 * $l - 1)) * $s;
|
||||||
|
$x = $c * (1 - abs(fmod($h * 6, 2) - 1));
|
||||||
|
$m = $l - $c / 2;
|
||||||
|
|
||||||
|
$values = match (true) {
|
||||||
|
$h < 1 / 6 => [$c, $x, 0],
|
||||||
|
$h < 2 / 6 => [$x, $c, 0],
|
||||||
|
$h < 3 / 6 => [0, $c, $x],
|
||||||
|
$h < 4 / 6 => [0, $x, $c],
|
||||||
|
$h < 5 / 6 => [$x, 0, $c],
|
||||||
|
default => [$c, 0, $x],
|
||||||
|
};
|
||||||
|
|
||||||
|
$values = array_map(function ($value) use ($m) {
|
||||||
|
return $value + $m;
|
||||||
|
}, $values);
|
||||||
|
|
||||||
|
array_push($values, 1); // append alpha channel value
|
||||||
|
|
||||||
|
return $this->colorFromNormalized($values);
|
||||||
|
}
|
||||||
|
}
|
48
src/Colors/Rgb/Decoders/HexColorDecoder.php
Normal file
48
src/Colors/Rgb/Decoders/HexColorDecoder.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Color;
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class HexColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decode hexadecimal rgb colors with and without transparency
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/^#?(?P<hex>[a-f\d]{3}(?:[a-f\d]?|(?:[a-f\d]{3}(?:[a-f\d]{2})?)?)\b)$/i';
|
||||||
|
if (preg_match($pattern, $input, $matches) != 1) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = str_split($matches['hex']);
|
||||||
|
$values = match (strlen($matches['hex'])) {
|
||||||
|
3, 4 => str_split($matches['hex']),
|
||||||
|
6, 8 => str_split($matches['hex'], 2),
|
||||||
|
default => throw new DecoderException('Unable to decode input'),
|
||||||
|
};
|
||||||
|
|
||||||
|
$values = array_map(function ($value) {
|
||||||
|
return match (strlen($value)) {
|
||||||
|
1 => hexdec($value . $value),
|
||||||
|
2 => hexdec($value),
|
||||||
|
default => throw new DecoderException('Unable to decode input'),
|
||||||
|
};
|
||||||
|
}, $values);
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
}
|
172
src/Colors/Rgb/Decoders/HtmlColornameDecoder.php
Normal file
172
src/Colors/Rgb/Decoders/HtmlColornameDecoder.php
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class HtmlColornameDecoder extends HexColorDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
protected static $names = [
|
||||||
|
'lightsalmon' => '#ffa07a',
|
||||||
|
'salmon' => '#fa8072',
|
||||||
|
'darksalmon' => '#e9967a',
|
||||||
|
'lightcoral' => '#f08080',
|
||||||
|
'indianred' => '#cd5c5c',
|
||||||
|
'crimson' => '#dc143c',
|
||||||
|
'firebrick' => '#b22222',
|
||||||
|
'red' => '#ff0000',
|
||||||
|
'darkred' => '#8b0000',
|
||||||
|
'coral' => '#ff7f50',
|
||||||
|
'tomato' => '#ff6347',
|
||||||
|
'orangered' => '#ff4500',
|
||||||
|
'gold' => '#ffd700',
|
||||||
|
'orange' => '#ffa500',
|
||||||
|
'darkorange' => '#ff8c00',
|
||||||
|
'lightyellow' => '#ffffe0',
|
||||||
|
'lemonchiffon' => '#fffacd',
|
||||||
|
'lightgoldenrodyellow' => '#fafad2',
|
||||||
|
'papayawhip' => '#ffefd5',
|
||||||
|
'moccasin' => '#ffe4b5',
|
||||||
|
'peachpuff' => '#ffdab9',
|
||||||
|
'palegoldenrod' => '#eee8aa',
|
||||||
|
'khaki' => '#f0e68c',
|
||||||
|
'darkkhaki' => '#bdb76b',
|
||||||
|
'yellow' => '#ffff00',
|
||||||
|
'lawngreen' => '#7cfc00',
|
||||||
|
'chartreuse' => '#7fff00',
|
||||||
|
'limegreen' => '#32cd32',
|
||||||
|
'lime' => '#00ff00',
|
||||||
|
'forestgreen' => '#228b22',
|
||||||
|
'green' => '#008000',
|
||||||
|
'darkgreen' => '#006400',
|
||||||
|
'greenyellow' => '#adff2f',
|
||||||
|
'yellowgreen' => '#9acd32',
|
||||||
|
'springgreen' => '#00ff7f',
|
||||||
|
'mediumspringgreen' => '#00fa9a',
|
||||||
|
'lightgreen' => '#90ee90',
|
||||||
|
'palegreen' => '#98fb98',
|
||||||
|
'darkseagreen' => '#8fbc8f',
|
||||||
|
'mediumseagre' => 'en #3cb371',
|
||||||
|
'seagreen' => '#2e8b57',
|
||||||
|
'olive' => '#808000',
|
||||||
|
'darkolivegreen' => '#556b2f',
|
||||||
|
'olivedrab' => '#6b8e23',
|
||||||
|
'lightcyan' => '#e0ffff',
|
||||||
|
'cyan' => '#00ffff',
|
||||||
|
'aqua' => '#00ffff',
|
||||||
|
'aquamarine' => '#7fffd4',
|
||||||
|
'mediumaquamarine' => '#66cdaa',
|
||||||
|
'paleturquoise' => '#afeeee',
|
||||||
|
'turquoise' => '#40e0d0',
|
||||||
|
'mediumturquoise' => '#48d1cc',
|
||||||
|
'darkturquoise' => '#00ced1',
|
||||||
|
'lightseagreen' => '#20b2aa',
|
||||||
|
'cadetblue' => '#5f9ea0',
|
||||||
|
'darkcyan' => '#008b8b',
|
||||||
|
'teal' => '#008080',
|
||||||
|
'powderblue' => '#b0e0e6',
|
||||||
|
'lightblue' => '#add8e6',
|
||||||
|
'lightskyblue' => '#87cefa',
|
||||||
|
'skyblue' => '#87ceeb',
|
||||||
|
'deepskyblue' => '#00bfff',
|
||||||
|
'lightsteelblue' => '#b0c4de',
|
||||||
|
'dodgerblue' => '#1e90ff',
|
||||||
|
'cornflowerblue' => '#6495ed',
|
||||||
|
'steelblue' => '#4682b4',
|
||||||
|
'royalblue' => '#4169e1',
|
||||||
|
'blue' => '#0000ff',
|
||||||
|
'mediumblue' => '#0000cd',
|
||||||
|
'darkblue' => '#00008b',
|
||||||
|
'navy' => '#000080',
|
||||||
|
'midnightblue' => '#191970',
|
||||||
|
'mediumslateblue' => '#7b68ee',
|
||||||
|
'slateblue' => '#6a5acd',
|
||||||
|
'darkslateblue' => '#483d8b',
|
||||||
|
'lavender' => '#e6e6fa',
|
||||||
|
'thistle' => '#d8bfd8',
|
||||||
|
'plum' => '#dda0dd',
|
||||||
|
'violet' => '#ee82ee',
|
||||||
|
'orchid' => '#da70d6',
|
||||||
|
'fuchsia' => '#ff00ff',
|
||||||
|
'magenta' => '#ff00ff',
|
||||||
|
'mediumorchid' => '#ba55d3',
|
||||||
|
'mediumpurple' => '#9370db',
|
||||||
|
'blueviolet' => '#8a2be2',
|
||||||
|
'darkviolet' => '#9400d3',
|
||||||
|
'darkorchid' => '#9932cc',
|
||||||
|
'darkmagenta' => '#8b008b',
|
||||||
|
'purple' => '#800080',
|
||||||
|
'indigo' => '#4b0082',
|
||||||
|
'pink' => '#ffc0cb',
|
||||||
|
'lightpink' => '#ffb6c1',
|
||||||
|
'hotpink' => '#ff69b4',
|
||||||
|
'deeppink' => '#ff1493',
|
||||||
|
'palevioletred' => '#db7093',
|
||||||
|
'mediumvioletred' => '#c71585',
|
||||||
|
'white' => '#ffffff',
|
||||||
|
'snow' => '#fffafa',
|
||||||
|
'honeydew' => '#f0fff0',
|
||||||
|
'mintcream' => '#f5fffa',
|
||||||
|
'azure' => '#f0ffff',
|
||||||
|
'aliceblue' => '#f0f8ff',
|
||||||
|
'ghostwhite' => '#f8f8ff',
|
||||||
|
'whitesmoke' => '#f5f5f5',
|
||||||
|
'seashell' => '#fff5ee',
|
||||||
|
'beige' => '#f5f5dc',
|
||||||
|
'oldlace' => '#fdf5e6',
|
||||||
|
'floralwhite' => '#fffaf0',
|
||||||
|
'ivory' => '#fffff0',
|
||||||
|
'antiquewhite' => '#faebd7',
|
||||||
|
'linen' => '#faf0e6',
|
||||||
|
'lavenderblush' => '#fff0f5',
|
||||||
|
'mistyrose' => '#ffe4e1',
|
||||||
|
'gainsboro' => '#dcdcdc',
|
||||||
|
'lightgray' => '#d3d3d3',
|
||||||
|
'silver' => '#c0c0c0',
|
||||||
|
'darkgray' => '#a9a9a9',
|
||||||
|
'gray' => '#808080',
|
||||||
|
'dimgray' => '#696969',
|
||||||
|
'lightslategray' => '#778899',
|
||||||
|
'slategray' => '#708090',
|
||||||
|
'darkslategray' => '#2f4f4f',
|
||||||
|
'black' => '#000000',
|
||||||
|
'cornsilk' => '#fff8dc',
|
||||||
|
'blanchedalmond' => '#ffebcd',
|
||||||
|
'bisque' => '#ffe4c4',
|
||||||
|
'navajowhite' => '#ffdead',
|
||||||
|
'wheat' => '#f5deb3',
|
||||||
|
'burlywood' => '#deb887',
|
||||||
|
'tan' => '#d2b48c',
|
||||||
|
'rosybrown' => '#bc8f8f',
|
||||||
|
'sandybrown' => '#f4a460',
|
||||||
|
'goldenrod' => '#daa520',
|
||||||
|
'peru' => '#cd853f',
|
||||||
|
'chocolate' => '#d2691e',
|
||||||
|
'saddlebrown' => '#8b4513',
|
||||||
|
'sienna' => '#a0522d',
|
||||||
|
'brown' => '#a52a2a',
|
||||||
|
'maroon' => '#800000',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode html color names
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists(strtolower($input), static::$names)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode(static::$names[strtolower($input)]);
|
||||||
|
}
|
||||||
|
}
|
49
src/Colors/Rgb/Decoders/StringColorDecoder.php
Normal file
49
src/Colors/Rgb/Decoders/StringColorDecoder.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Color;
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class StringColorDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Decode rgb color strings
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
*/
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = '/^s?rgba?\((?P<r>[0-9\.]+%?), ?(?P<g>[0-9\.]+%?), ?(?P<b>[0-9\.]+%?)(?:, ?(?P<a>(?:1)|(?:1\.0*)|(?:0)|(?:0?\.\d+%?)|(?:\d{1,3}%)))?\)$/i';
|
||||||
|
if (preg_match($pattern, $input, $matches) != 1) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
// rgb values
|
||||||
|
$values = array_map(function ($value) {
|
||||||
|
return match (strpos($value, '%')) {
|
||||||
|
false => intval(trim($value)),
|
||||||
|
default => intval(round(floatval(trim(str_replace('%', '', $value))) / 100 * 255)),
|
||||||
|
};
|
||||||
|
}, [$matches['r'], $matches['g'], $matches['b']]);
|
||||||
|
|
||||||
|
// alpha value
|
||||||
|
if (array_key_exists('a', $matches)) {
|
||||||
|
$values[] = match (true) {
|
||||||
|
strpos($matches['a'], '%') => round(intval(trim(str_replace('%', '', $matches['a']))) / 2.55),
|
||||||
|
default => intval(round(floatval(trim($matches['a'])) * 255)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Color(...$values);
|
||||||
|
}
|
||||||
|
}
|
23
src/Colors/Rgb/Decoders/TransparentColorDecoder.php
Normal file
23
src/Colors/Rgb/Decoders/TransparentColorDecoder.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Colors\Rgb\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
|
||||||
|
class TransparentColorDecoder extends HexColorDecoder
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strtolower($input) != 'transparent') {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode('#ff00ff00');
|
||||||
|
}
|
||||||
|
}
|
179
src/Drivers/AbstractDecoder.php
Normal file
179
src/Drivers/AbstractDecoder.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Intervention\Image\Collection;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\CollectionInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Traits\CanBuildFilePointer;
|
||||||
|
|
||||||
|
abstract class AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
use CanBuildFilePointer;
|
||||||
|
|
||||||
|
public function __construct(protected ?AbstractDecoder $successor = null)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to decode given input to image or color object
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return ImageInterface|ColorInterface
|
||||||
|
* @throws DecoderException
|
||||||
|
*/
|
||||||
|
final public function handle($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$decoded = $this->decode($input);
|
||||||
|
} catch (DecoderException $e) {
|
||||||
|
if (!$this->hasSuccessor()) {
|
||||||
|
throw new DecoderException($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->successor->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if current decoder has a successor
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function hasSuccessor(): bool
|
||||||
|
{
|
||||||
|
return $this->successor !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return media type (MIME) of given input
|
||||||
|
*
|
||||||
|
* @param string $input
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function mediaType(string $input): string
|
||||||
|
{
|
||||||
|
$pointer = $this->buildFilePointer($input);
|
||||||
|
$type = mime_content_type($pointer);
|
||||||
|
fclose($pointer);
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract and return EXIF data from given image data string
|
||||||
|
*
|
||||||
|
* @param string $image_data
|
||||||
|
* @return CollectionInterface
|
||||||
|
*/
|
||||||
|
protected function extractExifData(string $image_data): CollectionInterface
|
||||||
|
{
|
||||||
|
if (!function_exists('exif_read_data')) {
|
||||||
|
return new Collection();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pointer = $this->buildFilePointer($image_data);
|
||||||
|
$data = @exif_read_data($pointer, null, true);
|
||||||
|
fclose($pointer);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Collection(is_array($data) ? $data : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if given input is base64 encoded data
|
||||||
|
*
|
||||||
|
* @param mixed $input
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isValidBase64(mixed $input): bool
|
||||||
|
{
|
||||||
|
if (!is_string($input)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64_encode(base64_decode($input)) === str_replace(["\n", "\r"], '', $input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse data uri
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
protected function parseDataUri($value): object
|
||||||
|
{
|
||||||
|
$pattern = "/^data:(?P<mediatype>\w+\/[-+.\w]+)?" .
|
||||||
|
"(?P<parameters>(;[-\w]+=[-\w]+)*)(?P<base64>;base64)?,(?P<data>.*)/";
|
||||||
|
|
||||||
|
$result = preg_match($pattern, $value, $matches);
|
||||||
|
|
||||||
|
return new class ($matches, $result)
|
||||||
|
{
|
||||||
|
private $matches;
|
||||||
|
private $result;
|
||||||
|
|
||||||
|
public function __construct($matches, $result)
|
||||||
|
{
|
||||||
|
$this->matches = $matches;
|
||||||
|
$this->result = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return (bool) $this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mediaType(): ?string
|
||||||
|
{
|
||||||
|
if (isset($this->matches['mediatype']) && !empty($this->matches['mediatype'])) {
|
||||||
|
return $this->matches['mediatype'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasMediaType(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->mediaType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parameters(): array
|
||||||
|
{
|
||||||
|
if (isset($this->matches['parameters']) && !empty($this->matches['parameters'])) {
|
||||||
|
return explode(';', trim($this->matches['parameters'], ';'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBase64Encoded(): bool
|
||||||
|
{
|
||||||
|
if (isset($this->matches['base64']) && $this->matches['base64'] === ';base64') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): ?string
|
||||||
|
{
|
||||||
|
if (isset($this->matches['data']) && !empty($this->matches['data'])) {
|
||||||
|
return $this->matches['data'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
41
src/Drivers/AbstractDrawModifier.php
Normal file
41
src/Drivers/AbstractDrawModifier.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DrawableInterface;
|
||||||
|
use Intervention\Image\Interfaces\PointInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property DrawableInterface $drawable
|
||||||
|
*/
|
||||||
|
abstract class AbstractDrawModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function position(): PointInterface
|
||||||
|
{
|
||||||
|
return $this->drawable->position();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function backgroundColor(): ColorInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$color = $this->driver()->handleInput($this->drawable->backgroundColor());
|
||||||
|
} catch (DecoderException $e) {
|
||||||
|
return $this->driver()->handleInput('transparent');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function borderColor(): ColorInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$color = $this->driver()->handleInput($this->drawable->borderColor());
|
||||||
|
} catch (DecoderException $e) {
|
||||||
|
return $this->driver()->handleInput('transparent');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $color;
|
||||||
|
}
|
||||||
|
}
|
62
src/Drivers/AbstractDriver.php
Normal file
62
src/Drivers/AbstractDriver.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Analyzers\AbstractAnalyzer;
|
||||||
|
use Intervention\Image\Encoders\AbstractEncoder;
|
||||||
|
use Intervention\Image\Exceptions\NotSupportedException;
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
use Intervention\Image\Modifiers\AbstractModifier;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
abstract class AbstractDriver implements DriverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return a specialized version for the current driver of the given object
|
||||||
|
*
|
||||||
|
* @param object $input
|
||||||
|
* @return object
|
||||||
|
* @throws NotSupportedException
|
||||||
|
*/
|
||||||
|
public function resolve(object $input): object
|
||||||
|
{
|
||||||
|
if ($this->isExternal($input)) {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
$driver_namespace = (new ReflectionClass($this))->getNamespaceName();
|
||||||
|
$class_path = substr(get_class($input), strlen("Intervention\\Image\\"));
|
||||||
|
$specialized = $driver_namespace . "\\" . $class_path;
|
||||||
|
|
||||||
|
if (! class_exists($specialized)) {
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"Class '" . $class_path . "' is not supported by " . $this->id() . " driver."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $specialized($input, $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if given object is external custom modifier, analyzer or encoder
|
||||||
|
*
|
||||||
|
* @param object $input
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isExternal(object $input): bool
|
||||||
|
{
|
||||||
|
if ($input instanceof AbstractModifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input instanceof AbstractAnalyzer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input instanceof AbstractEncoder) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
52
src/Drivers/AbstractInputHandler.php
Normal file
52
src/Drivers/AbstractInputHandler.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\InputHandlerInterface;
|
||||||
|
|
||||||
|
abstract class AbstractInputHandler implements InputHandlerInterface
|
||||||
|
{
|
||||||
|
protected array $decoders = [];
|
||||||
|
|
||||||
|
public function __construct(array $decoders = [])
|
||||||
|
{
|
||||||
|
$this->decoders = count($decoders) ? $decoders : $this->decoders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see InputHandlerInterface::handle()
|
||||||
|
*/
|
||||||
|
public function handle($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
return $this->chain()->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stack the decoder array into a nested decoder object
|
||||||
|
*
|
||||||
|
* @return AbstractDecoder
|
||||||
|
*/
|
||||||
|
protected function chain(): AbstractDecoder
|
||||||
|
{
|
||||||
|
if (count($this->decoders) == 0) {
|
||||||
|
throw new DecoderException('No decoders found in ' . get_class($this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get instance of last decoder in stack
|
||||||
|
list($classname) = array_slice(array_reverse($this->decoders), 0, 1);
|
||||||
|
$chain = new $classname();
|
||||||
|
|
||||||
|
// build decoder chain
|
||||||
|
foreach (array_slice(array_reverse($this->decoders), 1) as $classname) {
|
||||||
|
$chain = new $classname($chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chain;
|
||||||
|
}
|
||||||
|
}
|
93
src/Drivers/AbstractTextModifier.php
Normal file
93
src/Drivers/AbstractTextModifier.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Geometry\Point;
|
||||||
|
use Intervention\Image\Geometry\Polygon;
|
||||||
|
use Intervention\Image\Geometry\Rectangle;
|
||||||
|
use Intervention\Image\Interfaces\FontInterface;
|
||||||
|
use Intervention\Image\Typography\TextBlock;
|
||||||
|
use Intervention\Image\Typography\Line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property FontInterface $font
|
||||||
|
*/
|
||||||
|
abstract class AbstractTextModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
abstract protected function boxSize(string $text): Polygon;
|
||||||
|
|
||||||
|
public function leadingInPixels(): int
|
||||||
|
{
|
||||||
|
return intval(round($this->fontSizeInPixels() * $this->font->lineHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function capHeight(): int
|
||||||
|
{
|
||||||
|
return $this->boxSize('T')->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fontSizeInPixels(): int
|
||||||
|
{
|
||||||
|
return $this->boxSize('Hy')->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build TextBlock object from text string and align every line
|
||||||
|
* according to text writers font object and position.
|
||||||
|
*
|
||||||
|
* @return TextBlock
|
||||||
|
*/
|
||||||
|
public function alignedTextBlock(Point $position, string $text): TextBlock
|
||||||
|
{
|
||||||
|
$lines = new TextBlock($text);
|
||||||
|
$boundingBox = $this->boundingBox($lines, $position);
|
||||||
|
$pivot = $boundingBox->last();
|
||||||
|
|
||||||
|
$leading = $this->leadingInPixels();
|
||||||
|
$blockWidth = $this->lineWidth($lines->longestLine());
|
||||||
|
|
||||||
|
$x = $pivot->x();
|
||||||
|
$y = $this->font->hasFilename() ? $pivot->y() + $this->capHeight() : $pivot->y();
|
||||||
|
$x_adjustment = 0;
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$line_width = $this->lineWidth($line);
|
||||||
|
$x_adjustment = $this->font->alignment() == 'left' ? 0 : $blockWidth - $line_width;
|
||||||
|
$x_adjustment = $this->font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment;
|
||||||
|
$x_adjustment = $this->font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment;
|
||||||
|
$position = new Point($x + $x_adjustment, $y);
|
||||||
|
$position->rotate($this->font->angle(), $pivot);
|
||||||
|
$line->setPosition($position);
|
||||||
|
$y += $leading;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boundingBox(TextBlock $block, Point $pivot = null): Polygon
|
||||||
|
{
|
||||||
|
$pivot = $pivot ? $pivot : new Point();
|
||||||
|
|
||||||
|
// bounding box
|
||||||
|
$box = (new Rectangle(
|
||||||
|
$this->lineWidth($block->longestLine()),
|
||||||
|
$this->leadingInPixels() * ($block->count() - 1) + $this->capHeight()
|
||||||
|
));
|
||||||
|
|
||||||
|
// set pivot
|
||||||
|
$box->setPivot($pivot);
|
||||||
|
|
||||||
|
// align
|
||||||
|
$box->align($this->font->alignment());
|
||||||
|
$box->valign($this->font->valignment());
|
||||||
|
|
||||||
|
$box->rotate($this->font->angle());
|
||||||
|
|
||||||
|
return $box;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function lineWidth(Line $line): int
|
||||||
|
{
|
||||||
|
return $this->boxSize((string) $line)->width();
|
||||||
|
}
|
||||||
|
}
|
31
src/Drivers/DriverSpecializedAnalyzer.php
Normal file
31
src/Drivers/DriverSpecializedAnalyzer.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\AnalyzerInterface;
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
|
||||||
|
abstract class DriverSpecializedAnalyzer implements AnalyzerInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected AnalyzerInterface $analyzer,
|
||||||
|
protected DriverInterface $driver
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function driver(): DriverInterface
|
||||||
|
{
|
||||||
|
return $this->driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to read attributes of underlying analyzer
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
return $this->analyzer->$name;
|
||||||
|
}
|
||||||
|
}
|
47
src/Drivers/DriverSpecializedEncoder.php
Normal file
47
src/Drivers/DriverSpecializedEncoder.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
|
|
||||||
|
abstract class DriverSpecializedEncoder implements EncoderInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected EncoderInterface $encoder,
|
||||||
|
protected DriverInterface $driver
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function driver(): DriverInterface
|
||||||
|
{
|
||||||
|
return $this->driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to read attributes of underlying endcoder
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
return $this->encoder->$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get return value of callback through output buffer
|
||||||
|
*
|
||||||
|
* @param callable $callback
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getBuffered(callable $callback): string
|
||||||
|
{
|
||||||
|
ob_start();
|
||||||
|
$callback();
|
||||||
|
$buffer = ob_get_contents();
|
||||||
|
ob_end_clean();
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
}
|
43
src/Drivers/DriverSpecializedModifier.php
Normal file
43
src/Drivers/DriverSpecializedModifier.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
|
|
||||||
|
abstract class DriverSpecializedModifier implements ModifierInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected ModifierInterface $modifier,
|
||||||
|
protected DriverInterface $driver
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function driver(): DriverInterface
|
||||||
|
{
|
||||||
|
return $this->driver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to read attributes of underlying modifier
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get(string $name): mixed
|
||||||
|
{
|
||||||
|
return $this->modifier->$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to call methods of underlying modifier
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param array $arguments
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __call(string $name, array $arguments): mixed
|
||||||
|
{
|
||||||
|
return $this->modifier->$name(...$arguments);
|
||||||
|
}
|
||||||
|
}
|
15
src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
Normal file
15
src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedAnalyzer;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class ColorspaceAnalyzer extends DriverSpecializedAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return new Colorspace();
|
||||||
|
}
|
||||||
|
}
|
14
src/Drivers/Gd/Analyzers/HeightAnalyzer.php
Normal file
14
src/Drivers/Gd/Analyzers/HeightAnalyzer.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedAnalyzer;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class HeightAnalyzer extends DriverSpecializedAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return imagesy($image->core()->native());
|
||||||
|
}
|
||||||
|
}
|
39
src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
Normal file
39
src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use GdImage;
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedAnalyzer;
|
||||||
|
use Intervention\Image\Exceptions\GeometryException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $x
|
||||||
|
* @property int $y
|
||||||
|
* @property int $frame_key
|
||||||
|
*/
|
||||||
|
class PixelColorAnalyzer extends DriverSpecializedAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return $this->colorAt(
|
||||||
|
$image->colorspace(),
|
||||||
|
$image->core()->frame($this->frame_key)->native()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function colorAt(ColorspaceInterface $colorspace, GdImage $gd): ColorInterface
|
||||||
|
{
|
||||||
|
$index = @imagecolorat($gd, $this->x, $this->y);
|
||||||
|
|
||||||
|
if ($index === false) {
|
||||||
|
throw new GeometryException(
|
||||||
|
'The specified position is not in the valid image area.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->driver()->colorProcessor($colorspace)->nativeToColor($index);
|
||||||
|
}
|
||||||
|
}
|
27
src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
Normal file
27
src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Collection;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $x
|
||||||
|
* @property int $y
|
||||||
|
*/
|
||||||
|
class PixelColorsAnalyzer extends PixelColorAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
$colors = new Collection();
|
||||||
|
$colorspace = $image->colorspace();
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
$colors->push(
|
||||||
|
parent::colorAt($colorspace, $frame->native())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $colors;
|
||||||
|
}
|
||||||
|
}
|
15
src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
Normal file
15
src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedAnalyzer;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Resolution;
|
||||||
|
|
||||||
|
class ResolutionAnalyzer extends DriverSpecializedAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return new Resolution(...imageresolution($image->core()->native()));
|
||||||
|
}
|
||||||
|
}
|
14
src/Drivers/Gd/Analyzers/WidthAnalyzer.php
Normal file
14
src/Drivers/Gd/Analyzers/WidthAnalyzer.php
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Analyzers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedAnalyzer;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class WidthAnalyzer extends DriverSpecializedAnalyzer
|
||||||
|
{
|
||||||
|
public function analyze(ImageInterface $image): mixed
|
||||||
|
{
|
||||||
|
return imagesx($image->core()->native());
|
||||||
|
}
|
||||||
|
}
|
78
src/Drivers/Gd/ColorProcessor.php
Normal file
78
src/Drivers/Gd/ColorProcessor.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Alpha;
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||||
|
use Intervention\Image\Colors\Rgb\Color;
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||||
|
use Intervention\Image\Exceptions\ColorException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
|
||||||
|
class ColorProcessor implements ColorProcessorInterface
|
||||||
|
{
|
||||||
|
public function __construct(protected ColorspaceInterface $colorspace = new Colorspace())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function colorToNative(ColorInterface $color): int
|
||||||
|
{
|
||||||
|
// convert color to colorspace
|
||||||
|
$color = $color->convertTo($this->colorspace);
|
||||||
|
|
||||||
|
// gd only supports rgb so the channels can be accessed directly
|
||||||
|
$r = $color->channel(Red::class)->value();
|
||||||
|
$g = $color->channel(Green::class)->value();
|
||||||
|
$b = $color->channel(Blue::class)->value();
|
||||||
|
$a = $color->channel(Alpha::class)->value();
|
||||||
|
|
||||||
|
// convert alpha value to gd alpha
|
||||||
|
// ([opaque]255-0[transparent]) to ([opaque]0-127[transparent])
|
||||||
|
$a = (int) $this->convertRange($a, 0, 255, 127, 0);
|
||||||
|
|
||||||
|
return ($a << 24) + ($r << 16) + ($g << 8) + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nativeToColor(mixed $value): ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_int($value)) {
|
||||||
|
throw new ColorException("GD driver can only decode colors in integer format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$a = ($value >> 24) & 0xFF;
|
||||||
|
$r = ($value >> 16) & 0xFF;
|
||||||
|
$g = ($value >> 8) & 0xFF;
|
||||||
|
$b = $value & 0xFF;
|
||||||
|
|
||||||
|
// convert gd apha integer to intervention alpha integer
|
||||||
|
// ([opaque]0-127[transparent]) to ([opaque]255-0[transparent])
|
||||||
|
$a = (int) static::convertRange($a, 127, 0, 0, 255);
|
||||||
|
|
||||||
|
return new Color($r, $g, $b, $a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert input in range (min) to (max) to the corresponding value
|
||||||
|
* in target range (targetMin) to (targetMax).
|
||||||
|
*
|
||||||
|
* @param float|int $input
|
||||||
|
* @param float|int $min
|
||||||
|
* @param float|int $max
|
||||||
|
* @param float|int $targetMin
|
||||||
|
* @param float|int $targetMax
|
||||||
|
* @return float|int
|
||||||
|
*/
|
||||||
|
protected function convertRange(
|
||||||
|
float|int $input,
|
||||||
|
float|int $min,
|
||||||
|
float|int $max,
|
||||||
|
float|int $targetMin,
|
||||||
|
float|int $targetMax
|
||||||
|
): float|int {
|
||||||
|
return ceil(((($input - $min) * ($targetMax - $targetMin)) / ($max - $min)) + $targetMin);
|
||||||
|
}
|
||||||
|
}
|
58
src/Drivers/Gd/Core.php
Normal file
58
src/Drivers/Gd/Core.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
|
use Intervention\Image\Collection;
|
||||||
|
use Intervention\Image\Interfaces\CoreInterface;
|
||||||
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
|
|
||||||
|
class Core extends Collection implements CoreInterface
|
||||||
|
{
|
||||||
|
protected int $loops = 0;
|
||||||
|
|
||||||
|
public function add(FrameInterface $frame): CoreInterface
|
||||||
|
{
|
||||||
|
$this->push($frame);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function native(): mixed
|
||||||
|
{
|
||||||
|
return $this->first()->native();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNative(mixed $native): self
|
||||||
|
{
|
||||||
|
$this->empty()->push(new Frame($native));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function frame(int $position): FrameInterface
|
||||||
|
{
|
||||||
|
return $this->getAtPosition($position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loops(): int
|
||||||
|
{
|
||||||
|
return $this->loops;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLoops(int $loops): self
|
||||||
|
{
|
||||||
|
$this->loops = $loops;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function first(): FrameInterface
|
||||||
|
{
|
||||||
|
return parent::first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function last(): FrameInterface
|
||||||
|
{
|
||||||
|
return parent::last();
|
||||||
|
}
|
||||||
|
}
|
20
src/Drivers/Gd/Decoders/Base64ImageDecoder.php
Normal file
20
src/Drivers/Gd/Decoders/Base64ImageDecoder.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class Base64ImageDecoder extends BinaryImageDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! $this->isValidBase64($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode(base64_decode($input));
|
||||||
|
}
|
||||||
|
}
|
96
src/Drivers/Gd/Decoders/BinaryImageDecoder.php
Normal file
96
src/Drivers/Gd/Decoders/BinaryImageDecoder.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Frame;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Gif\Decoder as GifDecoder;
|
||||||
|
use Intervention\Gif\Splitter as GifSplitter;
|
||||||
|
use Intervention\Image\Drivers\Gd\Core;
|
||||||
|
use Intervention\Image\Drivers\Gd\Driver;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
|
||||||
|
class BinaryImageDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (!is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->mediaType($input) == 'image/gif') {
|
||||||
|
return $this->decodeGif($input); // decode (animated) gif
|
||||||
|
}
|
||||||
|
|
||||||
|
// build image instance
|
||||||
|
$image = new Image(
|
||||||
|
new Driver(),
|
||||||
|
$this->coreFromString($input),
|
||||||
|
$this->extractExifData($input)
|
||||||
|
);
|
||||||
|
|
||||||
|
// fix image orientation
|
||||||
|
return match ($image->exif('IFD0.Orientation')) {
|
||||||
|
2 => $image->flip(),
|
||||||
|
3 => $image->rotate(180),
|
||||||
|
4 => $image->rotate(180)->flip(),
|
||||||
|
5 => $image->rotate(270)->flip(),
|
||||||
|
6 => $image->rotate(270),
|
||||||
|
7 => $image->rotate(90)->flip(),
|
||||||
|
8 => $image->rotate(90),
|
||||||
|
default => $image
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function coreFromString(string $input): Core
|
||||||
|
{
|
||||||
|
$data = @imagecreatefromstring($input);
|
||||||
|
|
||||||
|
if ($data === false) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageistruecolor($data)) {
|
||||||
|
imagepalettetotruecolor($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
imagesavealpha($data, true);
|
||||||
|
|
||||||
|
return new Core([
|
||||||
|
new Frame($data)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function decodeGif(string $input): ImageInterface
|
||||||
|
{
|
||||||
|
$gif = GifDecoder::decode($input);
|
||||||
|
|
||||||
|
if (!$gif->isAnimated()) {
|
||||||
|
return new Image(
|
||||||
|
new Driver(),
|
||||||
|
$this->coreFromString($input)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$splitter = GifSplitter::create($gif)->split();
|
||||||
|
$delays = $splitter->getDelays();
|
||||||
|
|
||||||
|
// build core
|
||||||
|
$core = new Core();
|
||||||
|
$core->setLoops($gif->getMainApplicationExtension()?->getLoops());
|
||||||
|
foreach ($splitter->coalesceToResources() as $key => $data) {
|
||||||
|
$core->push(
|
||||||
|
(new Frame($data))->setDelay($delays[$key] / 100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Image(
|
||||||
|
new Driver(),
|
||||||
|
$core
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
21
src/Drivers/Gd/Decoders/ColorObjectDecoder.php
Normal file
21
src/Drivers/Gd/Decoders/ColorObjectDecoder.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class ColorObjectDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_a($input, ColorInterface::class)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
}
|
30
src/Drivers/Gd/Decoders/DataUriImageDecoder.php
Normal file
30
src/Drivers/Gd/Decoders/DataUriImageDecoder.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class DataUriImageDecoder extends BinaryImageDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (!is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$uri = $this->parseDataUri($input);
|
||||||
|
|
||||||
|
if (! $uri->isValid()) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($uri->isBase64Encoded()) {
|
||||||
|
return parent::decode(base64_decode($uri->data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode(urldecode($uri->data()));
|
||||||
|
}
|
||||||
|
}
|
29
src/Drivers/Gd/Decoders/FilePathImageDecoder.php
Normal file
29
src/Drivers/Gd/Decoders/FilePathImageDecoder.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class FilePathImageDecoder extends BinaryImageDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_string($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (! @is_file($input)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode(file_get_contents($input));
|
||||||
|
}
|
||||||
|
}
|
25
src/Drivers/Gd/Decoders/FilePointerImageDecoder.php
Normal file
25
src/Drivers/Gd/Decoders/FilePointerImageDecoder.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class FilePointerImageDecoder extends BinaryImageDecoder
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (!is_resource($input) || !in_array(get_resource_type($input), ['file', 'stream'])) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = '';
|
||||||
|
@rewind($input);
|
||||||
|
while (!feof($input)) {
|
||||||
|
$contents .= fread($input, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode($contents);
|
||||||
|
}
|
||||||
|
}
|
21
src/Drivers/Gd/Decoders/ImageObjectDecoder.php
Normal file
21
src/Drivers/Gd/Decoders/ImageObjectDecoder.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDecoder;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class ImageObjectDecoder extends AbstractDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_a($input, ImageInterface::class)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
}
|
21
src/Drivers/Gd/Decoders/SplFileInfoImageDecoder.php
Normal file
21
src/Drivers/Gd/Decoders/SplFileInfoImageDecoder.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Decoders;
|
||||||
|
|
||||||
|
use SplFileInfo;
|
||||||
|
use Intervention\Image\Exceptions\DecoderException;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\DecoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class SplFileInfoImageDecoder extends FilePathImageDecoder implements DecoderInterface
|
||||||
|
{
|
||||||
|
public function decode($input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
if (! is_a($input, SplFileInfo::class)) {
|
||||||
|
throw new DecoderException('Unable to decode input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::decode($input->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
105
src/Drivers/Gd/Driver.php
Normal file
105
src/Drivers/Gd/Driver.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDriver;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
class Driver extends AbstractDriver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see DriverInterface::id()
|
||||||
|
*/
|
||||||
|
public function id(): string
|
||||||
|
{
|
||||||
|
return 'GD';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see DriverInterface::createImage()
|
||||||
|
*/
|
||||||
|
public function createImage(int $width, int $height): ImageInterface
|
||||||
|
{
|
||||||
|
// build new transparent GDImage
|
||||||
|
$data = imagecreatetruecolor($width, $height);
|
||||||
|
imagesavealpha($data, true);
|
||||||
|
$background = imagecolorallocatealpha($data, 255, 0, 255, 127);
|
||||||
|
imagealphablending($data, false);
|
||||||
|
imagefill($data, 0, 0, $background);
|
||||||
|
imagecolortransparent($data, $background);
|
||||||
|
|
||||||
|
return new Image(
|
||||||
|
$this,
|
||||||
|
new Core([
|
||||||
|
new Frame($data)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see DriverInterface::createAnimation()
|
||||||
|
*/
|
||||||
|
public function createAnimation(callable $init): ImageInterface
|
||||||
|
{
|
||||||
|
$animation = new class ($this)
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected DriverInterface $driver,
|
||||||
|
public Core $core = new Core()
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add($source, float $delay = 1): self
|
||||||
|
{
|
||||||
|
$this->core->add(
|
||||||
|
$this->driver->handleInput($source)->core()->first()->setDelay($delay)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(): ImageInterface
|
||||||
|
{
|
||||||
|
return new Image(
|
||||||
|
$this->driver,
|
||||||
|
$this->core
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$init($animation);
|
||||||
|
|
||||||
|
return call_user_func($animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see DriverInterface::handleInput()
|
||||||
|
*/
|
||||||
|
public function handleInput(mixed $input): ImageInterface|ColorInterface
|
||||||
|
{
|
||||||
|
return (new InputHandler())->handle($input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @see DriverInterface::colorProcessor()
|
||||||
|
*/
|
||||||
|
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface
|
||||||
|
{
|
||||||
|
return new ColorProcessor($colorspace);
|
||||||
|
}
|
||||||
|
}
|
23
src/Drivers/Gd/Encoders/AvifEncoder.php
Normal file
23
src/Drivers/Gd/Encoders/AvifEncoder.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $quality
|
||||||
|
*/
|
||||||
|
class AvifEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imageavif($gd, null, $this->quality);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/avif');
|
||||||
|
}
|
||||||
|
}
|
25
src/Drivers/Gd/Encoders/BmpEncoder.php
Normal file
25
src/Drivers/Gd/Encoders/BmpEncoder.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\Modifiers\LimitColorsModifier;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $color_limit
|
||||||
|
*/
|
||||||
|
class BmpEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imagebmp($gd, null, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/bmp');
|
||||||
|
}
|
||||||
|
}
|
48
src/Drivers/Gd/Encoders/GifEncoder.php
Normal file
48
src/Drivers/Gd/Encoders/GifEncoder.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Gif\Builder as GifBuilder;
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\Modifiers\LimitColorsModifier;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $color_limit
|
||||||
|
*/
|
||||||
|
class GifEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
if ($image->isAnimated()) {
|
||||||
|
return $this->encodeAnimated($image);
|
||||||
|
}
|
||||||
|
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imagegif($gd);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/gif');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function encodeAnimated(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$builder = GifBuilder::canvas(
|
||||||
|
$image->width(),
|
||||||
|
$image->height(),
|
||||||
|
$image->loops()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
$builder->addFrame(
|
||||||
|
$this->encode($frame->toImage($image->driver())),
|
||||||
|
$frame->delay()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new EncodedImage($builder->encode(), 'image/gif');
|
||||||
|
}
|
||||||
|
}
|
23
src/Drivers/Gd/Encoders/JpegEncoder.php
Normal file
23
src/Drivers/Gd/Encoders/JpegEncoder.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $quality
|
||||||
|
*/
|
||||||
|
class JpegEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imagejpeg($gd, null, $this->quality);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/jpeg');
|
||||||
|
}
|
||||||
|
}
|
25
src/Drivers/Gd/Encoders/PngEncoder.php
Normal file
25
src/Drivers/Gd/Encoders/PngEncoder.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Modifiers\LimitColorsModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $color_limit
|
||||||
|
*/
|
||||||
|
class PngEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$image = $image->modify(new LimitColorsModifier($this->color_limit));
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imagepng($gd, null, -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/png');
|
||||||
|
}
|
||||||
|
}
|
23
src/Drivers/Gd/Encoders/WebpEncoder.php
Normal file
23
src/Drivers/Gd/Encoders/WebpEncoder.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Encoders;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedEncoder;
|
||||||
|
use Intervention\Image\EncodedImage;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $quality
|
||||||
|
*/
|
||||||
|
class WebpEncoder extends DriverSpecializedEncoder
|
||||||
|
{
|
||||||
|
public function encode(ImageInterface $image): EncodedImage
|
||||||
|
{
|
||||||
|
$gd = $image->core()->native();
|
||||||
|
$data = $this->getBuffered(function () use ($gd) {
|
||||||
|
imagewebp($gd, null, $this->quality);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new EncodedImage($data, 'image/webp');
|
||||||
|
}
|
||||||
|
}
|
102
src/Drivers/Gd/Frame.php
Normal file
102
src/Drivers/Gd/Frame.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
|
use GdImage;
|
||||||
|
use Intervention\Image\Geometry\Rectangle;
|
||||||
|
use Intervention\Image\Image;
|
||||||
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
|
|
||||||
|
class Frame implements FrameInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected GdImage $native,
|
||||||
|
protected float $delay = 0,
|
||||||
|
protected int $dispose = 1,
|
||||||
|
protected int $offset_left = 0,
|
||||||
|
protected int $offset_top = 0
|
||||||
|
) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toImage(DriverInterface $driver): ImageInterface
|
||||||
|
{
|
||||||
|
return new Image($driver, new Core([$this]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNative($native): FrameInterface
|
||||||
|
{
|
||||||
|
$this->native = $native;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function native(): GdImage
|
||||||
|
{
|
||||||
|
return $this->native;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function size(): SizeInterface
|
||||||
|
{
|
||||||
|
return new Rectangle(imagesx($this->native), imagesy($this->native));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delay(): float
|
||||||
|
{
|
||||||
|
return $this->delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDelay(float $delay): FrameInterface
|
||||||
|
{
|
||||||
|
$this->delay = $delay;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispose(): int
|
||||||
|
{
|
||||||
|
return $this->dispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDispose(int $dispose): FrameInterface
|
||||||
|
{
|
||||||
|
$this->dispose = $dispose;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOffset(int $left, int $top): FrameInterface
|
||||||
|
{
|
||||||
|
$this->offset_left = $left;
|
||||||
|
$this->offset_top = $top;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetLeft(): int
|
||||||
|
{
|
||||||
|
return $this->offset_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOffsetLeft(int $offset): FrameInterface
|
||||||
|
{
|
||||||
|
$this->offset_left = $offset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetTop(): int
|
||||||
|
{
|
||||||
|
return $this->offset_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOffsetTop(int $offset): FrameInterface
|
||||||
|
{
|
||||||
|
$this->offset_top = $offset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
41
src/Drivers/Gd/InputHandler.php
Normal file
41
src/Drivers/Gd/InputHandler.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Decoders\HexColorDecoder as RgbHexColorDecoder;
|
||||||
|
use Intervention\Image\Colors\Rgb\Decoders\StringColorDecoder as RgbStringColorDecoder;
|
||||||
|
use Intervention\Image\Colors\Rgb\Decoders\HtmlColornameDecoder;
|
||||||
|
use Intervention\Image\Colors\Rgb\Decoders\TransparentColorDecoder;
|
||||||
|
use Intervention\Image\Colors\Cmyk\Decoders\StringColorDecoder as CmykStringColorDecoder;
|
||||||
|
use Intervention\Image\Colors\Hsv\Decoders\StringColorDecoder as HsvStringColorDecoder;
|
||||||
|
use Intervention\Image\Colors\Hsl\Decoders\StringColorDecoder as HslStringColorDecoder;
|
||||||
|
use Intervention\Image\Drivers\AbstractInputHandler;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\ImageObjectDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\ColorObjectDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\FilePointerImageDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\FilePathImageDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\BinaryImageDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\DataUriImageDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\Base64ImageDecoder;
|
||||||
|
use Intervention\Image\Drivers\Gd\Decoders\SplFileInfoImageDecoder;
|
||||||
|
|
||||||
|
class InputHandler extends AbstractInputHandler
|
||||||
|
{
|
||||||
|
protected array $decoders = [
|
||||||
|
ImageObjectDecoder::class,
|
||||||
|
ColorObjectDecoder::class,
|
||||||
|
RgbHexColorDecoder::class,
|
||||||
|
RgbStringColorDecoder::class,
|
||||||
|
CmykStringColorDecoder::class,
|
||||||
|
HsvStringColorDecoder::class,
|
||||||
|
HslStringColorDecoder::class,
|
||||||
|
TransparentColorDecoder::class,
|
||||||
|
HtmlColornameDecoder::class,
|
||||||
|
FilePointerImageDecoder::class,
|
||||||
|
FilePathImageDecoder::class,
|
||||||
|
SplFileInfoImageDecoder::class,
|
||||||
|
BinaryImageDecoder::class,
|
||||||
|
DataUriImageDecoder::class,
|
||||||
|
Base64ImageDecoder::class,
|
||||||
|
];
|
||||||
|
}
|
23
src/Drivers/Gd/Modifiers/BlurModifier.php
Normal file
23
src/Drivers/Gd/Modifiers/BlurModifier.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $amount
|
||||||
|
*/
|
||||||
|
class BlurModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
for ($i = 0; $i < $this->amount; $i++) {
|
||||||
|
imagefilter($frame->native(), IMG_FILTER_GAUSSIAN_BLUR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
21
src/Drivers/Gd/Modifiers/BrightnessModifier.php
Normal file
21
src/Drivers/Gd/Modifiers/BrightnessModifier.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $level
|
||||||
|
*/
|
||||||
|
class BrightnessModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
imagefilter($frame->native(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
28
src/Drivers/Gd/Modifiers/ColorizeModifier.php
Normal file
28
src/Drivers/Gd/Modifiers/ColorizeModifier.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $red
|
||||||
|
* @property int $green
|
||||||
|
* @property int $blue
|
||||||
|
*/
|
||||||
|
class ColorizeModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
// normalize colorize levels
|
||||||
|
$red = round($this->red * 2.55);
|
||||||
|
$green = round($this->green * 2.55);
|
||||||
|
$blue = round($this->blue * 2.55);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
imagefilter($frame->native(), IMG_FILTER_COLORIZE, $red, $green, $blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
25
src/Drivers/Gd/Modifiers/ColorspaceModifier.php
Normal file
25
src/Drivers/Gd/Modifiers/ColorspaceModifier.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Exceptions\NotSupportedException;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method ColorspaceInterface targetColorspace()
|
||||||
|
*/
|
||||||
|
class ColorspaceModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
if (!is_a($this->targetColorspace(), RgbColorspace::class)) {
|
||||||
|
throw new NotSupportedException(
|
||||||
|
'Only RGB colorspace is supported with GD driver.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
90
src/Drivers/Gd/Modifiers/ContainModifier.php
Normal file
90
src/Drivers/Gd/Modifiers/ContainModifier.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Blue;
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Green;
|
||||||
|
use Intervention\Image\Colors\Rgb\Channels\Red;
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
|
use Intervention\Image\Modifiers\FillModifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method SizeInterface getCropSize(ImageInterface $image)
|
||||||
|
* @method SizeInterface getResizeSize(ImageInterface $image)
|
||||||
|
* @property int $width
|
||||||
|
* @property int $height
|
||||||
|
* @property mixed $background
|
||||||
|
* @property string $position
|
||||||
|
*/
|
||||||
|
class ContainModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
$crop = $this->getCropSize($image);
|
||||||
|
$resize = $this->getResizeSize($image);
|
||||||
|
$background = $this->driver()->handleInput($this->background);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
$this->modify($frame, $crop, $resize, $background);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function modify(
|
||||||
|
FrameInterface $frame,
|
||||||
|
SizeInterface $crop,
|
||||||
|
SizeInterface $resize,
|
||||||
|
ColorInterface $background
|
||||||
|
): void {
|
||||||
|
// create new gd image
|
||||||
|
$modified = $this->driver()->createImage(
|
||||||
|
$resize->width(),
|
||||||
|
$resize->height()
|
||||||
|
)->modify(
|
||||||
|
new FillModifier($background)
|
||||||
|
)->core()->native();
|
||||||
|
|
||||||
|
// make image area transparent to keep transparency
|
||||||
|
// even if background-color is set
|
||||||
|
$transparent = imagecolorallocatealpha(
|
||||||
|
$modified,
|
||||||
|
$background->channel(Red::class)->value(),
|
||||||
|
$background->channel(Green::class)->value(),
|
||||||
|
$background->channel(Blue::class)->value(),
|
||||||
|
127,
|
||||||
|
);
|
||||||
|
imagealphablending($modified, false); // do not blend / just overwrite
|
||||||
|
imagecolortransparent($modified, $transparent);
|
||||||
|
imagefilledrectangle(
|
||||||
|
$modified,
|
||||||
|
$crop->pivot()->x(),
|
||||||
|
$crop->pivot()->y(),
|
||||||
|
$crop->pivot()->x() + $crop->width() - 1,
|
||||||
|
$crop->pivot()->y() + $crop->height() - 1,
|
||||||
|
$transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
// copy image from original with blending alpha
|
||||||
|
imagealphablending($modified, true);
|
||||||
|
imagecopyresampled(
|
||||||
|
$modified,
|
||||||
|
$frame->native(),
|
||||||
|
$crop->pivot()->x(),
|
||||||
|
$crop->pivot()->y(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$crop->width(),
|
||||||
|
$crop->height(),
|
||||||
|
$frame->size()->width(),
|
||||||
|
$frame->size()->height()
|
||||||
|
);
|
||||||
|
|
||||||
|
// set new content as recource
|
||||||
|
$frame->setNative($modified);
|
||||||
|
}
|
||||||
|
}
|
21
src/Drivers/Gd/Modifiers/ContrastModifier.php
Normal file
21
src/Drivers/Gd/Modifiers/ContrastModifier.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $level
|
||||||
|
*/
|
||||||
|
class ContrastModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
imagefilter($frame->native(), IMG_FILTER_CONTRAST, ($this->level * -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
17
src/Drivers/Gd/Modifiers/CoverDownModifier.php
Normal file
17
src/Drivers/Gd/Modifiers/CoverDownModifier.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $width
|
||||||
|
* @property int $height
|
||||||
|
*/
|
||||||
|
class CoverDownModifier extends CoverModifier
|
||||||
|
{
|
||||||
|
public function getResizeSize(SizeInterface $size): SizeInterface
|
||||||
|
{
|
||||||
|
return $size->scaleDown($this->width, $this->height);
|
||||||
|
}
|
||||||
|
}
|
66
src/Drivers/Gd/Modifiers/CoverModifier.php
Normal file
66
src/Drivers/Gd/Modifiers/CoverModifier.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method SizeInterface getResizeSize(ImageInterface $image)
|
||||||
|
* @method SizeInterface getCropSize(ImageInterface $image)
|
||||||
|
*/
|
||||||
|
class CoverModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
$crop = $this->getCropSize($image);
|
||||||
|
$resize = $this->getResizeSize($crop);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
$this->modifyFrame($frame, $crop, $resize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void
|
||||||
|
{
|
||||||
|
// create new image
|
||||||
|
$modified = $this->driver()->createImage(
|
||||||
|
$resize->width(),
|
||||||
|
$resize->height()
|
||||||
|
)->core()->native();
|
||||||
|
|
||||||
|
// get original image
|
||||||
|
$original = $frame->native();
|
||||||
|
|
||||||
|
// preserve transparency
|
||||||
|
$transIndex = imagecolortransparent($original);
|
||||||
|
|
||||||
|
if ($transIndex != -1) {
|
||||||
|
$rgba = imagecolorsforindex($modified, $transIndex);
|
||||||
|
$transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
|
||||||
|
imagefill($modified, 0, 0, $transColor);
|
||||||
|
imagecolortransparent($modified, $transColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy content from resource
|
||||||
|
imagecopyresampled(
|
||||||
|
$modified,
|
||||||
|
$original,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$crop->pivot()->x(),
|
||||||
|
$crop->pivot()->y(),
|
||||||
|
$resize->width(),
|
||||||
|
$resize->height(),
|
||||||
|
$crop->width(),
|
||||||
|
$crop->height()
|
||||||
|
);
|
||||||
|
|
||||||
|
// set new content as resource
|
||||||
|
$frame->setNative($modified);
|
||||||
|
}
|
||||||
|
}
|
66
src/Drivers/Gd/Modifiers/CropModifier.php
Normal file
66
src/Drivers/Gd/Modifiers/CropModifier.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\FrameInterface;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method SizeInterface crop(ImageInterface $image)
|
||||||
|
* @property int $offset_x
|
||||||
|
* @property int $offset_y
|
||||||
|
*/
|
||||||
|
class CropModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
$crop = $this->crop($image);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
$this->cropFrame($frame, $crop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cropFrame(FrameInterface $frame, SizeInterface $resizeTo): void
|
||||||
|
{
|
||||||
|
// create new image
|
||||||
|
$modified = $this->driver()
|
||||||
|
->createImage($resizeTo->width(), $resizeTo->height())
|
||||||
|
->core()
|
||||||
|
->native();
|
||||||
|
|
||||||
|
// get original image
|
||||||
|
$original = $frame->native();
|
||||||
|
|
||||||
|
// preserve transparency
|
||||||
|
$transIndex = imagecolortransparent($original);
|
||||||
|
|
||||||
|
if ($transIndex != -1) {
|
||||||
|
$rgba = imagecolorsforindex($modified, $transIndex);
|
||||||
|
$transColor = imagecolorallocatealpha($modified, $rgba['red'], $rgba['green'], $rgba['blue'], 127);
|
||||||
|
imagefill($modified, 0, 0, $transColor);
|
||||||
|
imagecolortransparent($modified, $transColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy content from resource
|
||||||
|
imagecopyresampled(
|
||||||
|
$modified,
|
||||||
|
$original,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
$resizeTo->pivot()->x() + $this->offset_x,
|
||||||
|
$resizeTo->pivot()->y() + $this->offset_y,
|
||||||
|
$resizeTo->width(),
|
||||||
|
$resizeTo->height(),
|
||||||
|
$resizeTo->width(),
|
||||||
|
$resizeTo->height(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// set new content as recource
|
||||||
|
$frame->setNative($modified);
|
||||||
|
}
|
||||||
|
}
|
67
src/Drivers/Gd/Modifiers/DrawEllipseModifier.php
Normal file
67
src/Drivers/Gd/Modifiers/DrawEllipseModifier.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDrawModifier;
|
||||||
|
use Intervention\Image\Geometry\Ellipse;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Ellipse $drawable
|
||||||
|
*/
|
||||||
|
class DrawEllipseModifier extends AbstractDrawModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
if ($this->drawable->hasBorder()) {
|
||||||
|
// slightly smaller ellipse to keep 1px bordered edges clean
|
||||||
|
if ($this->drawable->hasBackgroundColor()) {
|
||||||
|
imagefilledellipse(
|
||||||
|
$frame->native(),
|
||||||
|
$this->position()->x(),
|
||||||
|
$this->position()->y(),
|
||||||
|
$this->drawable->width() - 1,
|
||||||
|
$this->drawable->height() - 1,
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->backgroundColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gd's imageellipse ignores imagesetthickness
|
||||||
|
// so i use imagearc with 360 degrees instead.
|
||||||
|
imagesetthickness(
|
||||||
|
$frame->native(),
|
||||||
|
$this->drawable->borderSize(),
|
||||||
|
);
|
||||||
|
|
||||||
|
imagearc(
|
||||||
|
$frame->native(),
|
||||||
|
$this->position()->x(),
|
||||||
|
$this->position()->y(),
|
||||||
|
$this->drawable->width(),
|
||||||
|
$this->drawable->height(),
|
||||||
|
0,
|
||||||
|
360,
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->borderColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
imagefilledellipse(
|
||||||
|
$frame->native(),
|
||||||
|
$this->position()->x(),
|
||||||
|
$this->position()->y(),
|
||||||
|
$this->drawable->width(),
|
||||||
|
$this->drawable->height(),
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->backgroundColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
33
src/Drivers/Gd/Modifiers/DrawLineModifier.php
Normal file
33
src/Drivers/Gd/Modifiers/DrawLineModifier.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDrawModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
use Intervention\Image\Geometry\Line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method ColorInterface backgroundColor()
|
||||||
|
* @property Line $drawable
|
||||||
|
*/
|
||||||
|
class DrawLineModifier extends AbstractDrawModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
imageline(
|
||||||
|
$frame->native(),
|
||||||
|
$this->drawable->start()->x(),
|
||||||
|
$this->drawable->start()->y(),
|
||||||
|
$this->drawable->end()->x(),
|
||||||
|
$this->drawable->end()->y(),
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->backgroundColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
32
src/Drivers/Gd/Modifiers/DrawPixelModifier.php
Normal file
32
src/Drivers/Gd/Modifiers/DrawPixelModifier.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\DriverSpecializedModifier;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\PointInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property PointInterface $position
|
||||||
|
* @property mixed $color
|
||||||
|
*/
|
||||||
|
class DrawPixelModifier extends DriverSpecializedModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
$color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->driver()->handleInput($this->color)
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
imagesetpixel(
|
||||||
|
$frame->native(),
|
||||||
|
$this->position->x(),
|
||||||
|
$this->position->y(),
|
||||||
|
$color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
45
src/Drivers/Gd/Modifiers/DrawPolygonModifier.php
Normal file
45
src/Drivers/Gd/Modifiers/DrawPolygonModifier.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Drivers\AbstractDrawModifier;
|
||||||
|
use Intervention\Image\Geometry\Polygon;
|
||||||
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method Point position()
|
||||||
|
* @method ColorInterface backgroundColor()
|
||||||
|
* @method ColorInterface borderColor()
|
||||||
|
* @property Polygon $drawable
|
||||||
|
*/
|
||||||
|
class DrawPolygonModifier extends AbstractDrawModifier
|
||||||
|
{
|
||||||
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
|
{
|
||||||
|
foreach ($image as $frame) {
|
||||||
|
if ($this->drawable->hasBackgroundColor()) {
|
||||||
|
imagefilledpolygon(
|
||||||
|
$frame->native(),
|
||||||
|
$this->drawable->toArray(),
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->backgroundColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->drawable->hasBorder()) {
|
||||||
|
imagesetthickness($frame->native(), $this->drawable->borderSize());
|
||||||
|
imagepolygon(
|
||||||
|
$frame->native(),
|
||||||
|
$this->drawable->toArray(),
|
||||||
|
$this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||||
|
$this->borderColor()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user