diff --git a/.example.env b/.example.env
index aa607c9c5..5a9d116ba 100644
--- a/.example.env
+++ b/.example.env
@@ -1 +1,88 @@
-APP_DEBUG=true
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=likeadmin
DB_USERNAME=root
DB_PASSWORD=root
SESSION_DRIVER=file
REDIS_CONNECTION=default
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=24
YLY_PARTNER=25991
YLY_API_KEY=d955cc2296d69b4094c6465aad360dc6b19a8c77
YLY_REQUEST_URL=http://open.10ss.net:8888
UNIQUE_IDENTIFICATION = "11d3"
DEMO_ENV = ""
\ No newline at end of file
+APP_DEBUG=true
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=likeadmin
+DB_USERNAME=root
+DB_PASSWORD=root
+
+SESSION_DRIVER=file
+
+REDIS_CONNECTION=default
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+REDIS_DB=24
+
+YLY_PARTNER=25991
+YLY_API_KEY=d955cc2296d69b4094c6465aad360dc6b19a8c77
+YLY_REQUEST_URL=http://open.10ss.net:8888
+
+UNIQUE_IDENTIFICATION = "11d3"
+DEMO_ENV = ""
+
+ $manager = new ImageManager(
+ Driver::class
+ );
+ // open an image file
+ $image = $manager->create(250,300)->fill('#ffffff');
+ $image->text('莲花池农贸市场', 125, 30, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ $font->align('center'); // 设置文字对齐方式,这里以居中对齐为例
+ });
+ $image->text('单号:PF1717393175823834', 10, 60, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+
+ $image->text('下单时间:2024-5-15 18:00:35', 10, 75, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('================================', 10, 90, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('单价', 10, 105, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('数量', 115, 105, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('小计', 210, 105, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('白菜大白菜', 10, 120, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('236.60元', 10, 135, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('13KG', 115, 135, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('12896.34元', 180, 135, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('================================', 10, 145, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('应付款30.01元', 10, 155, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('实付款30.01元', 10, 170, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('支付方式:微信支付', 10, 185, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('支付单号:fn1717552219113289171', 10, 200, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('联系电话:0830-2669767', 10, 215, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('================================', 10, 230, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
+ $image->text('欢迎下次光临!', 85, 260, function ($font) {
+ $font->file(runtime_path() . '/ali.ttf'); // 设置字体文件路径,这里以微软雅黑字体为例
+ });
\ No newline at end of file
diff --git a/app/admin/controller/store_branch_product/StoreBranchProductController.php b/app/admin/controller/store_branch_product/StoreBranchProductController.php
index b54108a79..e7706f37b 100644
--- a/app/admin/controller/store_branch_product/StoreBranchProductController.php
+++ b/app/admin/controller/store_branch_product/StoreBranchProductController.php
@@ -7,7 +7,7 @@ use app\admin\controller\BaseAdminController;
use app\admin\lists\store_product\StoreProductLists;
use app\admin\logic\store_product\StoreProductLogic;
use app\admin\validate\store_product\StoreProductValidate;
-
+use app\common\model\store_branch_product\StoreBranchProduct;
/**
* 门店商品辅助控制器
@@ -65,7 +65,32 @@ class StoreBranchProductController extends BaseAdminController
return $this->success('编辑成功', [], 1, 1);
}
-
+ /**
+ * @notes 编辑门店商品辅助价格
+ * @return \think\response\Json
+ * @author likeadmin
+ * @date 2024/05/31 10:53
+ */
+ public function edit_price()
+ {
+ $params =$this->request->post();
+ $res= StoreBranchProduct::where('id',$params['id'])->update(['price'=>$params['price']]);
+ if($res){
+ return $this->success('编辑成功', [], 1, 1);
+ }else{
+ return $this->fail('编辑失败', [], 1, 1);
+ }
+ }
+ /**
+ * @notes 编辑门店商品辅助库存
+ * @return \think\response\Json
+ * @author likeadmin
+ * @date 2024/05/31 10:53
+ */
+ public function edit_stock()
+ {
+ return $this->success('编辑成功', [], 1, 1);
+ }
/**
* @notes 删除门店商品辅助列表
* @return \think\response\Json
diff --git a/app/admin/lists/store_finance_flow/StoreFinanceFlowLists.php b/app/admin/lists/store_finance_flow/StoreFinanceFlowLists.php
index f41563b43..6cdb9bff9 100644
--- a/app/admin/lists/store_finance_flow/StoreFinanceFlowLists.php
+++ b/app/admin/lists/store_finance_flow/StoreFinanceFlowLists.php
@@ -8,6 +8,7 @@ use app\common\enum\OrderEnum;
use app\common\enum\PayEnum;
use app\common\model\store_finance_flow\StoreFinanceFlow;
use app\common\lists\ListsSearchInterface;
+use app\common\model\system_store\SystemStore;
use app\common\model\system_store\SystemStoreStaff;
use app\common\model\user\User;
@@ -29,7 +30,7 @@ class StoreFinanceFlowLists extends BaseAdminDataLists implements ListsSearchInt
public function setSearch(): array
{
return [
- '=' => ['store_id', 'user_id', 'create_time'],
+ '=' => ['store_id', 'uid', 'create_time'],
];
}
@@ -46,20 +47,37 @@ class StoreFinanceFlowLists extends BaseAdminDataLists implements ListsSearchInt
public function lists(): array
{
return StoreFinanceFlow::where($this->searchWhere)
+ ->when(!empty($this->params['start_time']), function ($query) {
+ $query->whereTime('create_time', '>=', $this->params['start_time']);
+ })
+ ->when(!empty($this->params['end_time']), function ($query) {
+ if ($this->params['end_time'] == $this->params['start_time']) {
+ $this->params['end_time'] = strtotime($this->params['end_time']) + 86399;
+ }
+ $query->whereTime('create_time', '<=', $this->params['end_time']);
+ })
+ ->when(!empty($this->request->adminInfo['store_id']), function ($query) {
+ $query->where('store_id', '=', $this->request->adminInfo['store_id']);
+ })
->limit($this->limitOffset, $this->limitLength)
->order(['id' => 'desc'])
->select()->each(function ($item) {
if($item['user_id']<=0){
$item['nickname']='游客';
}else{
- $item['nickname']=User::where('id',$item['user_id'])->value('nickname');
+ $id=$item['user_id'];
+ $item['nickname']=User::where('id',$item['user_id'])->value('nickname')."($id)";
}
- if($item['financial_pm']==0){
- $item['number']='-'.$item['number'];
- }else{
- $item['number']='+'.$item['number'];
+ if (!empty($this->request->adminInfo['store_id'])) {
+ $item['financial_pm'] = $item['financial_pm'] == 0 ? 1 : 0;
+ }
+ if ($item['financial_pm'] == 0) {
+ $item['number'] = '-' . $item['number'];
+ } else {
+ $item['number'] = '+' . $item['number'];
}
$item['staff_name']=SystemStoreStaff::where('id',$item['staff_id'])->value('staff_name');
+ $item['store_name']=$item['store_id']>0?SystemStore::where('id',$item['store_id'])->value('name'):'';
$item['pay_type_name']=PayEnum::getPaySceneDesc($item['pay_type']);
$item['financial_type_name']=OrderEnum::getFinancialType($item['financial_type']);
})
@@ -78,4 +96,4 @@ class StoreFinanceFlowLists extends BaseAdminDataLists implements ListsSearchInt
return StoreFinanceFlow::where($this->searchWhere)->count();
}
-}
\ No newline at end of file
+}
diff --git a/app/admin/lists/system_store/SystemStoreLists.php b/app/admin/lists/system_store/SystemStoreLists.php
index e10aaa4fc..6fa15c6e3 100644
--- a/app/admin/lists/system_store/SystemStoreLists.php
+++ b/app/admin/lists/system_store/SystemStoreLists.php
@@ -26,7 +26,8 @@ class SystemStoreLists extends BaseAdminDataLists implements ListsSearchInterfac
public function setSearch(): array
{
return [
- '=' => ['name', 'phone'],
+ '=' => ['phone'],
+ '%like%'=>['name']
];
}
diff --git a/app/common/logic/PayNotifyLogic.php b/app/common/logic/PayNotifyLogic.php
index d30c7de4e..0a38dcec8 100644
--- a/app/common/logic/PayNotifyLogic.php
+++ b/app/common/logic/PayNotifyLogic.php
@@ -151,7 +151,6 @@ class PayNotifyLogic extends BaseLogic
if (!$order->save()) {
throw new \Exception('订单保存出错');
}
- self::afterPay($order);
}
/**
@@ -204,9 +203,9 @@ class PayNotifyLogic extends BaseLogic
$financeLogic->user = ['uid' => $order['uid']];
if ($order->pay_type != 9 || $order->pay_type != 10) {
$financeLogic->in($transaction_id,$order['pay_price'], OrderEnum::USER_ORDER_PAY);
+ $financeLogic->out($transaction_id,$order['pay_price'], OrderEnum::MERCHANT_ORDER_OBTAINS, $order['store_id'], $order['staff_id'], 0);
+ $financeLogic->save();
}
- $financeLogic->out($transaction_id,$order['pay_price'], OrderEnum::MERCHANT_ORDER_OBTAINS, $order['store_id'], $order['staff_id'], 0);
- $financeLogic->save();
}
//等级处理
diff --git a/app/common/logic/StoreFinanceFlowLogic.php b/app/common/logic/StoreFinanceFlowLogic.php
index 838abece3..7a4c6c607 100644
--- a/app/common/logic/StoreFinanceFlowLogic.php
+++ b/app/common/logic/StoreFinanceFlowLogic.php
@@ -26,7 +26,7 @@ class StoreFinanceFlowLogic extends BaseLogic
*/
public function out($transaction_id,$number, $financialType, $storeId = 0, $staffId = 0, $status = 1)
{
- $this->setData($number, $financialType, 1, $storeId, $staffId, $status,$transaction_id);
+ $this->setData($number, $financialType, 0, $storeId, $staffId, $status,$transaction_id);
}
/**
@@ -40,7 +40,7 @@ class StoreFinanceFlowLogic extends BaseLogic
*/
public function in($transaction_id,$number, $financialType, $storeId = 0, $staffId = 0, $status = 1)
{
- $this->setData($number, $financialType, 0, $storeId, $staffId, $status,$transaction_id);
+ $this->setData($number, $financialType, 1, $storeId, $staffId, $status,$transaction_id);
}
public function setData($number, $financialType, $pm, $storeId, $staffId, $status,$transaction_id)
diff --git a/app/store/controller/finance/FinanceController.php b/app/store/controller/finance/FinanceController.php
new file mode 100644
index 000000000..99e20b9ed
--- /dev/null
+++ b/app/store/controller/finance/FinanceController.php
@@ -0,0 +1,51 @@
+ 'id', 'desc' => 'ID', 'type' => 'int'],
+ ['name' => 'order_id', 'desc' => '订单编号', 'type' => 'string'],
+ ['name' => 'pay_price', 'desc' => '支付金额', 'type' => 'string'],
+ ['name' => 'pay_time', 'desc' => '支付时间', 'type' => 'float'],
+ ['name' => 'pay_type', 'desc' => '支付方式', 'type' => 'float'],
+ ['name' => 'status_name', 'desc' => '状态', 'type' => 'int'],
+ ['name' => 'staff_name', 'desc' => '店员', 'type' => 'int'],
+ ['name' => 'nickname', 'desc' => '用户昵称', 'type' => 'string'],
+ ['name' => 'avatar', 'desc' => '用户头像', 'type' => 'string'],
+ ['name' => 'product', 'desc' => '商品信息', 'type' => 'array', 'children' => [
+ ['name' => 'cart_info', 'desc' => '商品信息', 'type' => 'array', 'children' => [
+ ['name' => 'name', 'desc' => '商品名称', 'type' => 'int'],
+ ['name' => 'image', 'desc' => '图片', 'type' => 'string'],
+ ['name' => 'cart_num', 'desc' => '购买数量', 'type' => 'string'],
+ ['name' => 'price', 'desc' => '单价', 'type' => 'string'],
+ ]],
+ ]],
+ ]),
+ ]
+ public function lists()
+ {
+ return $this->dataLists(new StoreFinanceFlowLists());
+ }
+
+}
diff --git a/composer.json b/composer.json
index dc009c264..e99957382 100644
--- a/composer.json
+++ b/composer.json
@@ -54,7 +54,8 @@
"ext-bcmath": "*",
"jpush/jpush": "^3.6",
"workerman/crontab": "^1.0",
- "hg/apidoc": "^5.2"
+ "hg/apidoc": "^5.2",
+ "intervention/image": "^3.6"
},
"suggest": {
"ext-event": "For better performance. "
diff --git a/composer.lock b/composer.lock
index 299054553..c1bf8c8a8 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "1af6ecc42a475bea0bb2a849fdce3e61",
+ "content-hash": "dbe3251ac873990fcca149094effefa5",
"packages": [
{
"name": "aliyuncs/oss-sdk-php",
@@ -1953,6 +1953,154 @@
},
"time": "2024-04-07T17:47:33+00:00"
},
+ {
+ "name": "intervention/gif",
+ "version": "4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Intervention/gif.git",
+ "reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Intervention/gif/zipball/3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
+ "reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1",
+ "phpunit/phpunit": "^10.0",
+ "slevomat/coding-standard": "~8.0",
+ "squizlabs/php_codesniffer": "^3.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Gif\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@intervention.io",
+ "homepage": "https://intervention.io/"
+ }
+ ],
+ "description": "Native PHP GIF Encoder/Decoder",
+ "homepage": "https://github.com/intervention/gif",
+ "keywords": [
+ "animation",
+ "gd",
+ "gif",
+ "image"
+ ],
+ "support": {
+ "issues": "https://github.com/Intervention/gif/issues",
+ "source": "https://github.com/Intervention/gif/tree/4.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/interventionio",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/Intervention",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-26T17:23:47+00:00"
+ },
+ {
+ "name": "intervention/image",
+ "version": "3.6.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Intervention/image.git",
+ "reference": "193324ec88bc5ad4039e57ce9b926ae28dfde813"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/193324ec88bc5ad4039e57ce9b926ae28dfde813",
+ "reference": "193324ec88bc5ad4039e57ce9b926ae28dfde813",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "intervention/gif": "^4.1",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.6",
+ "phpstan/phpstan": "^1",
+ "phpunit/phpunit": "^10.0",
+ "slevomat/coding-standard": "~8.0",
+ "squizlabs/php_codesniffer": "^3.8"
+ },
+ "suggest": {
+ "ext-exif": "Recommended to be able to read EXIF data properly."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Image\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@intervention.io",
+ "homepage": "https://intervention.io/"
+ }
+ ],
+ "description": "PHP image manipulation",
+ "homepage": "https://image.intervention.io/",
+ "keywords": [
+ "gd",
+ "image",
+ "imagick",
+ "resize",
+ "thumbnail",
+ "watermark"
+ ],
+ "support": {
+ "issues": "https://github.com/Intervention/image/issues",
+ "source": "https://github.com/Intervention/image/tree/3.6.4"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/interventionio",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/Intervention",
+ "type": "github"
+ }
+ ],
+ "time": "2024-05-08T13:53:15+00:00"
+ },
{
"name": "jpush/jpush",
"version": "v3.6.8",
@@ -7065,5 +7213,5 @@
"ext-bcmath": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.3.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index bfdc3ef26..01441bb1d 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -81,6 +81,8 @@ return array(
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'JPush\\' => array($vendorDir . '/jpush/jpush/src/JPush'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
+ 'Intervention\\Image\\' => array($vendorDir . '/intervention/image/src'),
+ 'Intervention\\Gif\\' => array($vendorDir . '/intervention/gif/src'),
'Illuminate\\Support\\' => array($vendorDir . '/illuminate/collections', $vendorDir . '/illuminate/conditionable', $vendorDir . '/illuminate/macroable', $vendorDir . '/illuminate/support'),
'Illuminate\\Redis\\' => array($vendorDir . '/illuminate/redis'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 8b40842c0..46e248865 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -172,6 +172,8 @@ class ComposerStaticInitcefecbcff919f3c1c8084830bbb72adc
'I' =>
array (
'Invoker\\' => 8,
+ 'Intervention\\Image\\' => 19,
+ 'Intervention\\Gif\\' => 17,
'Illuminate\\Support\\' => 19,
'Illuminate\\Redis\\' => 17,
'Illuminate\\Contracts\\' => 21,
@@ -529,6 +531,14 @@ class ComposerStaticInitcefecbcff919f3c1c8084830bbb72adc
array (
0 => __DIR__ . '/..' . '/php-di/invoker/src',
),
+ 'Intervention\\Image\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/intervention/image/src',
+ ),
+ 'Intervention\\Gif\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/intervention/gif/src',
+ ),
'Illuminate\\Support\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/collections',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 83a6fd32b..99c51c0ea 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -2031,6 +2031,160 @@
},
"install-path": "../illuminate/support"
},
+ {
+ "name": "intervention/gif",
+ "version": "4.1.0",
+ "version_normalized": "4.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Intervention/gif.git",
+ "reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Intervention/gif/zipball/3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
+ "reference": "3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1",
+ "phpunit/phpunit": "^10.0",
+ "slevomat/coding-standard": "~8.0",
+ "squizlabs/php_codesniffer": "^3.8"
+ },
+ "time": "2024-03-26T17:23:47+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Gif\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@intervention.io",
+ "homepage": "https://intervention.io/"
+ }
+ ],
+ "description": "Native PHP GIF Encoder/Decoder",
+ "homepage": "https://github.com/intervention/gif",
+ "keywords": [
+ "animation",
+ "gd",
+ "gif",
+ "image"
+ ],
+ "support": {
+ "issues": "https://github.com/Intervention/gif/issues",
+ "source": "https://github.com/Intervention/gif/tree/4.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/interventionio",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/Intervention",
+ "type": "github"
+ }
+ ],
+ "install-path": "../intervention/gif"
+ },
+ {
+ "name": "intervention/image",
+ "version": "3.6.4",
+ "version_normalized": "3.6.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Intervention/image.git",
+ "reference": "193324ec88bc5ad4039e57ce9b926ae28dfde813"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/193324ec88bc5ad4039e57ce9b926ae28dfde813",
+ "reference": "193324ec88bc5ad4039e57ce9b926ae28dfde813",
+ "shasum": "",
+ "mirrors": [
+ {
+ "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
+ "preferred": true
+ }
+ ]
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "intervention/gif": "^4.1",
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.6",
+ "phpstan/phpstan": "^1",
+ "phpunit/phpunit": "^10.0",
+ "slevomat/coding-standard": "~8.0",
+ "squizlabs/php_codesniffer": "^3.8"
+ },
+ "suggest": {
+ "ext-exif": "Recommended to be able to read EXIF data properly."
+ },
+ "time": "2024-05-08T13:53:15+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Image\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@intervention.io",
+ "homepage": "https://intervention.io/"
+ }
+ ],
+ "description": "PHP image manipulation",
+ "homepage": "https://image.intervention.io/",
+ "keywords": [
+ "gd",
+ "image",
+ "imagick",
+ "resize",
+ "thumbnail",
+ "watermark"
+ ],
+ "support": {
+ "issues": "https://github.com/Intervention/image/issues",
+ "source": "https://github.com/Intervention/image/tree/3.6.4"
+ },
+ "funding": [
+ {
+ "url": "https://paypal.me/interventionio",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/Intervention",
+ "type": "github"
+ }
+ ],
+ "install-path": "../intervention/image"
+ },
{
"name": "jpush/jpush",
"version": "v3.6.8",
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 383e159a5..412a0844b 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -1,9 +1,9 @@
array(
'name' => 'workerman/webman',
- 'pretty_version' => 'dev-main',
- 'version' => 'dev-main',
- 'reference' => '272597cc5c5f8a88f8124e91ea19bf8217a25a58',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -262,6 +262,24 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'intervention/gif' => array(
+ 'pretty_version' => '4.1.0',
+ 'version' => '4.1.0.0',
+ 'reference' => '3a2b5f8a8856e8877cdab5c47e51aab2d4cb23a3',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../intervention/gif',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'intervention/image' => array(
+ 'pretty_version' => '3.6.4',
+ 'version' => '3.6.4.0',
+ 'reference' => '193324ec88bc5ad4039e57ce9b926ae28dfde813',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../intervention/image',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'jpush/jpush' => array(
'pretty_version' => 'v3.6.8',
'version' => '3.6.8.0',
@@ -1011,9 +1029,9 @@
'dev_requirement' => false,
),
'workerman/webman' => array(
- 'pretty_version' => 'dev-main',
- 'version' => 'dev-main',
- 'reference' => '272597cc5c5f8a88f8124e91ea19bf8217a25a58',
+ 'pretty_version' => '1.0.0+no-version-set',
+ 'version' => '1.0.0.0',
+ 'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
index 4c3a5d68f..f71b2f899 100644
--- a/vendor/composer/platform_check.php
+++ b/vendor/composer/platform_check.php
@@ -8,6 +8,10 @@ if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}
+if (PHP_INT_SIZE !== 8) {
+ $issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
+}
+
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
diff --git a/vendor/intervention/gif/LICENSE b/vendor/intervention/gif/LICENSE
new file mode 100644
index 000000000..85441dc8e
--- /dev/null
+++ b/vendor/intervention/gif/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2020-2024 Oliver Vogel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/intervention/gif/README.md b/vendor/intervention/gif/README.md
new file mode 100644
index 000000000..bf769d33c
--- /dev/null
+++ b/vendor/intervention/gif/README.md
@@ -0,0 +1,100 @@
+# Intervention GIF
+## Native PHP GIF Encoder/Decoder
+
+[](https://packagist.org/packages/intervention/gif)
+
+[](https://packagist.org/packages/intervention/gif/stats)
+
+Intervention GIF is a PHP encoder and decoder for the GIF image format that
+does not depend on any image processing extension.
+
+Only the special `Splitter::class` class divides the data stream of an animated
+GIF into individual `GDImage` objects for each frame and is therefore dependent
+on the GD library.
+
+The library is the main component of [Intervention
+Image](https://github.com/Intervention/image) for processing animated GIF files
+with the GD library, but also works independently.
+
+## Installation
+
+You can easily install this package using [Composer](https://getcomposer.org).
+Just request the package with the following command:
+
+```bash
+composer require intervention/gif
+```
+
+## Code Examples
+
+### Decoding
+
+```php
+use Intervention\Gif\Decoder;
+
+// Decode filepath to Intervention\Gif\GifDataStream::class
+$gif = Decoder::decode('images/animation.gif');
+
+// Decoder can also handle binary content directly
+$gif = Decoder::decode($contents);
+```
+
+### Encoding
+
+Use the Builder class to create a new GIF image.
+
+```php
+use Intervention\Gif\Builder;
+
+// create new gif canvas
+$gif = Builder::canvas(width: 32, height: 32);
+
+// add animation frames to canvas
+$delay = .25; // delay in seconds after next frame is displayed
+$left = 0; // position offset (left)
+$top = 0; // position offset (top)
+
+// add animation frames with optional delay in seconds
+// and optional position offset for each frame
+$gif->addFrame('images/frame01.gif', $delay, $left, $top);
+$gif->addFrame('images/frame02.gif', $delay, $left);
+$gif->addFrame('images/frame03.gif', $delay);
+$gif->addFrame('images/frame04.gif');
+
+// set loop count; 0 for infinite looping
+$gif->setLoops(12);
+
+// encode
+$data = $gif->encode();
+```
+
+
+## Requirements
+
+- PHP >= 8.1
+
+## 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
+```
+
+## Authors
+
+This library is developed and maintained by [Oliver Vogel](https://intervention.io)
+
+Thanks to the community of [contributors](https://github.com/Intervention/gif/graphs/contributors) who have helped to improve this project.
+
+## License
+
+Intervention GIF is licensed under the [MIT License](LICENSE).
diff --git a/vendor/intervention/gif/composer.json b/vendor/intervention/gif/composer.json
new file mode 100644
index 000000000..e61c9c6ff
--- /dev/null
+++ b/vendor/intervention/gif/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "intervention/gif",
+ "description": "Native PHP GIF Encoder/Decoder",
+ "homepage": "https://github.com/intervention/gif",
+ "keywords": [
+ "image",
+ "gd",
+ "gif",
+ "animation"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@intervention.io",
+ "homepage": "https://intervention.io/"
+ }
+ ],
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "phpstan/phpstan": "^1",
+ "squizlabs/php_codesniffer": "^3.8",
+ "slevomat/coding-standard": "~8.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Gif\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Intervention\\Gif\\Tests\\": "tests"
+ }
+ },
+ "minimum-stability": "stable",
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ }
+}
diff --git a/vendor/intervention/gif/phpunit.xml.dist b/vendor/intervention/gif/phpunit.xml.dist
new file mode 100644
index 000000000..6a2bf0730
--- /dev/null
+++ b/vendor/intervention/gif/phpunit.xml.dist
@@ -0,0 +1,13 @@
+
+
+
+
+ ./tests/Unit/
+
+
+
+
+ src
+
+
+
diff --git a/vendor/intervention/gif/src/AbstractEntity.php b/vendor/intervention/gif/src/AbstractEntity.php
new file mode 100644
index 000000000..2be7eb991
--- /dev/null
+++ b/vendor/intervention/gif/src/AbstractEntity.php
@@ -0,0 +1,37 @@
+getShortName();
+ }
+
+ /**
+ * Cast object to string
+ *
+ * @return string
+ */
+ public function __toString(): string
+ {
+ return $this->encode();
+ }
+}
diff --git a/vendor/intervention/gif/src/AbstractExtension.php b/vendor/intervention/gif/src/AbstractExtension.php
new file mode 100644
index 000000000..329ac5019
--- /dev/null
+++ b/vendor/intervention/gif/src/AbstractExtension.php
@@ -0,0 +1,10 @@
+application);
+ }
+
+ public function setApplication(string $value): self
+ {
+ $this->application = $value;
+
+ return $this;
+ }
+
+ public function getApplication(): string
+ {
+ return $this->application;
+ }
+
+ public function addBlock(DataSubBlock $block): self
+ {
+ $this->blocks[] = $block;
+
+ return $this;
+ }
+
+ public function setBlocks(array $blocks): self
+ {
+ $this->blocks = $blocks;
+
+ return $this;
+ }
+
+ public function getBlocks(): array
+ {
+ return $this->blocks;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/Color.php b/vendor/intervention/gif/src/Blocks/Color.php
new file mode 100644
index 000000000..368160b2c
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/Color.php
@@ -0,0 +1,100 @@
+r;
+ }
+
+ /**
+ * Set red value
+ *
+ * @param int $value
+ */
+ public function setRed(int $value): self
+ {
+ $this->r = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get green value
+ *
+ * @return int
+ */
+ public function getGreen()
+ {
+ return $this->g;
+ }
+
+ /**
+ * Set green value
+ *
+ * @param int $value
+ */
+ public function setGreen(int $value): self
+ {
+ $this->g = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get blue value
+ *
+ * @return int
+ */
+ public function getBlue()
+ {
+ return $this->b;
+ }
+
+ /**
+ * Set blue value
+ *
+ * @param int $value
+ */
+ public function setBlue(int $value): self
+ {
+ $this->b = $value;
+
+ return $this;
+ }
+
+ /**
+ * Return hash value of current color
+ *
+ * @return string
+ */
+ public function getHash(): string
+ {
+ return md5($this->r . $this->g . $this->b);
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/ColorTable.php b/vendor/intervention/gif/src/Blocks/ColorTable.php
new file mode 100644
index 000000000..c80daf14c
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/ColorTable.php
@@ -0,0 +1,151 @@
+colors);
+ }
+
+ /**
+ * Add color to table
+ *
+ * @param int $r
+ * @param int $g
+ * @param int $b
+ */
+ public function addRgb(int $r, int $g, int $b): self
+ {
+ $this->addColor(new Color($r, $g, $b));
+
+ return $this;
+ }
+
+ /**
+ * Add color to table
+ *
+ * @param Color $color
+ */
+ public function addColor(Color $color): self
+ {
+ $this->colors[] = $color;
+
+ return $this;
+ }
+
+ /**
+ * Reset colors to array of color objects
+ *
+ * @param array $colors
+ */
+ public function setColors(array $colors): self
+ {
+ $this->empty();
+ foreach ($colors as $color) {
+ $this->addColor($color);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Count colors of current instance
+ *
+ * @return int
+ */
+ public function countColors(): int
+ {
+ return count($this->colors);
+ }
+
+ /**
+ * Determine if any colors are present on the current table
+ *
+ * @return bool
+ */
+ public function hasColors(): bool
+ {
+ return $this->countColors() >= 1;
+ }
+
+ /**
+ * Empty color table
+ *
+ * @return self
+ */
+ public function empty(): self
+ {
+ $this->colors = [];
+
+ return $this;
+ }
+
+ /**
+ * Get size of color table in logical screen descriptor
+ *
+ * @return int
+ */
+ public function getLogicalSize(): int
+ {
+ switch ($this->countColors()) {
+ case 4:
+ return 1;
+
+ case 8:
+ return 2;
+
+ case 16:
+ return 3;
+
+ case 32:
+ return 4;
+
+ case 64:
+ return 5;
+
+ case 128:
+ return 6;
+
+ case 256:
+ return 7;
+
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Calculate the number of bytes contained by the current table
+ *
+ * @return int
+ */
+ public function getByteSize(): int
+ {
+ if (!$this->hasColors()) {
+ return 0;
+ }
+
+ return 3 * pow(2, $this->getLogicalSize() + 1);
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/CommentExtension.php b/vendor/intervention/gif/src/Blocks/CommentExtension.php
new file mode 100644
index 000000000..a6ddbdfb6
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/CommentExtension.php
@@ -0,0 +1,52 @@
+comments;
+ }
+
+ /**
+ * Get one comment by key
+ *
+ * @param int $key
+ * @return mixed
+ */
+ public function getComment(int $key): mixed
+ {
+ return array_key_exists($key, $this->comments) ? $this->comments[$key] : null;
+ }
+
+ /**
+ * Set comment text
+ *
+ * @param string $value
+ */
+ public function addComment(string $value): self
+ {
+ $this->comments[] = $value;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/DataSubBlock.php b/vendor/intervention/gif/src/Blocks/DataSubBlock.php
new file mode 100644
index 000000000..4c2f618a9
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/DataSubBlock.php
@@ -0,0 +1,30 @@
+getSize() > 255) {
+ throw new FormatException(
+ 'Data Sub-Block can not have a block size larger than 255 bytes.'
+ );
+ }
+ }
+
+ public function getSize(): int
+ {
+ return strlen($this->value);
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/FrameBlock.php b/vendor/intervention/gif/src/Blocks/FrameBlock.php
new file mode 100644
index 000000000..acfc7bbe4
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/FrameBlock.php
@@ -0,0 +1,175 @@
+setTableBasedImage($entity);
+ break;
+
+ case $entity instanceof GraphicControlExtension:
+ $this->setGraphicControlExtension($entity);
+ break;
+
+ case $entity instanceof ImageDescriptor:
+ $this->setImageDescriptor($entity);
+ break;
+
+ case $entity instanceof ColorTable:
+ $this->setColorTable($entity);
+ break;
+
+ case $entity instanceof ImageData:
+ $this->setImageData($entity);
+ break;
+
+ case $entity instanceof PlainTextExtension:
+ $this->setPlainTextExtension($entity);
+ break;
+
+ case $entity instanceof NetscapeApplicationExtension:
+ $this->addApplicationExtension($entity);
+ break;
+
+ case $entity instanceof ApplicationExtension:
+ $this->addApplicationExtension($entity);
+ break;
+
+ case $entity instanceof CommentExtension:
+ $this->addCommentExtension($entity);
+ break;
+ }
+
+ return $this;
+ }
+
+ public function getApplicationExtensions(): array
+ {
+ return $this->applicationExtensions;
+ }
+
+ public function getCommentExtensions(): array
+ {
+ return $this->commentExtensions;
+ }
+
+ public function setGraphicControlExtension(GraphicControlExtension $extension): self
+ {
+ $this->graphicControlExtension = $extension;
+
+ return $this;
+ }
+
+ public function getGraphicControlExtension(): ?GraphicControlExtension
+ {
+ return $this->graphicControlExtension;
+ }
+
+ public function setImageDescriptor(ImageDescriptor $descriptor): self
+ {
+ $this->imageDescriptor = $descriptor;
+ return $this;
+ }
+
+ public function getImageDescriptor(): ImageDescriptor
+ {
+ return $this->imageDescriptor;
+ }
+
+ public function setColorTable(ColorTable $table): self
+ {
+ $this->colorTable = $table;
+
+ return $this;
+ }
+
+ public function getColorTable(): ?ColorTable
+ {
+ return $this->colorTable;
+ }
+
+ public function hasColorTable(): bool
+ {
+ return !is_null($this->colorTable);
+ }
+
+ public function setImageData(ImageData $data): self
+ {
+ $this->imageData = $data;
+
+ return $this;
+ }
+
+ public function getImageData(): ImageData
+ {
+ return $this->imageData;
+ }
+
+ public function setPlainTextExtension(PlainTextExtension $extension): self
+ {
+ $this->plainTextExtension = $extension;
+
+ return $this;
+ }
+
+ public function getPlainTextExtension(): ?PlainTextExtension
+ {
+ return $this->plainTextExtension;
+ }
+
+ public function addApplicationExtension(ApplicationExtension $extension): self
+ {
+ $this->applicationExtensions[] = $extension;
+
+ return $this;
+ }
+
+ public function addCommentExtension(CommentExtension $extension): self
+ {
+ $this->commentExtensions[] = $extension;
+
+ return $this;
+ }
+
+ public function getNetscapeExtension(): ?NetscapeApplicationExtension
+ {
+ $extensions = array_filter($this->applicationExtensions, function ($extension) {
+ return $extension instanceof NetscapeApplicationExtension;
+ });
+
+ return count($extensions) ? reset($extensions) : null;
+ }
+
+ public function setTableBasedImage(TableBasedImage $tableBasedImage): self
+ {
+ $this->setImageDescriptor($tableBasedImage->getImageDescriptor());
+
+ if ($colorTable = $tableBasedImage->getColorTable()) {
+ $this->setColorTable($colorTable);
+ }
+
+ $this->setImageData($tableBasedImage->getImageData());
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/GraphicControlExtension.php b/vendor/intervention/gif/src/Blocks/GraphicControlExtension.php
new file mode 100644
index 000000000..f95e15af0
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/GraphicControlExtension.php
@@ -0,0 +1,159 @@
+delay = $value;
+
+ return $this;
+ }
+
+ /**
+ * Return delay time (1/100 second)
+ *
+ * @return int
+ */
+ public function getDelay(): int
+ {
+ return $this->delay;
+ }
+
+ /**
+ * Set disposal method
+ *
+ * @param DisposalMethod $method
+ * @return self
+ */
+ public function setDisposalMethod(DisposalMethod $method): self
+ {
+ $this->disposalMethod = $method;
+
+ return $this;
+ }
+
+ /**
+ * Get disposal method
+ *
+ * @return DisposalMethod
+ */
+ public function getDisposalMethod(): DisposalMethod
+ {
+ return $this->disposalMethod;
+ }
+
+ /**
+ * Get transparent color index
+ *
+ * @return int
+ */
+ public function getTransparentColorIndex(): int
+ {
+ return $this->transparentColorIndex;
+ }
+
+ /**
+ * Set transparent color index
+ *
+ * @param int $index
+ */
+ public function setTransparentColorIndex(int $index): self
+ {
+ $this->transparentColorIndex = $index;
+
+ return $this;
+ }
+
+ /**
+ * Get current transparent color existance
+ *
+ * @return bool
+ */
+ public function getTransparentColorExistance(): bool
+ {
+ return $this->transparentColorExistance;
+ }
+
+ /**
+ * Set existance flag of transparent color
+ *
+ * @param bool $existance
+ */
+ public function setTransparentColorExistance(bool $existance = true): self
+ {
+ $this->transparentColorExistance = $existance;
+
+ return $this;
+ }
+
+ /**
+ * Get user input flag
+ *
+ * @return bool
+ */
+ public function getUserInput(): bool
+ {
+ return $this->userInput;
+ }
+
+ /**
+ * Set user input flag
+ *
+ * @param bool $value
+ */
+ public function setUserInput(bool $value = true): self
+ {
+ $this->userInput = $value;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/Header.php b/vendor/intervention/gif/src/Blocks/Header.php
new file mode 100644
index 000000000..ee561b91a
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/Header.php
@@ -0,0 +1,42 @@
+version = $value;
+
+ return $this;
+ }
+
+ /**
+ * Return current version
+ *
+ * @return string
+ */
+ public function getVersion(): string
+ {
+ return $this->version;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/ImageData.php b/vendor/intervention/gif/src/Blocks/ImageData.php
new file mode 100644
index 000000000..45ada6010
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/ImageData.php
@@ -0,0 +1,80 @@
+lzw_min_code_size;
+ }
+
+ /**
+ * Set lzw min. code size
+ *
+ * @param int $size
+ * @return ImageData
+ */
+ public function setLzwMinCodeSize(int $size): self
+ {
+ $this->lzw_min_code_size = $size;
+
+ return $this;
+ }
+
+ /**
+ * Get current data sub blocks
+ *
+ * @return array
+ */
+ public function getBlocks(): array
+ {
+ return $this->blocks;
+ }
+
+ /**
+ * Addd sub block
+ *
+ * @param DataSubBlock $block
+ * @return ImageData
+ */
+ public function addBlock(DataSubBlock $block): self
+ {
+ $this->blocks[] = $block;
+
+ return $this;
+ }
+
+ /**
+ * Determine if data sub blocks are present
+ *
+ * @return bool
+ */
+ public function hasBlocks(): bool
+ {
+ return count($this->blocks) >= 1;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/ImageDescriptor.php b/vendor/intervention/gif/src/Blocks/ImageDescriptor.php
new file mode 100644
index 000000000..22482caa4
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/ImageDescriptor.php
@@ -0,0 +1,246 @@
+width);
+ }
+
+ /**
+ * Get current width
+ *
+ * @return int
+ */
+ public function getHeight(): int
+ {
+ return intval($this->height);
+ }
+
+ /**
+ * Get current Top
+ *
+ * @return int
+ */
+ public function getTop(): int
+ {
+ return intval($this->top);
+ }
+
+ /**
+ * Get current Left
+ *
+ * @return int
+ */
+ public function getLeft(): int
+ {
+ return intval($this->left);
+ }
+
+ /**
+ * Set size of current instance
+ *
+ * @param int $width
+ * @param int $height
+ */
+ public function setSize(int $width, int $height): self
+ {
+ $this->width = $width;
+ $this->height = $height;
+
+ return $this;
+ }
+
+ /**
+ * Set position of current instance
+ *
+ * @param int $left
+ * @param int $top
+ */
+ public function setPosition(int $left, int $top): self
+ {
+ $this->left = $left;
+ $this->top = $top;
+
+ return $this;
+ }
+
+ /**
+ * Determine if frame is interlaced
+ *
+ * @return bool
+ */
+ public function isInterlaced(): bool
+ {
+ return $this->interlaced === true;
+ }
+
+ /**
+ * Set or unset interlaced value
+ *
+ * @param bool $value
+ */
+ public function setInterlaced(bool $value = true): self
+ {
+ $this->interlaced = $value;
+
+ return $this;
+ }
+
+ /**
+ * Determine if local color table is present
+ *
+ * @return bool
+ */
+ public function getLocalColorTableExistance(): bool
+ {
+ return $this->localColorTableExistance;
+ }
+
+ /**
+ * Alias for getLocalColorTableExistance
+ *
+ * @return bool
+ */
+ public function hasLocalColorTable(): bool
+ {
+ return $this->getLocalColorTableExistance();
+ }
+
+ /**
+ * Set local color table flag
+ *
+ * @param bool $existance
+ * @return self
+ */
+ public function setLocalColorTableExistance($existance = true): self
+ {
+ $this->localColorTableExistance = $existance;
+
+ return $this;
+ }
+
+ /**
+ * Get local color table sorted flag
+ *
+ * @return bool
+ */
+ public function getLocalColorTableSorted(): bool
+ {
+ return $this->localColorTableSorted;
+ }
+
+ /**
+ * Set local color table sorted flag
+ *
+ * @param bool $sorted
+ * @return self
+ */
+ public function setLocalColorTableSorted($sorted = true): self
+ {
+ $this->localColorTableSorted = $sorted;
+
+ return $this;
+ }
+
+ /**
+ * Get size of local color table
+ *
+ * @return int
+ */
+ public function getLocalColorTableSize(): int
+ {
+ return $this->localColorTableSize;
+ }
+
+ /**
+ * Get byte size of global color table
+ *
+ * @return int
+ */
+ public function getLocalColorTableByteSize(): int
+ {
+ return 3 * pow(2, $this->getLocalColorTableSize() + 1);
+ }
+
+ /**
+ * Set size of local color table
+ *
+ * @param int $size
+ */
+ public function setLocalColorTableSize(int $size): self
+ {
+ $this->localColorTableSize = $size;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/LogicalScreenDescriptor.php b/vendor/intervention/gif/src/Blocks/LogicalScreenDescriptor.php
new file mode 100644
index 000000000..f107e878b
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/LogicalScreenDescriptor.php
@@ -0,0 +1,254 @@
+width = $width;
+ $this->height = $height;
+
+ return $this;
+ }
+
+ /**
+ * Get width of current instance
+ *
+ * @return int
+ */
+ public function getWidth(): int
+ {
+ return intval($this->width);
+ }
+
+ /**
+ * Get height of current instance
+ *
+ * @return int
+ */
+ public function getHeight(): int
+ {
+ return intval($this->height);
+ }
+
+ /**
+ * Determine if global color table is present
+ *
+ * @return bool
+ */
+ public function getGlobalColorTableExistance(): bool
+ {
+ return $this->globalColorTableExistance;
+ }
+
+ /**
+ * Alias of getGlobalColorTableExistance
+ *
+ * @return bool
+ */
+ public function hasGlobalColorTable(): bool
+ {
+ return $this->getGlobalColorTableExistance();
+ }
+
+ /**
+ * Set global color table flag
+ *
+ * @param bool $existance
+ * @return self
+ */
+ public function setGlobalColorTableExistance($existance = true): self
+ {
+ $this->globalColorTableExistance = $existance;
+
+ return $this;
+ }
+
+ /**
+ * Get global color table sorted flag
+ *
+ * @return bool
+ */
+ public function getGlobalColorTableSorted(): bool
+ {
+ return $this->globalColorTableSorted;
+ }
+
+ /**
+ * Set global color table sorted flag
+ *
+ * @param bool $sorted
+ * @return self
+ */
+ public function setGlobalColorTableSorted($sorted = true): self
+ {
+ $this->globalColorTableSorted = $sorted;
+
+ return $this;
+ }
+
+ /**
+ * Get size of global color table
+ *
+ * @return int
+ */
+ public function getGlobalColorTableSize(): int
+ {
+ return $this->globalColorTableSize;
+ }
+
+ /**
+ * Get byte size of global color table
+ *
+ * @return int
+ */
+ public function getGlobalColorTableByteSize(): int
+ {
+ return 3 * pow(2, $this->getGlobalColorTableSize() + 1);
+ }
+
+ /**
+ * Set size of global color table
+ *
+ * @param int $size
+ */
+ public function setGlobalColorTableSize(int $size): self
+ {
+ $this->globalColorTableSize = $size;
+
+ return $this;
+ }
+
+ /**
+ * Get background color index
+ *
+ * @return int
+ */
+ public function getBackgroundColorIndex(): int
+ {
+ return $this->backgroundColorIndex;
+ }
+
+ /**
+ * Set background color index
+ *
+ * @param int $index
+ */
+ public function setBackgroundColorIndex(int $index): self
+ {
+ $this->backgroundColorIndex = $index;
+
+ return $this;
+ }
+
+ /**
+ * Get current pixel aspect ration
+ *
+ * @return int
+ */
+ public function getPixelAspectRatio(): int
+ {
+ return $this->pixelAspectRatio;
+ }
+
+ /**
+ * Set pixel aspect ratio
+ *
+ * @param int $ratio
+ */
+ public function setPixelAspectRatio(int $ratio): self
+ {
+ $this->pixelAspectRatio = $ratio;
+
+ return $this;
+ }
+
+ /**
+ * Get color resolution
+ *
+ * @return int
+ */
+ public function getBitsPerPixel()
+ {
+ return $this->bitsPerPixel;
+ }
+
+ /**
+ * Set color resolution
+ *
+ * @param int $value
+ */
+ public function setBitsPerPixel(int $value): self
+ {
+ $this->bitsPerPixel = $value;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/NetscapeApplicationExtension.php b/vendor/intervention/gif/src/Blocks/NetscapeApplicationExtension.php
new file mode 100644
index 000000000..41c273e0a
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/NetscapeApplicationExtension.php
@@ -0,0 +1,32 @@
+setApplication(self::IDENTIFIER . self::AUTH_CODE);
+ $this->setBlocks([new DataSubBlock(self::SUB_BLOCK_PREFIX . "\x00\x00")]);
+ }
+
+ public function getLoops(): int
+ {
+ return unpack('v*', substr($this->getBlocks()[0]->getValue(), 1))[1];
+ }
+
+ public function setLoops(int $loops): self
+ {
+ $this->setBlocks([
+ new DataSubBlock(self::SUB_BLOCK_PREFIX . pack('v*', $loops))
+ ]);
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/PlainTextExtension.php b/vendor/intervention/gif/src/Blocks/PlainTextExtension.php
new file mode 100644
index 000000000..9db84bfb8
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/PlainTextExtension.php
@@ -0,0 +1,63 @@
+text;
+ }
+
+ /**
+ * Add text
+ *
+ * @param string $text
+ */
+ public function addText(string $text): self
+ {
+ $this->text[] = $text;
+
+ return $this;
+ }
+
+ /**
+ * Set text array of extension
+ *
+ * @param array $text
+ */
+ public function setText(array $text): self
+ {
+ $this->text = $text;
+
+ return $this;
+ }
+
+ /**
+ * Determine if any text is present
+ *
+ * @return bool
+ */
+ public function hasText(): bool
+ {
+ return count($this->text) > 0;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/TableBasedImage.php b/vendor/intervention/gif/src/Blocks/TableBasedImage.php
new file mode 100644
index 000000000..cbd447bdf
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/TableBasedImage.php
@@ -0,0 +1,50 @@
+imageDescriptor;
+ }
+
+ public function setImageDescriptor(ImageDescriptor $descriptor): self
+ {
+ $this->imageDescriptor = $descriptor;
+
+ return $this;
+ }
+
+ public function getImageData(): ImageData
+ {
+ return $this->imageData;
+ }
+
+ public function setImageData(ImageData $data): self
+ {
+ $this->imageData = $data;
+
+ return $this;
+ }
+
+ public function getColorTable(): ?ColorTable
+ {
+ return $this->colorTable;
+ }
+
+ public function setColorTable(ColorTable $table): self
+ {
+ $this->colorTable = $table;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/gif/src/Blocks/Trailer.php b/vendor/intervention/gif/src/Blocks/Trailer.php
new file mode 100644
index 000000000..2c68bc496
--- /dev/null
+++ b/vendor/intervention/gif/src/Blocks/Trailer.php
@@ -0,0 +1,12 @@
+gif;
+ }
+
+ /**
+ * Set canvas size of gif
+ *
+ * @param int $width
+ * @param int $height
+ * @return Builder
+ */
+ public function setSize(int $width, int $height): self
+ {
+ $this->gif->getLogicalScreenDescriptor()->setSize($width, $height);
+
+ return $this;
+ }
+
+ /**
+ * Create new canvas
+ *
+ * @param int $width
+ * @param int $height
+ * @return self
+ */
+ public static function canvas(int $width, int $height): self
+ {
+ return (new self())->setSize($width, $height);
+ }
+
+ /**
+ * Set loop count
+ *
+ * @param int $loops
+ * @return Builder
+ * @throws Exception
+ */
+ public function setLoops(int $loops): self
+ {
+ if (count($this->gif->getFrames()) === 0) {
+ throw new Exception('Add at least one frame before setting the loop count');
+ }
+
+ if ($loops >= 0) {
+ // add frame count to existing or new netscape extension on first frame
+ if (!$this->gif->getFirstFrame()->getNetscapeExtension()) {
+ $this->gif->getFirstFrame()->addApplicationExtension(
+ new NetscapeApplicationExtension()
+ );
+ }
+ $this->gif->getFirstFrame()->getNetscapeExtension()->setLoops($loops);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create new animation frame from given source
+ * which can be path to a file or GIF image data
+ *
+ * @param string $source
+ * @param float $delay time delay in seconds
+ * @param int $left position offset in pixels from left
+ * @param int $top position offset in pixels from top
+ * @param bool $interlaced
+ * @return Builder
+ */
+ public function addFrame(
+ string $source,
+ float $delay = 0,
+ int $left = 0,
+ int $top = 0,
+ bool $interlaced = false
+ ): self {
+ $frame = new FrameBlock();
+ $source = Decoder::decode($source);
+
+ // store delay
+ $frame->setGraphicControlExtension(
+ $this->buildGraphicControlExtension(
+ $source,
+ intval($delay * 100)
+ )
+ );
+
+ // store image
+ $frame->setTableBasedImage(
+ $this->buildTableBasedImage($source, $left, $top, $interlaced)
+ );
+
+ // add frame
+ $this->gif->addFrame($frame);
+
+ return $this;
+ }
+
+ /**
+ * Build new graphic control extension with given delay & disposal method
+ *
+ * @param GifDataStream $source
+ * @param int $delay
+ * @param DisposalMethod $disposalMethod
+ * @return GraphicControlExtension
+ */
+ protected function buildGraphicControlExtension(
+ GifDataStream $source,
+ int $delay,
+ DisposalMethod $disposalMethod = DisposalMethod::BACKGROUND
+ ): GraphicControlExtension {
+ // create extension
+ $extension = new GraphicControlExtension($delay, $disposalMethod);
+
+ // set transparency index
+ $control = $source->getFirstFrame()->getGraphicControlExtension();
+ if ($control && $control->getTransparentColorExistance()) {
+ $extension->setTransparentColorExistance();
+ $extension->setTransparentColorIndex(
+ $control->getTransparentColorIndex()
+ );
+ }
+
+ return $extension;
+ }
+
+ /**
+ * Build table based image object from given source
+ *
+ * @param GifDataStream $source
+ * @param int $left
+ * @param int $top
+ * @param bool $interlaced
+ * @return TableBasedImage
+ */
+ protected function buildTableBasedImage(
+ GifDataStream $source,
+ int $left,
+ int $top,
+ bool $interlaced
+ ): TableBasedImage {
+ $block = new TableBasedImage();
+ $block->setImageDescriptor(new ImageDescriptor());
+
+ // set global color table from source as local color table
+ $block->getImageDescriptor()->setLocalColorTableExistance();
+ $block->setColorTable($source->getGlobalColorTable());
+
+ $block->getImageDescriptor()->setLocalColorTableSorted(
+ $source->getLogicalScreenDescriptor()->getGlobalColorTableSorted()
+ );
+
+ $block->getImageDescriptor()->setLocalColorTableSize(
+ $source->getLogicalScreenDescriptor()->getGlobalColorTableSize()
+ );
+
+ $block->getImageDescriptor()->setSize(
+ $source->getLogicalScreenDescriptor()->getWidth(),
+ $source->getLogicalScreenDescriptor()->getHeight()
+ );
+
+ // set position
+ $block->getImageDescriptor()->setPosition($left, $top);
+
+ // set interlaced flag
+ $block->getImageDescriptor()->setInterlaced($interlaced);
+
+ // add image data from source
+ $block->setImageData($source->getFirstFrame()->getImageData());
+
+ return $block;
+ }
+
+ /**
+ * Encode the current build
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return $this->gif->encode();
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoder.php b/vendor/intervention/gif/src/Decoder.php
new file mode 100644
index 000000000..cd88ad4eb
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoder.php
@@ -0,0 +1,30 @@
+ self::getHandleFromFilePath($input),
+ is_string($input) => self::getHandleFromData($input),
+ default => throw new DecoderException('Decoder input must be either file path or binary data.')
+ }
+ );
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/AbstractDecoder.php b/vendor/intervention/gif/src/Decoders/AbstractDecoder.php
new file mode 100644
index 000000000..c005e71c4
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/AbstractDecoder.php
@@ -0,0 +1,153 @@
+handle = $handle;
+
+ return $this;
+ }
+
+ /**
+ * Read given number of bytes and move file pointer
+ *
+ * @param int $length
+ * @return string
+ */
+ protected function getNextBytes(int $length): string
+ {
+ return fread($this->handle, $length);
+ }
+
+ /**
+ * Read given number of bytes and move pointer back to previous position
+ *
+ * @param int $length
+ * @return string
+ */
+ protected function viewNextBytes(int $length): string
+ {
+ $bytes = $this->getNextBytes($length);
+ $this->movePointer($length * -1);
+
+ return $bytes;
+ }
+
+ /**
+ * Read next byte and move pointer back to previous position
+ *
+ * @return string
+ */
+ protected function viewNextByte(): string
+ {
+ return $this->viewNextBytes(1);
+ }
+
+ /**
+ * Read all remaining bytes from file handler
+ *
+ * @return string
+ */
+ protected function getRemainingBytes(): string
+ {
+ $all = '';
+ do {
+ $byte = fread($this->handle, 1);
+ $all .= $byte;
+ } while (!feof($this->handle));
+
+ return $all;
+ }
+
+ /**
+ * Get next byte in stream and move file pointer
+ *
+ * @return string
+ */
+ protected function getNextByte(): string
+ {
+ return $this->getNextBytes(1);
+ }
+
+ /**
+ * Move file pointer on handle by given offset
+ *
+ * @param int $offset
+ * @return self
+ */
+ protected function movePointer(int $offset): self
+ {
+ fseek($this->handle, $offset, SEEK_CUR);
+
+ return $this;
+ }
+
+ /**
+ * Decode multi byte value
+ *
+ * @return int
+ */
+ protected function decodeMultiByte(string $bytes): int
+ {
+ return unpack('v*', $bytes)[1];
+ }
+
+ /**
+ * Set length
+ *
+ * @param int $length
+ */
+ public function setLength(int $length): self
+ {
+ $this->length = $length;
+
+ return $this;
+ }
+
+ /**
+ * Get length
+ *
+ * @return null|int
+ */
+ public function getLength(): ?int
+ {
+ return $this->length;
+ }
+
+ /**
+ * Get current handle position
+ *
+ * @return int
+ */
+ public function getPosition(): int
+ {
+ return ftell($this->handle);
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/AbstractPackedBitDecoder.php b/vendor/intervention/gif/src/Decoders/AbstractPackedBitDecoder.php
new file mode 100644
index 000000000..69066fa7d
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/AbstractPackedBitDecoder.php
@@ -0,0 +1,43 @@
+getPackedBits($byte)[$num];
+ }
+
+ /**
+ * Get packed bits
+ *
+ * @param int $start
+ * @param int $length
+ * @return string
+ */
+ protected function getPackedBits(string $byte, int $start = 0, int $length = 8): string
+ {
+ $bits = str_pad(decbin($this->decodePackedByte($byte)), 8, '0', STR_PAD_LEFT);
+
+ return substr($bits, $start, $length);
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/ApplicationExtensionDecoder.php b/vendor/intervention/gif/src/Decoders/ApplicationExtensionDecoder.php
new file mode 100644
index 000000000..fb00e470f
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/ApplicationExtensionDecoder.php
@@ -0,0 +1,67 @@
+getNextByte(); // marker
+ $this->getNextByte(); // label
+ $blocksize = $this->decodeBlockSize($this->getNextByte());
+ $application = $this->getNextBytes($blocksize);
+
+ if ($application === NetscapeApplicationExtension::IDENTIFIER . NetscapeApplicationExtension::AUTH_CODE) {
+ $result = new NetscapeApplicationExtension();
+
+ // skip length
+ $this->getNextByte();
+
+ $result->setBlocks([
+ new DataSubBlock(
+ $this->getNextBytes(3)
+ )
+ ]);
+
+ // skip terminator
+ $this->getNextByte();
+
+ return $result;
+ }
+
+ $result->setApplication($application);
+
+ // decode data sub blocks
+ $blocksize = $this->decodeBlockSize($this->getNextByte());
+ while ($blocksize > 0) {
+ $result->addBlock(new DataSubBlock($this->getNextBytes($blocksize)));
+ $blocksize = $this->decodeBlockSize($this->getNextByte());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Decode block size of ApplicationExtension from given byte
+ *
+ * @param string $byte
+ * @return int
+ */
+ protected function decodeBlockSize(string $byte): int
+ {
+ return (int) @unpack('C', $byte)[1];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/ColorDecoder.php b/vendor/intervention/gif/src/Decoders/ColorDecoder.php
new file mode 100644
index 000000000..f21d90ad1
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/ColorDecoder.php
@@ -0,0 +1,36 @@
+setRed($this->decodeColorValue($this->getNextByte()));
+ $color->setGreen($this->decodeColorValue($this->getNextByte()));
+ $color->setBlue($this->decodeColorValue($this->getNextByte()));
+
+ return $color;
+ }
+
+ /**
+ * Decode red value from source
+ *
+ * @return int
+ */
+ protected function decodeColorValue(string $byte): int
+ {
+ return unpack('C', $byte)[1];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/ColorTableDecoder.php b/vendor/intervention/gif/src/Decoders/ColorTableDecoder.php
new file mode 100644
index 000000000..dd1fec0b4
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/ColorTableDecoder.php
@@ -0,0 +1,26 @@
+getLength() / 3); $i++) {
+ $table->addColor(Color::decode($this->handle));
+ }
+
+ return $table;
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/CommentExtensionDecoder.php b/vendor/intervention/gif/src/Decoders/CommentExtensionDecoder.php
new file mode 100644
index 000000000..f14698eba
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/CommentExtensionDecoder.php
@@ -0,0 +1,58 @@
+getNextBytes(2); // skip marker & label
+
+ $extension = new CommentExtension();
+ foreach ($this->decodeComments() as $comment) {
+ $extension->addComment($comment);
+ }
+
+ return $extension;
+ }
+
+ /**
+ * Decode comment from current source
+ *
+ * @return array
+ */
+ protected function decodeComments(): array
+ {
+ $comments = [];
+
+ do {
+ $byte = $this->getNextByte();
+ $size = $this->decodeBlocksize($byte);
+ if ($size > 0) {
+ $comments[] = $this->getNextBytes($size);
+ }
+ } while ($byte !== CommentExtension::TERMINATOR);
+
+ return $comments;
+ }
+
+ /**
+ * Decode blocksize of following comment
+ *
+ * @param string $byte
+ * @return int
+ */
+ protected function decodeBlocksize(string $byte): int
+ {
+ return (int) @unpack('C', $byte)[1];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/DataSubBlockDecoder.php b/vendor/intervention/gif/src/Decoders/DataSubBlockDecoder.php
new file mode 100644
index 000000000..39cec16b9
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/DataSubBlockDecoder.php
@@ -0,0 +1,23 @@
+getNextByte();
+ $size = (int) unpack('C', $char)[1];
+
+ return new DataSubBlock($this->getNextBytes($size));
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/FrameBlockDecoder.php b/vendor/intervention/gif/src/Decoders/FrameBlockDecoder.php
new file mode 100644
index 000000000..cd814a42c
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/FrameBlockDecoder.php
@@ -0,0 +1,47 @@
+viewNextBytes(2)) {
+ AbstractExtension::MARKER . GraphicControlExtension::LABEL
+ => GraphicControlExtension::decode($this->handle),
+ AbstractExtension::MARKER . NetscapeApplicationExtension::LABEL
+ => NetscapeApplicationExtension::decode($this->handle),
+ AbstractExtension::MARKER . ApplicationExtension::LABEL
+ => ApplicationExtension::decode($this->handle),
+ AbstractExtension::MARKER . PlainTextExtension::LABEL
+ => PlainTextExtension::decode($this->handle),
+ AbstractExtension::MARKER . CommentExtension::LABEL
+ => CommentExtension::decode($this->handle),
+ default => match ($this->viewNextByte()) {
+ ImageDescriptor::SEPARATOR => TableBasedImage::decode($this->handle),
+ default => throw new DecoderException('Unable to decode Data Block'),
+ }
+ };
+
+ $frame->addEntity($block);
+ } while (!($block instanceof TableBasedImage));
+
+ return $frame;
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/GifDataStreamDecoder.php b/vendor/intervention/gif/src/Decoders/GifDataStreamDecoder.php
new file mode 100644
index 000000000..d207d8f89
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/GifDataStreamDecoder.php
@@ -0,0 +1,57 @@
+setHeader(
+ Header::decode($this->handle),
+ );
+
+ $gif->setLogicalScreenDescriptor(
+ LogicalScreenDescriptor::decode($this->handle),
+ );
+
+ if ($gif->getLogicalScreenDescriptor()->hasGlobalColorTable()) {
+ $length = $gif->getLogicalScreenDescriptor()->getGlobalColorTableByteSize();
+ $gif->setGlobalColorTable(
+ ColorTable::decode($this->handle, $length)
+ );
+ }
+
+ while ($this->viewNextByte() != Trailer::MARKER) {
+ match ($this->viewNextBytes(2)) {
+ // trailing "global" comment blocks which are not part of "FrameBlock"
+ AbstractExtension::MARKER . CommentExtension::LABEL
+ => $gif->addComment(
+ CommentExtension::decode($this->handle)
+ ),
+ default => $gif->addFrame(
+ FrameBlock::decode($this->handle)
+ ),
+ };
+ }
+
+ return $gif;
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/GraphicControlExtensionDecoder.php b/vendor/intervention/gif/src/Decoders/GraphicControlExtensionDecoder.php
new file mode 100644
index 000000000..6c85c1177
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/GraphicControlExtensionDecoder.php
@@ -0,0 +1,95 @@
+getNextBytes(3); // skip marker, label & bytesize
+
+ // byte #4
+ $packedField = $this->getNextByte();
+ $result->setDisposalMethod($this->decodeDisposalMethod($packedField));
+ $result->setUserInput($this->decodeUserInput($packedField));
+ $result->setTransparentColorExistance($this->decodeTransparentColorExistance($packedField));
+
+ // bytes 5-6
+ $result->setDelay($this->decodeDelay($this->getNextBytes(2)));
+
+ // byte #7
+ $result->setTransparentColorIndex($this->decodeTransparentColorIndex(
+ $this->getNextByte()
+ ));
+
+ // byte #8 (terminator)
+ $this->getNextByte();
+
+ return $result;
+ }
+
+ /**
+ * Decode disposal method
+ *
+ * @return DisposalMethod
+ */
+ protected function decodeDisposalMethod(string $byte): DisposalMethod
+ {
+ return DisposalMethod::from(
+ bindec($this->getPackedBits($byte, 3, 3))
+ );
+ }
+
+ /**
+ * Decode user input flag
+ *
+ * @return bool
+ */
+ protected function decodeUserInput(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 6);
+ }
+
+ /**
+ * Decode transparent color existance
+ *
+ * @return bool
+ */
+ protected function decodeTransparentColorExistance(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 7);
+ }
+
+ /**
+ * Decode delay value
+ *
+ * @return int
+ */
+ protected function decodeDelay(string $bytes): int
+ {
+ return unpack('v*', $bytes)[1];
+ }
+
+ /**
+ * Decode transparent color index
+ *
+ * @return int
+ */
+ protected function decodeTransparentColorIndex(string $byte): int
+ {
+ return unpack('C', $byte)[1];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/HeaderDecoder.php b/vendor/intervention/gif/src/Decoders/HeaderDecoder.php
new file mode 100644
index 000000000..9f8b5b6dd
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/HeaderDecoder.php
@@ -0,0 +1,40 @@
+setVersion($this->decodeVersion());
+
+ return $header;
+ }
+
+ /**
+ * Decode version string
+ *
+ * @return string
+ */
+ protected function decodeVersion(): string
+ {
+ $parsed = (bool) preg_match("/^GIF(?P[0-9]{2}[a-z])$/", $this->getNextBytes(6), $matches);
+
+ if ($parsed === false) {
+ throw new DecoderException('Unable to parse file header.');
+ }
+
+ return $matches['version'];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/ImageDataDecoder.php b/vendor/intervention/gif/src/Decoders/ImageDataDecoder.php
new file mode 100644
index 000000000..633492538
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/ImageDataDecoder.php
@@ -0,0 +1,38 @@
+getNextByte();
+ $size = (int) unpack('C', $char)[1];
+ $data->setLzwMinCodeSize($size);
+
+ do {
+ // decode sub blocks
+ $char = $this->getNextByte();
+ $size = (int) unpack('C', $char)[1];
+ if ($size > 0) {
+ $data->addBlock(new DataSubBlock($this->getNextBytes($size)));
+ }
+ } while ($char !== AbstractEntity::TERMINATOR);
+
+ return $data;
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/ImageDescriptorDecoder.php b/vendor/intervention/gif/src/Decoders/ImageDescriptorDecoder.php
new file mode 100644
index 000000000..96c078a2b
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/ImageDescriptorDecoder.php
@@ -0,0 +1,92 @@
+getNextByte(); // skip separator
+
+ $descriptor->setPosition(
+ $this->decodeMultiByte($this->getNextBytes(2)),
+ $this->decodeMultiByte($this->getNextBytes(2))
+ );
+
+ $descriptor->setSize(
+ $this->decodeMultiByte($this->getNextBytes(2)),
+ $this->decodeMultiByte($this->getNextBytes(2))
+ );
+
+ $packedField = $this->getNextByte();
+
+ $descriptor->setLocalColorTableExistance(
+ $this->decodeLocalColorTableExistance($packedField)
+ );
+
+ $descriptor->setLocalColorTableSorted(
+ $this->decodeLocalColorTableSorted($packedField)
+ );
+
+ $descriptor->setLocalColorTableSize(
+ $this->decodeLocalColorTableSize($packedField)
+ );
+
+ $descriptor->setInterlaced(
+ $this->decodeInterlaced($packedField)
+ );
+
+ return $descriptor;
+ }
+
+ /**
+ * Decode local color table existance
+ *
+ * @return bool
+ */
+ protected function decodeLocalColorTableExistance(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 0);
+ }
+
+ /**
+ * Decode local color table sort method
+ *
+ * @return bool
+ */
+ protected function decodeLocalColorTableSorted(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 2);
+ }
+
+ /**
+ * Decode local color table size
+ *
+ * @return int
+ */
+ protected function decodeLocalColorTableSize(string $byte): int
+ {
+ return bindec($this->getPackedBits($byte, 5, 3));
+ }
+
+ /**
+ * Decode interlaced flag
+ *
+ * @return bool
+ */
+ protected function decodeInterlaced(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 1);
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/LogicalScreenDescriptorDecoder.php b/vendor/intervention/gif/src/Decoders/LogicalScreenDescriptorDecoder.php
new file mode 100644
index 000000000..da82928b2
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/LogicalScreenDescriptorDecoder.php
@@ -0,0 +1,137 @@
+setSize(
+ $this->decodeWidth($this->getNextBytes(2)),
+ $this->decodeHeight($this->getNextBytes(2))
+ );
+
+ // byte 5
+ $packedField = $this->getNextByte();
+
+ $logicalScreenDescriptor->setGlobalColorTableExistance(
+ $this->decodeGlobalColorTableExistance($packedField)
+ );
+
+ $logicalScreenDescriptor->setBitsPerPixel(
+ $this->decodeBitsPerPixel($packedField)
+ );
+
+ $logicalScreenDescriptor->setGlobalColorTableSorted(
+ $this->decodeGlobalColorTableSorted($packedField)
+ );
+
+ $logicalScreenDescriptor->setGlobalColorTableSize(
+ $this->decodeGlobalColorTableSize($packedField)
+ );
+
+ // byte 6
+ $logicalScreenDescriptor->setBackgroundColorIndex(
+ $this->decodeBackgroundColorIndex($this->getNextByte())
+ );
+
+ // byte 7
+ $logicalScreenDescriptor->setPixelAspectRatio(
+ $this->decodePixelAspectRatio($this->getNextByte())
+ );
+
+ return $logicalScreenDescriptor;
+ }
+
+ /**
+ * Decode width
+ *
+ * @return int
+ */
+ protected function decodeWidth(string $source): int
+ {
+ return unpack('v*', $source)[1];
+ }
+
+ /**
+ * Decode height
+ *
+ * @return int
+ */
+ protected function decodeHeight(string $source): int
+ {
+ return unpack('v*', $source)[1];
+ }
+
+ /**
+ * Decode existance of global color table
+ *
+ * @return bool
+ */
+ protected function decodeGlobalColorTableExistance(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 0);
+ }
+
+ /**
+ * Decode color resolution in bits per pixel
+ *
+ * @return int
+ */
+ protected function decodeBitsPerPixel(string $byte): int
+ {
+ return bindec($this->getPackedBits($byte, 1, 3)) + 1;
+ }
+
+ /**
+ * Decode global color table sorted status
+ *
+ * @return bool
+ */
+ protected function decodeGlobalColorTableSorted(string $byte): bool
+ {
+ return $this->hasPackedBit($byte, 4);
+ }
+
+ /**
+ * Decode size of global color table
+ *
+ * @return int
+ */
+ protected function decodeGlobalColorTableSize(string $byte): int
+ {
+ return bindec($this->getPackedBits($byte, 5, 3));
+ }
+
+ /**
+ * Decode background color index
+ *
+ * @return int
+ */
+ protected function decodeBackgroundColorIndex(string $source): int
+ {
+ return unpack('C', $source)[1];
+ }
+
+ /**
+ * Decode pixel aspect ratio
+ *
+ * @return int
+ */
+ protected function decodePixelAspectRatio(string $source): int
+ {
+ return unpack('C', $source)[1];
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/NetscapeApplicationExtensionDecoder.php b/vendor/intervention/gif/src/Decoders/NetscapeApplicationExtensionDecoder.php
new file mode 100644
index 000000000..3074f89f9
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/NetscapeApplicationExtensionDecoder.php
@@ -0,0 +1,9 @@
+getNextBytes(2);
+
+ // skip info block
+ $this->getNextBytes($this->getInfoBlockSize());
+
+ // text blocks
+ $extension->setText($this->decodeTextBlocks());
+
+ return $extension;
+ }
+
+ /**
+ * Get number of bytes in header block
+ *
+ * @return int
+ */
+ protected function getInfoBlockSize(): int
+ {
+ return unpack('C', $this->getNextByte())[1];
+ }
+
+ /**
+ * Decode text sub blocks
+ *
+ * @return array
+ */
+ protected function decodeTextBlocks(): array
+ {
+ $blocks = [];
+
+ do {
+ $char = $this->getNextByte();
+ $size = (int) unpack('C', $char)[1];
+ if ($size > 0) {
+ $blocks[] = $this->getNextBytes($size);
+ }
+ } while ($char !== PlainTextExtension::TERMINATOR);
+
+ return $blocks;
+ }
+}
diff --git a/vendor/intervention/gif/src/Decoders/TableBasedImageDecoder.php b/vendor/intervention/gif/src/Decoders/TableBasedImageDecoder.php
new file mode 100644
index 000000000..83a6426a6
--- /dev/null
+++ b/vendor/intervention/gif/src/Decoders/TableBasedImageDecoder.php
@@ -0,0 +1,35 @@
+setImageDescriptor(ImageDescriptor::decode($this->handle));
+
+ if ($block->getImageDescriptor()->hasLocalColorTable()) {
+ $block->setColorTable(
+ ColorTable::decode(
+ $this->handle,
+ $block->getImageDescriptor()->getLocalColorTableByteSize()
+ )
+ );
+ }
+
+ $block->setImageData(
+ ImageData::decode($this->handle)
+ );
+
+ return $block;
+ }
+}
diff --git a/vendor/intervention/gif/src/DisposalMethod.php b/vendor/intervention/gif/src/DisposalMethod.php
new file mode 100644
index 000000000..7b05044f0
--- /dev/null
+++ b/vendor/intervention/gif/src/DisposalMethod.php
@@ -0,0 +1,13 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ ApplicationExtension::MARKER,
+ ApplicationExtension::LABEL,
+ pack('C', $this->source->getBlockSize()),
+ $this->source->getApplication(),
+ implode('', array_map(function ($block) {
+ return $block->encode();
+ }, $this->source->getBlocks())),
+ ApplicationExtension::TERMINATOR,
+ ]);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/ColorEncoder.php b/vendor/intervention/gif/src/Encoders/ColorEncoder.php
new file mode 100644
index 000000000..1d5799c0b
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/ColorEncoder.php
@@ -0,0 +1,44 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ $this->encodeColorValue($this->source->getRed()),
+ $this->encodeColorValue($this->source->getGreen()),
+ $this->encodeColorValue($this->source->getBlue()),
+ ]);
+ }
+
+ /**
+ * Encode color value
+ *
+ * @return string
+ */
+ protected function encodeColorValue(int $value): string
+ {
+ return pack('C', $value);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/ColorTableEncoder.php b/vendor/intervention/gif/src/Encoders/ColorTableEncoder.php
new file mode 100644
index 000000000..a3c4bff0b
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/ColorTableEncoder.php
@@ -0,0 +1,32 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', array_map(function ($color) {
+ return $color->encode();
+ }, $this->source->getColors()));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/CommentExtensionEncoder.php b/vendor/intervention/gif/src/Encoders/CommentExtensionEncoder.php
new file mode 100644
index 000000000..4cbea5a4a
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/CommentExtensionEncoder.php
@@ -0,0 +1,47 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ CommentExtension::MARKER,
+ CommentExtension::LABEL,
+ $this->encodeComments(),
+ CommentExtension::TERMINATOR,
+ ]);
+ }
+
+ /**
+ * Encode comment blocks
+ *
+ * @return string
+ */
+ protected function encodeComments(): string
+ {
+ return implode('', array_map(function ($comment) {
+ return pack('C', strlen($comment)) . $comment;
+ }, $this->source->getComments()));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/DataSubBlockEncoder.php b/vendor/intervention/gif/src/Encoders/DataSubBlockEncoder.php
new file mode 100644
index 000000000..a14746938
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/DataSubBlockEncoder.php
@@ -0,0 +1,30 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return pack('C', $this->source->getSize()) . $this->source->getValue();
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/FrameBlockEncoder.php b/vendor/intervention/gif/src/Encoders/FrameBlockEncoder.php
new file mode 100644
index 000000000..ef244a16d
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/FrameBlockEncoder.php
@@ -0,0 +1,41 @@
+source = $source;
+ }
+
+ public function encode(): string
+ {
+ $graphicControlExtension = $this->source->getGraphicControlExtension();
+ $colorTable = $this->source->getColorTable();
+ $plainTextExtension = $this->source->getPlainTextExtension();
+
+ return implode('', [
+ implode('', array_map(function ($extension) {
+ return $extension->encode();
+ }, $this->source->getApplicationExtensions())),
+ implode('', array_map(function ($extension) {
+ return $extension->encode();
+ }, $this->source->getCommentExtensions())),
+ $plainTextExtension ? $plainTextExtension->encode() : '',
+ $graphicControlExtension ? $graphicControlExtension->encode() : '',
+ $this->source->getImageDescriptor()->encode(),
+ $colorTable ? $colorTable->encode() : '',
+ $this->source->getImageData()->encode(),
+ ]);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/GifDataStreamEncoder.php b/vendor/intervention/gif/src/Encoders/GifDataStreamEncoder.php
new file mode 100644
index 000000000..16098f119
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/GifDataStreamEncoder.php
@@ -0,0 +1,70 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ $this->source->getHeader()->encode(),
+ $this->source->getLogicalScreenDescriptor()->encode(),
+ $this->maybeEncodeGlobalColorTable(),
+ $this->encodeFrames(),
+ $this->encodeComments(),
+ $this->source->getTrailer()->encode(),
+ ]);
+ }
+
+ protected function maybeEncodeGlobalColorTable(): string
+ {
+ if (!$this->source->hasGlobalColorTable()) {
+ return '';
+ }
+
+ return $this->source->getGlobalColorTable()->encode();
+ }
+
+ /**
+ * Encode data blocks of source
+ *
+ * @return string
+ */
+ protected function encodeFrames(): string
+ {
+ return implode('', array_map(function ($frame) {
+ return $frame->encode();
+ }, $this->source->getFrames()));
+ }
+
+ /**
+ * Encode comment extension blocks of source
+ *
+ * @return string
+ */
+ protected function encodeComments(): string
+ {
+ return implode('', array_map(function ($commentExtension) {
+ return $commentExtension->encode();
+ }, $this->source->getComments()));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/GraphicControlExtensionEncoder.php b/vendor/intervention/gif/src/Encoders/GraphicControlExtensionEncoder.php
new file mode 100644
index 000000000..eef4e9afd
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/GraphicControlExtensionEncoder.php
@@ -0,0 +1,73 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ GraphicControlExtension::MARKER,
+ GraphicControlExtension::LABEL,
+ GraphicControlExtension::BLOCKSIZE,
+ $this->encodePackedField(),
+ $this->encodeDelay(),
+ $this->encodeTransparentColorIndex(),
+ GraphicControlExtension::TERMINATOR,
+ ]);
+ }
+
+ /**
+ * Encode delay time
+ *
+ * @return string
+ */
+ protected function encodeDelay(): string
+ {
+ return pack('v*', $this->source->getDelay());
+ }
+
+ /**
+ * Encode transparent color index
+ *
+ * @return string
+ */
+ protected function encodeTransparentColorIndex(): string
+ {
+ return pack('C', $this->source->getTransparentColorIndex());
+ }
+
+ /**
+ * Encode packed field
+ *
+ * @return string
+ */
+ protected function encodePackedField(): string
+ {
+ return pack('C', bindec(implode('', [
+ str_pad('0', 3, '0', STR_PAD_LEFT),
+ str_pad(decbin($this->source->getDisposalMethod()->value), 3, '0', STR_PAD_LEFT),
+ (int) $this->source->getUserInput(),
+ (int) $this->source->getTransparentColorExistance(),
+ ])));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/HeaderEncoder.php b/vendor/intervention/gif/src/Encoders/HeaderEncoder.php
new file mode 100644
index 000000000..336ce8bc9
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/HeaderEncoder.php
@@ -0,0 +1,30 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return Header::SIGNATURE . $this->source->getVersion();
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/ImageDataEncoder.php b/vendor/intervention/gif/src/Encoders/ImageDataEncoder.php
new file mode 100644
index 000000000..33c3eec78
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/ImageDataEncoder.php
@@ -0,0 +1,42 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ if (!$this->source->hasBlocks()) {
+ throw new EncoderException("No data blocks in ImageData.");
+ }
+
+ return implode('', [
+ pack('C', $this->source->getLzwMinCodeSize()),
+ implode('', array_map(function ($block) {
+ return $block->encode();
+ }, $this->source->getBlocks())),
+ AbstractEntity::TERMINATOR,
+ ]);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/ImageDescriptorEncoder.php b/vendor/intervention/gif/src/Encoders/ImageDescriptorEncoder.php
new file mode 100644
index 000000000..2566a1064
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/ImageDescriptorEncoder.php
@@ -0,0 +1,113 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ ImageDescriptor::SEPARATOR,
+ $this->encodeLeft(),
+ $this->encodeTop(),
+ $this->encodeWidth(),
+ $this->encodeHeight(),
+ $this->encodePackedField(),
+ ]);
+ }
+
+ /**
+ * Encode left value
+ *
+ * @return string
+ */
+ protected function encodeLeft(): string
+ {
+ return pack('v*', $this->source->getLeft());
+ }
+
+ /**
+ * Encode top value
+ *
+ * @return string
+ */
+ protected function encodeTop(): string
+ {
+ return pack('v*', $this->source->getTop());
+ }
+
+ /**
+ * Encode width value
+ *
+ * @return string
+ */
+ protected function encodeWidth(): string
+ {
+ return pack('v*', $this->source->getWidth());
+ }
+
+ /**
+ * Encode height value
+ *
+ * @return string
+ */
+ protected function encodeHeight(): string
+ {
+ return pack('v*', $this->source->getHeight());
+ }
+
+ /**
+ * Encode size of local color table
+ *
+ * @return string
+ */
+ protected function encodeLocalColorTableSize(): string
+ {
+ return str_pad(decbin($this->source->getLocalColorTableSize()), 3, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Encode reserved field
+ *
+ * @return string
+ */
+ protected function encodeReservedField(): string
+ {
+ return str_pad('0', 2, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Encode packed field
+ *
+ * @return string
+ */
+ protected function encodePackedField(): string
+ {
+ return pack('C', bindec(implode('', [
+ (int) $this->source->getLocalColorTableExistance(),
+ (int) $this->source->isInterlaced(),
+ (int) $this->source->getLocalColorTableSorted(),
+ $this->encodeReservedField(),
+ $this->encodeLocalColorTableSize(),
+ ])));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/LogicalScreenDescriptorEncoder.php b/vendor/intervention/gif/src/Encoders/LogicalScreenDescriptorEncoder.php
new file mode 100644
index 000000000..b9404163d
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/LogicalScreenDescriptorEncoder.php
@@ -0,0 +1,111 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ $this->encodeWidth(),
+ $this->encodeHeight(),
+ $this->encodePackedField(),
+ $this->encodeBackgroundColorIndex(),
+ $this->encodePixelAspectRatio(),
+ ]);
+ }
+
+ /**
+ * Encode width of current instance
+ *
+ * @return string
+ */
+ protected function encodeWidth(): string
+ {
+ return pack('v*', $this->source->getWidth());
+ }
+
+ /**
+ * Encode height of current instance
+ *
+ * @return string
+ */
+ protected function encodeHeight(): string
+ {
+ return pack('v*', $this->source->getHeight());
+ }
+
+ /**
+ * Encode background color index of global color table
+ *
+ * @return string
+ */
+ protected function encodeBackgroundColorIndex(): string
+ {
+ return pack('C', $this->source->getBackgroundColorIndex());
+ }
+
+ /**
+ * Encode pixel aspect ratio
+ *
+ * @return string
+ */
+ protected function encodePixelAspectRatio(): string
+ {
+ return pack('C', $this->source->getPixelAspectRatio());
+ }
+
+ /**
+ * Return color resolution for encoding
+ *
+ * @return string
+ */
+ protected function encodeColorResolution(): string
+ {
+ return str_pad(decbin($this->source->getBitsPerPixel() - 1), 3, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Encode size of global color table
+ *
+ * @return string
+ */
+ protected function encodeGlobalColorTableSize(): string
+ {
+ return str_pad(decbin($this->source->getGlobalColorTableSize()), 3, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Encode packed field of current instance
+ *
+ * @return string
+ */
+ protected function encodePackedField(): string
+ {
+ return pack('C', bindec(implode('', [
+ (int) $this->source->getGlobalColorTableExistance(),
+ $this->encodeColorResolution(),
+ (int) $this->source->getGlobalColorTableSorted(),
+ $this->encodeGlobalColorTableSize(),
+ ])));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/NetscapeApplicationExtensionEncoder.php b/vendor/intervention/gif/src/Encoders/NetscapeApplicationExtensionEncoder.php
new file mode 100644
index 000000000..dae1607a2
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/NetscapeApplicationExtensionEncoder.php
@@ -0,0 +1,40 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return implode('', [
+ ApplicationExtension::MARKER,
+ ApplicationExtension::LABEL,
+ pack('C', $this->source->getBlockSize()),
+ $this->source->getApplication(),
+ implode('', array_map(function ($block) {
+ return $block->encode();
+ }, $this->source->getBlocks())),
+ ApplicationExtension::TERMINATOR,
+ ]);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/PlainTextExtensionEncoder.php b/vendor/intervention/gif/src/Encoders/PlainTextExtensionEncoder.php
new file mode 100644
index 000000000..54d448a75
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/PlainTextExtensionEncoder.php
@@ -0,0 +1,62 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ if (!$this->source->hasText()) {
+ return '';
+ }
+
+ return implode('', [
+ PlainTextExtension::MARKER,
+ PlainTextExtension::LABEL,
+ $this->encodeHead(),
+ $this->encodeTexts(),
+ PlainTextExtension::TERMINATOR,
+ ]);
+ }
+
+ /**
+ * Encode head block
+ *
+ * @return string
+ */
+ protected function encodeHead(): string
+ {
+ return "\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+ }
+
+ /**
+ * Encode text chunks
+ *
+ * @return string
+ */
+ protected function encodeTexts(): string
+ {
+ return implode('', array_map(function ($text) {
+ return pack('C', strlen($text)) . $text;
+ }, $this->source->getText()));
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/TableBasedImageEncoder.php b/vendor/intervention/gif/src/Encoders/TableBasedImageEncoder.php
new file mode 100644
index 000000000..1f3867ce5
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/TableBasedImageEncoder.php
@@ -0,0 +1,29 @@
+source = $source;
+ }
+
+ public function encode(): string
+ {
+ return implode('', [
+ $this->source->getImageDescriptor()->encode(),
+ $this->source->getColorTable() ? $this->source->getColorTable()->encode() : '',
+ $this->source->getImageData()->encode(),
+ ]);
+ }
+}
diff --git a/vendor/intervention/gif/src/Encoders/TrailerEncoder.php b/vendor/intervention/gif/src/Encoders/TrailerEncoder.php
new file mode 100644
index 000000000..c24b8a642
--- /dev/null
+++ b/vendor/intervention/gif/src/Encoders/TrailerEncoder.php
@@ -0,0 +1,30 @@
+source = $source;
+ }
+
+ /**
+ * Encode current source
+ *
+ * @return string
+ */
+ public function encode(): string
+ {
+ return Trailer::MARKER;
+ }
+}
diff --git a/vendor/intervention/gif/src/Exceptions/DecoderException.php b/vendor/intervention/gif/src/Exceptions/DecoderException.php
new file mode 100644
index 000000000..7b4650e21
--- /dev/null
+++ b/vendor/intervention/gif/src/Exceptions/DecoderException.php
@@ -0,0 +1,9 @@
+header;
+ }
+
+ /**
+ * Set header
+ *
+ * @param Header $header
+ */
+ public function setHeader(Header $header): self
+ {
+ $this->header = $header;
+
+ return $this;
+ }
+
+ /**
+ * Get logical screen descriptor
+ *
+ * @return LogicalScreenDescriptor
+ */
+ public function getLogicalScreenDescriptor(): LogicalScreenDescriptor
+ {
+ return $this->logicalScreenDescriptor;
+ }
+
+ /**
+ * Set logical screen descriptor
+ *
+ * @param LogicalScreenDescriptor $descriptor
+ * @return GifDataStream
+ */
+ public function setLogicalScreenDescriptor(LogicalScreenDescriptor $descriptor): self
+ {
+ $this->logicalScreenDescriptor = $descriptor;
+
+ return $this;
+ }
+
+ /**
+ * Return global color table if available else null
+ *
+ * @return null|ColorTable
+ */
+ public function getGlobalColorTable(): ?ColorTable
+ {
+ return $this->globalColorTable;
+ }
+
+ /**
+ * Set global color table
+ *
+ * @param ColorTable $table
+ * @return GifDataStream
+ */
+ public function setGlobalColorTable(ColorTable $table): self
+ {
+ $this->globalColorTable = $table;
+ $this->logicalScreenDescriptor->setGlobalColorTableExistance(true);
+ $this->logicalScreenDescriptor->setGlobalColorTableSize(
+ $table->getLogicalSize()
+ );
+
+ return $this;
+ }
+
+ /**
+ * Get main graphic control extension
+ *
+ * @return NetscapeApplicationExtension
+ */
+ public function getMainApplicationExtension(): ?NetscapeApplicationExtension
+ {
+ foreach ($this->frames as $frame) {
+ if ($extension = $frame->getNetscapeExtension()) {
+ return $extension;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get array of frames
+ *
+ * @return array
+ */
+ public function getFrames(): array
+ {
+ return $this->frames;
+ }
+
+ /**
+ * Return array of "global" comments
+ *
+ * @return array
+ */
+ public function getComments(): array
+ {
+ return $this->comments;
+ }
+
+ /**
+ * Return first frame
+ *
+ * @return null|FrameBlock
+ */
+ public function getFirstFrame(): ?FrameBlock
+ {
+ if (!array_key_exists(0, $this->frames)) {
+ return null;
+ }
+
+ return $this->frames[0];
+ }
+
+ /**
+ * Add frame
+ *
+ * @param FrameBlock $frame
+ * @return GifDataStream
+ */
+ public function addFrame(FrameBlock $frame): self
+ {
+ $this->frames[] = $frame;
+
+ return $this;
+ }
+
+ /**
+ * Add comment extension
+ *
+ * @param CommentExtension $comment
+ * @return GifDataStream
+ */
+ public function addComment(CommentExtension $comment): self
+ {
+ $this->comments[] = $comment;
+
+ return $this;
+ }
+
+ /**
+ * Set the current data
+ *
+ * @param array $frames
+ */
+ public function setFrames(array $frames): self
+ {
+ $this->frames = $frames;
+
+ return $this;
+ }
+
+ /**
+ * Get trailer
+ *
+ * @return Trailer
+ */
+ public function getTrailer(): Trailer
+ {
+ return new Trailer();
+ }
+
+ /**
+ * Determine if gif is animated
+ *
+ * @return bool
+ */
+ public function isAnimated(): bool
+ {
+ return count($this->getFrames()) > 1;
+ }
+
+ /**
+ * Determine if global color table is set
+ *
+ * @return bool
+ */
+ public function hasGlobalColorTable(): bool
+ {
+ return !is_null($this->globalColorTable);
+ }
+}
diff --git a/vendor/intervention/gif/src/Splitter.php b/vendor/intervention/gif/src/Splitter.php
new file mode 100644
index 000000000..7482facf3
--- /dev/null
+++ b/vendor/intervention/gif/src/Splitter.php
@@ -0,0 +1,277 @@
+frames);
+ }
+
+ /**
+ * Get frames
+ *
+ * @return array
+ */
+ public function getFrames(): array
+ {
+ return $this->frames;
+ }
+
+ /**
+ * Get delays
+ *
+ * @return array
+ */
+ public function getDelays(): array
+ {
+ return $this->delays;
+ }
+
+ /**
+ * Set stream of instance
+ *
+ * @param GifDataStream $stream
+ */
+ public function setStream(GifDataStream $stream): self
+ {
+ $this->stream = $stream;
+
+ return $this;
+ }
+
+ /**
+ * Static constructor method
+ *
+ * @param GifDataStream $stream
+ * @return Splitter
+ */
+ public static function create(GifDataStream $stream): self
+ {
+ return new self($stream);
+ }
+
+ /**
+ * Split current stream into array of seperate streams for each frame
+ *
+ * @return Splitter
+ */
+ public function split(): self
+ {
+ $this->frames = [];
+
+ foreach ($this->stream->getFrames() as $frame) {
+ // create separate stream for each frame
+ $gif = Builder::canvas(
+ $this->stream->getLogicalScreenDescriptor()->getWidth(),
+ $this->stream->getLogicalScreenDescriptor()->getHeight()
+ )->getGifDataStream();
+
+ // check if working stream has global color table
+ if ($this->stream->hasGlobalColorTable()) {
+ $gif->setGlobalColorTable($this->stream->getGlobalColorTable());
+ $gif->getLogicalScreenDescriptor()->setGlobalColorTableExistance(true);
+
+ $gif->getLogicalScreenDescriptor()->setGlobalColorTableSorted(
+ $this->stream->getLogicalScreenDescriptor()->getGlobalColorTableSorted()
+ );
+
+ $gif->getLogicalScreenDescriptor()->setGlobalColorTableSize(
+ $this->stream->getLogicalScreenDescriptor()->getGlobalColorTableSize()
+ );
+
+ $gif->getLogicalScreenDescriptor()->setBackgroundColorIndex(
+ $this->stream->getLogicalScreenDescriptor()->getBackgroundColorIndex()
+ );
+
+ $gif->getLogicalScreenDescriptor()->setPixelAspectRatio(
+ $this->stream->getLogicalScreenDescriptor()->getPixelAspectRatio()
+ );
+
+ $gif->getLogicalScreenDescriptor()->setBitsPerPixel(
+ $this->stream->getLogicalScreenDescriptor()->getBitsPerPixel()
+ );
+ }
+
+ // copy original frame
+ $gif->addFrame($frame);
+
+ $this->frames[] = $gif;
+ $this->delays[] = match (is_object($frame->getGraphicControlExtension())) {
+ true => $frame->getGraphicControlExtension()->getDelay(),
+ default => 0,
+ };
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return array of GD library resources for each frame
+ *
+ * @return array
+ */
+ public function toResources(): array
+ {
+ $resources = [];
+
+ foreach ($this->frames as $frame) {
+ if (is_a($frame, GifDataStream::class)) {
+ $resource = imagecreatefromstring($frame->encode());
+ imagepalettetotruecolor($resource);
+ imagesavealpha($resource, true);
+ $resources[] = $resource;
+ }
+ }
+
+ return $resources;
+ }
+
+ /**
+ * Return array of coalesced GD library resources for each frame
+ *
+ * @return array
+ */
+ public function coalesceToResources(): array
+ {
+ $resources = $this->toResources();
+
+ // static gif files don't need to be coalesced
+ if (count($resources) === 1) {
+ return $resources;
+ }
+
+ $width = imagesx($resources[0]);
+ $height = imagesy($resources[0]);
+ $transparent = imagecolortransparent($resources[0]);
+
+ foreach ($resources as $key => $resource) {
+ // get meta data
+ $gif = $this->frames[$key];
+ $descriptor = $gif->getFirstFrame()->getImageDescriptor();
+ $offset_x = $descriptor->getLeft();
+ $offset_y = $descriptor->getTop();
+ $w = $descriptor->getWidth();
+ $h = $descriptor->getHeight();
+
+ if (in_array($this->getDisposalMethod($gif), [DisposalMethod::NONE, DisposalMethod::PREVIOUS])) {
+ if ($key >= 1) {
+ // create normalized gd image
+ $canvas = imagecreatetruecolor($width, $height);
+ if (imagecolortransparent($resource) != -1) {
+ $transparent = imagecolortransparent($resource);
+ } else {
+ $transparent = imagecolorallocatealpha($resource, 255, 0, 255, 127);
+ }
+
+ // fill with transparent
+ imagefill($canvas, 0, 0, $transparent);
+ imagecolortransparent($canvas, $transparent);
+ imagealphablending($canvas, true);
+
+ // insert last as base
+ imagecopy(
+ $canvas,
+ $resources[$key - 1],
+ 0,
+ 0,
+ 0,
+ 0,
+ $width,
+ $height
+ );
+
+ // insert resource
+ imagecopy(
+ $canvas,
+ $resource,
+ $offset_x,
+ $offset_y,
+ 0,
+ 0,
+ $w,
+ $h
+ );
+ } else {
+ imagealphablending($resource, true);
+ $canvas = $resource;
+ }
+ } else {
+ // create normalized gd image
+ $canvas = imagecreatetruecolor($width, $height);
+ if (imagecolortransparent($resource) != -1) {
+ $transparent = imagecolortransparent($resource);
+ } else {
+ $transparent = imagecolorallocatealpha($resource, 255, 0, 255, 127);
+ }
+
+ // fill with transparent
+ imagefill($canvas, 0, 0, $transparent);
+ imagecolortransparent($canvas, $transparent);
+ imagealphablending($canvas, true);
+
+ // insert frame resource
+ imagecopy(
+ $canvas,
+ $resource,
+ $offset_x,
+ $offset_y,
+ 0,
+ 0,
+ $w,
+ $h
+ );
+ }
+
+ $resources[$key] = $canvas;
+ }
+
+ return $resources;
+ }
+
+ /**
+ * Find and return disposal method of given gif data stream
+ *
+ * @param GifDataStream $gif
+ * @return DisposalMethod
+ */
+ private function getDisposalMethod(GifDataStream $gif): DisposalMethod
+ {
+ return $gif->getFirstFrame()->getGraphicControlExtension()->getDisposalMethod();
+ }
+}
diff --git a/vendor/intervention/gif/src/Traits/CanDecode.php b/vendor/intervention/gif/src/Traits/CanDecode.php
new file mode 100644
index 000000000..30fcb2c65
--- /dev/null
+++ b/vendor/intervention/gif/src/Traits/CanDecode.php
@@ -0,0 +1,51 @@
+decode();
+ }
+
+ /**
+ * Get decoder for current instance
+ *
+ * @param resource $source
+ * @param null|int $length
+ * @return AbstractDecoder
+ */
+ protected static function getDecoder($source, ?int $length = null): AbstractDecoder
+ {
+ $classname = self::getDecoderClassname();
+
+ if (!class_exists($classname)) {
+ throw new DecoderException("Decoder for '" . static::class . "' not found.");
+ }
+
+ return new $classname($source, $length);
+ }
+
+ /**
+ * Get classname of decoder for current classname
+ *
+ * @return string
+ */
+ protected static function getDecoderClassname(): string
+ {
+ return sprintf('Intervention\Gif\Decoders\%sDecoder', self::getShortClassname());
+ }
+}
diff --git a/vendor/intervention/gif/src/Traits/CanEncode.php b/vendor/intervention/gif/src/Traits/CanEncode.php
new file mode 100644
index 000000000..836aea3be
--- /dev/null
+++ b/vendor/intervention/gif/src/Traits/CanEncode.php
@@ -0,0 +1,47 @@
+getEncoder()->encode();
+ }
+
+ /**
+ * Get encoder object for current entity
+ *
+ * @return AbstractEncoder
+ */
+ protected function getEncoder(): AbstractEncoder
+ {
+ $classname = $this->getEncoderClassname();
+
+ if (!class_exists($classname)) {
+ throw new EncoderException("Encoder for '" . $this::class . "' not found.");
+ }
+
+ return new $classname($this);
+ }
+
+ /**
+ * Get encoder classname for current entity
+ *
+ * @return string
+ */
+ protected function getEncoderClassname(): string
+ {
+ return sprintf('Intervention\Gif\Encoders\%sEncoder', $this->getShortClassname());
+ }
+}
diff --git a/vendor/intervention/gif/src/Traits/CanHandleFiles.php b/vendor/intervention/gif/src/Traits/CanHandleFiles.php
new file mode 100644
index 000000000..45b9a3e5c
--- /dev/null
+++ b/vendor/intervention/gif/src/Traits/CanHandleFiles.php
@@ -0,0 +1,55 @@
+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
+
+This package contains a Docker image for building a test suite and an analysis
+container. You must have Docker installed on your system to run all tests using
+the following command.
+
+```bash
+docker-compose run --rm --build tests
+```
+
+Run the static analyzer on the code base.
+
+```bash
+docker-compose run --rm --build analysis
+```
+
+## Security
+
+If you discover any security related issues, please email oliver@intervention.io directly.
+
+## Authors
+
+This library is developed and maintained by [Oliver Vogel](https://intervention.io)
+
+Thanks to the community of [contributors](https://github.com/Intervention/image/graphs/contributors) who have helped to improve this project.
+
+## License
+
+Intervention Image is licensed under the [MIT License](LICENSE).
diff --git a/vendor/intervention/image/src/Analyzers/ColorspaceAnalyzer.php b/vendor/intervention/image/src/Analyzers/ColorspaceAnalyzer.php
new file mode 100644
index 000000000..d99222c49
--- /dev/null
+++ b/vendor/intervention/image/src/Analyzers/ColorspaceAnalyzer.php
@@ -0,0 +1,11 @@
+
+ */
+class Collection implements CollectionInterface, IteratorAggregate, Countable
+{
+ /**
+ * Create new collection object
+ *
+ * @param array $items
+ * @return void
+ */
+ public function __construct(protected array $items = [])
+ {
+ }
+
+ /**
+ * Static constructor
+ *
+ * @param array $items
+ * @return self
+ */
+ public static function create(array $items = []): self
+ {
+ return new self($items);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::has()
+ */
+ public function has(int|string $key): bool
+ {
+ return array_key_exists($key, $this->items);
+ }
+
+ /**
+ * Returns Iterator
+ *
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::toArray()
+ */
+ public function toArray(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * Count items in collection
+ *
+ * @return int
+ */
+ 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 int $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];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::get()
+ */
+ 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('.', (string) $query);
+
+ $result = $default;
+ $items = $this->items;
+ foreach ($query as $key) {
+ if (!is_array($items) || !array_key_exists($key, $items)) {
+ $result = $default;
+ break;
+ }
+
+ $result = $items[$key];
+ $items = $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Map each item of collection by given callback
+ *
+ * @param callable $callback
+ * @return self
+ */
+ 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 self
+ */
+ public function filter(callable $callback): self
+ {
+ $items = array_filter($this->items, function ($item) use ($callback) {
+ return $callback($item);
+ });
+
+ return new self($items);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::empty()
+ */
+ public function empty(): CollectionInterface
+ {
+ $this->items = [];
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::slice()
+ */
+ public function slice(int $offset, ?int $length = null): CollectionInterface
+ {
+ $this->items = array_slice($this->items, $offset, $length);
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/AbstractColor.php b/vendor/intervention/image/src/Colors/AbstractColor.php
new file mode 100644
index 000000000..e69278858
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/AbstractColor.php
@@ -0,0 +1,97 @@
+
+ */
+ protected array $channels;
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::channels()
+ */
+ public function channels(): array
+ {
+ return $this->channels;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::channel()
+ */
+ public function channel(string $classname): ColorChannelInterface
+ {
+ $channels = array_filter($this->channels(), function (ColorChannelInterface $channel) use ($classname) {
+ return $channel::class == $classname;
+ });
+
+ if (count($channels) == 0) {
+ throw new ColorException('Color channel ' . $classname . ' could not be found.');
+ }
+
+ return reset($channels);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::normalize()
+ */
+ 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();
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/AbstractColorChannel.php b/vendor/intervention/image/src/Colors/AbstractColorChannel.php
new file mode 100644
index 000000000..b883db2bb
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/AbstractColorChannel.php
@@ -0,0 +1,93 @@
+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(int $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();
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Cmyk/Channels/Cyan.php b/vendor/intervention/image/src/Colors/Cmyk/Channels/Cyan.php
new file mode 100644
index 000000000..f56b095c7
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Cmyk/Channels/Cyan.php
@@ -0,0 +1,20 @@
+channels = [
+ new Cyan($c),
+ new Magenta($m),
+ new Yellow($y),
+ new Key($k),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::create()
+ */
+ public static function create(mixed $input): ColorInterface
+ {
+ return (new class ([
+ Decoders\StringColorDecoder::class,
+ ]) extends AbstractInputHandler
+ {
+ })->handle($input);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::colorspace()
+ */
+ public function colorspace(): ColorspaceInterface
+ {
+ return new Colorspace();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::toHex()
+ */
+ public function toHex(string $prefix = ''): string
+ {
+ return $this->convertTo(RgbColorspace::class)->toHex($prefix);
+ }
+
+ /**
+ * Return the CMYK cyan channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function cyan(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Cyan::class);
+ }
+
+ /**
+ * Return the CMYK magenta channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function magenta(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Magenta::class);
+ }
+
+ /**
+ * Return the CMYK yellow channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function yellow(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Yellow::class);
+ }
+
+ /**
+ * Return the CMYK key channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function key(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Key::class);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::toString()
+ */
+ public function toString(): string
+ {
+ return sprintf(
+ 'cmyk(%d%%, %d%%, %d%%, %d%%)',
+ $this->cyan()->value(),
+ $this->magenta()->value(),
+ $this->yellow()->value(),
+ $this->key()->value()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::isGreyscale()
+ */
+ public function isGreyscale(): bool
+ {
+ return 0 === array_sum([
+ $this->cyan()->value(),
+ $this->magenta()->value(),
+ $this->yellow()->value(),
+ ]);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::isTransparent()
+ */
+ public function isTransparent(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Cmyk/Colorspace.php b/vendor/intervention/image/src/Colors/Cmyk/Colorspace.php
new file mode 100644
index 000000000..66b9b496e
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Cmyk/Colorspace.php
@@ -0,0 +1,81 @@
+
+ */
+ public static array $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);
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ public function importColor(ColorInterface $color): ColorInterface
+ {
+ return match ($color::class) {
+ RgbColor::class => $this->importRgbColor($color),
+ HsvColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
+ HslColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
+ default => $color,
+ };
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return Color
+ * @throws ColorException
+ */
+ protected function importRgbColor(ColorInterface $color): CmykColor
+ {
+ if (!($color instanceof RgbColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ $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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Cmyk/Decoders/StringColorDecoder.php b/vendor/intervention/image/src/Colors/Cmyk/Decoders/StringColorDecoder.php
new file mode 100644
index 000000000..2775f0e53
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Cmyk/Decoders/StringColorDecoder.php
@@ -0,0 +1,39 @@
+[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsl/Channels/Hue.php b/vendor/intervention/image/src/Colors/Hsl/Channels/Hue.php
new file mode 100644
index 000000000..049a849c7
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsl/Channels/Hue.php
@@ -0,0 +1,30 @@
+channels = [
+ new Hue($h),
+ new Saturation($s),
+ new Luminance($l),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::colorspace()
+ */
+ 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
+ {
+ /** @throws void */
+ return $this->channel(Hue::class);
+ }
+
+ /**
+ * Return the Saturation channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function saturation(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Saturation::class);
+ }
+
+ /**
+ * Return the Luminance channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function luminance(): ColorChannelInterface
+ {
+ /** @throws void */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::isTransparent()
+ */
+ public function isTransparent(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsl/Colorspace.php b/vendor/intervention/image/src/Colors/Hsl/Colorspace.php
new file mode 100644
index 000000000..2192bc11d
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsl/Colorspace.php
@@ -0,0 +1,140 @@
+
+ */
+ public static array $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);
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ public function importColor(ColorInterface $color): ColorInterface
+ {
+ return match ($color::class) {
+ CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
+ RgbColor::class => $this->importRgbColor($color),
+ HsvColor::class => $this->importHsvColor($color),
+ default => $color,
+ };
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importRgbColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof RgbColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ // 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)),
+ );
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importHsvColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof HsvColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ // 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)),
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsl/Decoders/StringColorDecoder.php b/vendor/intervention/image/src/Colors/Hsl/Decoders/StringColorDecoder.php
new file mode 100644
index 000000000..779df96ff
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsl/Decoders/StringColorDecoder.php
@@ -0,0 +1,42 @@
+[0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsv/Channels/Hue.php b/vendor/intervention/image/src/Colors/Hsv/Channels/Hue.php
new file mode 100644
index 000000000..bbfc1feb1
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsv/Channels/Hue.php
@@ -0,0 +1,30 @@
+channels = [
+ new Hue($h),
+ new Saturation($s),
+ new Value($v),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::colorspace()
+ */
+ 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
+ {
+ /** @throws void */
+ return $this->channel(Hue::class);
+ }
+
+ /**
+ * Return the Saturation channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function saturation(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Saturation::class);
+ }
+
+ /**
+ * Return the Value channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function value(): ColorChannelInterface
+ {
+ /** @throws void */
+ 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;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::isTransparent()
+ */
+ public function isTransparent(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsv/Colorspace.php b/vendor/intervention/image/src/Colors/Hsv/Colorspace.php
new file mode 100644
index 000000000..d02110523
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsv/Colorspace.php
@@ -0,0 +1,128 @@
+
+ */
+ public static array $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);
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ public function importColor(ColorInterface $color): ColorInterface
+ {
+ return match ($color::class) {
+ CmykColor::class => $this->importRgbColor($color->convertTo(RgbColorspace::class)),
+ RgbColor::class => $this->importRgbColor($color),
+ HslColor::class => $this->importHslColor($color),
+ default => $color,
+ };
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importRgbColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof RgbColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ // 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))
+ );
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importHslColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof HslColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ // 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]);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Hsv/Decoders/StringColorDecoder.php b/vendor/intervention/image/src/Colors/Hsv/Decoders/StringColorDecoder.php
new file mode 100644
index 000000000..9a4d26459
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Hsv/Decoders/StringColorDecoder.php
@@ -0,0 +1,42 @@
+[0-9\.]+), ?(?P[0-9\.]+%?), ?(?P[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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Profile.php b/vendor/intervention/image/src/Colors/Profile.php
new file mode 100644
index 000000000..37d9ce73e
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Profile.php
@@ -0,0 +1,12 @@
+normalize(), 6));
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Channels/Blue.php b/vendor/intervention/image/src/Colors/Rgb/Channels/Blue.php
new file mode 100644
index 000000000..1be1610b2
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Channels/Blue.php
@@ -0,0 +1,9 @@
+channels = [
+ new Red($r),
+ new Green($g),
+ new Blue($b),
+ new Alpha($a),
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::colorspace()
+ */
+ 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
+ {
+ /** @throws void */
+ return $this->channel(Red::class);
+ }
+
+ /**
+ * Return the RGB green color channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function green(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Green::class);
+ }
+
+ /**
+ * Return the RGB blue color channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function blue(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Blue::class);
+ }
+
+ /**
+ * Return the colors alpha channel
+ *
+ * @return ColorChannelInterface
+ */
+ public function alpha(): ColorChannelInterface
+ {
+ /** @throws void */
+ return $this->channel(Alpha::class);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::toHex()
+ */
+ public function toHex(string $prefix = ''): string
+ {
+ if ($this->isTransparent()) {
+ return sprintf(
+ '%s%02x%02x%02x%02x',
+ $prefix,
+ $this->red()->value(),
+ $this->green()->value(),
+ $this->blue()->value(),
+ $this->alpha()->value()
+ );
+ }
+
+ return sprintf(
+ '%s%02x%02x%02x',
+ $prefix,
+ $this->red()->value(),
+ $this->green()->value(),
+ $this->blue()->value()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::toString()
+ */
+ public function toString(): string
+ {
+ if ($this->isTransparent()) {
+ return sprintf(
+ 'rgba(%d, %d, %d, %.1F)',
+ $this->red()->value(),
+ $this->green()->value(),
+ $this->blue()->value(),
+ $this->alpha()->normalize(),
+ );
+ }
+
+ return sprintf(
+ 'rgb(%d, %d, %d)',
+ $this->red()->value(),
+ $this->green()->value(),
+ $this->blue()->value()
+ );
+ }
+
+ /**
+ * {@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;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ColorInterface::isTransparent()
+ */
+ public function isTransparent(): bool
+ {
+ return $this->alpha()->value() < $this->alpha()->max();
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Colorspace.php b/vendor/intervention/image/src/Colors/Rgb/Colorspace.php
new file mode 100644
index 000000000..35deb7564
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Colorspace.php
@@ -0,0 +1,147 @@
+
+ */
+ public static array $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);
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ public function importColor(ColorInterface $color): ColorInterface
+ {
+ return match ($color::class) {
+ CmykColor::class => $this->importCmykColor($color),
+ HsvColor::class => $this->importHsvColor($color),
+ HslColor::class => $this->importHslColor($color),
+ default => $color,
+ };
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importCmykColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof CmykColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ 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())),
+ );
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importHsvColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof HsvColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ $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);
+ }
+
+ /**
+ * @param ColorInterface $color
+ * @return ColorInterface
+ * @throws ColorException
+ */
+ protected function importHslColor(ColorInterface $color): ColorInterface
+ {
+ if (!($color instanceof HslColor)) {
+ throw new ColorException('Unabled to import color of type ' . $color::class . '.');
+ }
+
+ // 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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Decoders/HexColorDecoder.php b/vendor/intervention/image/src/Colors/Rgb/Decoders/HexColorDecoder.php
new file mode 100644
index 000000000..36bf62c58
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Decoders/HexColorDecoder.php
@@ -0,0 +1,50 @@
+[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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Decoders/HtmlColornameDecoder.php b/vendor/intervention/image/src/Colors/Rgb/Decoders/HtmlColornameDecoder.php
new file mode 100644
index 000000000..2c64f2ba3
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Decoders/HtmlColornameDecoder.php
@@ -0,0 +1,179 @@
+
+ */
+ protected static array $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(mixed $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)]);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Decoders/StringColorDecoder.php b/vendor/intervention/image/src/Colors/Rgb/Decoders/StringColorDecoder.php
new file mode 100644
index 000000000..38f977d16
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Decoders/StringColorDecoder.php
@@ -0,0 +1,52 @@
+[0-9\.]+%?), ?(?P[0-9\.]+%?), ?(?P[0-9\.]+%?)' .
+ '(?:, ?(?P(?: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);
+ }
+}
diff --git a/vendor/intervention/image/src/Colors/Rgb/Decoders/TransparentColorDecoder.php b/vendor/intervention/image/src/Colors/Rgb/Decoders/TransparentColorDecoder.php
new file mode 100644
index 000000000..c3823d744
--- /dev/null
+++ b/vendor/intervention/image/src/Colors/Rgb/Decoders/TransparentColorDecoder.php
@@ -0,0 +1,25 @@
+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;
+ }
+
+ /**
+ * Determine if the given input is GIF data format
+ *
+ * @param string $input
+ * @return bool
+ */
+ protected function isGifFormat(string $input): bool
+ {
+ return 1 === preg_match(
+ "/^47494638(37|39)61/",
+ strtoupper(substr(bin2hex($input), 0, 32))
+ );
+ }
+
+ /**
+ * Determine if given input is a path to an existing regular file
+ *
+ * @param mixed $input
+ * @return bool
+ */
+ protected function isFile(mixed $input): bool
+ {
+ if (!is_string($input)) {
+ return false;
+ }
+
+ if (strlen($input) > PHP_MAXPATHLEN) {
+ return false;
+ }
+
+ try {
+ if (!@is_file($input)) {
+ return false;
+ }
+ } catch (Exception) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Extract and return EXIF data from given input which can be binary image
+ * data or a file path.
+ *
+ * @param string $path_or_data
+ * @return CollectionInterface
+ */
+ protected function extractExifData(string $path_or_data): CollectionInterface
+ {
+ if (!function_exists('exif_read_data')) {
+ return new Collection();
+ }
+
+ try {
+ $source = match (true) {
+ $this->isFile($path_or_data) => $path_or_data, // path
+ default => $this->buildFilePointer($path_or_data), // data
+ };
+
+ // extract exif data
+ $data = @exif_read_data($source, null, true);
+ if (is_resource($source)) {
+ fclose($source);
+ }
+ } catch (Exception) {
+ $data = [];
+ }
+
+ return new Collection(is_array($data) ? $data : []);
+ }
+
+ /**
+ * 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 $input
+ * @return object
+ */
+ protected function parseDataUri(mixed $input): object
+ {
+ $pattern = "/^data:(?P\w+\/[-+.\w]+)?" .
+ "(?P(;[-\w]+=[-\w]+)*)(?P;base64)?,(?P.*)/";
+
+ $result = preg_match($pattern, $input, $matches);
+
+ return new class ($matches, $result)
+ {
+ /**
+ * @var array
+ */
+ private array $matches;
+ private int|false $result;
+
+ /**
+ * @param array $matches
+ * @param int|false $result
+ * @return void
+ */
+ public function __construct(array $matches, int|false $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 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;
+ }
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/AbstractDriver.php b/vendor/intervention/image/src/Drivers/AbstractDriver.php
new file mode 100644
index 000000000..bbef4608f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/AbstractDriver.php
@@ -0,0 +1,80 @@
+checkHealth();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::specialize()
+ */
+ public function specialize(
+ ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface $object
+ ): ModifierInterface|AnalyzerInterface|EncoderInterface|DecoderInterface {
+ // return object directly if no specializing is possible
+ if (!($object instanceof SpecializableInterface)) {
+ return $object;
+ }
+
+ // return directly if object is already specialized
+ if ($object instanceof SpecializedInterface) {
+ return $object;
+ }
+
+ // resolve classname for specializable object
+ $driver_namespace = (new ReflectionClass($this))->getNamespaceName();
+ $object_path = substr($object::class, strlen("Intervention\\Image\\"));
+ $specialized_classname = $driver_namespace . "\\" . $object_path;
+
+ if (!class_exists($specialized_classname)) {
+ throw new NotSupportedException(
+ "Class '" . $object_path . "' is not supported by " . $this->id() . " driver."
+ );
+ }
+
+ // create driver specialized object with specializable properties of generic object
+ $specialized = (new $specialized_classname(...$object->specializable()));
+
+ // attach driver
+ return $specialized->setDriver($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::specializeMultiple()
+ */
+ public function specializeMultiple(array $objects): array
+ {
+ return array_map(function ($object) {
+ return $this->specialize(
+ match (true) {
+ is_string($object) => new $object(),
+ is_object($object) => $object,
+ }
+ );
+ }, $objects);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/AbstractEncoder.php b/vendor/intervention/image/src/Drivers/AbstractEncoder.php
new file mode 100644
index 000000000..7b2a9f579
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/AbstractEncoder.php
@@ -0,0 +1,40 @@
+encode($this);
+ }
+
+ /**
+ * Get return value of callback through output buffer
+ *
+ * @param callable $callback
+ * @return string
+ */
+ protected function buffered(callable $callback): string
+ {
+ ob_start();
+ $callback();
+ $buffer = ob_get_contents();
+ ob_end_clean();
+
+ return $buffer;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/AbstractFontProcessor.php b/vendor/intervention/image/src/Drivers/AbstractFontProcessor.php
new file mode 100644
index 000000000..e7817af86
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/AbstractFontProcessor.php
@@ -0,0 +1,179 @@
+wrapTextBlock(new TextBlock($text), $font);
+ $pivot = $this->buildPivot($lines, $font, $position);
+
+ $leading = $this->leading($font);
+ $blockWidth = $this->boxSize((string) $lines->longestLine(), $font)->width();
+
+ $x = $pivot->x();
+ $y = $font->hasFilename() ? $pivot->y() + $this->capHeight($font) : $pivot->y();
+ $x_adjustment = 0;
+
+ foreach ($lines as $line) {
+ $line_width = $this->boxSize((string) $line, $font)->width();
+ $x_adjustment = $font->alignment() == 'left' ? 0 : $blockWidth - $line_width;
+ $x_adjustment = $font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment;
+ $x_adjustment = $font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment;
+ $position = new Point($x + $x_adjustment, $y);
+ $position->rotate($font->angle(), $pivot);
+ $line->setPosition($position);
+ $y += $leading;
+ }
+
+ return $lines;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::nativeFontSize()
+ */
+ public function nativeFontSize(FontInterface $font): float
+ {
+ return $font->size();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::typographicalSize()
+ */
+ public function typographicalSize(FontInterface $font): int
+ {
+ return $this->boxSize('Hy', $font)->height();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::capHeight()
+ */
+ public function capHeight(FontInterface $font): int
+ {
+ return $this->boxSize('T', $font)->height();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::leading()
+ */
+ public function leading(FontInterface $font): int
+ {
+ return intval(round($this->typographicalSize($font) * $font->lineHeight()));
+ }
+
+ /**
+ * Reformat a text block by wrapping each line before the given maximum width
+ *
+ * @param TextBlock $block
+ * @param FontInterface $font
+ * @throws FontException
+ * @return TextBlock
+ */
+ protected function wrapTextBlock(TextBlock $block, FontInterface $font): TextBlock
+ {
+ $newLines = [];
+ foreach ($block as $line) {
+ foreach ($this->wrapLine($line, $font) as $newLine) {
+ $newLines[] = $newLine;
+ }
+ }
+
+ return $block->setLines($newLines);
+ }
+
+ /**
+ * Check if a line exceeds the given maximum width and wrap it if necessary.
+ * The output will be an array of formatted lines that are all within the
+ * maximum width.
+ *
+ * @param Line $line
+ * @param FontInterface $font
+ * @throws FontException
+ * @return array
+ */
+ protected function wrapLine(Line $line, FontInterface $font): array
+ {
+ // no wrap width - no wrapping
+ if (is_null($font->wrapWidth())) {
+ return [$line];
+ }
+
+ $wrapped = [];
+ $formattedLine = new Line();
+
+ foreach ($line as $word) {
+ // calculate width of newly formatted line
+ $lineWidth = $this->boxSize(match ($formattedLine->count()) {
+ 0 => $word,
+ default => (string) $formattedLine . ' ' . $word,
+ }, $font)->width();
+
+ // decide if word fits on current line or a new line must be created
+ if ($line->count() === 1 || $lineWidth <= $font->wrapWidth()) {
+ $formattedLine->add($word);
+ } else {
+ if ($formattedLine->count()) {
+ $wrapped[] = $formattedLine;
+ }
+ $formattedLine = new Line($word);
+ }
+ }
+
+ $wrapped[] = $formattedLine;
+
+ return $wrapped;
+ }
+
+ /**
+ * Build pivot point of textblock according to the font settings and based on given position
+ *
+ * @param TextBlock $block
+ * @param FontInterface $font
+ * @param PointInterface $position
+ * @throws FontException
+ * @return PointInterface
+ */
+ protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface
+ {
+ // bounding box
+ $box = (new Rectangle(
+ $this->boxSize((string) $block->longestLine(), $font)->width(),
+ $this->leading($font) * ($block->count() - 1) + $this->capHeight($font)
+ ));
+
+ // set position
+ $box->setPivot($position);
+
+ // alignment
+ $box->align($font->alignment());
+ $box->valign($font->valignment());
+ $box->rotate($font->angle());
+
+ return $box->last();
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/AbstractInputHandler.php b/vendor/intervention/image/src/Drivers/AbstractInputHandler.php
new file mode 100644
index 000000000..bb0795467
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/AbstractInputHandler.php
@@ -0,0 +1,71 @@
+
+ */
+ protected array $decoders = [];
+
+ /**
+ * Create new input handler instance with given decoder classnames
+ *
+ * @param array $decoders
+ * @return void
+ */
+ 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
+ *
+ * @throws DecoderException
+ * @return AbstractDecoder
+ */
+ protected function chain(): AbstractDecoder
+ {
+ if (count($this->decoders) == 0) {
+ throw new DecoderException('No decoders found in ' . $this::class);
+ }
+
+ // get last decoder in stack
+ list($decoder) = array_slice(array_reverse($this->decoders), 0, 1);
+ $chain = ($decoder instanceof DecoderInterface) ? $decoder : new $decoder();
+
+ // only accept DecoderInterface
+ if (!($chain instanceof DecoderInterface)) {
+ throw new DecoderException('Decoder must implement in ' . DecoderInterface::class);
+ }
+
+ // build decoder chain
+ foreach (array_slice(array_reverse($this->decoders), 1) as $decoder) {
+ $chain = ($decoder instanceof DecoderInterface) ? new ($decoder::class)($chain) : new $decoder($chain);
+ }
+
+ return $chain;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php b/vendor/intervention/image/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
new file mode 100644
index 000000000..b1082cfd3
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Analyzers/ColorspaceAnalyzer.php
@@ -0,0 +1,18 @@
+core()->native());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php b/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
new file mode 100644
index 000000000..60a0f7fc6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorAnalyzer.php
@@ -0,0 +1,42 @@
+colorAt(
+ $image->colorspace(),
+ $image->core()->frame($this->frame_key)->native()
+ );
+ }
+
+ /**
+ * @throws GeometryException
+ * @throws ColorException
+ */
+ 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);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php b/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
new file mode 100644
index 000000000..41d7135ab
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Analyzers/PixelColorsAnalyzer.php
@@ -0,0 +1,25 @@
+colorspace();
+
+ foreach ($image as $frame) {
+ $colors->push(
+ parent::colorAt($colorspace, $frame->native())
+ );
+ }
+
+ return $colors;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php b/vendor/intervention/image/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
new file mode 100644
index 000000000..21ed03060
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Analyzers/ResolutionAnalyzer.php
@@ -0,0 +1,18 @@
+core()->native()));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Analyzers/WidthAnalyzer.php b/vendor/intervention/image/src/Drivers/Gd/Analyzers/WidthAnalyzer.php
new file mode 100644
index 000000000..b57a7e143
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Analyzers/WidthAnalyzer.php
@@ -0,0 +1,17 @@
+core()->native());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Cloner.php b/vendor/intervention/image/src/Drivers/Gd/Cloner.php
new file mode 100644
index 000000000..3813a1be4
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Cloner.php
@@ -0,0 +1,95 @@
+ new Rectangle(imagesx($gd), imagesy($gd)),
+ default => $size,
+ };
+
+ // create new gd image with same size or new given size
+ $clone = imagecreatetruecolor($size->width(), $size->height());
+
+ // copy resolution to clone
+ $resolution = imageresolution($gd);
+ if (is_array($resolution) && array_key_exists(0, $resolution) && array_key_exists(1, $resolution)) {
+ imageresolution($clone, $resolution[0], $resolution[1]);
+ }
+
+ // fill with background
+ $processor = new ColorProcessor();
+ imagefill($clone, 0, 0, $processor->colorToNative($background));
+ imagealphablending($clone, true);
+ imagesavealpha($clone, true);
+
+ return $clone;
+ }
+
+ /**
+ * Create a clone of an GdImage that is positioned on the specified background color.
+ * Possible transparent areas are mixed with this color.
+ *
+ * @param GdImage $gd
+ * @param ColorInterface $background
+ * @throws ColorException
+ * @return GdImage
+ */
+ public static function cloneBlended(GdImage $gd, ColorInterface $background): GdImage
+ {
+ // create empty canvas with same size
+ $clone = static::cloneEmpty($gd, background: $background);
+
+ // transfer actual image to clone
+ imagecopy($clone, $gd, 0, 0, 0, 0, imagesx($gd), imagesy($gd));
+
+ return $clone;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/ColorProcessor.php b/vendor/intervention/image/src/Drivers/Gd/ColorProcessor.php
new file mode 100644
index 000000000..c26603407
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/ColorProcessor.php
@@ -0,0 +1,80 @@
+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);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Core.php b/vendor/intervention/image/src/Drivers/Gd/Core.php
new file mode 100644
index 000000000..a00d68ff2
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Core.php
@@ -0,0 +1,119 @@
+push($frame);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::native()
+ */
+ public function native(): mixed
+ {
+ return $this->first()->native();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::setNative()
+ */
+ public function setNative(mixed $native): self
+ {
+ $this->empty()->push(new Frame($native));
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::frame()
+ */
+ public function frame(int $position): FrameInterface
+ {
+ $frame = $this->getAtPosition($position);
+
+ if (!($frame instanceof FrameInterface)) {
+ throw new AnimationException('Frame #' . $position . ' could not be found in the image.');
+ }
+
+ return $frame;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::loops()
+ */
+ public function loops(): int
+ {
+ return $this->loops;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::setLoops()
+ */
+ public function setLoops(int $loops): self
+ {
+ $this->loops = $loops;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::first()
+ */
+ public function first(): FrameInterface
+ {
+ return parent::first();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::last()
+ */
+ public function last(): FrameInterface
+ {
+ return parent::last();
+ }
+
+ /**
+ * Clone instance
+ *
+ * @return void
+ */
+ public function __clone(): void
+ {
+ foreach ($this->items as $key => $frame) {
+ $this->items[$key] = clone $frame;
+ }
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/AbstractDecoder.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/AbstractDecoder.php
new file mode 100644
index 000000000..5003950d9
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/AbstractDecoder.php
@@ -0,0 +1,56 @@
+isValidBase64($input)) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ return parent::decode(base64_decode($input));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/BinaryImageDecoder.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/BinaryImageDecoder.php
new file mode 100644
index 000000000..c71b32321
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/BinaryImageDecoder.php
@@ -0,0 +1,70 @@
+isGifFormat($input)) {
+ true => $this->decodeGif($input),
+ default => $this->decodeBinary($input),
+ };
+ }
+
+ /**
+ * Decode image from given binary data
+ *
+ * @param string $input
+ * @throws RuntimeException
+ * @return ImageInterface
+ */
+ private function decodeBinary(string $input): ImageInterface
+ {
+ $gd = @imagecreatefromstring($input);
+
+ if ($gd === false) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ // create image instance
+ $image = parent::decode($gd);
+
+ // extract & set exif data
+ $image->setExif($this->extractExifData($input));
+
+ try {
+ // set mediaType on origin
+ $image->origin()->setMediaType(
+ $this->getMediaTypeByBinary($input)
+ );
+ } catch (DecoderException) {
+ }
+
+ // adjust image orientation
+ $image->modify(new AlignRotationModifier());
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/ColorObjectDecoder.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/ColorObjectDecoder.php
new file mode 100644
index 000000000..b1096aa62
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/ColorObjectDecoder.php
@@ -0,0 +1,23 @@
+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()));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePathImageDecoder.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePathImageDecoder.php
new file mode 100644
index 000000000..f83252ba6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePathImageDecoder.php
@@ -0,0 +1,60 @@
+isFile($input)) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ // detect media (mime) type
+ $mediaType = $this->getMediaTypeByFilePath($input);
+
+ $image = match ($mediaType) {
+ // gif files might be animated and therefore cannot
+ // be handled by the standard GD decoder.
+ 'image/gif' => $this->decodeGif($input),
+ default => parent::decode(match ($mediaType) {
+ 'image/jpeg', 'image/jpg', 'image/pjpeg' => imagecreatefromjpeg($input),
+ 'image/webp', 'image/x-webp' => imagecreatefromwebp($input),
+ 'image/png', 'image/x-png' => imagecreatefrompng($input),
+ 'image/avif', 'image/x-avif' => imagecreatefromavif($input),
+ 'image/bmp',
+ 'image/ms-bmp',
+ 'image/x-bitmap',
+ 'image/x-bmp',
+ 'image/x-ms-bmp',
+ 'image/x-win-bitmap',
+ 'image/x-windows-bmp',
+ 'image/x-xbitmap' => imagecreatefrombmp($input),
+ default => throw new DecoderException('Unable to decode input'),
+ }),
+ };
+
+ // set file path & mediaType on origin
+ $image->origin()->setFilePath($input);
+ $image->origin()->setMediaType($mediaType);
+
+ // extract exif
+ $image->setExif($this->extractExifData($input));
+
+ // adjust image orientation
+ $image->modify(new AlignRotationModifier());
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePointerImageDecoder.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePointerImageDecoder.php
new file mode 100644
index 000000000..66a39f015
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/FilePointerImageDecoder.php
@@ -0,0 +1,27 @@
+getRealPath());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php b/vendor/intervention/image/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
new file mode 100644
index 000000000..9b1452b99
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Decoders/Traits/CanDecodeGif.php
@@ -0,0 +1,54 @@
+split();
+ $delays = $splitter->getDelays();
+
+ // build core
+ $core = new Core();
+
+ // set loops on core
+ if ($loops = $gif->getMainApplicationExtension()?->getLoops()) {
+ $core->setLoops($loops);
+ }
+
+ // add GDImage instances to core
+ foreach ($splitter->coalesceToResources() as $key => $native) {
+ $core->push(
+ (new Frame($native))->setDelay($delays[$key] / 100)
+ );
+ }
+
+ // create image
+ $image = new Image(new Driver(), $core);
+
+ // set media type
+ $image->origin()->setMediaType('image/gif');
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Driver.php b/vendor/intervention/image/src/Drivers/Gd/Driver.php
new file mode 100644
index 000000000..090faebd4
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Driver.php
@@ -0,0 +1,169 @@
+core->add(
+ $this->driver->handleInput($source)->core()->first()->setDelay($delay)
+ );
+
+ return $this;
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ 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, array $decoders = []): ImageInterface|ColorInterface
+ {
+ return (new InputHandler($this->specializeMultiple($decoders)))->handle($input);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::colorProcessor()
+ */
+ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface
+ {
+ return new ColorProcessor($colorspace);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::fontProcessor()
+ */
+ public function fontProcessor(): FontProcessorInterface
+ {
+ return new FontProcessor();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::supports()
+ */
+ public function supports(string|Format|FileExtension|MediaType $identifier): bool
+ {
+ try {
+ $format = Format::create($identifier);
+ } catch (NotSupportedException) {
+ return false;
+ }
+
+ return match ($format) {
+ Format::JPEG => boolval(imagetypes() & IMG_JPEG),
+ Format::WEBP => boolval(imagetypes() & IMG_WEBP),
+ Format::GIF => boolval(imagetypes() & IMG_GIF),
+ Format::PNG => boolval(imagetypes() & IMG_PNG),
+ Format::AVIF => boolval(imagetypes() & IMG_AVIF),
+ Format::BMP => boolval(imagetypes() & IMG_BMP),
+ default => false,
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/AvifEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/AvifEncoder.php
new file mode 100644
index 000000000..cc5908738
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/AvifEncoder.php
@@ -0,0 +1,23 @@
+core()->native();
+ $data = $this->buffered(function () use ($gd) {
+ imageavif($gd, null, $this->quality);
+ });
+
+ return new EncodedImage($data, 'image/avif');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/BmpEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/BmpEncoder.php
new file mode 100644
index 000000000..811cb93e5
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/BmpEncoder.php
@@ -0,0 +1,22 @@
+buffered(function () use ($image) {
+ imagebmp($image->core()->native(), null, false);
+ });
+
+ return new EncodedImage($data, 'image/bmp');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/GifEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/GifEncoder.php
new file mode 100644
index 000000000..1031a528c
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/GifEncoder.php
@@ -0,0 +1,60 @@
+isAnimated()) {
+ return $this->encodeAnimated($image);
+ }
+
+ $gd = $image->core()->native();
+ $data = $this->buffered(function () use ($gd) {
+ imageinterlace($gd, $this->interlaced);
+ imagegif($gd);
+ imageinterlace($gd, false);
+ });
+
+ return new EncodedImage($data, 'image/gif');
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ protected function encodeAnimated(ImageInterface $image): EncodedImage
+ {
+ $builder = GifBuilder::canvas(
+ $image->width(),
+ $image->height()
+ );
+
+ foreach ($image as $frame) {
+ $builder->addFrame(
+ source: (string) $this->encode($frame->toImage($image->driver())),
+ delay: $frame->delay(),
+ interlaced: $this->interlaced
+ );
+ }
+
+ try {
+ $builder->setLoops($image->loops());
+ } catch (Exception $e) {
+ throw new EncoderException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ return new EncodedImage($builder->encode(), 'image/gif');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/JpegEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/JpegEncoder.php
new file mode 100644
index 000000000..dd0a4e718
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/JpegEncoder.php
@@ -0,0 +1,26 @@
+core()->native(), background: $image->blendingColor());
+
+ $data = $this->buffered(function () use ($output) {
+ imageinterlace($output, $this->progressive);
+ imagejpeg($output, null, $this->quality);
+ });
+
+ return new EncodedImage($data, 'image/jpeg');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/PngEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/PngEncoder.php
new file mode 100644
index 000000000..6b20ea91b
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/PngEncoder.php
@@ -0,0 +1,25 @@
+core()->native();
+ $data = $this->buffered(function () use ($gd) {
+ imageinterlace($gd, $this->interlaced);
+ imagepng($gd, null, -1);
+ imageinterlace($gd, false);
+ });
+
+ return new EncodedImage($data, 'image/png');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Encoders/WebpEncoder.php b/vendor/intervention/image/src/Drivers/Gd/Encoders/WebpEncoder.php
new file mode 100644
index 000000000..5a838c769
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Encoders/WebpEncoder.php
@@ -0,0 +1,23 @@
+quality === 100 ? IMG_WEBP_LOSSLESS : $this->quality;
+ $data = $this->buffered(function () use ($image, $quality) {
+ imagewebp($image->core()->native(), null, $quality);
+ });
+
+ return new EncodedImage($data, 'image/webp');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/FontProcessor.php b/vendor/intervention/image/src/Drivers/Gd/FontProcessor.php
new file mode 100644
index 000000000..e7254081f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/FontProcessor.php
@@ -0,0 +1,98 @@
+hasFilename()) {
+ // calculate box size from gd font
+ $box = new Rectangle(0, 0);
+ $chars = mb_strlen($text);
+ if ($chars > 0) {
+ $box->setWidth(
+ $chars * $this->gdCharacterWidth((int) $font->filename())
+ );
+ $box->setHeight(
+ $this->gdCharacterHeight((int) $font->filename())
+ );
+ }
+ return $box;
+ }
+
+ // calculate box size from ttf font file with angle 0
+ $box = imageftbbox(
+ $this->nativeFontSize($font),
+ 0,
+ $font->filename(),
+ $text
+ );
+
+ // build size from points
+ return new Rectangle(
+ intval(abs($box[4] - $box[0])),
+ intval(abs($box[5] - $box[1]))
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::nativeFontSize()
+ */
+ public function nativeFontSize(FontInterface $font): float
+ {
+ return floatval(round($font->size() * .76, 6));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FontProcessorInterface::leading()
+ */
+ public function leading(FontInterface $font): int
+ {
+ return (int) round(parent::leading($font) * .8);
+ }
+
+ /**
+ * Return width of a single character
+ *
+ * @param int $gdfont
+ * @return int
+ */
+ protected function gdCharacterWidth(int $gdfont): int
+ {
+ return $gdfont + 4;
+ }
+
+ /**
+ * Return height of a single character
+ *
+ * @param int $gdfont
+ * @return int
+ */
+ protected function gdCharacterHeight(int $gdfont): int
+ {
+ return match ($gdfont) {
+ 2, 3 => 14,
+ 4, 5 => 16,
+ default => 8,
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Frame.php b/vendor/intervention/image/src/Drivers/Gd/Frame.php
new file mode 100644
index 000000000..af1f3f197
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Frame.php
@@ -0,0 +1,190 @@
+native = $native;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::native()
+ */
+ public function native(): GdImage
+ {
+ return $this->native;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::size()
+ */
+ public function size(): SizeInterface
+ {
+ return new Rectangle(imagesx($this->native), imagesy($this->native));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::delay()
+ */
+ public function delay(): float
+ {
+ return $this->delay;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::setDelay()
+ */
+ public function setDelay(float $delay): FrameInterface
+ {
+ $this->delay = $delay;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::dispose()
+ */
+ public function dispose(): int
+ {
+ return $this->dispose;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::setDispose()
+ */
+ public function setDispose(int $dispose): FrameInterface
+ {
+ $this->dispose = $dispose;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::setOffset()
+ */
+ public function setOffset(int $left, int $top): FrameInterface
+ {
+ $this->offset_left = $left;
+ $this->offset_top = $top;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::offsetLeft()
+ */
+ public function offsetLeft(): int
+ {
+ return $this->offset_left;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::setOffsetLeft()
+ */
+ public function setOffsetLeft(int $offset): FrameInterface
+ {
+ $this->offset_left = $offset;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::offsetTop()
+ */
+ public function offsetTop(): int
+ {
+ return $this->offset_top;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FrameInterface::setOffsetTop()
+ */
+ public function setOffsetTop(int $offset): FrameInterface
+ {
+ $this->offset_top = $offset;
+
+ return $this;
+ }
+
+ /**
+ * This workaround helps cloning GdImages which is currently not possible.
+ *
+ * @throws ColorException
+ * @return void
+ */
+ public function __clone(): void
+ {
+ $this->native = Cloner::clone($this->native);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/InputHandler.php b/vendor/intervention/image/src/Drivers/Gd/InputHandler.php
new file mode 100644
index 000000000..d16289352
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/InputHandler.php
@@ -0,0 +1,50 @@
+
+ */
+ protected array $decoders = [
+ NativeObjectDecoder::class,
+ 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,
+ ];
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/AlignRotationModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/AlignRotationModifier.php
new file mode 100644
index 000000000..38383b972
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/AlignRotationModifier.php
@@ -0,0 +1,26 @@
+exif('IFD0.Orientation')) {
+ 2 => $image->flop(),
+ 3 => $image->rotate(180),
+ 4 => $image->rotate(180)->flop(),
+ 5 => $image->rotate(270)->flop(),
+ 6 => $image->rotate(270),
+ 7 => $image->rotate(90)->flop(),
+ 8 => $image->rotate(90),
+ default => $image
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php
new file mode 100644
index 000000000..33e3abac5
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlendTransparencyModifier.php
@@ -0,0 +1,34 @@
+driver()->handleInput(
+ $this->color ? $this->color : $image->blendingColor()
+ );
+
+ foreach ($image as $frame) {
+ // create new canvas with blending color as background
+ $modified = Cloner::cloneBlended(
+ $frame->native(),
+ background: $color
+ );
+
+ // set new gd image
+ $frame->setNative($modified);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlurModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlurModifier.php
new file mode 100644
index 000000000..5357c40e1
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BlurModifier.php
@@ -0,0 +1,23 @@
+amount; $i++) {
+ imagefilter($frame->native(), IMG_FILTER_GAUSSIAN_BLUR);
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/BrightnessModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BrightnessModifier.php
new file mode 100644
index 000000000..747d51d65
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/BrightnessModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FILTER_BRIGHTNESS, intval($this->level * 2.55));
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorizeModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorizeModifier.php
new file mode 100644
index 000000000..4458a5532
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorizeModifier.php
@@ -0,0 +1,26 @@
+red * 2.55);
+ $green = (int) round($this->green * 2.55);
+ $blue = (int) round($this->blue * 2.55);
+
+ foreach ($image as $frame) {
+ imagefilter($frame->native(), IMG_FILTER_COLORIZE, $red, $green, $blue);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorspaceModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorspaceModifier.php
new file mode 100644
index 000000000..748ebc7a4
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ColorspaceModifier.php
@@ -0,0 +1,25 @@
+targetColorspace(), RgbColorspace::class)) {
+ throw new NotSupportedException(
+ 'Only RGB colorspace is supported by GD driver.'
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContainModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContainModifier.php
new file mode 100644
index 000000000..148d2c9e0
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContainModifier.php
@@ -0,0 +1,86 @@
+getCropSize($image);
+ $resize = $this->getResizeSize($image);
+ $background = $this->driver()->handleInput($this->background);
+ $blendingColor = $image->blendingColor();
+
+ foreach ($image as $frame) {
+ $this->modify($frame, $crop, $resize, $background, $blendingColor);
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws ColorException
+ */
+ protected function modify(
+ FrameInterface $frame,
+ SizeInterface $crop,
+ SizeInterface $resize,
+ ColorInterface $background,
+ ColorInterface $blendingColor
+ ): void {
+ // create new gd image
+ $modified = Cloner::cloneEmpty($frame->native(), $resize, $background);
+
+ // make image area transparent to keep transparency
+ // even if background-color is set
+ $transparent = imagecolorallocatealpha(
+ $modified,
+ $blendingColor->channel(Red::class)->value(),
+ $blendingColor->channel(Green::class)->value(),
+ $blendingColor->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 resource
+ $frame->setNative($modified);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContrastModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContrastModifier.php
new file mode 100644
index 000000000..8567ac357
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ContrastModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FILTER_CONTRAST, ($this->level * -1));
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverDownModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverDownModifier.php
new file mode 100644
index 000000000..dc6fbd2d8
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverDownModifier.php
@@ -0,0 +1,19 @@
+scaleDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverModifier.php
new file mode 100644
index 000000000..4343d43d1
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CoverModifier.php
@@ -0,0 +1,54 @@
+getCropSize($image);
+ $resize = $this->getResizeSize($crop);
+
+ foreach ($image as $frame) {
+ $this->modifyFrame($frame, $crop, $resize);
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws ColorException
+ */
+ protected function modifyFrame(FrameInterface $frame, SizeInterface $crop, SizeInterface $resize): void
+ {
+ // create new image
+ $modified = Cloner::cloneEmpty($frame->native(), $resize);
+
+ // copy content from resource
+ imagecopyresampled(
+ $modified,
+ $frame->native(),
+ 0,
+ 0,
+ $crop->pivot()->x(),
+ $crop->pivot()->y(),
+ $resize->width(),
+ $resize->height(),
+ $crop->width(),
+ $crop->height()
+ );
+
+ // set new content as resource
+ $frame->setNative($modified);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/CropModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CropModifier.php
new file mode 100644
index 000000000..56a019fdf
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/CropModifier.php
@@ -0,0 +1,122 @@
+size();
+ $crop = $this->crop($image);
+ $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ foreach ($image as $frame) {
+ $this->cropFrame($frame, $originalSize, $crop, $background);
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws ColorException
+ */
+ protected function cropFrame(
+ FrameInterface $frame,
+ SizeInterface $originalSize,
+ SizeInterface $resizeTo,
+ int $background
+ ): void {
+ // create new image with transparent background
+ $modified = Cloner::cloneEmpty($frame->native(), $resizeTo);
+
+ // define offset
+ $offset_x = $resizeTo->pivot()->x() + $this->offset_x;
+ $offset_y = $resizeTo->pivot()->y() + $this->offset_y;
+
+ // define target width & height
+ $targetWidth = min($resizeTo->width(), $originalSize->width());
+ $targetHeight = min($resizeTo->height(), $originalSize->height());
+ $targetWidth = $targetWidth < $originalSize->width() ? $targetWidth + $offset_x : $targetWidth;
+ $targetHeight = $targetHeight < $originalSize->height() ? $targetHeight + $offset_y : $targetHeight;
+
+ // copy content from resource
+ imagecopyresampled(
+ $modified,
+ $frame->native(),
+ $offset_x * -1,
+ $offset_y * -1,
+ 0,
+ 0,
+ $targetWidth,
+ $targetHeight,
+ $targetWidth,
+ $targetHeight
+ );
+
+ // don't alpha blend for covering areas
+ imagealphablending($modified, false);
+
+ // cover the possible newly created areas with background color
+ if ($resizeTo->width() > $originalSize->width() || $this->offset_x > 0) {
+ imagefilledrectangle(
+ $modified,
+ $originalSize->width() + ($this->offset_x * -1) - $resizeTo->pivot()->x(),
+ 0,
+ $resizeTo->width(),
+ $resizeTo->height(),
+ $background
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ($resizeTo->height() > $originalSize->height() || $this->offset_y > 0) {
+ imagefilledrectangle(
+ $modified,
+ ($this->offset_x * -1) - $resizeTo->pivot()->x(),
+ $originalSize->height() + ($this->offset_y * -1) - $resizeTo->pivot()->y(),
+ ($this->offset_x * -1) + $originalSize->width() - 1 - $resizeTo->pivot()->x(),
+ $resizeTo->height(),
+ $background
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ((($this->offset_x * -1) - $resizeTo->pivot()->x() - 1) > 0) {
+ imagefilledrectangle(
+ $modified,
+ 0,
+ 0,
+ ($this->offset_x * -1) - $resizeTo->pivot()->x() - 1,
+ $resizeTo->height(),
+ $background
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ((($this->offset_y * -1) - $resizeTo->pivot()->y() - 1) > 0) {
+ imagefilledrectangle(
+ $modified,
+ ($this->offset_x * -1) - $resizeTo->pivot()->x(),
+ 0,
+ ($this->offset_x * -1) + $originalSize->width() - $resizeTo->pivot()->x() - 1,
+ ($this->offset_y * -1) - $resizeTo->pivot()->y() - 1,
+ $background
+ );
+ }
+
+ // set new content as resource
+ $frame->setNative($modified);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php
new file mode 100644
index 000000000..da9bd573b
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawEllipseModifier.php
@@ -0,0 +1,73 @@
+drawable->hasBorder()) {
+ imagealphablending($frame->native(), true);
+
+ // slightly smaller ellipse to keep 1px bordered edges clean
+ if ($this->drawable->hasBackgroundColor()) {
+ imagefilledellipse(
+ $frame->native(),
+ $this->drawable()->position()->x(),
+ $this->drawable->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->drawable()->position()->x(),
+ $this->drawable()->position()->y(),
+ $this->drawable->width(),
+ $this->drawable->height(),
+ 0,
+ 360,
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ )
+ );
+ } else {
+ imagealphablending($frame->native(), true);
+ imagefilledellipse(
+ $frame->native(),
+ $this->drawable()->position()->x(),
+ $this->drawable()->position()->y(),
+ $this->drawable->width(),
+ $this->drawable->height(),
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ )
+ );
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawLineModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawLineModifier.php
new file mode 100644
index 000000000..ccf6974fe
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawLineModifier.php
@@ -0,0 +1,39 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ );
+
+ foreach ($image as $frame) {
+ imagealphablending($frame->native(), true);
+ imageantialias($frame->native(), true);
+ imagesetthickness($frame->native(), $this->drawable->width());
+ imageline(
+ $frame->native(),
+ $this->drawable->start()->x(),
+ $this->drawable->start()->y(),
+ $this->drawable->end()->x(),
+ $this->drawable->end()->y(),
+ $color
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPixelModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPixelModifier.php
new file mode 100644
index 000000000..f626ddfbc
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPixelModifier.php
@@ -0,0 +1,31 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->color)
+ );
+
+ foreach ($image as $frame) {
+ imagealphablending($frame->native(), true);
+ imagesetpixel(
+ $frame->native(),
+ $this->position->x(),
+ $this->position->y(),
+ $color
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPolygonModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPolygonModifier.php
new file mode 100644
index 000000000..43628c519
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawPolygonModifier.php
@@ -0,0 +1,46 @@
+drawable->hasBackgroundColor()) {
+ imagealphablending($frame->native(), true);
+ imagefilledpolygon(
+ $frame->native(),
+ $this->drawable->toArray(),
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ )
+ );
+ }
+
+ if ($this->drawable->hasBorder()) {
+ imagealphablending($frame->native(), true);
+ imagesetthickness($frame->native(), $this->drawable->borderSize());
+ imagepolygon(
+ $frame->native(),
+ $this->drawable->toArray(),
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ )
+ );
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php
new file mode 100644
index 000000000..df4a44d41
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/DrawRectangleModifier.php
@@ -0,0 +1,56 @@
+drawable->position();
+
+ foreach ($image as $frame) {
+ // draw background
+ if ($this->drawable->hasBackgroundColor()) {
+ imagealphablending($frame->native(), true);
+ imagefilledrectangle(
+ $frame->native(),
+ $position->x(),
+ $position->y(),
+ $position->x() + $this->drawable->width(),
+ $position->y() + $this->drawable->height(),
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ )
+ );
+ }
+
+ // draw border
+ if ($this->drawable->hasBorder()) {
+ imagealphablending($frame->native(), true);
+ imagesetthickness($frame->native(), $this->drawable->borderSize());
+ imagerectangle(
+ $frame->native(),
+ $position->x(),
+ $position->y(),
+ $position->x() + $this->drawable->width(),
+ $position->y() + $this->drawable->height(),
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ )
+ );
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/FillModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FillModifier.php
new file mode 100644
index 000000000..0fe860faf
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FillModifier.php
@@ -0,0 +1,62 @@
+color($image);
+
+ foreach ($image as $frame) {
+ if ($this->hasPosition()) {
+ $this->floodFillWithColor($frame, $color);
+ } else {
+ $this->fillAllWithColor($frame, $color);
+ }
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ private function color(ImageInterface $image): int
+ {
+ return $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->color)
+ );
+ }
+
+ private function floodFillWithColor(FrameInterface $frame, int $color): void
+ {
+ imagefill(
+ $frame->native(),
+ $this->position->x(),
+ $this->position->y(),
+ $color
+ );
+ }
+
+ private function fillAllWithColor(FrameInterface $frame, int $color): void
+ {
+ imagealphablending($frame->native(), true);
+ imagefilledrectangle(
+ $frame->native(),
+ 0,
+ 0,
+ $frame->size()->width() - 1,
+ $frame->size()->height() - 1,
+ $color
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlipModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlipModifier.php
new file mode 100644
index 000000000..8e832cce0
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlipModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FLIP_VERTICAL);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlopModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlopModifier.php
new file mode 100644
index 000000000..238bc264a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/FlopModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FLIP_HORIZONTAL);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/GammaModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/GammaModifier.php
new file mode 100644
index 000000000..0815a7bad
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/GammaModifier.php
@@ -0,0 +1,21 @@
+native(), 1, $this->gamma);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/GreyscaleModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/GreyscaleModifier.php
new file mode 100644
index 000000000..2eb04c236
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/GreyscaleModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FILTER_GRAYSCALE);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/InvertModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/InvertModifier.php
new file mode 100644
index 000000000..65d42b4e7
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/InvertModifier.php
@@ -0,0 +1,21 @@
+native(), IMG_FILTER_NEGATE);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/PadModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/PadModifier.php
new file mode 100644
index 000000000..73e11b4af
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/PadModifier.php
@@ -0,0 +1,9 @@
+native(), IMG_FILTER_PIXELATE, $this->size, true);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/PlaceModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/PlaceModifier.php
new file mode 100644
index 000000000..eaad5c2b2
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/PlaceModifier.php
@@ -0,0 +1,114 @@
+driver()->handleInput($this->element);
+ $position = $this->getPosition($image, $watermark);
+
+ foreach ($image as $frame) {
+ imagealphablending($frame->native(), true);
+
+ if ($this->opacity === 100) {
+ $this->placeOpaque($frame, $watermark, $position);
+ } else {
+ $this->placeTransparent($frame, $watermark, $position);
+ }
+ }
+
+ return $image;
+ }
+
+ /**
+ * Insert watermark with 100% opacity
+ *
+ * @param FrameInterface $frame
+ * @param ImageInterface $watermark
+ * @param PointInterface $position
+ * @throws RuntimeException
+ * @return void
+ */
+ private function placeOpaque(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void
+ {
+ imagecopy(
+ $frame->native(),
+ $watermark->core()->native(),
+ $position->x(),
+ $position->y(),
+ 0,
+ 0,
+ $watermark->width(),
+ $watermark->height()
+ );
+ }
+
+ /**
+ * Insert watermark transparent with current opacity
+ *
+ * Unfortunately, the original PHP function imagecopymerge does not work reliably.
+ * For example, any transparency of the image to be inserted is not applied correctly.
+ * For this reason, a new GDImage is created into which the original image is inserted
+ * in the first step and the watermark is inserted with 100% opacity in the second
+ * step. This combination is then transferred to the original image again with the
+ * respective opacity.
+ *
+ * Please note: Unfortunately, there is still an edge case, when a transparent image
+ * is placed on a transparent background, the "double" transparent areas appear opaque!
+ *
+ * @param FrameInterface $frame
+ * @param ImageInterface $watermark
+ * @param PointInterface $position
+ * @throws RuntimeException
+ * @return void
+ */
+ private function placeTransparent(FrameInterface $frame, ImageInterface $watermark, PointInterface $position): void
+ {
+ $cut = imagecreatetruecolor($watermark->width(), $watermark->height());
+
+ imagecopy(
+ $cut,
+ $frame->native(),
+ 0,
+ 0,
+ $position->x(),
+ $position->y(),
+ imagesx($cut),
+ imagesy($cut)
+ );
+
+ imagecopy(
+ $cut,
+ $watermark->core()->native(),
+ 0,
+ 0,
+ 0,
+ 0,
+ imagesx($cut),
+ imagesy($cut)
+ );
+
+ imagecopymerge(
+ $frame->native(),
+ $cut,
+ $position->x(),
+ $position->y(),
+ 0,
+ 0,
+ $watermark->width(),
+ $watermark->height(),
+ $this->opacity
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ProfileModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ProfileModifier.php
new file mode 100644
index 000000000..5a5310a13
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ProfileModifier.php
@@ -0,0 +1,20 @@
+limit <= 0) {
+ throw new InputException('Quantization limit must be greater than 0.');
+ }
+
+ // no color reduction if the limit is higher than the colors in the img
+ $colorCount = imagecolorstotal($image->core()->native());
+ if ($colorCount > 0 && $this->limit > $colorCount) {
+ return $image;
+ }
+
+ $width = $image->width();
+ $height = $image->height();
+
+ $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ foreach ($image as $frame) {
+ // create new image for color quantization
+ $reduced = Cloner::cloneEmpty($frame->native(), background: $image->blendingColor());
+
+ // fill with background
+ imagefill($reduced, 0, 0, $background);
+
+ // set transparency
+ imagecolortransparent($reduced, $background);
+
+ // copy original image (colors are limited automatically in the copy process)
+ imagecopy($reduced, $frame->native(), 0, 0, 0, 0, $width, $height);
+
+ // gd library does not support color quantization directly therefore the
+ // colors are decrease by transforming the image to a palette version
+ imagetruecolortopalette($reduced, true, $this->limit);
+
+ $frame->setNative($reduced);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php
new file mode 100644
index 000000000..345798f93
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/RemoveAnimationModifier.php
@@ -0,0 +1,21 @@
+core()->setNative(
+ $this->chosenFrame($image, $this->position)->native()
+ );
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php
new file mode 100644
index 000000000..458a4fd09
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasModifier.php
@@ -0,0 +1,82 @@
+cropSize($image);
+ $background = $this->driver()->handleInput($this->background);
+
+ foreach ($image as $frame) {
+ $this->modify($frame, $resize, $background);
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws ColorException
+ */
+ protected function modify(
+ FrameInterface $frame,
+ SizeInterface $resize,
+ ColorInterface $background,
+ ): void {
+ // create new canvas with target size & target background color
+ $modified = Cloner::cloneEmpty($frame->native(), $resize, $background);
+
+ // 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
+ imagefilledrectangle(
+ $modified,
+ $resize->pivot()->x() * -1,
+ $resize->pivot()->y() * -1,
+ $resize->pivot()->x() * -1 + $frame->size()->width() - 1,
+ $resize->pivot()->y() * -1 + $frame->size()->height() - 1,
+ $transparent
+ );
+
+ // copy image from original with blending alpha
+ imagealphablending($modified, true);
+ imagecopyresampled(
+ $modified,
+ $frame->native(),
+ $resize->pivot()->x() * -1,
+ $resize->pivot()->y() * -1,
+ 0,
+ 0,
+ $frame->size()->width(),
+ $frame->size()->height(),
+ $frame->size()->width(),
+ $frame->size()->height()
+ );
+
+ // set new content as resource
+ $frame->setNative($modified);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifier.php
new file mode 100644
index 000000000..53e375ffe
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeCanvasRelativeModifier.php
@@ -0,0 +1,16 @@
+size()->resizeDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeModifier.php
new file mode 100644
index 000000000..faf67aaf6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResizeModifier.php
@@ -0,0 +1,61 @@
+getAdjustedSize($image);
+ foreach ($image as $frame) {
+ $this->resizeFrame($frame, $resizeTo);
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws ColorException
+ */
+ private function resizeFrame(FrameInterface $frame, SizeInterface $resizeTo): void
+ {
+ // create empty canvas in target size
+ $modified = Cloner::cloneEmpty($frame->native(), $resizeTo);
+
+ // copy content from resource
+ imagecopyresampled(
+ $modified,
+ $frame->native(),
+ $resizeTo->pivot()->x(),
+ $resizeTo->pivot()->y(),
+ 0,
+ 0,
+ $resizeTo->width(),
+ $resizeTo->height(),
+ $frame->size()->width(),
+ $frame->size()->height()
+ );
+
+ // set new content as resource
+ $frame->setNative($modified);
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ protected function getAdjustedSize(ImageInterface $image): SizeInterface
+ {
+ return $image->size()->resize($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResolutionModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResolutionModifier.php
new file mode 100644
index 000000000..6fcc23355
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ResolutionModifier.php
@@ -0,0 +1,24 @@
+x));
+ $y = intval(round($this->y));
+
+ foreach ($image as $frame) {
+ imageresolution($frame->native(), $x, $y);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/RotateModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/RotateModifier.php
new file mode 100644
index 000000000..868112681
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/RotateModifier.php
@@ -0,0 +1,104 @@
+driver()->handleInput($this->background);
+
+ foreach ($image as $frame) {
+ $this->modifyFrame($frame, $background);
+ }
+
+ return $image;
+ }
+
+ /**
+ * Apply rotation modification on given frame, given background
+ * color is used for newly create image areas
+ *
+ * @param FrameInterface $frame
+ * @throws ColorException
+ * @param ColorInterface $background
+ * @return void
+ */
+ protected function modifyFrame(FrameInterface $frame, ColorInterface $background): void
+ {
+ // get transparent color from frame core
+ $transparent = match ($transparent = imagecolortransparent($frame->native())) {
+ -1 => imagecolorallocatealpha(
+ $frame->native(),
+ $background->channel(Red::class)->value(),
+ $background->channel(Green::class)->value(),
+ $background->channel(Blue::class)->value(),
+ 127
+ ),
+ default => $transparent,
+ };
+
+ // rotate original image against transparent background
+ $rotated = imagerotate(
+ $frame->native(),
+ $this->rotationAngle(),
+ $transparent
+ );
+
+ // create size from original after rotation
+ $container = (new Rectangle(
+ imagesx($rotated),
+ imagesy($rotated),
+ ))->movePivot('center');
+
+ // create size from original and rotate points
+ $cutout = (new Rectangle(
+ imagesx($frame->native()),
+ imagesy($frame->native()),
+ $container->pivot()
+ ))->align('center')
+ ->valign('center')
+ ->rotate($this->rotationAngle() * -1);
+
+ // create new gd image
+ $modified = Cloner::cloneEmpty($frame->native(), $container, $background);
+
+ // draw the cutout on new gd image to have a transparent
+ // background where the rotated image will be placed
+ imagealphablending($modified, false);
+ imagefilledpolygon(
+ $modified,
+ $cutout->toArray(),
+ imagecolortransparent($modified)
+ );
+
+ // place rotated image on new gd image
+ imagealphablending($modified, true);
+ imagecopy(
+ $modified,
+ $rotated,
+ 0,
+ 0,
+ 0,
+ 0,
+ imagesx($rotated),
+ imagesy($rotated)
+ );
+
+ $frame->setNative($modified);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleDownModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleDownModifier.php
new file mode 100644
index 000000000..606978d48
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleDownModifier.php
@@ -0,0 +1,16 @@
+size()->scaleDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleModifier.php
new file mode 100644
index 000000000..2486cbb6a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/ScaleModifier.php
@@ -0,0 +1,16 @@
+size()->scale($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/SharpenModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/SharpenModifier.php
new file mode 100644
index 000000000..a1c593970
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/SharpenModifier.php
@@ -0,0 +1,40 @@
+matrix();
+ foreach ($image as $frame) {
+ imageconvolution($frame->native(), $matrix, 1, 0);
+ }
+
+ return $image;
+ }
+
+ /**
+ * Create matrix to be used by imageconvolution()
+ *
+ * @return array>
+ */
+ private function matrix(): array
+ {
+ $min = $this->amount >= 10 ? $this->amount * -0.01 : 0;
+ $max = $this->amount * -0.025;
+ $abs = ((4 * $min + 4 * $max) * -1) + 1;
+
+ return [
+ [$min, $max, $min],
+ [$max, $abs, $max],
+ [$min, $max, $min]
+ ];
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/SliceAnimationModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/SliceAnimationModifier.php
new file mode 100644
index 000000000..84b04c366
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/SliceAnimationModifier.php
@@ -0,0 +1,24 @@
+offset >= $image->count()) {
+ throw new AnimationException('Offset is not in the range of frames.');
+ }
+
+ $image->core()->slice($this->offset, $this->length);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/TextModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/TextModifier.php
new file mode 100644
index 000000000..66551baff
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/TextModifier.php
@@ -0,0 +1,142 @@
+driver()->fontProcessor();
+ $lines = $fontProcessor->textBlock($this->text, $this->font, $this->position);
+
+ // decode text colors
+ $textColor = $this->gdTextColor($image);
+ $strokeColor = $this->gdStrokeColor($image);
+
+ foreach ($image as $frame) {
+ imagealphablending($frame->native(), true);
+ if ($this->font->hasFilename()) {
+ foreach ($lines as $line) {
+ foreach ($this->strokeOffsets($this->font) as $offset) {
+ imagettftext(
+ $frame->native(),
+ $fontProcessor->nativeFontSize($this->font),
+ $this->font->angle() * -1,
+ $line->position()->x() + $offset->x(),
+ $line->position()->y() + $offset->y(),
+ $strokeColor,
+ $this->font->filename(),
+ (string) $line
+ );
+ }
+
+ imagettftext(
+ $frame->native(),
+ $fontProcessor->nativeFontSize($this->font),
+ $this->font->angle() * -1,
+ $line->position()->x(),
+ $line->position()->y(),
+ $textColor,
+ $this->font->filename(),
+ (string) $line
+ );
+ }
+ } else {
+ foreach ($lines as $line) {
+ foreach ($this->strokeOffsets($this->font) as $offset) {
+ imagestring(
+ $frame->native(),
+ $this->gdFont(),
+ $line->position()->x() + $offset->x(),
+ $line->position()->y() + $offset->y(),
+ (string) $line,
+ $strokeColor
+ );
+ }
+
+ imagestring(
+ $frame->native(),
+ $this->gdFont(),
+ $line->position()->x(),
+ $line->position()->y(),
+ (string) $line,
+ $textColor
+ );
+ }
+ }
+ }
+
+ return $image;
+ }
+
+ /**
+ * Decode text color in GD compatible format
+ *
+ * @param ImageInterface $image
+ * @return int
+ * @throws RuntimeException
+ * @throws ColorException
+ */
+ protected function gdTextColor(ImageInterface $image): int
+ {
+ return $this
+ ->driver()
+ ->colorProcessor($image->colorspace())
+ ->colorToNative(parent::textColor());
+ }
+
+ /**
+ * Decode color for stroke (outline) effect in GD compatible format
+ *
+ * @param ImageInterface $image
+ * @return int
+ * @throws RuntimeException
+ * @throws ColorException
+ */
+ protected function gdStrokeColor(ImageInterface $image): int
+ {
+ if (!$this->font->hasStrokeEffect()) {
+ return 0;
+ }
+
+ $color = parent::strokeColor();
+
+ if ($color->isTransparent()) {
+ throw new ColorException(
+ 'The stroke color must be fully opaque.'
+ );
+ }
+
+ return $this
+ ->driver()
+ ->colorProcessor($image->colorspace())
+ ->colorToNative($color);
+ }
+
+ /**
+ * Return GD's internal font size (if no ttf file is set)
+ *
+ * @return int
+ */
+ private function gdFont(): int
+ {
+ if (is_numeric($this->font->filename())) {
+ return intval($this->font->filename());
+ }
+
+ return 1;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Gd/Modifiers/TrimModifier.php b/vendor/intervention/image/src/Drivers/Gd/Modifiers/TrimModifier.php
new file mode 100644
index 000000000..7c011e0d5
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Gd/Modifiers/TrimModifier.php
@@ -0,0 +1,82 @@
+isAnimated()) {
+ throw new NotSupportedException('Trim modifier cannot be applied to animated images.');
+ }
+
+ // apply tolerance with a min. value of .5 because the default tolerance of '0' should
+ // already trim away similar colors which is not the case with imagecropauto.
+ $trimmed = imagecropauto(
+ $image->core()->native(),
+ IMG_CROP_THRESHOLD,
+ max([.5, $this->tolerance / 10]),
+ $this->trimColor($image)
+ );
+
+ // if the tolerance is very high, it is possible that no image is left.
+ // imagick returns a 1x1 pixel image in this case. this does the same.
+ if ($trimmed === false) {
+ $trimmed = $this->driver()->createImage(1, 1)->core()->native();
+ }
+
+ $image->core()->setNative($trimmed);
+
+ return $image;
+ }
+
+ /**
+ * Create an average color from the colors of the four corner points of the given image
+ *
+ * @param ImageInterface $image
+ * @throws RuntimeException
+ * @throws AnimationException
+ * @return int
+ */
+ private function trimColor(ImageInterface $image): int
+ {
+ // trim color base
+ $red = 0;
+ $green = 0;
+ $blue = 0;
+
+ // corner coordinates
+ $size = $image->size();
+ $cornerPoints = [
+ new Point(0, 0),
+ new Point($size->width() - 1, 0),
+ new Point(0, $size->height() - 1),
+ new Point($size->width() - 1, $size->height() - 1),
+ ];
+
+ // create an average color to be used in trim operation
+ foreach ($cornerPoints as $pos) {
+ $cornerColor = imagecolorat($image->core()->native(), $pos->x(), $pos->y());
+ $rgb = imagecolorsforindex($image->core()->native(), $cornerColor);
+ $red += round(round(($rgb['red'] / 51)) * 51);
+ $green += round(round(($rgb['green'] / 51)) * 51);
+ $blue += round(round(($rgb['blue'] / 51)) * 51);
+ }
+
+ $red = (int) round($red / 4);
+ $green = (int) round($green / 4);
+ $blue = (int) round($blue / 4);
+
+ return imagecolorallocate($image->core()->native(), $red, $green, $blue);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php
new file mode 100644
index 000000000..181b4e46b
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ColorspaceAnalyzer.php
@@ -0,0 +1,23 @@
+core()->native()->getImageColorspace()) {
+ Imagick::COLORSPACE_CMYK => new CmykColorspace(),
+ default => new RgbColorspace(),
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php
new file mode 100644
index 000000000..b3ab99695
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/HeightAnalyzer.php
@@ -0,0 +1,17 @@
+core()->native()->getImageHeight();
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php
new file mode 100644
index 000000000..d060e34de
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorAnalyzer.php
@@ -0,0 +1,36 @@
+colorAt(
+ $image->colorspace(),
+ $image->core()->frame($this->frame_key)->native()
+ );
+ }
+
+ /**
+ * @throws ColorException
+ */
+ protected function colorAt(ColorspaceInterface $colorspace, Imagick $imagick): ColorInterface
+ {
+ return $this->driver()
+ ->colorProcessor($colorspace)
+ ->nativeToColor(
+ $imagick->getImagePixelColor($this->x, $this->y)
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php
new file mode 100644
index 000000000..577b9c8c8
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/PixelColorsAnalyzer.php
@@ -0,0 +1,25 @@
+colorspace();
+
+ foreach ($image as $frame) {
+ $colors->push(
+ parent::colorAt($colorspace, $frame->native())
+ );
+ }
+
+ return $colors;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php
new file mode 100644
index 000000000..61fef32fd
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ProfileAnalyzer.php
@@ -0,0 +1,25 @@
+core()->native()->getImageProfiles('icc');
+
+ if (!array_key_exists('icc', $profiles)) {
+ throw new ColorException('No ICC profile found in image.');
+ }
+
+ return new Profile($profiles['icc']);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php
new file mode 100644
index 000000000..6bd4731e6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/ResolutionAnalyzer.php
@@ -0,0 +1,18 @@
+core()->native()->getImageResolution());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php
new file mode 100644
index 000000000..77b1d5027
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Analyzers/WidthAnalyzer.php
@@ -0,0 +1,17 @@
+core()->native()->getImageWidth();
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/ColorProcessor.php b/vendor/intervention/image/src/Drivers/Imagick/ColorProcessor.php
new file mode 100644
index 000000000..3cbb04dd6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/ColorProcessor.php
@@ -0,0 +1,44 @@
+convertTo($this->colorspace)
+ );
+ }
+
+ public function nativeToColor(mixed $native): ColorInterface
+ {
+ return match ($this->colorspace::class) {
+ CmykColorspace::class => $this->colorspace->colorFromNormalized([
+ $native->getColorValue(Imagick::COLOR_CYAN),
+ $native->getColorValue(Imagick::COLOR_MAGENTA),
+ $native->getColorValue(Imagick::COLOR_YELLOW),
+ $native->getColorValue(Imagick::COLOR_BLACK),
+ ]),
+ default => $this->colorspace->colorFromNormalized([
+ $native->getColorValue(Imagick::COLOR_RED),
+ $native->getColorValue(Imagick::COLOR_GREEN),
+ $native->getColorValue(Imagick::COLOR_BLUE),
+ $native->getColorValue(Imagick::COLOR_ALPHA),
+ ]),
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Core.php b/vendor/intervention/image/src/Drivers/Imagick/Core.php
new file mode 100644
index 000000000..e0bc94e9a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Core.php
@@ -0,0 +1,326 @@
+
+ */
+class Core implements CoreInterface, Iterator
+{
+ protected int $iteratorIndex = 0;
+
+ /**
+ * Create new core instance
+ *
+ * @param Imagick $imagick
+ * @return void
+ */
+ public function __construct(protected Imagick $imagick)
+ {
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::has()
+ */
+ public function has(int|string $key): bool
+ {
+ try {
+ $result = $this->imagick->setIteratorIndex($key);
+ } catch (ImagickException) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::push()
+ */
+ public function push($item): CollectionInterface
+ {
+ return $this->add($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::get()
+ */
+ public function get(int|string $key, $default = null): mixed
+ {
+ try {
+ $this->imagick->setIteratorIndex($key);
+ } catch (ImagickException) {
+ return $default;
+ }
+
+ return new Frame($this->imagick->current());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::getAtPosition()
+ */
+ public function getAtPosition(int $key = 0, $default = null): mixed
+ {
+ return $this->get($key, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::empty()
+ */
+ public function empty(): CollectionInterface
+ {
+ $this->imagick->clear();
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::slice()
+ */
+ public function slice(int $offset, ?int $length = null): CollectionInterface
+ {
+ $allowed_indexes = [];
+ $length = is_null($length) ? $this->count() : $length;
+ for ($i = $offset; $i < $offset + $length; $i++) {
+ $allowed_indexes[] = $i;
+ }
+
+ $sliced = new Imagick();
+ foreach ($this->imagick as $key => $native) {
+ if (in_array($key, $allowed_indexes)) {
+ $sliced->addImage($native->getImage());
+ }
+ }
+
+ $sliced = $sliced->coalesceImages();
+ $sliced->setImageIterations($this->imagick->getImageIterations());
+
+ $this->imagick = $sliced;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::add()
+ */
+ public function add(FrameInterface $frame): CoreInterface
+ {
+ $imagick = $frame->native();
+
+ $imagick->setImageDelay(
+ (int) round($frame->delay() * 100)
+ );
+
+ $imagick->setImageDispose($frame->dispose());
+
+ $size = $frame->size();
+ $imagick->setImagePage(
+ $size->width(),
+ $size->height(),
+ $frame->offsetLeft(),
+ $frame->offsetTop()
+ );
+
+ $this->imagick->addImage($imagick);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::count()
+ */
+ public function count(): int
+ {
+ return $this->imagick->getNumberImages();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see Iterator::rewind()
+ */
+ public function current(): mixed
+ {
+ $this->imagick->setIteratorIndex($this->iteratorIndex);
+
+ return new Frame($this->imagick->current());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see Iterator::rewind()
+ */
+ public function next(): void
+ {
+ $this->iteratorIndex = $this->iteratorIndex + 1;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see Iterator::rewind()
+ */
+ public function key(): mixed
+ {
+ return $this->iteratorIndex;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see Iterator::rewind()
+ */
+ public function valid(): bool
+ {
+ try {
+ $result = $this->imagick->setIteratorIndex($this->iteratorIndex);
+ } catch (ImagickException) {
+ return false;
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see Iterator::rewind()
+ */
+ public function rewind(): void
+ {
+ $this->iteratorIndex = 0;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::native()
+ */
+ public function native(): mixed
+ {
+ return $this->imagick;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::setNative()
+ */
+ public function setNative(mixed $native): CoreInterface
+ {
+ $this->imagick = $native;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::frame()
+ */
+ public function frame(int $position): FrameInterface
+ {
+ foreach ($this->imagick as $core) {
+ if ($core->getIteratorIndex() == $position) {
+ return new Frame($core);
+ }
+ }
+
+ throw new AnimationException('Frame #' . $position . ' could not be found in the image.');
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::loops()
+ */
+ public function loops(): int
+ {
+ return $this->imagick->getImageIterations();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CoreInterface::setLoops()
+ */
+ public function setLoops(int $loops): CoreInterface
+ {
+ $this->imagick = $this->imagick->coalesceImages();
+ $this->imagick->setImageIterations($loops);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::first()
+ */
+ public function first(): FrameInterface
+ {
+ return $this->frame(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectableInterface::last()
+ */
+ public function last(): FrameInterface
+ {
+ return $this->frame($this->count() - 1);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see CollectionInterface::toArray()
+ */
+ public function toArray(): array
+ {
+ $frames = [];
+
+ foreach ($this as $frame) {
+ $frames[] = $frame;
+ }
+
+ return $frames;
+ }
+
+ /**
+ * Clone instance
+ *
+ * @return void
+ */
+ public function __clone(): void
+ {
+ $this->imagick = clone $this->imagick;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/Base64ImageDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/Base64ImageDecoder.php
new file mode 100644
index 000000000..ecddde3cb
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/Base64ImageDecoder.php
@@ -0,0 +1,21 @@
+isValidBase64($input)) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ return parent::decode(base64_decode($input));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php
new file mode 100644
index 000000000..177a36e63
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/BinaryImageDecoder.php
@@ -0,0 +1,36 @@
+readImageBlob($input);
+ } catch (ImagickException) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ // decode image
+ $image = parent::decode($imagick);
+
+ // extract exif data
+ $image->setExif($this->extractExifData($input));
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php
new file mode 100644
index 000000000..138046b54
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/ColorObjectDecoder.php
@@ -0,0 +1,22 @@
+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()));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php
new file mode 100644
index 000000000..639c0c88d
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePathImageDecoder.php
@@ -0,0 +1,39 @@
+isFile($input)) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ try {
+ $imagick = new Imagick();
+ $imagick->readImage($input);
+ } catch (ImagickException) {
+ throw new DecoderException('Unable to decode input');
+ }
+
+ // decode image
+ $image = parent::decode($imagick);
+
+ // set file path on origin
+ $image->origin()->setFilePath($input);
+
+ // extract exif data
+ $image->setExif($this->extractExifData($input));
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePointerImageDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePointerImageDecoder.php
new file mode 100644
index 000000000..3145a2b7a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/FilePointerImageDecoder.php
@@ -0,0 +1,27 @@
+getImageFormat() != 'JPEG') {
+ $input = $input->coalesceImages();
+ }
+
+ $image = new Image(
+ new Driver(),
+ new Core($input)
+ );
+
+ // adjust image rotatation
+ $image->modify(new AlignRotationModifier());
+
+ // set media type on origin
+ $image->origin()->setMediaType($input->getImageMimeType());
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Decoders/SplFileInfoImageDecoder.php b/vendor/intervention/image/src/Drivers/Imagick/Decoders/SplFileInfoImageDecoder.php
new file mode 100644
index 000000000..ad96b7cec
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Decoders/SplFileInfoImageDecoder.php
@@ -0,0 +1,22 @@
+getRealPath());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Driver.php b/vendor/intervention/image/src/Drivers/Imagick/Driver.php
new file mode 100644
index 000000000..2c01f83b3
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Driver.php
@@ -0,0 +1,159 @@
+newImage($width, $height, $background, 'png');
+ $imagick->setType(Imagick::IMGTYPE_UNDEFINED);
+ $imagick->setImageType(Imagick::IMGTYPE_UNDEFINED);
+ $imagick->setColorspace(Imagick::COLORSPACE_SRGB);
+ $imagick->setImageResolution(96, 96);
+ $imagick->setImageBackgroundColor($background);
+
+ return new Image($this, new Core($imagick));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::createAnimation()
+ */
+ public function createAnimation(callable $init): ImageInterface
+ {
+ $imagick = new Imagick();
+ $imagick->setFormat('gif');
+
+ $animation = new class ($this, $imagick)
+ {
+ public function __construct(
+ protected DriverInterface $driver,
+ public Imagick $imagick
+ ) {
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ public function add(mixed $source, float $delay = 1): self
+ {
+ $native = $this->driver->handleInput($source)->core()->native();
+ $native->setImageDelay(intval(round($delay * 100)));
+
+ $this->imagick->addImage($native);
+
+ return $this;
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ public function __invoke(): ImageInterface
+ {
+ return new Image(
+ $this->driver,
+ new Core($this->imagick)
+ );
+ }
+ };
+
+ $init($animation);
+
+ return call_user_func($animation);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::handleInput()
+ */
+ public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface
+ {
+ return (new InputHandler($this->specializeMultiple($decoders)))->handle($input);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::colorProcessor()
+ */
+ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface
+ {
+ return new ColorProcessor($colorspace);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::fontProcessor()
+ */
+ public function fontProcessor(): FontProcessorInterface
+ {
+ return new FontProcessor();
+ }
+
+ public function supports(string|Format|FileExtension|MediaType $identifier): bool
+ {
+ try {
+ $format = Format::create($identifier);
+ } catch (NotSupportedException) {
+ return false;
+ }
+
+ return count(Imagick::queryFormats($format->name)) >= 1;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/AvifEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/AvifEncoder.php
new file mode 100644
index 000000000..b60c5e693
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/AvifEncoder.php
@@ -0,0 +1,30 @@
+core()->native();
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+ $imagick->setCompressionQuality($this->quality);
+ $imagick->setImageCompressionQuality($this->quality);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/avif');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/BmpEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/BmpEncoder.php
new file mode 100644
index 000000000..293767def
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/BmpEncoder.php
@@ -0,0 +1,28 @@
+core()->native();
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/bmp');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/GifEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/GifEncoder.php
new file mode 100644
index 000000000..41783a391
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/GifEncoder.php
@@ -0,0 +1,33 @@
+core()->native();
+
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+
+ if ($this->interlaced) {
+ $imagick->setInterlaceScheme(Imagick::INTERLACE_LINE);
+ }
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/gif');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/HeicEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/HeicEncoder.php
new file mode 100644
index 000000000..64af42f10
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/HeicEncoder.php
@@ -0,0 +1,28 @@
+core()->native();
+
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompressionQuality($this->quality);
+ $imagick->setImageCompressionQuality($this->quality);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/heic');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/Jpeg2000Encoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/Jpeg2000Encoder.php
new file mode 100644
index 000000000..db0cc13a1
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/Jpeg2000Encoder.php
@@ -0,0 +1,33 @@
+core()->native();
+ $imagick->setImageBackgroundColor('white');
+ $imagick->setBackgroundColor('white');
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+ $imagick->setCompressionQuality($this->quality);
+ $imagick->setImageCompressionQuality($this->quality);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/jp2');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/JpegEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/JpegEncoder.php
new file mode 100644
index 000000000..b70f0e36a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/JpegEncoder.php
@@ -0,0 +1,46 @@
+driver()
+ ->colorProcessor($image->colorspace())
+ ->colorToNative($image->blendingColor());
+
+ // set alpha value to 1 because Imagick renders
+ // possible full transparent colors as black
+ $background->setColorValue(Imagick::COLOR_ALPHA, 1);
+
+ $imagick = $image->core()->native();
+ $imagick->setImageBackgroundColor($background);
+ $imagick->setBackgroundColor($background);
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+ $imagick->setCompressionQuality($this->quality);
+ $imagick->setImageCompressionQuality($this->quality);
+ $imagick->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
+
+ if ($this->progressive) {
+ $imagick->setInterlaceScheme(Imagick::INTERLACE_PLANE);
+ }
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/jpeg');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/PngEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/PngEncoder.php
new file mode 100644
index 000000000..b6614ba1b
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/PngEncoder.php
@@ -0,0 +1,32 @@
+core()->native();
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+
+ if ($this->interlaced) {
+ $imagick->setInterlaceScheme(Imagick::INTERLACE_LINE);
+ }
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/png');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/TiffEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/TiffEncoder.php
new file mode 100644
index 000000000..a03e0ae3e
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/TiffEncoder.php
@@ -0,0 +1,29 @@
+core()->native();
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($imagick->getImageCompression());
+ $imagick->setImageCompression($imagick->getImageCompression());
+ $imagick->setCompressionQuality($this->quality);
+ $imagick->setImageCompressionQuality($this->quality);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/tiff');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Encoders/WebpEncoder.php b/vendor/intervention/image/src/Drivers/Imagick/Encoders/WebpEncoder.php
new file mode 100644
index 000000000..552b23ab7
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Encoders/WebpEncoder.php
@@ -0,0 +1,33 @@
+core()->native();
+ $imagick->setImageBackgroundColor(new ImagickPixel('transparent'));
+
+ $imagick = $imagick->mergeImageLayers(Imagick::LAYERMETHOD_MERGE);
+ $imagick->setFormat($format);
+ $imagick->setImageFormat($format);
+ $imagick->setCompression($compression);
+ $imagick->setImageCompression($compression);
+ $imagick->setImageCompressionQuality($this->quality);
+
+ return new EncodedImage($imagick->getImagesBlob(), 'image/webp');
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/FontProcessor.php b/vendor/intervention/image/src/Drivers/Imagick/FontProcessor.php
new file mode 100644
index 000000000..6ca1de8e5
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/FontProcessor.php
@@ -0,0 +1,72 @@
+toImagickDraw($font);
+ $dimensions = (new Imagick())->queryFontMetrics($draw, $text);
+
+ return new Rectangle(
+ intval(round($dimensions['textWidth'])),
+ intval(round($dimensions['ascender'] + $dimensions['descender'])),
+ );
+ }
+
+ /**
+ * Imagick::annotateImage() needs an ImagickDraw object - this method takes
+ * the font object as the base and adds an optional passed color to the new
+ * ImagickDraw object.
+ *
+ * @param FontInterface $font
+ * @param null|ImagickPixel $color
+ * @throws FontException
+ * @throws ImagickDrawException
+ * @throws ImagickException
+ * @return ImagickDraw
+ */
+ public function toImagickDraw(FontInterface $font, ?ImagickPixel $color = null): ImagickDraw
+ {
+ if (!$font->hasFilename()) {
+ throw new FontException('No font file specified.');
+ }
+
+ $draw = new ImagickDraw();
+ $draw->setStrokeAntialias(true);
+ $draw->setTextAntialias(true);
+ $draw->setFont($font->filename());
+ $draw->setFontSize($this->nativeFontSize($font));
+ $draw->setTextAlignment(Imagick::ALIGN_LEFT);
+
+ if ($color) {
+ $draw->setFillColor($color);
+ }
+
+ return $draw;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Frame.php b/vendor/intervention/image/src/Drivers/Imagick/Frame.php
new file mode 100644
index 000000000..2439c855d
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Frame.php
@@ -0,0 +1,170 @@
+native->setImageBackgroundColor($background);
+ $this->native->setBackgroundColor($background);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::toImage()
+ */
+ public function toImage(DriverInterface $driver): ImageInterface
+ {
+ return new Image($driver, new Core($this->native()));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setNative()
+ */
+ public function setNative($native): FrameInterface
+ {
+ $this->native = $native;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::native()
+ */
+ public function native(): Imagick
+ {
+ return $this->native;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::size()
+ */
+ public function size(): SizeInterface
+ {
+ return new Rectangle(
+ $this->native->getImageWidth(),
+ $this->native->getImageHeight()
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::delay()
+ */
+ public function delay(): float
+ {
+ return $this->native->getImageDelay() / 100;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setDelay()
+ */
+ public function setDelay(float $delay): FrameInterface
+ {
+ $this->native->setImageDelay(intval(round($delay * 100)));
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::dispose()
+ */
+ public function dispose(): int
+ {
+ return $this->native->getImageDispose();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setDispose()
+ */
+ public function setDispose(int $dispose): FrameInterface
+ {
+ $this->native->setImageDispose($dispose);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setOffset()
+ */
+ public function setOffset(int $left, int $top): FrameInterface
+ {
+ $this->native->setImagePage(
+ $this->native->getImageWidth(),
+ $this->native->getImageHeight(),
+ $left,
+ $top
+ );
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::offsetLeft()
+ */
+ public function offsetLeft(): int
+ {
+ return $this->native->getImagePage()['x'];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setOffsetLeft()
+ */
+ public function setOffsetLeft(int $offset): FrameInterface
+ {
+ return $this->setOffset($offset, $this->offsetTop());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::offsetTop()
+ */
+ public function offsetTop(): int
+ {
+ return $this->native->getImagePage()['y'];
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DriverInterface::setOffsetTop()
+ */
+ public function setOffsetTop(int $offset): FrameInterface
+ {
+ return $this->setOffset($this->offsetLeft(), $offset);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/InputHandler.php b/vendor/intervention/image/src/Drivers/Imagick/InputHandler.php
new file mode 100644
index 000000000..5c7b748b9
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/InputHandler.php
@@ -0,0 +1,50 @@
+
+ */
+ protected array $decoders = [
+ NativeObjectDecoder::class,
+ 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,
+ ];
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php
new file mode 100644
index 000000000..3427d4ad1
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/AlignRotationModifier.php
@@ -0,0 +1,54 @@
+core()->native()->getImageOrientation()) {
+ case Imagick::ORIENTATION_TOPRIGHT: // 2
+ $image->core()->native()->flopImage();
+ break;
+
+ case Imagick::ORIENTATION_BOTTOMRIGHT: // 3
+ $image->core()->native()->rotateimage("#000", 180);
+ break;
+
+ case Imagick::ORIENTATION_BOTTOMLEFT: // 4
+ $image->core()->native()->rotateimage("#000", 180);
+ $image->core()->native()->flopImage();
+ break;
+
+ case Imagick::ORIENTATION_LEFTTOP: // 5
+ $image->core()->native()->rotateimage("#000", -270);
+ $image->core()->native()->flopImage();
+ break;
+
+ case Imagick::ORIENTATION_RIGHTTOP: // 6
+ $image->core()->native()->rotateimage("#000", -270);
+ break;
+
+ case Imagick::ORIENTATION_RIGHTBOTTOM: // 7
+ $image->core()->native()->rotateimage("#000", -90);
+ $image->core()->native()->flopImage();
+ break;
+
+ case Imagick::ORIENTATION_LEFTBOTTOM: // 8
+ $image->core()->native()->rotateimage("#000", -90);
+ break;
+ }
+
+ // set new orientation in image
+ $image->core()->native()->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php
new file mode 100644
index 000000000..f6586793a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlendTransparencyModifier.php
@@ -0,0 +1,35 @@
+driver()->handleInput(
+ $this->color ? $this->color : $image->blendingColor()
+ );
+
+ // get imagickpixel from color
+ $pixel = $this->driver()
+ ->colorProcessor($image->colorspace())
+ ->colorToNative($color);
+
+ // merge transparent areas with the background color
+ foreach ($image as $frame) {
+ $frame->native()->setImageBackgroundColor($pixel);
+ $frame->native()->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
+ $frame->native()->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlurModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlurModifier.php
new file mode 100644
index 000000000..d7ece8221
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BlurModifier.php
@@ -0,0 +1,21 @@
+native()->blurImage(1 * $this->amount, 0.5 * $this->amount);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BrightnessModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BrightnessModifier.php
new file mode 100644
index 000000000..80fd9dfaa
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/BrightnessModifier.php
@@ -0,0 +1,21 @@
+native()->modulateImage(100 + $this->level, 100, 100);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorizeModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorizeModifier.php
new file mode 100644
index 000000000..c4abc30e8
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorizeModifier.php
@@ -0,0 +1,34 @@
+normalizeLevel($this->red);
+ $green = $this->normalizeLevel($this->green);
+ $blue = $this->normalizeLevel($this->blue);
+
+ foreach ($image as $frame) {
+ $qrange = $frame->native()->getQuantumRange();
+ $frame->native()->levelImage(0, $red, $qrange['quantumRangeLong'], Imagick::CHANNEL_RED);
+ $frame->native()->levelImage(0, $green, $qrange['quantumRangeLong'], Imagick::CHANNEL_GREEN);
+ $frame->native()->levelImage(0, $blue, $qrange['quantumRangeLong'], Imagick::CHANNEL_BLUE);
+ }
+
+ return $image;
+ }
+
+ private function normalizeLevel(int $level): int
+ {
+ return $level > 0 ? intval(round($level / 5)) : intval(round(($level + 100) / 100));
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorspaceModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorspaceModifier.php
new file mode 100644
index 000000000..37e10200f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ColorspaceModifier.php
@@ -0,0 +1,51 @@
+
+ */
+ protected static array $mapping = [
+ RgbColorspace::class => Imagick::COLORSPACE_SRGB,
+ CmykColorspace::class => Imagick::COLORSPACE_CMYK,
+ ];
+
+ public function apply(ImageInterface $image): ImageInterface
+ {
+ $colorspace = $this->targetColorspace();
+
+ $imagick = $image->core()->native();
+ $imagick->transformImageColorspace(
+ $this->getImagickColorspace($colorspace)
+ );
+
+ return $image;
+ }
+
+ /**
+ * @throws NotSupportedException
+ */
+ private function getImagickColorspace(ColorspaceInterface $colorspace): int
+ {
+ if (!array_key_exists($colorspace::class, self::$mapping)) {
+ throw new NotSupportedException('Given colorspace is not supported.');
+ }
+
+ return self::$mapping[$colorspace::class];
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContainModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContainModifier.php
new file mode 100644
index 000000000..b01bfd3ba
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContainModifier.php
@@ -0,0 +1,95 @@
+getCropSize($image);
+ $resize = $this->getResizeSize($image);
+ $transparent = new ImagickPixel('transparent');
+ $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ foreach ($image as $frame) {
+ $frame->native()->scaleImage(
+ $crop->width(),
+ $crop->height(),
+ );
+
+ $frame->native()->setBackgroundColor($transparent);
+ $frame->native()->setImageBackgroundColor($transparent);
+
+ $frame->native()->extentImage(
+ $resize->width(),
+ $resize->height(),
+ $crop->pivot()->x() * -1,
+ $crop->pivot()->y() * -1
+ );
+
+ if ($resize->width() > $crop->width()) {
+ // fill new emerged background
+ $draw = new ImagickDraw();
+ $draw->setFillColor($background);
+
+ $delta = abs($crop->pivot()->x());
+
+ if ($delta > 0) {
+ $draw->rectangle(
+ 0,
+ 0,
+ $delta - 1,
+ $resize->height()
+ );
+ }
+
+ $draw->rectangle(
+ $crop->width() + $delta,
+ 0,
+ $resize->width(),
+ $resize->height()
+ );
+
+ $frame->native()->drawImage($draw);
+ }
+
+ if ($resize->height() > $crop->height()) {
+ // fill new emerged background
+ $draw = new ImagickDraw();
+ $draw->setFillColor($background);
+
+ $delta = abs($crop->pivot()->y());
+
+ if ($delta > 0) {
+ $draw->rectangle(
+ 0,
+ 0,
+ $resize->width(),
+ $delta - 1
+ );
+ }
+
+ $draw->rectangle(
+ 0,
+ $crop->height() + $delta,
+ $resize->width(),
+ $resize->height()
+ );
+
+ $frame->native()->drawImage($draw);
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContrastModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContrastModifier.php
new file mode 100644
index 000000000..72083fa1f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ContrastModifier.php
@@ -0,0 +1,21 @@
+native()->sigmoidalContrastImage($this->level > 0, abs($this->level / 4), 0);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverDownModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverDownModifier.php
new file mode 100644
index 000000000..efc822978
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverDownModifier.php
@@ -0,0 +1,19 @@
+scaleDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverModifier.php
new file mode 100644
index 000000000..d133cfa8d
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CoverModifier.php
@@ -0,0 +1,34 @@
+getCropSize($image);
+ $resize = $this->getResizeSize($crop);
+
+ foreach ($image as $frame) {
+ $frame->native()->extentImage(
+ $crop->width(),
+ $crop->height(),
+ $crop->pivot()->x(),
+ $crop->pivot()->y()
+ );
+
+ $frame->native()->scaleImage(
+ $resize->width(),
+ $resize->height()
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CropModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CropModifier.php
new file mode 100644
index 000000000..067c1fd44
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/CropModifier.php
@@ -0,0 +1,93 @@
+size();
+ $crop = $this->crop($image);
+ $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ $transparent = new ImagickPixel('transparent');
+
+ $draw = new ImagickDraw();
+ $draw->setFillColor($background);
+
+ foreach ($image as $frame) {
+ $frame->native()->setBackgroundColor($transparent);
+ $frame->native()->setImageBackgroundColor($transparent);
+
+ // crop image
+ $frame->native()->extentImage(
+ $crop->width(),
+ $crop->height(),
+ $crop->pivot()->x() + $this->offset_x,
+ $crop->pivot()->y() + $this->offset_y
+ );
+
+ // repage
+ $frame->native()->setImagePage(
+ $crop->width(),
+ $crop->height(),
+ 0,
+ 0,
+ );
+
+ // cover the possible newly created areas with background color
+ if ($crop->width() > $originalSize->width() || $this->offset_x > 0) {
+ $draw->rectangle(
+ $originalSize->width() + ($this->offset_x * -1) - $crop->pivot()->x(),
+ 0,
+ $crop->width(),
+ $crop->height()
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ($crop->height() > $originalSize->height() || $this->offset_y > 0) {
+ $draw->rectangle(
+ ($this->offset_x * -1) - $crop->pivot()->x(),
+ $originalSize->height() + ($this->offset_y * -1) - $crop->pivot()->y(),
+ ($this->offset_x * -1) + $originalSize->width() - 1 - $crop->pivot()->x(),
+ $crop->height()
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ((($this->offset_x * -1) - $crop->pivot()->x() - 1) > 0) {
+ $draw->rectangle(
+ 0,
+ 0,
+ ($this->offset_x * -1) - $crop->pivot()->x() - 1,
+ $crop->height()
+ );
+ }
+
+ // cover the possible newly created areas with background color
+ if ((($this->offset_y * -1) - $crop->pivot()->y() - 1) > 0) {
+ $draw->rectangle(
+ ($this->offset_x * -1) - $crop->pivot()->x(),
+ 0,
+ ($this->offset_x * -1) + $originalSize->width() - $crop->pivot()->x() - 1,
+ ($this->offset_y * -1) - $crop->pivot()->y() - 1,
+ );
+ }
+
+ $frame->native()->drawImage($draw);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php
new file mode 100644
index 000000000..1508f9180
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawEllipseModifier.php
@@ -0,0 +1,51 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ );
+
+ $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ );
+
+ foreach ($image as $frame) {
+ $drawing = new ImagickDraw();
+ $drawing->setFillColor($background_color);
+
+ if ($this->drawable->hasBorder()) {
+ $drawing->setStrokeWidth($this->drawable->borderSize());
+ $drawing->setStrokeColor($border_color);
+ }
+
+ $drawing->ellipse(
+ $this->drawable->position()->x(),
+ $this->drawable->position()->y(),
+ $this->drawable->width() / 2,
+ $this->drawable->height() / 2,
+ 0,
+ 360
+ );
+
+ $frame->native()->drawImage($drawing);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawLineModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawLineModifier.php
new file mode 100644
index 000000000..ac09e51de
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawLineModifier.php
@@ -0,0 +1,42 @@
+setStrokeWidth($this->drawable->width());
+ $drawing->setFillOpacity(0);
+ $drawing->setStrokeColor(
+ $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ )
+ );
+
+ $drawing->line(
+ $this->drawable->start()->x(),
+ $this->drawable->start()->y(),
+ $this->drawable->end()->x(),
+ $this->drawable->end()->y(),
+ );
+
+ foreach ($image as $frame) {
+ $frame->native()->drawImage($drawing);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPixelModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPixelModifier.php
new file mode 100644
index 000000000..e69ecc279
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPixelModifier.php
@@ -0,0 +1,30 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->color)
+ );
+
+ $pixel = new ImagickDraw();
+ $pixel->setFillColor($color);
+ $pixel->point($this->position->x(), $this->position->y());
+
+ foreach ($image as $frame) {
+ $frame->native()->drawImage($pixel);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php
new file mode 100644
index 000000000..1d66b9e42
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawPolygonModifier.php
@@ -0,0 +1,62 @@
+drawable->hasBackgroundColor()) {
+ $background_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ );
+
+ $drawing->setFillColor($background_color);
+ }
+
+ if ($this->drawable->hasBorder()) {
+ $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ );
+
+ $drawing->setStrokeColor($border_color);
+ $drawing->setStrokeWidth($this->drawable->borderSize());
+ }
+
+ $drawing->polygon($this->points());
+
+ foreach ($image as $frame) {
+ $frame->native()->drawImage($drawing);
+ }
+
+ return $image;
+ }
+
+ /**
+ * Return points of drawable in processable form for ImagickDraw
+ *
+ * @return array>
+ */
+ private function points(): array
+ {
+ $points = [];
+ foreach ($this->drawable as $point) {
+ $points[] = ['x' => $point->x(), 'y' => $point->y()];
+ }
+
+ return $points;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php
new file mode 100644
index 000000000..8f8e79ea6
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/DrawRectangleModifier.php
@@ -0,0 +1,50 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->backgroundColor()
+ );
+
+ $border_color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->borderColor()
+ );
+
+ $drawing->setFillColor($background_color);
+ if ($this->drawable->hasBorder()) {
+ $drawing->setStrokeColor($border_color);
+ $drawing->setStrokeWidth($this->drawable->borderSize());
+ }
+
+ // build rectangle
+ $drawing->rectangle(
+ $this->drawable->position()->x(),
+ $this->drawable->position()->y(),
+ $this->drawable->position()->x() + $this->drawable->width(),
+ $this->drawable->position()->y() + $this->drawable->height()
+ );
+
+ foreach ($image as $frame) {
+ $frame->native()->drawImage($drawing);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FillModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FillModifier.php
new file mode 100644
index 000000000..0eee743d4
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FillModifier.php
@@ -0,0 +1,65 @@
+driver()->handleInput($this->color);
+ $pixel = $this->driver()
+ ->colorProcessor($image->colorspace())
+ ->colorToNative($color);
+
+ foreach ($image as $frame) {
+ if ($this->hasPosition()) {
+ $this->floodFillWithColor($frame, $pixel);
+ } else {
+ $this->fillAllWithColor($frame, $pixel);
+ }
+ }
+
+ return $image;
+ }
+
+ private function floodFillWithColor(FrameInterface $frame, ImagickPixel $pixel): void
+ {
+ $target = $frame->native()->getImagePixelColor(
+ $this->position->x(),
+ $this->position->y()
+ );
+
+ $frame->native()->floodfillPaintImage(
+ $pixel,
+ 100,
+ $target,
+ $this->position->x(),
+ $this->position->y(),
+ false,
+ Imagick::CHANNEL_ALL
+ );
+ }
+
+ private function fillAllWithColor(FrameInterface $frame, ImagickPixel $pixel): void
+ {
+ $draw = new ImagickDraw();
+ $draw->setFillColor($pixel);
+ $draw->rectangle(
+ 0,
+ 0,
+ $frame->native()->getImageWidth(),
+ $frame->native()->getImageHeight()
+ );
+ $frame->native()->drawImage($draw);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlipModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlipModifier.php
new file mode 100644
index 000000000..edfa12443
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlipModifier.php
@@ -0,0 +1,21 @@
+native()->flipImage();
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlopModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlopModifier.php
new file mode 100644
index 000000000..d05c239e2
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/FlopModifier.php
@@ -0,0 +1,21 @@
+native()->flopImage();
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GammaModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GammaModifier.php
new file mode 100644
index 000000000..f8b1c7ce5
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GammaModifier.php
@@ -0,0 +1,21 @@
+native()->gammaImage($this->gamma);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GreyscaleModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GreyscaleModifier.php
new file mode 100644
index 000000000..4bc2f2843
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/GreyscaleModifier.php
@@ -0,0 +1,21 @@
+native()->modulateImage(100, 0, 100);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/InvertModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/InvertModifier.php
new file mode 100644
index 000000000..d504ecb59
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/InvertModifier.php
@@ -0,0 +1,21 @@
+native()->negateImage(false);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PadModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PadModifier.php
new file mode 100644
index 000000000..d924ae160
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PadModifier.php
@@ -0,0 +1,9 @@
+pixelateFrame($frame);
+ }
+
+ return $image;
+ }
+
+ protected function pixelateFrame(FrameInterface $frame): void
+ {
+ $size = $frame->size();
+
+ $frame->native()->scaleImage(
+ (int) round(max(1, $size->width() / $this->size)),
+ (int) round(max(1, $size->height() / $this->size))
+ );
+
+ $frame->native()->scaleImage($size->width(), $size->height());
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PlaceModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PlaceModifier.php
new file mode 100644
index 000000000..aa2c6e869
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/PlaceModifier.php
@@ -0,0 +1,40 @@
+driver()->handleInput($this->element);
+ $position = $this->getPosition($image, $watermark);
+
+ // set opacity of watermark
+ if ($this->opacity < 100) {
+ $watermark->core()->native()->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
+ $watermark->core()->native()->evaluateImage(
+ Imagick::EVALUATE_DIVIDE,
+ $this->opacity > 0 ? 100 / $this->opacity : 1000,
+ Imagick::CHANNEL_ALPHA,
+ );
+ }
+
+ foreach ($image as $frame) {
+ $frame->native()->compositeImage(
+ $watermark->core()->native(),
+ Imagick::COMPOSITE_DEFAULT,
+ $position->x(),
+ $position->y()
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileModifier.php
new file mode 100644
index 000000000..22212d956
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileModifier.php
@@ -0,0 +1,25 @@
+core()->native();
+ $result = $imagick->profileImage('icc', (string) $this->profile);
+
+ if ($result === false) {
+ throw new ColorException('ICC color profile could not be set.');
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileRemovalModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileRemovalModifier.php
new file mode 100644
index 000000000..ef4a2aba7
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ProfileRemovalModifier.php
@@ -0,0 +1,25 @@
+core()->native();
+ $result = $imagick->profileImage('icc', null);
+
+ if ($result === false) {
+ throw new ColorException('ICC color profile could not be removed.');
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/QuantizeColorsModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/QuantizeColorsModifier.php
new file mode 100644
index 000000000..6a93482e0
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/QuantizeColorsModifier.php
@@ -0,0 +1,37 @@
+limit <= 0) {
+ throw new InputException('Quantization limit must be greater than 0.');
+ }
+
+ // no color reduction if the limit is higher than the colors in the img
+ if ($this->limit > $image->core()->native()->getImageColors()) {
+ return $image;
+ }
+
+ foreach ($image as $frame) {
+ $frame->native()->quantizeImage(
+ $this->limit,
+ $frame->native()->getImageColorspace(),
+ 0,
+ false,
+ false
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php
new file mode 100644
index 000000000..787524fab
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RemoveAnimationModifier.php
@@ -0,0 +1,26 @@
+chosenFrame($image, $this->position);
+ $imagick->addImage($frame->native()->getImage());
+
+ // set new imagick to image
+ $image->core()->setNative($imagick);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasModifier.php
new file mode 100644
index 000000000..c5bae4e8a
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasModifier.php
@@ -0,0 +1,85 @@
+size();
+ $resize = $this->cropSize($image);
+
+ $background = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ foreach ($image as $frame) {
+ $frame->native()->extentImage(
+ $resize->width(),
+ $resize->height(),
+ $resize->pivot()->x(),
+ $resize->pivot()->y()
+ );
+
+ if ($resize->width() > $size->width()) {
+ // fill new emerged background
+ $draw = new ImagickDraw();
+ $draw->setFillColor($background);
+
+ $delta = abs($resize->pivot()->x());
+
+ if ($delta > 0) {
+ $draw->rectangle(
+ 0,
+ 0,
+ $delta - 1,
+ $resize->height()
+ );
+ }
+
+ $draw->rectangle(
+ $size->width() + $delta,
+ 0,
+ $resize->width(),
+ $resize->height()
+ );
+ $frame->native()->drawImage($draw);
+ }
+
+ if ($resize->height() > $size->height()) {
+ // fill new emerged background
+ $draw = new ImagickDraw();
+ $draw->setFillColor($background);
+
+ $delta = abs($resize->pivot()->y());
+
+ if ($delta > 0) {
+ $draw->rectangle(
+ 0,
+ 0,
+ $resize->width(),
+ $delta - 1
+ );
+ }
+
+ $draw->rectangle(
+ 0,
+ $size->height() + $delta,
+ $resize->width(),
+ $resize->height()
+ );
+
+ $frame->native()->drawImage($draw);
+ }
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifier.php
new file mode 100644
index 000000000..e1fcb7403
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeCanvasRelativeModifier.php
@@ -0,0 +1,16 @@
+size()->resizeDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeModifier.php
new file mode 100644
index 000000000..459aad025
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResizeModifier.php
@@ -0,0 +1,36 @@
+getAdjustedSize($image);
+
+ foreach ($image as $frame) {
+ $frame->native()->scaleImage(
+ $resizeTo->width(),
+ $resizeTo->height()
+ );
+ }
+
+ return $image;
+ }
+
+ /**
+ * @throws RuntimeException
+ */
+ protected function getAdjustedSize(ImageInterface $image): SizeInterface
+ {
+ return $image->size()->resize($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResolutionModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResolutionModifier.php
new file mode 100644
index 000000000..426e683e2
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ResolutionModifier.php
@@ -0,0 +1,20 @@
+core()->native();
+ $imagick->setImageResolution($this->x, $this->y);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RotateModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RotateModifier.php
new file mode 100644
index 000000000..19ad2282f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/RotateModifier.php
@@ -0,0 +1,28 @@
+driver()->colorProcessor($image->colorspace())->colorToNative(
+ $this->driver()->handleInput($this->background)
+ );
+
+ foreach ($image as $frame) {
+ $frame->native()->rotateImage(
+ $background,
+ $this->rotationAngle() * -1
+ );
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleDownModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleDownModifier.php
new file mode 100644
index 000000000..ae874bb67
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleDownModifier.php
@@ -0,0 +1,16 @@
+size()->scaleDown($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleModifier.php
new file mode 100644
index 000000000..da8c2c483
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/ScaleModifier.php
@@ -0,0 +1,16 @@
+size()->scale($this->width, $this->height);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SharpenModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SharpenModifier.php
new file mode 100644
index 000000000..120d3c896
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SharpenModifier.php
@@ -0,0 +1,21 @@
+native()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0);
+ }
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php
new file mode 100644
index 000000000..166910799
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/SliceAnimationModifier.php
@@ -0,0 +1,24 @@
+offset >= $image->count()) {
+ throw new AnimationException('Offset is not in the range of frames.');
+ }
+
+ $image->core()->slice($this->offset, $this->length);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TextModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TextModifier.php
new file mode 100644
index 000000000..51f30bf78
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TextModifier.php
@@ -0,0 +1,151 @@
+processor()->textBlock($this->text, $this->font, $this->position);
+ $drawText = $this->imagickDrawText($image, $this->font);
+ $drawStroke = $this->imagickDrawStroke($image, $this->font);
+
+ foreach ($image as $frame) {
+ foreach ($lines as $line) {
+ foreach ($this->strokeOffsets($this->font) as $offset) {
+ // Draw the stroke outline under the actual text
+ $this->maybeDrawTextline($frame, $line, $drawStroke, $offset);
+ }
+
+ // Draw the actual text
+ $this->maybeDrawTextline($frame, $line, $drawText);
+ }
+ }
+
+ return $image;
+ }
+
+ /**
+ * Create an ImagickDraw object to draw text on the image
+ *
+ * @param ImageInterface $image
+ * @param FontInterface $font
+ * @throws RuntimeException
+ * @throws ColorException
+ * @throws FontException
+ * @throws ImagickDrawException
+ * @throws ImagickException
+ * @return ImagickDraw
+ */
+ private function imagickDrawText(ImageInterface $image, FontInterface $font): ImagickDraw
+ {
+ $color = $this->driver()->handleInput($font->color());
+
+ if ($font->hasStrokeEffect() && $color->isTransparent()) {
+ throw new ColorException(
+ 'The text color must be fully opaque when using the stroke effect.'
+ );
+ }
+
+ $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color);
+
+ return $this->processor()->toImagickDraw($font, $color);
+ }
+
+ /**
+ * Create a ImagickDraw object to draw the outline stroke effect on the Image
+ *
+ * @param ImageInterface $image
+ * @param FontInterface $font
+ * @throws RuntimeException
+ * @throws ColorException
+ * @throws FontException
+ * @throws ImagickDrawException
+ * @throws ImagickException
+ * @return null|ImagickDraw
+ */
+ private function imagickDrawStroke(ImageInterface $image, FontInterface $font): ?ImagickDraw
+ {
+ if (!$font->hasStrokeEffect()) {
+ return null;
+ }
+
+ $color = $this->driver()->handleInput($font->strokeColor());
+
+ if ($color->isTransparent()) {
+ throw new ColorException(
+ 'The stroke color must be fully opaque.'
+ );
+ }
+
+ $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative($color);
+
+ return $this->processor()->toImagickDraw($font, $color);
+ }
+
+ /**
+ * Maybe draw given line of text on frame instance depending on given
+ * ImageDraw instance. Optionally move line position by given offset.
+ *
+ * @param FrameInterface $frame
+ * @param Line $textline
+ * @param null|ImagickDraw $draw
+ * @param Point $offset
+ * @return void
+ */
+ private function maybeDrawTextline(
+ FrameInterface $frame,
+ Line $textline,
+ ?ImagickDraw $draw = null,
+ Point $offset = new Point(),
+ ): void {
+ if ($draw !== null) {
+ $frame->native()->annotateImage(
+ $draw,
+ $textline->position()->x() + $offset->x(),
+ $textline->position()->y() + $offset->y(),
+ $this->font->angle(),
+ (string) $textline
+ );
+ }
+ }
+
+ /**
+ * Return imagick font processor
+ *
+ * @throws FontException
+ * @return FontProcessor
+ */
+ private function processor(): FontProcessor
+ {
+ $processor = $this->driver()->fontProcessor();
+
+ if (!($processor instanceof FontProcessor)) {
+ throw new FontException('Font processor does not match the driver.');
+ }
+
+ return $processor;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TrimModifier.php b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TrimModifier.php
new file mode 100644
index 000000000..88bc46a26
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Imagick/Modifiers/TrimModifier.php
@@ -0,0 +1,26 @@
+isAnimated()) {
+ throw new NotSupportedException('Trim modifier cannot be applied to animated images.');
+ }
+
+ $imagick = $image->core()->native();
+ $imagick->trimImage(($this->tolerance / 100 * $imagick->getQuantum()) / 1.5);
+ $imagick->setImagePage(0, 0, 0, 0);
+
+ return $image;
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/Specializable.php b/vendor/intervention/image/src/Drivers/Specializable.php
new file mode 100644
index 000000000..30a1c3f2e
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/Specializable.php
@@ -0,0 +1,13 @@
+analyze($this);
+ }
+}
diff --git a/vendor/intervention/image/src/Drivers/SpecializableDecoder.php b/vendor/intervention/image/src/Drivers/SpecializableDecoder.php
new file mode 100644
index 000000000..8a9916f6f
--- /dev/null
+++ b/vendor/intervention/image/src/Drivers/SpecializableDecoder.php
@@ -0,0 +1,21 @@
+modify($this);
+ }
+}
diff --git a/vendor/intervention/image/src/EncodedImage.php b/vendor/intervention/image/src/EncodedImage.php
new file mode 100644
index 000000000..2caadcc90
--- /dev/null
+++ b/vendor/intervention/image/src/EncodedImage.php
@@ -0,0 +1,52 @@
+mediaType;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see EncodedImageInterface::mimetype()
+ */
+ public function mimetype(): string
+ {
+ return $this->mediaType();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see EncodedImageInterface::toDataUri()
+ */
+ public function toDataUri(): string
+ {
+ return sprintf('data:%s;base64,%s', $this->mediaType, base64_encode($this->data));
+ }
+}
diff --git a/vendor/intervention/image/src/Encoders/AutoEncoder.php b/vendor/intervention/image/src/Encoders/AutoEncoder.php
new file mode 100644
index 000000000..0c909d1c5
--- /dev/null
+++ b/vendor/intervention/image/src/Encoders/AutoEncoder.php
@@ -0,0 +1,25 @@
+encode(
+ $this->encoderByMediaType(
+ $image->origin()->mediaType()
+ )
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Encoders/AvifEncoder.php b/vendor/intervention/image/src/Encoders/AvifEncoder.php
new file mode 100644
index 000000000..89c1a8b43
--- /dev/null
+++ b/vendor/intervention/image/src/Encoders/AvifEncoder.php
@@ -0,0 +1,14 @@
+
+ */
+ protected array $options = [];
+
+ /**
+ * Create new encoder instance to encode to format of given file extension
+ *
+ * @param null|string|FileExtension $extension Target file extension for example "png"
+ * @return void
+ */
+ public function __construct(public null|string|FileExtension $extension = null, mixed ...$options)
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see EncoderInterface::encode()
+ */
+ public function encode(ImageInterface $image): EncodedImageInterface
+ {
+ $extension = is_null($this->extension) ? $image->origin()->fileExtension() : $this->extension;
+
+ return $image->encode(
+ $this->encoderByFileExtension(
+ $extension
+ )
+ );
+ }
+
+ /**
+ * Create matching encoder for given file extension
+ *
+ * @param null|string|FileExtension $extension
+ * @throws EncoderException
+ * @return EncoderInterface
+ */
+ protected function encoderByFileExtension(null|string|FileExtension $extension): EncoderInterface
+ {
+ if (empty($extension)) {
+ throw new EncoderException('No encoder found for empty file extension.');
+ }
+
+ try {
+ $extension = is_string($extension) ? FileExtension::from($extension) : $extension;
+ } catch (Error) {
+ throw new EncoderException('No encoder found for file extension (' . $extension . ').');
+ }
+
+ return $extension->format()->encoder(...$this->options);
+ }
+}
diff --git a/vendor/intervention/image/src/Encoders/FilePathEncoder.php b/vendor/intervention/image/src/Encoders/FilePathEncoder.php
new file mode 100644
index 000000000..5e807c083
--- /dev/null
+++ b/vendor/intervention/image/src/Encoders/FilePathEncoder.php
@@ -0,0 +1,39 @@
+encode(
+ $this->encoderByFileExtension(
+ is_null($this->path) ? $image->origin()->fileExtension() : pathinfo($this->path, PATHINFO_EXTENSION)
+ )
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Encoders/GifEncoder.php b/vendor/intervention/image/src/Encoders/GifEncoder.php
new file mode 100644
index 000000000..2f5d07441
--- /dev/null
+++ b/vendor/intervention/image/src/Encoders/GifEncoder.php
@@ -0,0 +1,14 @@
+
+ */
+ protected array $options = [];
+
+ /**
+ * Create new encoder instance
+ *
+ * @param null|string|MediaType $mediaType Target media type for example "image/jpeg"
+ * @return void
+ */
+ public function __construct(public null|string|MediaType $mediaType = null, mixed ...$options)
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see EncoderInterface::encode()
+ */
+ public function encode(ImageInterface $image): EncodedImageInterface
+ {
+ $mediaType = is_null($this->mediaType) ? $image->origin()->mediaType() : $this->mediaType;
+
+ return $image->encode(
+ $this->encoderByMediaType($mediaType)
+ );
+ }
+
+ /**
+ * Return new encoder by given media (MIME) type
+ *
+ * @param string|MediaType $mediaType
+ * @throws EncoderException
+ * @return EncoderInterface
+ */
+ protected function encoderByMediaType(string|MediaType $mediaType): EncoderInterface
+ {
+ try {
+ $mediaType = is_string($mediaType) ? MediaType::from($mediaType) : $mediaType;
+ } catch (Error) {
+ throw new EncoderException('No encoder found for media type (' . $mediaType . ').');
+ }
+
+ return $mediaType->format()->encoder(...$this->options);
+ }
+}
diff --git a/vendor/intervention/image/src/Encoders/PngEncoder.php b/vendor/intervention/image/src/Encoders/PngEncoder.php
new file mode 100644
index 000000000..b6bb6cb5d
--- /dev/null
+++ b/vendor/intervention/image/src/Encoders/PngEncoder.php
@@ -0,0 +1,14 @@
+data;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FilterInterface::toFilePointer()
+ */
+ public function toFilePointer()
+ {
+ return $this->buildFilePointer($this->toString());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FileInterface::size()
+ */
+ public function size(): int
+ {
+ return mb_strlen($this->data);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see FileInterface::__toString()
+ */
+ public function __toString(): string
+ {
+ return $this->toString();
+ }
+}
diff --git a/vendor/intervention/image/src/FileExtension.php b/vendor/intervention/image/src/FileExtension.php
new file mode 100644
index 000000000..3cfea7825
--- /dev/null
+++ b/vendor/intervention/image/src/FileExtension.php
@@ -0,0 +1,58 @@
+ Format::JPEG,
+ self::WEBP => Format::WEBP,
+ self::GIF => Format::GIF,
+ self::PNG => Format::PNG,
+ self::AVIF => Format::AVIF,
+ self::BMP => Format::BMP,
+ self::TIF,
+ self::TIFF => Format::TIFF,
+ self::JP2,
+ self::J2K,
+ self::JPF,
+ self::JPM,
+ self::JPG2,
+ self::J2C,
+ self::JPC,
+ self::JPX => Format::JP2,
+ self::HEIC,
+ self::HEIF => Format::HEIC,
+ };
+ }
+}
diff --git a/vendor/intervention/image/src/Format.php b/vendor/intervention/image/src/Format.php
new file mode 100644
index 000000000..1d28296c1
--- /dev/null
+++ b/vendor/intervention/image/src/Format.php
@@ -0,0 +1,131 @@
+format();
+ }
+
+ if ($identifier instanceof FileExtension) {
+ return $identifier->format();
+ }
+
+ try {
+ $format = MediaType::from(strtolower($identifier))->format();
+ } catch (Error) {
+ try {
+ $format = FileExtension::from(strtolower($identifier))->format();
+ } catch (Error) {
+ throw new NotSupportedException('Unable to create format from "' . $identifier . '".');
+ }
+ }
+
+ return $format;
+ }
+
+ /**
+ * Return the possible media (MIME) types for the current format
+ *
+ * @return array
+ */
+ public function mediaTypes(): array
+ {
+ return array_filter(MediaType::cases(), function ($mediaType) {
+ return $mediaType->format() === $this;
+ });
+ }
+
+ /**
+ * Return the possible file extension for the current format
+ *
+ * @return array
+ */
+ public function fileExtensions(): array
+ {
+ return array_filter(FileExtension::cases(), function ($fileExtension) {
+ return $fileExtension->format() === $this;
+ });
+ }
+
+ /**
+ * Create an encoder instance with given options that matches the format
+ *
+ * @param mixed $options
+ * @return EncoderInterface
+ */
+ public function encoder(mixed ...$options): EncoderInterface
+ {
+ // get classname of target encoder from current format
+ $classname = match ($this) {
+ self::AVIF => AvifEncoder::class,
+ self::BMP => BmpEncoder::class,
+ self::GIF => GifEncoder::class,
+ self::HEIC => HeicEncoder::class,
+ self::JP2 => Jpeg2000Encoder::class,
+ self::JPEG => JpegEncoder::class,
+ self::PNG => PngEncoder::class,
+ self::TIFF => TiffEncoder::class,
+ self::WEBP => WebpEncoder::class,
+ };
+
+ // get parameters of target encoder
+ $parameters = [];
+ $reflectionClass = new ReflectionClass($classname);
+ if ($constructor = $reflectionClass->getConstructor()) {
+ $parameters = array_map(
+ fn ($parameter) => $parameter->getName(),
+ $constructor->getParameters(),
+ );
+ }
+
+ // filter out unavailable options of target encoder
+ $options = array_filter(
+ $options,
+ fn ($key) => in_array($key, $parameters),
+ ARRAY_FILTER_USE_KEY,
+ );
+
+ return new $classname(...$options);
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Circle.php b/vendor/intervention/image/src/Geometry/Circle.php
new file mode 100644
index 000000000..2211d351e
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Circle.php
@@ -0,0 +1,69 @@
+setWidth($diameter);
+ $this->setHeight($diameter);
+
+ return $this;
+ }
+
+ /**
+ * Get diameter of circle
+ *
+ * @return int
+ */
+ public function diameter(): int
+ {
+ return $this->width();
+ }
+
+ /**
+ * Set radius of circle
+ *
+ * @param int $radius
+ * @return Circle
+ */
+ public function setRadius(int $radius): self
+ {
+ return $this->setDiameter(intval($radius * 2));
+ }
+
+ /**
+ * Get radius of circle
+ *
+ * @return int
+ */
+ public function radius(): int
+ {
+ return intval(round($this->diameter() / 2));
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Ellipse.php b/vendor/intervention/image/src/Geometry/Ellipse.php
new file mode 100644
index 000000000..731e52cc1
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Ellipse.php
@@ -0,0 +1,109 @@
+pivot;
+ }
+
+ /**
+ * Return pivot point of Ellipse
+ *
+ * @return PointInterface
+ */
+ public function pivot(): PointInterface
+ {
+ return $this->pivot;
+ }
+
+ /**
+ * Set size of Ellipse
+ *
+ * @param int $width
+ * @param int $height
+ * @return Ellipse
+ */
+ public function setSize(int $width, int $height): self
+ {
+ return $this->setWidth($width)->setHeight($height);
+ }
+
+ /**
+ * Set width of Ellipse
+ *
+ * @param int $width
+ * @return Ellipse
+ */
+ public function setWidth(int $width): self
+ {
+ $this->width = $width;
+
+ return $this;
+ }
+
+ /**
+ * Set height of Ellipse
+ *
+ * @param int $height
+ * @return Ellipse
+ */
+ public function setHeight(int $height): self
+ {
+ $this->height = $height;
+
+ return $this;
+ }
+
+ /**
+ * Get width of Ellipse
+ *
+ * @return int
+ */
+ public function width(): int
+ {
+ return $this->width;
+ }
+
+ /**
+ * Get height of Ellipse
+ *
+ * @return int
+ */
+ public function height(): int
+ {
+ return $this->height;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Factories/CircleFactory.php b/vendor/intervention/image/src/Geometry/Factories/CircleFactory.php
new file mode 100644
index 000000000..ed3565c95
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Factories/CircleFactory.php
@@ -0,0 +1,34 @@
+ellipse->setSize($radius * 2, $radius * 2);
+
+ return $this;
+ }
+
+ /**
+ * Set the diameter of the circle to be produced
+ *
+ * @param int $diameter
+ * @return CircleFactory
+ */
+ public function diameter(int $diameter): self
+ {
+ $this->ellipse->setSize($diameter, $diameter);
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Factories/EllipseFactory.php b/vendor/intervention/image/src/Geometry/Factories/EllipseFactory.php
new file mode 100644
index 000000000..0a6081e76
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Factories/EllipseFactory.php
@@ -0,0 +1,106 @@
+ellipse = is_a($init, Ellipse::class) ? $init : new Ellipse(0, 0, $pivot);
+
+ if (is_callable($init)) {
+ $init($this);
+ }
+ }
+
+ /**
+ * Set the size of the ellipse to be produced
+ *
+ * @param int $width
+ * @param int $height
+ * @return EllipseFactory
+ */
+ public function size(int $width, int $height): self
+ {
+ $this->ellipse->setSize($width, $height);
+
+ return $this;
+ }
+
+ /**
+ * Set the width of the ellipse to be produced
+ *
+ * @param int $width
+ * @return EllipseFactory
+ */
+ public function width(int $width): self
+ {
+ $this->ellipse->setWidth($width);
+
+ return $this;
+ }
+
+ /**
+ * Set the height of the ellipse to be produced
+ *
+ * @param int $height
+ * @return EllipseFactory
+ */
+ public function height(int $height): self
+ {
+ $this->ellipse->setHeight($height);
+
+ return $this;
+ }
+
+ /**
+ * Set the background color of the ellipse to be produced
+ *
+ * @param mixed $color
+ * @return EllipseFactory
+ */
+ public function background(mixed $color): self
+ {
+ $this->ellipse->setBackgroundColor($color);
+
+ return $this;
+ }
+
+ /**
+ * Set the border color & border size of the ellipse to be produced
+ *
+ * @param mixed $color
+ * @param int $size
+ * @return EllipseFactory
+ */
+ public function border(mixed $color, int $size = 1): self
+ {
+ $this->ellipse->setBorder($color, $size);
+
+ return $this;
+ }
+
+ /**
+ * Produce the ellipse
+ *
+ * @return Ellipse
+ */
+ public function __invoke(): Ellipse
+ {
+ return $this->ellipse;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Factories/LineFactory.php b/vendor/intervention/image/src/Geometry/Factories/LineFactory.php
new file mode 100644
index 000000000..531f4cf05
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Factories/LineFactory.php
@@ -0,0 +1,123 @@
+line = is_a($init, Line::class) ? $init : new Line(new Point(), new Point());
+
+ if (is_callable($init)) {
+ $init($this);
+ }
+ }
+
+ /**
+ * Set the color of the line to be produced
+ *
+ * @param mixed $color
+ * @return LineFactory
+ */
+ public function color(mixed $color): self
+ {
+ $this->line->setBackgroundColor($color);
+ $this->line->setBorderColor($color);
+
+ return $this;
+ }
+
+ /**
+ * Set the (background) color of the line to be produced
+ *
+ * @param mixed $color
+ * @return LineFactory
+ */
+ public function background(mixed $color): self
+ {
+ $this->line->setBackgroundColor($color);
+ $this->line->setBorderColor($color);
+
+ return $this;
+ }
+
+ /**
+ * Set the border size & border color of the line to be produced
+ *
+ * @param mixed $color
+ * @param int $size
+ * @return LineFactory
+ */
+ public function border(mixed $color, int $size = 1): self
+ {
+ $this->line->setBackgroundColor($color);
+ $this->line->setBorderColor($color);
+ $this->line->setWidth($size);
+
+ return $this;
+ }
+
+ /**
+ * Set the width of the line to be produced
+ *
+ * @param int $size
+ * @return LineFactory
+ */
+ public function width(int $size): self
+ {
+ $this->line->setWidth($size);
+
+ return $this;
+ }
+
+ /**
+ * Set the coordinates of the starting point of the line to be produced
+ *
+ * @param int $x
+ * @param int $y
+ * @return LineFactory
+ */
+ public function from(int $x, int $y): self
+ {
+ $this->line->setStart(new Point($x, $y));
+
+ return $this;
+ }
+
+ /**
+ * Set the coordinates of the end point of the line to be produced
+ *
+ * @param int $x
+ * @param int $y
+ * @return LineFactory
+ */
+ public function to(int $x, int $y): self
+ {
+ $this->line->setEnd(new Point($x, $y));
+
+ return $this;
+ }
+
+ /**
+ * Produce the line
+ *
+ * @return Line
+ */
+ public function __invoke(): Line
+ {
+ return $this->line;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Factories/PolygonFactory.php b/vendor/intervention/image/src/Geometry/Factories/PolygonFactory.php
new file mode 100644
index 000000000..f1acc7d93
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Factories/PolygonFactory.php
@@ -0,0 +1,79 @@
+polygon = is_a($init, Polygon::class) ? $init : new Polygon([]);
+
+ if (is_callable($init)) {
+ $init($this);
+ }
+ }
+
+ /**
+ * Add a point to the polygon to be produced
+ *
+ * @param int $x
+ * @param int $y
+ * @return PolygonFactory
+ */
+ public function point(int $x, int $y): self
+ {
+ $this->polygon->addPoint(new Point($x, $y));
+
+ return $this;
+ }
+
+ /**
+ * Set the background color of the polygon to be produced
+ *
+ * @param mixed $color
+ * @return PolygonFactory
+ */
+ public function background(mixed $color): self
+ {
+ $this->polygon->setBackgroundColor($color);
+
+ return $this;
+ }
+
+ /**
+ * Set the border color & border size of the polygon to be produced
+ *
+ * @param mixed $color
+ * @param int $size
+ * @return PolygonFactory
+ */
+ public function border(mixed $color, int $size = 1): self
+ {
+ $this->polygon->setBorder($color, $size);
+
+ return $this;
+ }
+
+ /**
+ * Produce the polygon
+ *
+ * @return Polygon
+ */
+ public function __invoke(): Polygon
+ {
+ return $this->polygon;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Factories/RectangleFactory.php b/vendor/intervention/image/src/Geometry/Factories/RectangleFactory.php
new file mode 100644
index 000000000..be402ffc1
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Factories/RectangleFactory.php
@@ -0,0 +1,106 @@
+rectangle = is_a($init, Rectangle::class) ? $init : new Rectangle(0, 0, $pivot);
+
+ if (is_callable($init)) {
+ $init($this);
+ }
+ }
+
+ /**
+ * Set the size of the rectangle to be produced
+ *
+ * @param int $width
+ * @param int $height
+ * @return RectangleFactory
+ */
+ public function size(int $width, int $height): self
+ {
+ $this->rectangle->setSize($width, $height);
+
+ return $this;
+ }
+
+ /**
+ * Set the width of the rectangle to be produced
+ *
+ * @param int $width
+ * @return RectangleFactory
+ */
+ public function width(int $width): self
+ {
+ $this->rectangle->setWidth($width);
+
+ return $this;
+ }
+
+ /**
+ * Set the height of the rectangle to be produced
+ *
+ * @param int $height
+ * @return RectangleFactory
+ */
+ public function height(int $height): self
+ {
+ $this->rectangle->setHeight($height);
+
+ return $this;
+ }
+
+ /**
+ * Set the background color of the rectangle to be produced
+ *
+ * @param mixed $color
+ * @return RectangleFactory
+ */
+ public function background(mixed $color): self
+ {
+ $this->rectangle->setBackgroundColor($color);
+
+ return $this;
+ }
+
+ /**
+ * Set the border color & border size of the rectangle to be produced
+ *
+ * @param mixed $color
+ * @param int $size
+ * @return RectangleFactory
+ */
+ public function border(mixed $color, int $size = 1): self
+ {
+ $this->rectangle->setBorder($color, $size);
+
+ return $this;
+ }
+
+ /**
+ * Produce the rectangle
+ *
+ * @return Rectangle
+ */
+ public function __invoke(): Rectangle
+ {
+ return $this->rectangle;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Line.php b/vendor/intervention/image/src/Geometry/Line.php
new file mode 100644
index 000000000..eb879f7a1
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Line.php
@@ -0,0 +1,140 @@
+start;
+ }
+
+ /**
+ * Return line width
+ *
+ * @return int
+ */
+ public function width(): int
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set line width
+ *
+ * @param int $width
+ * @return Line
+ */
+ public function setWidth(int $width): self
+ {
+ $this->width = $width;
+
+ return $this;
+ }
+
+ /**
+ * Get starting point of line
+ *
+ * @return Point
+ */
+ public function start(): Point
+ {
+ return $this->start;
+ }
+
+ /**
+ * get end point of line
+ *
+ * @return Point
+ */
+ public function end(): Point
+ {
+ return $this->end;
+ }
+
+ /**
+ * Set starting point of line
+ *
+ * @param Point $start
+ * @return Line
+ */
+ public function setStart(Point $start): self
+ {
+ $this->start = $start;
+
+ return $this;
+ }
+
+ /**
+ * Set starting point of line by coordinates
+ *
+ * @param int $x
+ * @param int $y
+ * @return Line
+ */
+ public function from(int $x, int $y): self
+ {
+ $this->start()->setX($x);
+ $this->start()->setY($y);
+
+ return $this;
+ }
+
+ /**
+ * Set end point of line by coordinates
+ *
+ * @param int $x
+ * @param int $y
+ * @return Line
+ */
+ public function to(int $x, int $y): self
+ {
+ $this->end()->setX($x);
+ $this->end()->setY($y);
+
+ return $this;
+ }
+
+ /**
+ * Set end point of line
+ *
+ * @param Point $end
+ * @return Line
+ */
+ public function setEnd(Point $end): self
+ {
+ $this->end = $end;
+
+ return $this;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Pixel.php b/vendor/intervention/image/src/Geometry/Pixel.php
new file mode 100644
index 000000000..188da3ecd
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Pixel.php
@@ -0,0 +1,47 @@
+background = $background;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::backgroundColor()
+ */
+ public function backgroundColor(): ColorInterface
+ {
+ return $this->background;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Point.php b/vendor/intervention/image/src/Geometry/Point.php
new file mode 100644
index 000000000..7f5248380
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Point.php
@@ -0,0 +1,129 @@
+x = $x;
+
+ return $this;
+ }
+
+ /**
+ * Get X coordinate
+ *
+ * @return int
+ */
+ public function x(): int
+ {
+ return $this->x;
+ }
+
+ /**
+ * Sets Y coordinate
+ *
+ * @param int $y
+ */
+ public function setY(int $y): self
+ {
+ $this->y = $y;
+
+ return $this;
+ }
+
+ /**
+ * Get Y coordinate
+ *
+ * @return int
+ */
+ public function y(): int
+ {
+ return $this->y;
+ }
+
+ /**
+ * Move X coordinate
+ *
+ * @param int $value
+ */
+ public function moveX(int $value): self
+ {
+ $this->x += $value;
+
+ return $this;
+ }
+
+ /**
+ * Move Y coordinate
+ *
+ * @param int $value
+ */
+ public function moveY(int $value): self
+ {
+ $this->y += $value;
+
+ return $this;
+ }
+
+ public function move(int $x, int $y): self
+ {
+ return $this->moveX($x)->moveY($y);
+ }
+
+ /**
+ * Sets both X and Y coordinate
+ *
+ * @param int $x
+ * @param int $y
+ * @return Point
+ */
+ public function setPosition(int $x, int $y): self
+ {
+ $this->setX($x);
+ $this->setY($y);
+
+ return $this;
+ }
+
+ /**
+ * Rotate point ccw around pivot
+ *
+ * @param float $angle
+ * @param PointInterface $pivot
+ * @return Point
+ */
+ public function rotate(float $angle, PointInterface $pivot): self
+ {
+ $sin = round(sin(deg2rad($angle)), 6);
+ $cos = round(cos(deg2rad($angle)), 6);
+
+ return $this->setPosition(
+ intval($cos * ($this->x() - $pivot->x()) - $sin * ($this->y() - $pivot->y()) + $pivot->x()),
+ intval($sin * ($this->x() - $pivot->x()) + $cos * ($this->y() - $pivot->y()) + $pivot->y())
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Polygon.php b/vendor/intervention/image/src/Geometry/Polygon.php
new file mode 100644
index 000000000..3f5e6d3e9
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Polygon.php
@@ -0,0 +1,446 @@
+
+ * @implements ArrayAccess
+ */
+class Polygon implements IteratorAggregate, Countable, ArrayAccess, DrawableInterface
+{
+ use HasBorder;
+ use HasBackgroundColor;
+
+ /**
+ * Create new polygon instance
+ *
+ * @param array $points
+ * @param PointInterface $pivot
+ * @return void
+ */
+ public function __construct(
+ protected array $points = [],
+ protected PointInterface $pivot = new Point()
+ ) {
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::position()
+ */
+ public function position(): PointInterface
+ {
+ return $this->pivot;
+ }
+
+ /**
+ * Implement iteration through all points of polygon
+ *
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ return new ArrayIterator($this->points);
+ }
+
+ /**
+ * Return current pivot point
+ *
+ * @return PointInterface
+ */
+ public function pivot(): PointInterface
+ {
+ return $this->pivot;
+ }
+
+ /**
+ * Change pivot point to given point
+ *
+ * @param Point $pivot
+ * @return Polygon
+ */
+ public function setPivot(Point $pivot): self
+ {
+ $this->pivot = $pivot;
+
+ return $this;
+ }
+
+ /**
+ * Return first point of polygon
+ *
+ * @return ?Point
+ */
+ public function first(): ?Point
+ {
+ if ($point = reset($this->points)) {
+ return $point;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return last point of polygon
+ *
+ * @return ?Point
+ */
+ public function last(): ?Point
+ {
+ if ($point = end($this->points)) {
+ return $point;
+ }
+
+ return null;
+ }
+
+ /**
+ * Return polygon's point count
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return count($this->points);
+ }
+
+ /**
+ * Determine if point exists at given offset
+ *
+ * @param mixed $offset
+ * @return bool
+ */
+ public function offsetExists($offset): bool
+ {
+ return array_key_exists($offset, $this->points);
+ }
+
+ /**
+ * Return point at given offset
+ *
+ * @param mixed $offset
+ * @return Point
+ */
+ public function offsetGet($offset): mixed
+ {
+ return $this->points[$offset];
+ }
+
+ /**
+ * Set point at given offset
+ *
+ * @param mixed $offset
+ * @param Point $value
+ * @return void
+ */
+ public function offsetSet($offset, $value): void
+ {
+ $this->points[$offset] = $value;
+ }
+
+ /**
+ * Unset offset at given offset
+ *
+ * @param mixed $offset
+ * @return void
+ */
+ public function offsetUnset($offset): void
+ {
+ unset($this->points[$offset]);
+ }
+
+ /**
+ * Add given point to polygon
+ *
+ * @param Point $point
+ * @return Polygon
+ */
+ public function addPoint(Point $point): self
+ {
+ $this->points[] = $point;
+
+ return $this;
+ }
+
+ /**
+ * Calculate total horizontal span of polygon
+ *
+ * @return int
+ */
+ public function width(): int
+ {
+ return abs($this->mostLeftPoint()->x() - $this->mostRightPoint()->x());
+ }
+
+ /**
+ * Calculate total vertical span of polygon
+ *
+ * @return int
+ */
+ public function height(): int
+ {
+ return abs($this->mostBottomPoint()->y() - $this->mostTopPoint()->y());
+ }
+
+ /**
+ * Return most left point of all points in polygon
+ *
+ * @return Point
+ */
+ public function mostLeftPoint(): Point
+ {
+ $points = [];
+ foreach ($this->points as $point) {
+ $points[] = $point;
+ }
+
+ usort($points, function ($a, $b) {
+ if ($a->x() === $b->x()) {
+ return 0;
+ }
+ return $a->x() < $b->x() ? -1 : 1;
+ });
+
+ return $points[0];
+ }
+
+ /**
+ * Return most right point in polygon
+ *
+ * @return Point
+ */
+ public function mostRightPoint(): Point
+ {
+ $points = [];
+ foreach ($this->points as $point) {
+ $points[] = $point;
+ }
+
+ usort($points, function ($a, $b) {
+ if ($a->x() === $b->x()) {
+ return 0;
+ }
+ return $a->x() > $b->x() ? -1 : 1;
+ });
+
+ return $points[0];
+ }
+
+ /**
+ * Return most top point in polygon
+ *
+ * @return Point
+ */
+ public function mostTopPoint(): Point
+ {
+ $points = [];
+ foreach ($this->points as $point) {
+ $points[] = $point;
+ }
+
+ usort($points, function ($a, $b) {
+ if ($a->y() === $b->y()) {
+ return 0;
+ }
+ return $a->y() > $b->y() ? -1 : 1;
+ });
+
+ return $points[0];
+ }
+
+ /**
+ * Return most bottom point in polygon
+ *
+ * @return Point
+ */
+ public function mostBottomPoint(): Point
+ {
+ $points = [];
+ foreach ($this->points as $point) {
+ $points[] = $point;
+ }
+
+ usort($points, function ($a, $b) {
+ if ($a->y() === $b->y()) {
+ return 0;
+ }
+ return $a->y() < $b->y() ? -1 : 1;
+ });
+
+ return $points[0];
+ }
+
+ /**
+ * Return point in absolute center of the polygon
+ *
+ * @return Point
+ */
+ public function centerPoint(): Point
+ {
+ return new Point(
+ $this->mostRightPoint()->x() - (intval(round($this->width() / 2))),
+ $this->mostTopPoint()->y() - (intval(round($this->height() / 2)))
+ );
+ }
+
+ /**
+ * Align all points of polygon horizontally to given position around pivot point
+ *
+ * @param string $position
+ * @return Polygon
+ */
+ public function align(string $position): self
+ {
+ switch (strtolower($position)) {
+ case 'center':
+ case 'middle':
+ $diff = $this->centerPoint()->x() - $this->pivot()->x();
+ break;
+
+ case 'right':
+ $diff = $this->mostRightPoint()->x() - $this->pivot()->x();
+ break;
+
+ default:
+ case 'left':
+ $diff = $this->mostLeftPoint()->x() - $this->pivot()->x();
+ break;
+ }
+
+ foreach ($this->points as $point) {
+ $point->setX(
+ intval($point->x() - $diff)
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Align all points of polygon vertically to given position around pivot point
+ *
+ * @param string $position
+ * @return Polygon
+ */
+ public function valign(string $position): self
+ {
+ switch (strtolower($position)) {
+ case 'center':
+ case 'middle':
+ $diff = $this->centerPoint()->y() - $this->pivot()->y();
+ break;
+
+ case 'top':
+ $diff = $this->mostTopPoint()->y() - $this->pivot()->y() - $this->height();
+ break;
+
+ default:
+ case 'bottom':
+ $diff = $this->mostBottomPoint()->y() - $this->pivot()->y() + $this->height();
+ break;
+ }
+
+ foreach ($this->points as $point) {
+ $point->setY(
+ intval($point->y() - $diff),
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Rotate points of polygon around pivot point with given angle
+ *
+ * @param float $angle
+ * @return Polygon
+ */
+ public function rotate(float $angle): self
+ {
+ $sin = sin(deg2rad($angle));
+ $cos = cos(deg2rad($angle));
+
+ foreach ($this->points as $point) {
+ // translate point to pivot
+ $point->setX(
+ intval($point->x() - $this->pivot()->x()),
+ );
+ $point->setY(
+ intval($point->y() - $this->pivot()->y()),
+ );
+
+ // rotate point
+ $x = $point->x() * $cos - $point->y() * $sin;
+ $y = $point->x() * $sin + $point->y() * $cos;
+
+ // translate point back
+ $point->setX(
+ intval($x + $this->pivot()->x()),
+ );
+ $point->setY(
+ intval($y + $this->pivot()->y()),
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Move all points by given amount on the x-axis
+ *
+ * @param int $amount
+ * @return Polygon
+ */
+ public function movePointsX(int $amount): self
+ {
+ foreach ($this->points as $point) {
+ $point->moveX($amount);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Move all points by given amount on the y-axis
+ *
+ * @param int $amount
+ * @return Polygon
+ */
+ public function movePointsY(int $amount): self
+ {
+ foreach ($this->points as $point) {
+ $point->moveY($amount);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Return array of all x/y values of all points of polygon
+ *
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $coordinates = [];
+ foreach ($this->points as $point) {
+ $coordinates[] = $point->x();
+ $coordinates[] = $point->y();
+ }
+
+ return $coordinates;
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Rectangle.php b/vendor/intervention/image/src/Geometry/Rectangle.php
new file mode 100644
index 000000000..a95d2355f
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Rectangle.php
@@ -0,0 +1,327 @@
+addPoint(new Point($this->pivot->x(), $this->pivot->y()));
+ $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y()));
+ $this->addPoint(new Point($this->pivot->x() + $width, $this->pivot->y() - $height));
+ $this->addPoint(new Point($this->pivot->x(), $this->pivot->y() - $height));
+ }
+
+ /**
+ * Set size of rectangle
+ *
+ * @param int $width
+ * @param int $height
+ * @return Rectangle
+ */
+ public function setSize(int $width, int $height): self
+ {
+ return $this->setWidth($width)->setHeight($height);
+ }
+
+ /**
+ * Set width of rectangle
+ *
+ * @param int $width
+ * @return Rectangle
+ */
+ public function setWidth(int $width): self
+ {
+ $this[1]->setX($this[0]->x() + $width);
+ $this[2]->setX($this[3]->x() + $width);
+
+ return $this;
+ }
+
+ /**
+ * Set height of rectangle
+ *
+ * @param int $height
+ * @return Rectangle
+ */
+ public function setHeight(int $height): self
+ {
+ $this[2]->setY($this[1]->y() + $height);
+ $this[3]->setY($this[0]->y() + $height);
+
+ return $this;
+ }
+
+ /**
+ * Return pivot point of rectangle
+ *
+ * @return PointInterface
+ */
+ public function pivot(): PointInterface
+ {
+ return $this->pivot;
+ }
+
+ /**
+ * Set pivot point of rectangle
+ *
+ * @param PointInterface $pivot
+ * @return Rectangle
+ */
+ public function setPivot(PointInterface $pivot): self
+ {
+ $this->pivot = $pivot;
+
+ return $this;
+ }
+
+ /**
+ * Move pivot to the given position in the rectangle and adjust the new
+ * position by given offset values.
+ *
+ * @param string $position
+ * @param int $offset_x
+ * @param int $offset_y
+ * @return Rectangle
+ */
+ public function movePivot(string $position, int $offset_x = 0, int $offset_y = 0): self
+ {
+ switch (strtolower($position)) {
+ case 'top':
+ case 'top-center':
+ case 'top-middle':
+ case 'center-top':
+ case 'middle-top':
+ $x = intval(round($this->width() / 2)) + $offset_x;
+ $y = 0 + $offset_y;
+ break;
+
+ case 'top-right':
+ case 'right-top':
+ $x = $this->width() - $offset_x;
+ $y = 0 + $offset_y;
+ break;
+
+ case 'left':
+ case 'left-center':
+ case 'left-middle':
+ case 'center-left':
+ case 'middle-left':
+ $x = 0 + $offset_x;
+ $y = intval(round($this->height() / 2)) + $offset_y;
+ break;
+
+ case 'right':
+ case 'right-center':
+ case 'right-middle':
+ case 'center-right':
+ case 'middle-right':
+ $x = $this->width() - $offset_x;
+ $y = intval(round($this->height() / 2)) + $offset_y;
+ break;
+
+ case 'bottom-left':
+ case 'left-bottom':
+ $x = 0 + $offset_x;
+ $y = $this->height() - $offset_y;
+ break;
+
+ case 'bottom':
+ case 'bottom-center':
+ case 'bottom-middle':
+ case 'center-bottom':
+ case 'middle-bottom':
+ $x = intval(round($this->width() / 2)) + $offset_x;
+ $y = $this->height() - $offset_y;
+ break;
+
+ case 'bottom-right':
+ case 'right-bottom':
+ $x = $this->width() - $offset_x;
+ $y = $this->height() - $offset_y;
+ break;
+
+ case 'center':
+ case 'middle':
+ case 'center-center':
+ case 'middle-middle':
+ $x = intval(round($this->width() / 2)) + $offset_x;
+ $y = intval(round($this->height() / 2)) + $offset_y;
+ break;
+
+ default:
+ case 'top-left':
+ case 'left-top':
+ $x = 0 + $offset_x;
+ $y = 0 + $offset_y;
+ break;
+ }
+
+ $this->pivot->setPosition($x, $y);
+
+ return $this;
+ }
+
+ /**
+ * Align pivot relative to given size at given position
+ *
+ * @param SizeInterface $size
+ * @param string $position
+ * @return Rectangle
+ */
+ public function alignPivotTo(SizeInterface $size, string $position): self
+ {
+ $reference = new self($size->width(), $size->height());
+ $reference->movePivot($position);
+
+ $this->movePivot($position)->setPivot(
+ $reference->relativePositionTo($this)
+ );
+
+ return $this;
+ }
+
+ /**
+ * Return relative position to given rectangle
+ *
+ * @param SizeInterface $rectangle
+ * @return PointInterface
+ */
+ public function relativePositionTo(SizeInterface $rectangle): PointInterface
+ {
+ return new Point(
+ $this->pivot()->x() - $rectangle->pivot()->x(),
+ $this->pivot()->y() - $rectangle->pivot()->y()
+ );
+ }
+
+ /**
+ * Return aspect ration of rectangle
+ *
+ * @return float
+ */
+ public function aspectRatio(): float
+ {
+ return $this->width() / $this->height();
+ }
+
+ /**
+ * Determine if rectangle fits into given rectangle
+ *
+ * @param SizeInterface $size
+ * @return bool
+ */
+ public function fitsInto(SizeInterface $size): bool
+ {
+ if ($this->width() > $size->width()) {
+ return false;
+ }
+
+ if ($this->height() > $size->height()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if rectangle has landscape format
+ *
+ * @return bool
+ */
+ public function isLandscape(): bool
+ {
+ return $this->width() > $this->height();
+ }
+
+ /**
+ * Determine if rectangle has landscape format
+ *
+ * @return bool
+ */
+ public function isPortrait(): bool
+ {
+ return $this->width() < $this->height();
+ }
+
+ /**
+ * Return most top left point of rectangle
+ *
+ * @return PointInterface
+ */
+ public function topLeftPoint(): PointInterface
+ {
+ return $this->points[0];
+ }
+
+ /**
+ * Return bottom right point of rectangle
+ *
+ * @return PointInterface
+ */
+ public function bottomRightPoint(): PointInterface
+ {
+ return $this->points[2];
+ }
+
+ /**
+ * @throws GeometryException
+ */
+ protected function resizer(?int $width = null, ?int $height = null): RectangleResizer
+ {
+ return new RectangleResizer($width, $height);
+ }
+
+ public function resize(?int $width = null, ?int $height = null): SizeInterface
+ {
+ return $this->resizer($width, $height)->resize($this);
+ }
+
+ public function resizeDown(?int $width = null, ?int $height = null): SizeInterface
+ {
+ return $this->resizer($width, $height)->resizeDown($this);
+ }
+
+ public function scale(?int $width = null, ?int $height = null): SizeInterface
+ {
+ return $this->resizer($width, $height)->scale($this);
+ }
+
+ public function scaleDown(?int $width = null, ?int $height = null): SizeInterface
+ {
+ return $this->resizer($width, $height)->scaleDown($this);
+ }
+
+ public function cover(int $width, int $height): SizeInterface
+ {
+ return $this->resizer($width, $height)->cover($this);
+ }
+
+ public function contain(int $width, int $height): SizeInterface
+ {
+ return $this->resizer($width, $height)->contain($this);
+ }
+
+ public function containMax(int $width, int $height): SizeInterface
+ {
+ return $this->resizer($width, $height)->containDown($this);
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Tools/RectangleResizer.php b/vendor/intervention/image/src/Geometry/Tools/RectangleResizer.php
new file mode 100644
index 000000000..ae9faa71f
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Tools/RectangleResizer.php
@@ -0,0 +1,304 @@
+width);
+ }
+
+ protected function getTargetWidth(): ?int
+ {
+ return $this->hasTargetWidth() ? $this->width : null;
+ }
+
+ protected function hasTargetHeight(): bool
+ {
+ return is_integer($this->height);
+ }
+
+ protected function getTargetHeight(): ?int
+ {
+ return $this->hasTargetHeight() ? $this->height : null;
+ }
+
+ /**
+ * @throws GeometryException
+ */
+ protected function getTargetSize(): SizeInterface
+ {
+ if (!$this->hasTargetWidth() || !$this->hasTargetHeight()) {
+ throw new GeometryException('Target size needs width and height.');
+ }
+
+ return new Rectangle($this->width, $this->height);
+ }
+
+ public function toWidth(int $width): self
+ {
+ $this->width = $width;
+
+ return $this;
+ }
+
+ public function toHeight(int $height): self
+ {
+ $this->height = $height;
+
+ return $this;
+ }
+
+ public function toSize(SizeInterface $size): self
+ {
+ $this->width = $size->width();
+ $this->height = $size->height();
+
+ return $this;
+ }
+
+ protected function getProportionalWidth(SizeInterface $size): int
+ {
+ if (!$this->hasTargetHeight()) {
+ return $size->width();
+ }
+
+ return max([1, (int) round($this->height * $size->aspectRatio())]);
+ }
+
+ protected function getProportionalHeight(SizeInterface $size): int
+ {
+ if (!$this->hasTargetWidth()) {
+ return $size->height();
+ }
+
+ return max([1, (int) round($this->width / $size->aspectRatio())]);
+ }
+
+ public function resize(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ if ($width = $this->getTargetWidth()) {
+ $resized->setWidth($width);
+ }
+
+ if ($height = $this->getTargetHeight()) {
+ $resized->setHeight($height);
+ }
+
+ return $resized;
+ }
+
+ public function resizeDown(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ if ($width = $this->getTargetWidth()) {
+ $resized->setWidth(
+ min($width, $size->width())
+ );
+ }
+
+ if ($height = $this->getTargetHeight()) {
+ $resized->setHeight(
+ min($height, $size->height())
+ );
+ }
+
+ return $resized;
+ }
+
+ public function scale(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ if ($this->hasTargetWidth() && $this->hasTargetHeight()) {
+ $resized->setWidth(min(
+ $this->getProportionalWidth($size),
+ $this->getTargetWidth()
+ ));
+ $resized->setHeight(min(
+ $this->getProportionalHeight($size),
+ $this->getTargetHeight()
+ ));
+ } elseif ($this->hasTargetWidth()) {
+ $resized->setWidth($this->getTargetWidth());
+ $resized->setHeight($this->getProportionalHeight($size));
+ } elseif ($this->hasTargetHeight()) {
+ $resized->setWidth($this->getProportionalWidth($size));
+ $resized->setHeight($this->getTargetHeight());
+ }
+
+ return $resized;
+ }
+
+ public function scaleDown(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ if ($this->hasTargetWidth() && $this->hasTargetHeight()) {
+ $resized->setWidth(min(
+ $this->getProportionalWidth($size),
+ $this->getTargetWidth(),
+ $size->width()
+ ));
+ $resized->setHeight(min(
+ $this->getProportionalHeight($size),
+ $this->getTargetHeight(),
+ $size->height()
+ ));
+ } elseif ($this->hasTargetWidth()) {
+ $resized->setWidth(min(
+ $this->getTargetWidth(),
+ $size->width()
+ ));
+ $resized->setHeight(min(
+ $this->getProportionalHeight($size),
+ $size->height()
+ ));
+ } elseif ($this->hasTargetHeight()) {
+ $resized->setWidth(min(
+ $this->getProportionalWidth($size),
+ $size->width()
+ ));
+ $resized->setHeight(min(
+ $this->getTargetHeight(),
+ $size->height()
+ ));
+ }
+
+ return $resized;
+ }
+
+ /**
+ * Scale given size to cover target size
+ *
+ * @param SizeInterface $size Size to be resized
+ * @throws GeometryException
+ * @return SizeInterface
+ */
+ public function cover(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ // auto height
+ $resized->setWidth($this->getTargetWidth());
+ $resized->setHeight($this->getProportionalHeight($size));
+
+ if ($resized->fitsInto($this->getTargetSize())) {
+ // auto width
+ $resized->setWidth($this->getProportionalWidth($size));
+ $resized->setHeight($this->getTargetHeight());
+ }
+
+ return $resized;
+ }
+
+ /**
+ * Scale given size to contain target size
+ *
+ * @param SizeInterface $size Size to be resized
+ * @throws GeometryException
+ * @return SizeInterface
+ */
+ public function contain(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ // auto height
+ $resized->setWidth($this->getTargetWidth());
+ $resized->setHeight($this->getProportionalHeight($size));
+
+ if (!$resized->fitsInto($this->getTargetSize())) {
+ // auto width
+ $resized->setWidth($this->getProportionalWidth($size));
+ $resized->setHeight($this->getTargetHeight());
+ }
+
+ return $resized;
+ }
+
+ /**
+ * Scale given size to contain target size but prevent upsizing
+ *
+ * @param SizeInterface $size Size to be resized
+ * @throws GeometryException
+ * @return SizeInterface
+ */
+ public function containDown(SizeInterface $size): SizeInterface
+ {
+ $resized = new Rectangle($size->width(), $size->height());
+
+ // auto height
+ $resized->setWidth(
+ min($size->width(), $this->getTargetWidth())
+ );
+
+ $resized->setHeight(
+ min($size->height(), $this->getProportionalHeight($size))
+ );
+
+ if (!$resized->fitsInto($this->getTargetSize())) {
+ // auto width
+ $resized->setWidth(
+ min($size->width(), $this->getProportionalWidth($size))
+ );
+ $resized->setHeight(
+ min($size->height(), $this->getTargetHeight())
+ );
+ }
+
+ return $resized;
+ }
+
+ /**
+ * Crop target size out of given size at given position (i.e. move the pivot point)
+ *
+ * @param SizeInterface $size
+ * @param string $position
+ * @return SizeInterface
+ */
+ public function crop(SizeInterface $size, string $position = 'top-left'): SizeInterface
+ {
+ return $this->resize($size)->alignPivotTo(
+ $size->movePivot($position),
+ $position
+ );
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Traits/HasBackgroundColor.php b/vendor/intervention/image/src/Geometry/Traits/HasBackgroundColor.php
new file mode 100644
index 000000000..a18c8b4ab
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Traits/HasBackgroundColor.php
@@ -0,0 +1,42 @@
+backgroundColor = $color;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::backgroundColor()
+ */
+ public function backgroundColor(): mixed
+ {
+ return $this->backgroundColor;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::hasBackgroundColor()
+ */
+ public function hasBackgroundColor(): bool
+ {
+ return !empty($this->backgroundColor);
+ }
+}
diff --git a/vendor/intervention/image/src/Geometry/Traits/HasBorder.php b/vendor/intervention/image/src/Geometry/Traits/HasBorder.php
new file mode 100644
index 000000000..57ff83ae9
--- /dev/null
+++ b/vendor/intervention/image/src/Geometry/Traits/HasBorder.php
@@ -0,0 +1,75 @@
+setBorderSize($size)->setBorderColor($color);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::setBorderSize()
+ */
+ public function setBorderSize(int $size): self
+ {
+ $this->borderSize = $size;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::borderSize()
+ */
+ public function borderSize(): int
+ {
+ return $this->borderSize;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::setBorderColor()
+ */
+ public function setBorderColor(mixed $color): self
+ {
+ $this->borderColor = $color;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::borderColor()
+ */
+ public function borderColor(): mixed
+ {
+ return $this->borderColor;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see DrawableInterface::hasBorder()
+ */
+ public function hasBorder(): bool
+ {
+ return $this->borderSize > 0 && !is_null($this->borderColor);
+ }
+}
diff --git a/vendor/intervention/image/src/Image.php b/vendor/intervention/image/src/Image.php
new file mode 100644
index 000000000..0c1731c7f
--- /dev/null
+++ b/vendor/intervention/image/src/Image.php
@@ -0,0 +1,1054 @@
+origin = new Origin();
+ $this->blendingColor = $this->colorspace()->importColor(
+ new Color(255, 255, 255, 0)
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::driver()
+ */
+ public function driver(): DriverInterface
+ {
+ return $this->driver;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::core()
+ */
+ public function core(): CoreInterface
+ {
+ return $this->core;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::origin()
+ */
+ public function origin(): Origin
+ {
+ return $this->origin;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setOrigin()
+ */
+ public function setOrigin(Origin $origin): ImageInterface
+ {
+ $this->origin = $origin;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::count()
+ */
+ public function count(): int
+ {
+ return $this->core->count();
+ }
+
+ /**
+ * Implementation of IteratorAggregate
+ *
+ * @return Traversable
+ */
+ public function getIterator(): Traversable
+ {
+ return $this->core;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::isAnimated()
+ */
+ public function isAnimated(): bool
+ {
+ return $this->count() > 1;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::removeAnimation(
+ */
+ public function removeAnimation(int|string $position = 0): ImageInterface
+ {
+ return $this->modify(new RemoveAnimationModifier($position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::sliceAnimation()
+ */
+ public function sliceAnimation(int $offset = 0, ?int $length = null): ImageInterface
+ {
+ return $this->modify(new SliceAnimationModifier($offset, $length));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::loops()
+ */
+ public function loops(): int
+ {
+ return $this->core->loops();
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setLoops()
+ */
+ public function setLoops(int $loops): ImageInterface
+ {
+ $this->core->setLoops($loops);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::exif()
+ */
+ public function exif(?string $query = null): mixed
+ {
+ return is_null($query) ? $this->exif : $this->exif->get($query);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setExif()
+ */
+ public function setExif(CollectionInterface $exif): ImageInterface
+ {
+ $this->exif = $exif;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::modify()
+ */
+ public function modify(ModifierInterface $modifier): ImageInterface
+ {
+ return $this->driver->specialize($modifier)->apply($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::analyze()
+ */
+ public function analyze(AnalyzerInterface $analyzer): mixed
+ {
+ return $this->driver->specialize($analyzer)->analyze($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::encode()
+ */
+ public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImageInterface
+ {
+ return $this->driver->specialize($encoder)->encode($this);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::save()
+ */
+ public function save(?string $path = null, mixed ...$options): ImageInterface
+ {
+ $path = is_null($path) ? $this->origin()->filePath() : $path;
+
+ if (is_null($path)) {
+ throw new EncoderException('Could not determine file path to save.');
+ }
+
+ try {
+ // try to determine encoding format by file extension of the path
+ $encoded = $this->encodeByPath($path, ...$options);
+ } catch (EncoderException) {
+ // fallback to encoding format by media type
+ $encoded = $this->encodeByMediaType(null, ...$options);
+ }
+
+ $encoded->save($path);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::width()
+ */
+ public function width(): int
+ {
+ return $this->analyze(new WidthAnalyzer());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::height()
+ */
+ public function height(): int
+ {
+ return $this->analyze(new HeightAnalyzer());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::size()
+ */
+ public function size(): SizeInterface
+ {
+ return new Rectangle($this->width(), $this->height());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::colorspace()
+ */
+ public function colorspace(): ColorspaceInterface
+ {
+ return $this->analyze(new ColorspaceAnalyzer());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setColorspace()
+ */
+ public function setColorspace(string|ColorspaceInterface $colorspace): ImageInterface
+ {
+ return $this->modify(new ColorspaceModifier($colorspace));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::resolution()
+ */
+ public function resolution(): ResolutionInterface
+ {
+ return $this->analyze(new ResolutionAnalyzer());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setResolution()
+ */
+ public function setResolution(float $x, float $y): ImageInterface
+ {
+ return $this->modify(new ResolutionModifier($x, $y));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::pickColor()
+ */
+ public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface
+ {
+ return $this->analyze(new PixelColorAnalyzer($x, $y, $frame_key));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::pickColors()
+ */
+ public function pickColors(int $x, int $y): CollectionInterface
+ {
+ return $this->analyze(new PixelColorsAnalyzer($x, $y));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::blendingColor()
+ */
+ public function blendingColor(): ColorInterface
+ {
+ return $this->blendingColor;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setBlendingColor()
+ */
+ public function setBlendingColor(mixed $color): ImageInterface
+ {
+ $this->blendingColor = $this->driver()->handleInput($color);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::blendTransparency()
+ */
+ public function blendTransparency(mixed $color = null): ImageInterface
+ {
+ return $this->modify(new BlendTransparencyModifier($color));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::profile()
+ */
+ public function profile(): ProfileInterface
+ {
+ return $this->analyze(new ProfileAnalyzer());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::setProfile()
+ */
+ public function setProfile(ProfileInterface $profile): ImageInterface
+ {
+ return $this->modify(new ProfileModifier($profile));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::removeProfile()
+ */
+ public function removeProfile(): ImageInterface
+ {
+ return $this->modify(new ProfileRemovalModifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::reduceColors()
+ */
+ public function reduceColors(int $limit, mixed $background = 'transparent'): ImageInterface
+ {
+ return $this->modify(new QuantizeColorsModifier($limit, $background));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::sharpen()
+ */
+ public function sharpen(int $amount = 10): ImageInterface
+ {
+ return $this->modify(new SharpenModifier($amount));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::invert()
+ */
+ public function invert(): ImageInterface
+ {
+ return $this->modify(new InvertModifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::pixelate()
+ */
+ public function pixelate(int $size): ImageInterface
+ {
+ return $this->modify(new PixelateModifier($size));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::greyscale()
+ */
+ public function greyscale(): ImageInterface
+ {
+ return $this->modify(new GreyscaleModifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::brightness()
+ */
+ public function brightness(int $level): ImageInterface
+ {
+ return $this->modify(new BrightnessModifier($level));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::contrast()
+ */
+ public function contrast(int $level): ImageInterface
+ {
+ return $this->modify(new ContrastModifier($level));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::gamma()
+ */
+ public function gamma(float $gamma): ImageInterface
+ {
+ return $this->modify(new GammaModifier($gamma));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::colorize()
+ */
+ public function colorize(int $red = 0, int $green = 0, int $blue = 0): ImageInterface
+ {
+ return $this->modify(new ColorizeModifier($red, $green, $blue));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::flip()
+ */
+ public function flip(): ImageInterface
+ {
+ return $this->modify(new FlipModifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::flop()
+ */
+ public function flop(): ImageInterface
+ {
+ return $this->modify(new FlopModifier());
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::blur()
+ */
+ public function blur(int $amount = 5): ImageInterface
+ {
+ return $this->modify(new BlurModifier($amount));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::rotate()
+ */
+ public function rotate(float $angle, mixed $background = 'ffffff'): ImageInterface
+ {
+ return $this->modify(new RotateModifier($angle, $background));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::text()
+ */
+ public function text(string $text, int $x, int $y, callable|FontInterface $font): ImageInterface
+ {
+ return $this->modify(
+ new TextModifier(
+ $text,
+ new Point($x, $y),
+ call_user_func(new FontFactory($font)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::resize()
+ */
+ public function resize(?int $width = null, ?int $height = null): ImageInterface
+ {
+ return $this->modify(new ResizeModifier($width, $height));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::resizeDown()
+ */
+ public function resizeDown(?int $width = null, ?int $height = null): ImageInterface
+ {
+ return $this->modify(new ResizeDownModifier($width, $height));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::scale()
+ */
+ public function scale(?int $width = null, ?int $height = null): ImageInterface
+ {
+ return $this->modify(new ScaleModifier($width, $height));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::scaleDown()
+ */
+ public function scaleDown(?int $width = null, ?int $height = null): ImageInterface
+ {
+ return $this->modify(new ScaleDownModifier($width, $height));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::cover()
+ */
+ public function cover(int $width, int $height, string $position = 'center'): ImageInterface
+ {
+ return $this->modify(new CoverModifier($width, $height, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::coverDown()
+ */
+ public function coverDown(int $width, int $height, string $position = 'center'): ImageInterface
+ {
+ return $this->modify(new CoverDownModifier($width, $height, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::resizeCanvas()
+ */
+ public function resizeCanvas(
+ ?int $width = null,
+ ?int $height = null,
+ mixed $background = 'ffffff',
+ string $position = 'center'
+ ): ImageInterface {
+ return $this->modify(new ResizeCanvasModifier($width, $height, $background, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::resizeCanvasRelative()
+ */
+ public function resizeCanvasRelative(
+ ?int $width = null,
+ ?int $height = null,
+ mixed $background = 'ffffff',
+ string $position = 'center'
+ ): ImageInterface {
+ return $this->modify(new ResizeCanvasRelativeModifier($width, $height, $background, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::padDown()
+ */
+ public function pad(
+ int $width,
+ int $height,
+ mixed $background = 'ffffff',
+ string $position = 'center'
+ ): ImageInterface {
+ return $this->modify(new PadModifier($width, $height, $background, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::pad()
+ */
+ public function contain(
+ int $width,
+ int $height,
+ mixed $background = 'ffffff',
+ string $position = 'center'
+ ): ImageInterface {
+ return $this->modify(new ContainModifier($width, $height, $background, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::crop()
+ */
+ public function crop(
+ int $width,
+ int $height,
+ int $offset_x = 0,
+ int $offset_y = 0,
+ mixed $background = 'ffffff',
+ string $position = 'top-left'
+ ): ImageInterface {
+ return $this->modify(new CropModifier($width, $height, $offset_x, $offset_y, $background, $position));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::trim()
+ */
+ public function trim(int $tolerance = 0): ImageInterface
+ {
+ return $this->modify(new TrimModifier($tolerance));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::place()
+ */
+ public function place(
+ mixed $element,
+ string $position = 'top-left',
+ int $offset_x = 0,
+ int $offset_y = 0,
+ int $opacity = 100
+ ): ImageInterface {
+ return $this->modify(new PlaceModifier($element, $position, $offset_x, $offset_y, $opacity));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::fill()
+ */
+ public function fill(mixed $color, ?int $x = null, ?int $y = null): ImageInterface
+ {
+ return $this->modify(
+ new FillModifier(
+ $color,
+ is_null($x) || is_null($y) ? null : new Point($x, $y),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawPixel()
+ */
+ public function drawPixel(int $x, int $y, mixed $color): ImageInterface
+ {
+ return $this->modify(new DrawPixelModifier(new Point($x, $y), $color));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawRectangle()
+ */
+ public function drawRectangle(int $x, int $y, callable|Rectangle $init): ImageInterface
+ {
+ return $this->modify(
+ new DrawRectangleModifier(
+ call_user_func(new RectangleFactory(new Point($x, $y), $init)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawEllipse()
+ */
+ public function drawEllipse(int $x, int $y, callable $init): ImageInterface
+ {
+ return $this->modify(
+ new DrawEllipseModifier(
+ call_user_func(new EllipseFactory(new Point($x, $y), $init)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawCircle()
+ */
+ public function drawCircle(int $x, int $y, callable $init): ImageInterface
+ {
+ return $this->modify(
+ new DrawEllipseModifier(
+ call_user_func(new CircleFactory(new Point($x, $y), $init)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawPolygon()
+ */
+ public function drawPolygon(callable $init): ImageInterface
+ {
+ return $this->modify(
+ new DrawPolygonModifier(
+ call_user_func(new PolygonFactory($init)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::drawLine()
+ */
+ public function drawLine(callable $init): ImageInterface
+ {
+ return $this->modify(
+ new DrawLineModifier(
+ call_user_func(new LineFactory($init)),
+ ),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::encodeByMediaType()
+ */
+ public function encodeByMediaType(null|string|MediaType $type = null, mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new MediaTypeEncoder($type, ...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::encodeByExtension()
+ */
+ public function encodeByExtension(
+ null|string|FileExtension $extension = null,
+ mixed ...$options
+ ): EncodedImageInterface {
+ return $this->encode(new FileExtensionEncoder($extension, ...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::encodeByPath()
+ */
+ public function encodeByPath(?string $path = null, mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new FilePathEncoder($path, ...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toJpeg()
+ */
+ public function toJpeg(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new JpegEncoder(...$options));
+ }
+
+ /**
+ * Alias of self::toJpeg()
+ *
+ * @param mixed $options
+ * @throws RuntimeException
+ * @return EncodedImageInterface
+ */
+ public function toJpg(mixed ...$options): EncodedImageInterface
+ {
+ return $this->toJpeg(...$options);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toJpeg()
+ */
+ public function toJpeg2000(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new Jpeg2000Encoder(...$options));
+ }
+
+ /**
+ * ALias of self::toJpeg2000()
+ *
+ * @param mixed $options
+ * @throws RuntimeException
+ * @return EncodedImageInterface
+ */
+ public function toJp2(mixed ...$options): EncodedImageInterface
+ {
+ return $this->toJpeg2000(...$options);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toPng()
+ */
+ public function toPng(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new PngEncoder(...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toGif()
+ */
+ public function toGif(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new GifEncoder(...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toWebp()
+ */
+ public function toWebp(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new WebpEncoder(...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toBitmap()
+ */
+ public function toBitmap(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new BmpEncoder(...$options));
+ }
+
+ /**
+ * Alias if self::toBitmap()
+ *
+ * @throws RuntimeException
+ * @return EncodedImageInterface
+ */
+ public function toBmp(mixed ...$options): EncodedImageInterface
+ {
+ return $this->toBitmap(...$options);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toAvif()
+ */
+ public function toAvif(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new AvifEncoder(...$options));
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toTiff()
+ */
+ public function toTiff(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new TiffEncoder(...$options));
+ }
+
+ /**
+ * Alias of self::toTiff()
+ *
+ * @param mixed $options
+ * @throws RuntimeException
+ * @return EncodedImageInterface
+ */
+ public function toTif(mixed ...$options): EncodedImageInterface
+ {
+ return $this->toTiff(...$options);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageInterface::toHeic()
+ */
+ public function toHeic(mixed ...$options): EncodedImageInterface
+ {
+ return $this->encode(new HeicEncoder(...$options));
+ }
+
+ /**
+ * Clone image
+ *
+ * @return void
+ */
+ public function __clone(): void
+ {
+ $this->driver = clone $this->driver;
+ $this->core = clone $this->core;
+ $this->exif = clone $this->exif;
+ }
+}
diff --git a/vendor/intervention/image/src/ImageManager.php b/vendor/intervention/image/src/ImageManager.php
new file mode 100644
index 000000000..383dea5f5
--- /dev/null
+++ b/vendor/intervention/image/src/ImageManager.php
@@ -0,0 +1,121 @@
+driver = $this->resolveDriver($driver);
+ }
+
+ /**
+ * Create image manager with given driver
+ *
+ * @link https://image.intervention.io/v3/basics/image-manager
+ * @param string|DriverInterface $driver
+ * @return ImageManager
+ */
+ public static function withDriver(string|DriverInterface $driver): self
+ {
+ return new self(self::resolveDriver($driver));
+ }
+
+ /**
+ * Create image manager with GD driver
+ *
+ * @link https://image.intervention.io/v3/basics/image-manager#static-gd-driver-constructor
+ * @return ImageManager
+ */
+ public static function gd(): self
+ {
+ return self::withDriver(GdDriver::class);
+ }
+
+ /**
+ * Create image manager with Imagick driver
+ *
+ * @link https://image.intervention.io/v3/basics/image-manager#static-imagick-driver-constructor
+ * @return ImageManager
+ */
+ public static function imagick(): self
+ {
+ return self::withDriver(ImagickDriver::class);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageManagerInterface::create()
+ */
+ public function create(int $width, int $height): ImageInterface
+ {
+ return $this->driver->createImage($width, $height);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageManagerInterface::read()
+ */
+ public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface
+ {
+ return $this->driver->handleInput(
+ $input,
+ match (true) {
+ is_string($decoders), is_a($decoders, DecoderInterface::class) => [$decoders],
+ default => $decoders,
+ }
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageManagerInterface::animate()
+ */
+ public function animate(callable $init): ImageInterface
+ {
+ return $this->driver->createAnimation($init);
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @see ImageManagerInterface::driver()
+ */
+ public function driver(): DriverInterface
+ {
+ return $this->driver;
+ }
+
+ /**
+ * Return driver object
+ *
+ * @param string|DriverInterface $driver
+ * @return DriverInterface
+ */
+ private static function resolveDriver(string|DriverInterface $driver): DriverInterface
+ {
+ if (is_object($driver)) {
+ return $driver;
+ }
+
+ return new $driver();
+ }
+}
diff --git a/vendor/intervention/image/src/Interfaces/AnalyzerInterface.php b/vendor/intervention/image/src/Interfaces/AnalyzerInterface.php
new file mode 100644
index 000000000..9a44690ef
--- /dev/null
+++ b/vendor/intervention/image/src/Interfaces/AnalyzerInterface.php
@@ -0,0 +1,19 @@
+
+ */
+interface CollectionInterface extends Traversable
+{
+ /**
+ * Determine if the collection has item at given key
+ *
+ * @param int|string $key
+ * @return bool
+ */
+ public function has(int|string $key): bool;
+
+ /**
+ * Add item to collection
+ *
+ * @param mixed $item
+ * @return CollectionInterface
+ */
+ public function push($item): self;
+
+ /**
+ * Return item for given key or return default is key does not exist
+ *
+ * @param int|string $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get(int|string $key, $default = null): mixed;
+
+ /**
+ * Return item at given numeric position starting at 0
+ *
+ * @param int $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function getAtPosition(int $key = 0, $default = null): mixed;
+
+ /**
+ * Return first item in collection
+ *
+ * @return mixed
+ */
+ public function first(): mixed;
+
+ /**
+ * Return last item in collection
+ *
+ * @return mixed
+ */
+ public function last(): mixed;
+
+ /**
+ * Return item count of collection
+ *
+ * @return int
+ */
+ public function count(): int;
+
+ /**
+ * Empty collection
+ *
+ * @return CollectionInterface
+ */
+ public function empty(): self;
+
+ /**
+ * Transform collection as array
+ *
+ * @return array
+ */
+ public function toArray(): array;
+
+ /**
+ * Extract items based on given values and discard the rest.
+ *
+ * @param int $offset
+ * @param null|int $length
+ * @return CollectionInterface
+ */
+ public function slice(int $offset, ?int $length = 0): self;
+}
diff --git a/vendor/intervention/image/src/Interfaces/ColorChannelInterface.php b/vendor/intervention/image/src/Interfaces/ColorChannelInterface.php
new file mode 100644
index 000000000..f7509f638
--- /dev/null
+++ b/vendor/intervention/image/src/Interfaces/ColorChannelInterface.php
@@ -0,0 +1,70 @@
+
+ */
+ public function toArray(): array;
+
+ /**
+ * Cast color object to hex encoded web color
+ *
+ * @return string
+ */
+ public function toHex(string $prefix = ''): string;
+
+ /**
+ * Return array of all color channels
+ *
+ * @return array
+ */
+ public function channels(): array;
+
+ /**
+ * Return array of normalized color channel values
+ *
+ * @return array
+ */
+ public function normalize(): array;
+
+ /**
+ * Retrieve the color channel by its classname
+ *
+ * @param string $classname
+ * @throws ColorException
+ * @return ColorChannelInterface
+ */
+ public function channel(string $classname): ColorChannelInterface;
+
+ /**
+ * Convert color to given colorspace
+ *
+ * @return ColorInterface
+ */
+ public function convertTo(string|ColorspaceInterface $colorspace): self;
+
+ /**
+ * Determine if the current color is gray
+ *
+ * @return bool
+ */
+ public function isGreyscale(): bool;
+
+ /**
+ * Determine if the current color is (semi) transparent
+ *
+ * @return bool
+ */
+ public function isTransparent(): bool;
+}
diff --git a/vendor/intervention/image/src/Interfaces/ColorProcessorInterface.php b/vendor/intervention/image/src/Interfaces/ColorProcessorInterface.php
new file mode 100644
index 000000000..a5ee90929
--- /dev/null
+++ b/vendor/intervention/image/src/Interfaces/ColorProcessorInterface.php
@@ -0,0 +1,28 @@
+ $normalized
+ * @return ColorInterface
+ */
+ public function colorFromNormalized(array $normalized): ColorInterface;
+}
diff --git a/vendor/intervention/image/src/Interfaces/CoreInterface.php b/vendor/intervention/image/src/Interfaces/CoreInterface.php
new file mode 100644
index 000000000..5374f736a
--- /dev/null
+++ b/vendor/intervention/image/src/Interfaces/CoreInterface.php
@@ -0,0 +1,82 @@
+
+ */
+ public function setNative(mixed $native): self;
+
+ /**
+ * Count number of frames of animated image core
+ *
+ * @return int
+ */
+ public function count(): int;
+
+ /**
+ * Return frame of given position in an animated image
+ *
+ * @param int $position
+ * @throws AnimationException
+ * @return FrameInterface
+ */
+ public function frame(int $position): FrameInterface;
+
+ /**
+ * Add new frame to core
+ *
+ * @param FrameInterface $frame
+ * @return CoreInterface
+ */
+ public function add(FrameInterface $frame): self;
+
+ /**
+ * Return number of repetitions of an animated image
+ *
+ * @return int
+ */
+ public function loops(): int;
+
+ /**
+ * Set the number of repetitions for an animation. Where a
+ * value of 0 means infinite repetition.
+ *
+ * @param int $loops
+ * @return CoreInterface
+ */
+ public function setLoops(int $loops): self;
+
+ /**
+ * Get first frame in core
+ *
+ * @throws AnimationException
+ * @return FrameInterface
+ */
+ public function first(): FrameInterface;
+
+ /**
+ * Get last frame in core
+ *
+ * @throws AnimationException
+ * @return FrameInterface
+ */
+ public function last(): FrameInterface;
+}
diff --git a/vendor/intervention/image/src/Interfaces/DecoderInterface.php b/vendor/intervention/image/src/Interfaces/DecoderInterface.php
new file mode 100644
index 000000000..6d67cc4c1
--- /dev/null
+++ b/vendor/intervention/image/src/Interfaces/DecoderInterface.php
@@ -0,0 +1,19 @@
+ $objects
+ * @return array