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 + +[![Latest Version](https://img.shields.io/packagist/v/intervention/gif.svg)](https://packagist.org/packages/intervention/gif) +![build](https://github.com/Intervention/gif/actions/workflows/build.yml/badge.svg) +[![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/gif.svg)](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 + */ + public function specializeMultiple(array $objects): array; + + /** + * Create new image instance with the current driver in given dimensions + * + * @param int $width + * @param int $height + * @throws RuntimeException + * @return ImageInterface + */ + public function createImage(int $width, int $height): ImageInterface; + + /** + * Create new animated image + * + * @param callable $init + * @return ImageInterface + */ + public function createAnimation(callable $init): ImageInterface; + + /** + * Handle given input by decoding it to ImageInterface or ColorInterface + * + * @param mixed $input + * @param array $decoders + * @throws RuntimeException + * @return ImageInterface|ColorInterface + */ + public function handleInput(mixed $input, array $decoders = []): ImageInterface|ColorInterface; + + /** + * Return color processor for the given colorspace + * + * @param ColorspaceInterface $colorspace + * @return ColorProcessorInterface + */ + public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface; + + /** + * Return font processor of the current driver + * + * @return FontProcessorInterface + */ + public function fontProcessor(): FontProcessorInterface; + + /** + * Check whether all requirements for operating the driver are met and + * throw exception if the check fails. + * + * @throws DriverException + * @return void + */ + public function checkHealth(): void; + + /** + * Check if the current driver supports the given format and if the + * underlying PHP extension was built with support for the format. + * + * @param string|Format|FileExtension|MediaType $identifier + * @return bool + */ + public function supports(string|Format|FileExtension|MediaType $identifier): bool; +} diff --git a/vendor/intervention/image/src/Interfaces/EncodedImageInterface.php b/vendor/intervention/image/src/Interfaces/EncodedImageInterface.php new file mode 100644 index 000000000..46c1dc042 --- /dev/null +++ b/vendor/intervention/image/src/Interfaces/EncodedImageInterface.php @@ -0,0 +1,29 @@ + + */ +interface ImageInterface extends IteratorAggregate, Countable +{ + /** + * Return driver of current image + * + * @return DriverInterface + */ + public function driver(): DriverInterface; + + /** + * Return core of current image + * + * @return CoreInterface + */ + public function core(): CoreInterface; + + /** + * Return the origin of the image + * + * @return Origin + */ + public function origin(): Origin; + + /** + * Set the origin of the image + * + * @param Origin $origin + * @return ImageInterface + */ + public function setOrigin(Origin $origin): self; + + /** + * Return width of current image + * + * @link https://image.intervention.io/v3/basics/meta-information#reading-the-pixel-width + * @throws RuntimeException + * @return int + */ + public function width(): int; + + /** + * Return height of current image + * + * @link https://image.intervention.io/v3/basics/meta-information#reading-the-pixel-height + * @throws RuntimeException + * @return int + */ + public function height(): int; + + /** + * Return size of current image + * + * @link https://image.intervention.io/v3/basics/meta-information#reading-the-image-size-as-an-object + * @throws RuntimeException + * @return SizeInterface + */ + public function size(): SizeInterface; + + /** + * Encode image with given encoder + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-images + * @param EncoderInterface $encoder + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function encode(EncoderInterface $encoder = new AutoEncoder()): EncodedImageInterface; + + /** + * Save the image to the specified path in the file system. If no path is + * given, the image will be saved at its original location. + * + * @link https://image.intervention.io/v3/basics/image-output#writing-images-directly + * @param null|string $path + * @throws RuntimeException + * @return ImageInterface + */ + public function save(?string $path = null, mixed ...$options): self; + + /** + * Apply given modifier to current image + * + * @link https://image.intervention.io/v3/modifying/custom-modifiers + * @param ModifierInterface $modifier + * @throws RuntimeException + * @return ImageInterface + */ + public function modify(ModifierInterface $modifier): self; + + /** + * Analyzer current image with given analyzer + * + * @param AnalyzerInterface $analyzer + * @throws RuntimeException + * @return mixed + */ + public function analyze(AnalyzerInterface $analyzer): mixed; + + /** + * Determine if current image is animated + * + * @link https://image.intervention.io/v3/modifying/animations#check-the-current-image-instance-for-animation + * @return bool + */ + public function isAnimated(): bool; + + /** + * Remove all frames but keep the one at the specified position + * + * It is possible to specify the position as integer or string values. + * With the former, the exact position passed is searched for, while + * string values must represent a percentage value between '0%' and '100%' + * and the respective frame position is only determined approximately. + * + * @link https://image.intervention.io/v3/modifying/animations#removing-animation + * @param int|string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function removeAnimation(int|string $position = 0): self; + + /** + * Extract animation frames based on given values and discard the rest + * + * @link https://image.intervention.io/v3/modifying/animations#changing-the-animation-frames + * @param int $offset + * @param null|int $length + * @throws RuntimeException + * @return ImageInterface + */ + public function sliceAnimation(int $offset = 0, ?int $length = null): self; + + /** + * Return loop count of animated image + * + * @link https://image.intervention.io/v3/modifying/animations#reading-the-animation-iteration-count + * @return int + */ + public function loops(): int; + + /** + * Set loop count of animated image + * + * @link https://image.intervention.io/v3/modifying/animations#changing-the-animation-iteration-count + * @param int $loops + * @return ImageInterface + */ + public function setLoops(int $loops): self; + + /** + * Return exif data of current image + * + * @link https://image.intervention.io/v3/basics/meta-information#exif-information + * @return mixed + */ + public function exif(?string $query = null): mixed; + + /** + * Set exif data for the image object + * + * @param CollectionInterface $exif + * @return ImageInterface + */ + public function setExif(CollectionInterface $exif): self; + + /** + * Return image resolution/density + * + * @link https://image.intervention.io/v3/basics/meta-information#image-resolution + * @throws RuntimeException + * @return ResolutionInterface + */ + public function resolution(): ResolutionInterface; + + /** + * Set image resolution + * + * @link https://image.intervention.io/v3/basics/meta-information#image-resolution + * @param float $x + * @param float $y + * @throws RuntimeException + * @return ImageInterface + */ + public function setResolution(float $x, float $y): self; + + /** + * Get the colorspace of the image + * + * @link https://image.intervention.io/v3/basics/colors#reading-the-colorspace + * @throws RuntimeException + * @return ColorspaceInterface + */ + public function colorspace(): ColorspaceInterface; + + /** + * Transform image to given colorspace + * + * @link https://image.intervention.io/v3/basics/colors#changing-the-colorspace + * @param string|ColorspaceInterface $colorspace + * @throws RuntimeException + * @return ImageInterface + */ + public function setColorspace(string|ColorspaceInterface $colorspace): self; + + /** + * Return color of pixel at given position on given frame position + * + * @link https://image.intervention.io/v3/basics/colors#color-information + * @param int $x + * @param int $y + * @param int $frame_key + * @throws RuntimeException + * @return ColorInterface + */ + public function pickColor(int $x, int $y, int $frame_key = 0): ColorInterface; + + /** + * Return all colors of pixel at given position for all frames of image + * + * @link https://image.intervention.io/v3/basics/colors#color-information + * @param int $x + * @param int $y + * @throws RuntimeException + * @return CollectionInterface + */ + public function pickColors(int $x, int $y): CollectionInterface; + + /** + * Return color that is mixed with transparent areas when converting to a format which + * does not support transparency. + * + * @link https://image.intervention.io/v3/basics/colors#transparency + * @return ColorInterface + */ + public function blendingColor(): ColorInterface; + + /** + * Set blending color will have no effect unless image is converted into a format + * which does not support transparency. + * + * @link https://image.intervention.io/v3/basics/colors#transparency + * @param mixed $color + * @throws RuntimeException + * @return ImageInterface + */ + public function setBlendingColor(mixed $color): self; + + /** + * Replace transparent areas of the image with given color + * + * @param mixed $color + * @throws RuntimeException + * @return ImageInterface + */ + public function blendTransparency(mixed $color = null): self; + + /** + * Retrieve ICC color profile of image + * + * @link https://image.intervention.io/v3/basics/colors#color-profiles + * @throws RuntimeException + * @return ProfileInterface + */ + public function profile(): ProfileInterface; + + /** + * Set given icc color profile to image + * + * @link https://image.intervention.io/v3/basics/colors#color-profiles + * @param ProfileInterface $profile + * @throws RuntimeException + * @return ImageInterface + */ + public function setProfile(ProfileInterface $profile): self; + + /** + * Remove ICC color profile from the current image + * + * @link https://image.intervention.io/v3/basics/colors#color-profiles + * @throws RuntimeException + * @return ImageInterface + */ + public function removeProfile(): self; + + /** + * Apply color quantization to the current image + * + * @link https://image.intervention.io/v3/modifying/effects#reduce-colors + * @param int $limit + * @param mixed $background + * @throws RuntimeException + * @return ImageInterface + */ + public function reduceColors(int $limit, mixed $background = 'transparent'): self; + + /** + * Sharpen the current image with given strength + * + * @link https://image.intervention.io/v3/modifying/effects#sharpening-effect + * @param int $amount + * @throws RuntimeException + * @return ImageInterface + */ + public function sharpen(int $amount = 10): self; + + /** + * Turn image into a greyscale version + * + * @link https://image.intervention.io/v3/modifying/effects#convert-image-to-a-greyscale-version + * @throws RuntimeException + * @return ImageInterface + */ + public function greyscale(): self; + + /** + * Adjust brightness of the current image + * + * @link https://image.intervention.io/v3/modifying/effects#changing-the-brightness + * @param int $level + * @throws RuntimeException + * @return ImageInterface + */ + public function brightness(int $level): self; + + /** + * Adjust color contrast of the current image + * + * @link https://image.intervention.io/v3/modifying/effects#changing-the-contrast + * @param int $level + * @throws RuntimeException + * @return ImageInterface + */ + public function contrast(int $level): self; + + /** + * Apply gamma correction on the current image + * + * @link https://image.intervention.io/v3/modifying/effects#gamma-correction + * @param float $gamma + * @throws RuntimeException + * @return ImageInterface + */ + public function gamma(float $gamma): self; + + /** + * Adjust the intensity of the RGB color channels + * + * @link https://image.intervention.io/v3/modifying/effects#color-correction + * @param int $red + * @param int $green + * @param int $blue + * @throws RuntimeException + * @return ImageInterface + */ + public function colorize(int $red = 0, int $green = 0, int $blue = 0): self; + + /** + * Mirror the current image horizontally + * + * @link https://image.intervention.io/v3/modifying/effects#mirror-image-vertically + * @throws RuntimeException + * @return ImageInterface + */ + public function flip(): self; + + /** + * Mirror the current image vertically + * + * @link https://image.intervention.io/v3/modifying/effects#mirror-image-horizontally + * @throws RuntimeException + * @return ImageInterface + */ + public function flop(): self; + + /** + * Blur current image by given strength + * + * @link https://image.intervention.io/v3/modifying/effects#blur-effect + * @param int $amount + * @throws RuntimeException + * @return ImageInterface + */ + public function blur(int $amount = 5): self; + + /** + * Invert the colors of the current image + * + * @link https://image.intervention.io/v3/modifying/effects#invert-colors + * @throws RuntimeException + * @return ImageInterface + */ + public function invert(): self; + + /** + * Apply pixelation filter effect on current image + * + * @link https://image.intervention.io/v3/modifying/effects#pixelation-effect + * @param int $size + * @throws RuntimeException + * @return ImageInterface + */ + public function pixelate(int $size): self; + + /** + * Rotate current image by given angle + * + * @link https://image.intervention.io/v3/modifying/effects#image-rotation + * @param float $angle + * @param string $background + * @throws RuntimeException + * @return ImageInterface + */ + public function rotate(float $angle, mixed $background = 'ffffff'): self; + + /** + * Draw text on image + * + * @ink https://image.intervention.io/v3/modifying/text-fonts + * @param string $text + * @param int $x + * @param int $y + * @param callable|FontInterface $font + * @throws RuntimeException + * @return ImageInterface + */ + public function text(string $text, int $x, int $y, callable|FontInterface $font): self; + + /** + * Resize image to the given width and/or height + * + * @link https://image.intervention.io/v3/modifying/resizing#simple-image-resizing + * @param null|int $width + * @param null|int $height + * @throws RuntimeException + * @return ImageInterface + */ + public function resize(?int $width = null, ?int $height = null): self; + + /** + * Resize image to the given width and/or height without exceeding the original dimensions + * + * @link https://image.intervention.io/v3/modifying/resizing#resizing-without-exceeding-the-original-size + * @param null|int $width + * @param null|int $height + * @throws RuntimeException + * @return ImageInterface + */ + public function resizeDown(?int $width = null, ?int $height = null): self; + + /** + * Resize image to the given width and/or height and keep the original aspect ratio + * + * @link https://image.intervention.io/v3/modifying/resizing#scaling-images + * @param null|int $width + * @param null|int $height + * @throws RuntimeException + * @return ImageInterface + */ + public function scale(?int $width = null, ?int $height = null): self; + + /** + * Resize image to the given width and/or height, keep the original aspect ratio + * and do not exceed the original image width or height + * + * @link https://image.intervention.io/v3/modifying/resizing#scaling-images-but-do-not-exceed-the-original-size + * @param null|int $width + * @param null|int $height + * @throws RuntimeException + * @return ImageInterface + */ + public function scaleDown(?int $width = null, ?int $height = null): self; + + /** + * Takes the given dimensions and scales it to the largest possible size matching + * the original size. Then this size is positioned on the original and cut out + * before being resized to the desired size from the arguments + * + * @link https://image.intervention.io/v3/modifying/resizing#fitted-image-resizing + * @param int $width + * @param int $height + * @param string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function cover(int $width, int $height, string $position = 'center'): self; + + /** + * Same as cover() but do not exceed the original image size + * + * @link https://image.intervention.io/v3/modifying/resizing#fitted-resizing-without-exceeding-the-original-size + * @param int $width + * @param int $height + * @param string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function coverDown(int $width, int $height, string $position = 'center'): self; + + /** + * Resize the boundaries of the current image to given width and height. + * An anchor position can be defined to determine where the original image + * is fixed. A background color can be passed to define the color of the + * new emerging areas. + * + * @link https://image.intervention.io/v3/modifying/resizing#resize-image-canvas + * @param null|int $width + * @param null|int $height + * @param string $position + * @param mixed $background + * @throws RuntimeException + * @return ImageInterface + */ + public function resizeCanvas( + ?int $width = null, + ?int $height = null, + mixed $background = 'ffffff', + string $position = 'center' + ): self; + + /** + * Resize canvas in the same way as resizeCanvas() but takes relative values + * for the width and height, which will be added or subtracted to the + * original image size. + * + * @link https://image.intervention.io/v3/modifying/resizing#resize-image-boundaries-relative-to-the-original + * @param null|int $width + * @param null|int $height + * @param string $position + * @param mixed $background + * @throws RuntimeException + * @return ImageInterface + */ + public function resizeCanvasRelative( + ?int $width = null, + ?int $height = null, + mixed $background = 'ffffff', + string $position = 'center' + ): self; + + /** + * Padded resizing means that the original image is scaled until it fits the + * defined target size with unchanged aspect ratio. The original image is + * not scaled up but only down. + * + * Compared to the cover() method, this method does not create cropped areas, + * but possibly new empty areas on the sides of the result image. These are + * filled with the specified background color. + * + * @link https://image.intervention.io/v3/modifying/resizing#padded-image-resizing + * @param int $width + * @param int $height + * @param string $background + * @param string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function pad( + int $width, + int $height, + mixed $background = 'ffffff', + string $position = 'center' + ): self; + + /** + * This method does the same as pad(), but the original image is also scaled + * up if the target size exceeds the original size. + * + * @link https://image.intervention.io/v3/modifying/resizing#padded-resizing-with-upscaling + * @param int $width + * @param int $height + * @param string $background + * @param string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function contain( + int $width, + int $height, + mixed $background = 'ffffff', + string $position = 'center' + ): self; + + /** + * Cut out a rectangular part of the current image with given width and + * height at a given position. Define optional x,y offset coordinates + * to move the cutout by the given amount of pixels. + * + * @link https://image.intervention.io/v3/modifying/resizing#crop-image + * @param int $width + * @param int $height + * @param int $offset_x + * @param int $offset_y + * @param mixed $background + * @param string $position + * @throws RuntimeException + * @return ImageInterface + */ + public function crop( + int $width, + int $height, + int $offset_x = 0, + int $offset_y = 0, + mixed $background = 'ffffff', + string $position = 'top-left' + ): self; + + /** + * Trim the image by removing border areas of similar color within a the given tolerance + * + * @param int $tolerance + * @throws RuntimeException + * @throws AnimationException + * @return ImageInterface + */ + public function trim(int $tolerance = 0): self; + + /** + * Place another image into the current image instance + * + * @link https://image.intervention.io/v3/modifying/inserting + * @param mixed $element + * @param string $position + * @param int $offset_x + * @param int $offset_y + * @param int $opacity + * @throws RuntimeException + * @return ImageInterface + */ + public function place( + mixed $element, + string $position = 'top-left', + int $offset_x = 0, + int $offset_y = 0, + int $opacity = 100 + ): self; + + /** + * Fill image with given color + * + * If coordinates are transferred in the form of X and Y values, the function + * is executed as a flood fill. This means that the color at the specified + * position is taken as a reference and all adjacent pixels are also filled + * with the same color. + * + * If no coordinates are specified, the entire image area is filled. + * + * @link https://image.intervention.io/v3/modifying/drawing#fill-images-with-color + * @param mixed $color + * @param null|int $x + * @param null|int $y + * @throws RuntimeException + * @return ImageInterface + */ + public function fill(mixed $color, ?int $x = null, ?int $y = null): self; + + /** + * Draw a single pixel at given position defined by the coordinates x and y in a given color. + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-a-pixel + * @param int $x + * @param int $y + * @param mixed $color + * @throws RuntimeException + * @return ImageInterface + */ + public function drawPixel(int $x, int $y, mixed $color): self; + + /** + * Draw a rectangle on the current image + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-a-rectangle + * @param int $x + * @param int $y + * @param callable $init + * @throws RuntimeException + * @return ImageInterface + */ + public function drawRectangle(int $x, int $y, callable $init): self; + + /** + * Draw ellipse on the current image + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-ellipses + * @param int $x + * @param int $y + * @param callable $init + * @throws RuntimeException + * @return ImageInterface + */ + public function drawEllipse(int $x, int $y, callable $init): self; + + /** + * Draw circle on the current image + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-a-circle + * @param int $x + * @param int $y + * @param callable $init + * @throws RuntimeException + * @return ImageInterface + */ + public function drawCircle(int $x, int $y, callable $init): self; + + /** + * Draw a polygon on the current image + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-a-polygon + * @param callable $init + * @throws RuntimeException + * @return ImageInterface + */ + public function drawPolygon(callable $init): self; + + /** + * Draw a line on the current image + * + * @link https://image.intervention.io/v3/modifying/drawing#drawing-a-line + * @param callable $init + * @throws RuntimeException + * @return ImageInterface + */ + public function drawLine(callable $init): self; + + /** + * Encode image to given media (mime) type. If no type is given the image + * will be encoded to the format of the originally read image. + * + * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-media-mime-type + * @param null|string|MediaType $type + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function encodeByMediaType(null|string|MediaType $type = null, mixed ...$options): EncodedImageInterface; + + /** + * Encode the image into the format represented by the given extension. If no + * extension is given the image will be encoded to the format of the + * originally read image. + * + * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-file-extension + * @param null|string|FileExtension $extension + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function encodeByExtension( + null|string|FileExtension $extension = null, + mixed ...$options + ): EncodedImageInterface; + + /** + * Encode the image into the format represented by the given extension of + * the given file path extension is given the image will be encoded to + * the format of the originally read image. + * + * @link https://image.intervention.io/v3/basics/image-output#encode-images-by-file-path + * @param null|string $path + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function encodeByPath(?string $path = null, mixed ...$options): EncodedImageInterface; + + /** + * Encode image to JPEG format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-jpeg-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + + public function toJpeg(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to Jpeg2000 format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-jpeg-2000-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toJpeg2000(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to Webp format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-webp-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toWebp(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to PNG format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-png-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toPng(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to GIF format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-gif-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toGif(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to Bitmap format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-windows-bitmap-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toBitmap(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to AVIF format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-av1-image-file-format-avif + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toAvif(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to TIFF format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-tiff-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toTiff(mixed ...$options): EncodedImageInterface; + + /** + * Encode image to HEIC format + * + * @link https://image.intervention.io/v3/basics/image-output#encoding-heic-format + * @param mixed $options + * @throws RuntimeException + * @return EncodedImageInterface + */ + public function toHeic(mixed ...$options): EncodedImageInterface; +} diff --git a/vendor/intervention/image/src/Interfaces/ImageManagerInterface.php b/vendor/intervention/image/src/Interfaces/ImageManagerInterface.php new file mode 100644 index 000000000..33278194e --- /dev/null +++ b/vendor/intervention/image/src/Interfaces/ImageManagerInterface.php @@ -0,0 +1,68 @@ +|DecoderInterface $decoders + * @throws RuntimeException + * @return ImageInterface + */ + public function read(mixed $input, string|array|DecoderInterface $decoders = []): ImageInterface; + + /** + * Create new animated image by given callback + * + * @link https://image.intervention.io/v3/basics/instantiation#creating-animations + * @param callable $init + * @return ImageInterface + */ + public function animate(callable $init): ImageInterface; + + /** + * Return currently used driver + * + * @return DriverInterface + */ + public function driver(): DriverInterface; +} diff --git a/vendor/intervention/image/src/Interfaces/InputHandlerInterface.php b/vendor/intervention/image/src/Interfaces/InputHandlerInterface.php new file mode 100644 index 000000000..6d89f07e0 --- /dev/null +++ b/vendor/intervention/image/src/Interfaces/InputHandlerInterface.php @@ -0,0 +1,19 @@ + + */ + public function specializable(): array; + + /** + * Set the driver for which the object is specialized + * + * @param DriverInterface $driver + * @return SpecializableInterface + */ + public function setDriver(DriverInterface $driver): self; + + /** + * Return the driver for which the object was specialized + * + * @return DriverInterface + */ + public function driver(): DriverInterface; +} diff --git a/vendor/intervention/image/src/Interfaces/SpecializedInterface.php b/vendor/intervention/image/src/Interfaces/SpecializedInterface.php new file mode 100644 index 000000000..dc8eaa95b --- /dev/null +++ b/vendor/intervention/image/src/Interfaces/SpecializedInterface.php @@ -0,0 +1,9 @@ + Format::JPEG, + self::IMAGE_WEBP, + self::IMAGE_X_WEBP => Format::WEBP, + self::IMAGE_GIF => Format::GIF, + self::IMAGE_PNG, + self::IMAGE_X_PNG => Format::PNG, + self::IMAGE_AVIF, + self::IMAGE_X_AVIF => Format::AVIF, + self::IMAGE_BMP, + self::IMAGE_MS_BMP, + self::IMAGE_X_BITMAP, + self::IMAGE_X_BMP, + self::IMAGE_X_MS_BMP, + self::IMAGE_X_XBITMAP, + self::IMAGE_X_WINDOWS_BMP, + self::IMAGE_X_WIN_BITMAP => Format::BMP, + self::IMAGE_TIFF => Format::TIFF, + self::IMAGE_JP2, + self::IMAGE_JPX, + self::IMAGE_JPM => Format::JP2, + self::IMAGE_HEIF, + self::IMAGE_HEIC => Format::HEIC, + }; + } +} diff --git a/vendor/intervention/image/src/ModifierStack.php b/vendor/intervention/image/src/ModifierStack.php new file mode 100644 index 000000000..8e58cb3a4 --- /dev/null +++ b/vendor/intervention/image/src/ModifierStack.php @@ -0,0 +1,49 @@ + $modifiers + * @return void + */ + public function __construct(protected array $modifiers) + { + } + + /** + * Apply all modifiers in stack to the given image + * + * @param ImageInterface $image + * @return ImageInterface + */ + public function apply(ImageInterface $image): ImageInterface + { + foreach ($this->modifiers as $modifier) { + $modifier->apply($image); + } + + return $image; + } + + /** + * Append new modifier to the stack + * + * @param ModifierInterface $modifier + * @return ModifierStack + */ + public function push(ModifierInterface $modifier): self + { + $this->modifiers[] = $modifier; + + return $this; + } +} diff --git a/vendor/intervention/image/src/Modifiers/AbstractDrawModifier.php b/vendor/intervention/image/src/Modifiers/AbstractDrawModifier.php new file mode 100644 index 000000000..3c10e0fb2 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/AbstractDrawModifier.php @@ -0,0 +1,49 @@ +driver()->handleInput($this->drawable()->backgroundColor()); + } catch (DecoderException) { + return $this->driver()->handleInput('transparent'); + } + + return $color; + } + + /** + * @throws RuntimeException + */ + public function borderColor(): ColorInterface + { + try { + $color = $this->driver()->handleInput($this->drawable()->borderColor()); + } catch (DecoderException) { + return $this->driver()->handleInput('transparent'); + } + + return $color; + } +} diff --git a/vendor/intervention/image/src/Modifiers/AlignRotationModifier.php b/vendor/intervention/image/src/Modifiers/AlignRotationModifier.php new file mode 100644 index 000000000..1af9b1b22 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/AlignRotationModifier.php @@ -0,0 +1,11 @@ +target)) { + return $this->target; + } + + if (in_array($this->target, ['rgb', 'RGB', RgbColorspace::class])) { + return new RgbColorspace(); + } + + if (in_array($this->target, ['cmyk', 'CMYK', CmykColorspace::class])) { + return new CmykColorspace(); + } + + throw new NotSupportedException('Given colorspace is not supported.'); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ContainModifier.php b/vendor/intervention/image/src/Modifiers/ContainModifier.php new file mode 100644 index 000000000..9f1fa3a96 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ContainModifier.php @@ -0,0 +1,43 @@ +size() + ->contain( + $this->width, + $this->height + ) + ->alignPivotTo( + $this->getResizeSize($image), + $this->position + ); + } + + public function getResizeSize(ImageInterface $image): SizeInterface + { + return new Rectangle($this->width, $this->height); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ContrastModifier.php b/vendor/intervention/image/src/Modifiers/ContrastModifier.php new file mode 100644 index 000000000..e61f58326 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ContrastModifier.php @@ -0,0 +1,14 @@ +size(); + $crop = new Rectangle($this->width, $this->height); + + return $crop->contain( + $imagesize->width(), + $imagesize->height() + )->alignPivotTo($imagesize, $this->position); + } + + /** + * @throws RuntimeException + */ + public function getResizeSize(SizeInterface $size): SizeInterface + { + return $size->scale($this->width, $this->height); + } +} diff --git a/vendor/intervention/image/src/Modifiers/CropModifier.php b/vendor/intervention/image/src/Modifiers/CropModifier.php new file mode 100644 index 000000000..ffc6b4999 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/CropModifier.php @@ -0,0 +1,38 @@ +width, $this->height); + $crop->align($this->position); + + return $crop->alignPivotTo( + $image->size(), + $this->position + ); + } +} diff --git a/vendor/intervention/image/src/Modifiers/DrawEllipseModifier.php b/vendor/intervention/image/src/Modifiers/DrawEllipseModifier.php new file mode 100644 index 000000000..59c06bd66 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/DrawEllipseModifier.php @@ -0,0 +1,20 @@ +drawable; + } +} diff --git a/vendor/intervention/image/src/Modifiers/DrawLineModifier.php b/vendor/intervention/image/src/Modifiers/DrawLineModifier.php new file mode 100644 index 000000000..4997124bf --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/DrawLineModifier.php @@ -0,0 +1,20 @@ +drawable; + } +} diff --git a/vendor/intervention/image/src/Modifiers/DrawPixelModifier.php b/vendor/intervention/image/src/Modifiers/DrawPixelModifier.php new file mode 100644 index 000000000..6e4d732ac --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/DrawPixelModifier.php @@ -0,0 +1,17 @@ +drawable; + } +} diff --git a/vendor/intervention/image/src/Modifiers/DrawRectangleModifier.php b/vendor/intervention/image/src/Modifiers/DrawRectangleModifier.php new file mode 100644 index 000000000..03d521c25 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/DrawRectangleModifier.php @@ -0,0 +1,20 @@ +drawable; + } +} diff --git a/vendor/intervention/image/src/Modifiers/FillModifier.php b/vendor/intervention/image/src/Modifiers/FillModifier.php new file mode 100644 index 000000000..46bbc519d --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/FillModifier.php @@ -0,0 +1,22 @@ +position); + } +} diff --git a/vendor/intervention/image/src/Modifiers/FlipModifier.php b/vendor/intervention/image/src/Modifiers/FlipModifier.php new file mode 100644 index 000000000..502cb3e57 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/FlipModifier.php @@ -0,0 +1,11 @@ +size() + ->containMax( + $this->width, + $this->height + ) + ->alignPivotTo( + $this->getResizeSize($image), + $this->position + ); + } +} diff --git a/vendor/intervention/image/src/Modifiers/PixelateModifier.php b/vendor/intervention/image/src/Modifiers/PixelateModifier.php new file mode 100644 index 000000000..51cb770b7 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/PixelateModifier.php @@ -0,0 +1,14 @@ +size()->movePivot( + $this->position, + $this->offset_x, + $this->offset_y + ); + + $watermark_size = $watermark->size()->movePivot( + $this->position + ); + + return $image_size->relativePositionTo($watermark_size); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ProfileModifier.php b/vendor/intervention/image/src/Modifiers/ProfileModifier.php new file mode 100644 index 000000000..99e844b7c --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ProfileModifier.php @@ -0,0 +1,15 @@ +core()->frame($position); + } + + if (preg_match("/^(?P[0-9]{1,3})%$/", $position, $matches) != 1) { + throw new InputException( + 'Position must be either integer or a percent value as string.' + ); + } + + $total = count($image); + $position = intval(round($total / 100 * intval($matches['percent']))); + $position = $position == $total ? $position - 1 : $position; + + return $image->core()->frame($position); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ResizeCanvasModifier.php b/vendor/intervention/image/src/Modifiers/ResizeCanvasModifier.php new file mode 100644 index 000000000..8fafbf92a --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ResizeCanvasModifier.php @@ -0,0 +1,47 @@ + new Rectangle( + is_null($this->width) ? $image->width() : $image->width() + $this->width, + is_null($this->height) ? $image->height() : $image->height() + $this->height, + ), + default => new Rectangle( + is_null($this->width) ? $image->width() : $this->width, + is_null($this->height) ? $image->height() : $this->height, + ), + }; + + return $size->alignPivotTo($image->size(), $this->position); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ResizeCanvasRelativeModifier.php b/vendor/intervention/image/src/Modifiers/ResizeCanvasRelativeModifier.php new file mode 100644 index 000000000..1d8652079 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ResizeCanvasRelativeModifier.php @@ -0,0 +1,9 @@ +angle, 360); + } +} diff --git a/vendor/intervention/image/src/Modifiers/ScaleDownModifier.php b/vendor/intervention/image/src/Modifiers/ScaleDownModifier.php new file mode 100644 index 000000000..a0f6ac999 --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/ScaleDownModifier.php @@ -0,0 +1,9 @@ +driver()->handleInput($this->font->color()); + + if ($this->font->hasStrokeEffect() && $color->isTransparent()) { + throw new ColorException( + 'The text color must be fully opaque when using the stroke effect.' + ); + } + + return $color; + } + + /** + * Decode outline stroke color + * + * @throws RuntimeException + * @throws ColorException + * @return ColorInterface + */ + protected function strokeColor(): ColorInterface + { + $color = $this->driver()->handleInput($this->font->strokeColor()); + + if ($color->isTransparent()) { + throw new ColorException( + 'The stroke color must be fully opaque.' + ); + } + + return $color; + } + + /** + * Return array of offset points to draw text stroke effect below the actual text + * + * @param FontInterface $font + * @return array + */ + protected function strokeOffsets(FontInterface $font): array + { + $offsets = []; + + if ($font->strokeWidth() <= 0) { + return $offsets; + } + + for ($x = $font->strokeWidth() * -1; $x <= $font->strokeWidth(); $x++) { + for ($y = $font->strokeWidth() * -1; $y <= $font->strokeWidth(); $y++) { + $offsets[] = new Point($x, $y); + } + } + + return $offsets; + } +} diff --git a/vendor/intervention/image/src/Modifiers/TrimModifier.php b/vendor/intervention/image/src/Modifiers/TrimModifier.php new file mode 100644 index 000000000..89fa3992a --- /dev/null +++ b/vendor/intervention/image/src/Modifiers/TrimModifier.php @@ -0,0 +1,14 @@ +mediaType; + } + + /** + * Alias of self::mediaType() + */ + public function mimetype(): string + { + return $this->mediaType(); + } + + /** + * Set media type of current instance + * + * @param string $type + * @return Origin + */ + public function setMediaType(string $type): self + { + $this->mediaType = $type; + + return $this; + } + + /** + * Return file path of origin + * + * @return null|string + */ + public function filePath(): ?string + { + return $this->filePath; + } + + /** + * Set file path for origin + * + * @param string $path + * @return Origin + */ + public function setFilePath(string $path): self + { + $this->filePath = $path; + + return $this; + } + + /** + * Return file extension if origin was created from file path + * + * @return null|string + */ + public function fileExtension(): ?string + { + return empty($this->filePath) ? null : pathinfo($this->filePath, PATHINFO_EXTENSION); + } +} diff --git a/vendor/intervention/image/src/Resolution.php b/vendor/intervention/image/src/Resolution.php new file mode 100644 index 000000000..ef4a6921c --- /dev/null +++ b/vendor/intervention/image/src/Resolution.php @@ -0,0 +1,148 @@ +x; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::setX() + */ + public function setX(float $x): self + { + $this->x = $x; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::y() + */ + public function y(): float + { + return $this->y; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::setY() + */ + public function setY(float $y): self + { + $this->y = $y; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::setPerUnit() + */ + protected function setPerUnit(int $per_unit): self + { + $this->per_unit = $per_unit; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::unit() + */ + public function unit(): string + { + return match ($this->per_unit) { + self::PER_CM => 'dpcm', + default => 'dpi', + }; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::perInch() + */ + public function perInch(): self + { + return match ($this->per_unit) { + self::PER_CM => $this + ->setPerUnit(self::PER_INCH) + ->setX($this->x * (1 / 2.54)) + ->setY($this->y * (1 / 2.54)), + default => $this + }; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::perCm() + */ + public function perCm(): self + { + return match ($this->per_unit) { + self::PER_INCH => $this + ->setPerUnit(self::PER_CM) + ->setX($this->x / (1 / 2.54)) + ->setY($this->y / (1 / 2.54)), + default => $this, + }; + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::toString() + */ + public function toString(): string + { + return sprintf("%1\$.2f x %2\$.2f %3\$s", $this->x, $this->y, $this->unit()); + } + + /** + * {@inheritdoc} + * + * @see ResolutionInterface::__toString() + */ + public function __toString(): string + { + return $this->toString(); + } +} diff --git a/vendor/intervention/image/src/Traits/CanBeDriverSpecialized.php b/vendor/intervention/image/src/Traits/CanBeDriverSpecialized.php new file mode 100644 index 000000000..463050f4c --- /dev/null +++ b/vendor/intervention/image/src/Traits/CanBeDriverSpecialized.php @@ -0,0 +1,60 @@ +getConstructor()) { + foreach ($constructor->getParameters() as $parameter) { + $specializable[$parameter->getName()] = $this->{$parameter->getName()}; + } + } + + return $specializable; + } + + /** + * {@inheritdoc} + * + * @see SpecializableInterface::driver() + */ + public function driver(): DriverInterface + { + return $this->driver; + } + + /** + * {@inheritdoc} + * + * @see SpecializableInterface::setDriver() + */ + public function setDriver(DriverInterface $driver): SpecializableInterface + { + $this->driver = $driver; + + return $this; + } +} diff --git a/vendor/intervention/image/src/Traits/CanBuildFilePointer.php b/vendor/intervention/image/src/Traits/CanBuildFilePointer.php new file mode 100644 index 000000000..8d9d3dd0f --- /dev/null +++ b/vendor/intervention/image/src/Traits/CanBuildFilePointer.php @@ -0,0 +1,23 @@ +filename = $filename; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setSize() + */ + public function setSize(float $size): FontInterface + { + $this->size = $size; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::size() + */ + public function size(): float + { + return $this->size; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setAngle() + */ + public function setAngle(float $angle): FontInterface + { + $this->angle = $angle; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::angle() + */ + public function angle(): float + { + return $this->angle; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setFilename() + */ + public function setFilename(string $filename): FontInterface + { + $this->filename = $filename; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::filename() + */ + public function filename(): ?string + { + return $this->filename; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::hasFilename() + */ + public function hasFilename(): bool + { + return !is_null($this->filename) && is_file($this->filename); + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setColor() + */ + public function setColor(mixed $color): FontInterface + { + $this->color = $color; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::color() + */ + public function color(): mixed + { + return $this->color; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setStrokeColor() + */ + public function setStrokeColor(mixed $color): FontInterface + { + $this->strokeColor = $color; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::strokeColor() + */ + public function strokeColor(): mixed + { + return $this->strokeColor; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setStrokeWidth() + */ + public function setStrokeWidth(int $width): FontInterface + { + if (!in_array($width, range(0, 10))) { + throw new FontException( + 'The stroke width must be in the range from 0 to 10.' + ); + } + + $this->strokeWidth = $width; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::strokeWidth() + */ + public function strokeWidth(): int + { + return $this->strokeWidth; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::hasStrokeEffect() + */ + public function hasStrokeEffect(): bool + { + return $this->strokeWidth > 0; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::alignment() + */ + public function alignment(): string + { + return $this->alignment; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setAlignment() + */ + public function setAlignment(string $value): FontInterface + { + $this->alignment = $value; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::valignment() + */ + public function valignment(): string + { + return $this->valignment; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setValignment() + */ + public function setValignment(string $value): FontInterface + { + $this->valignment = $value; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setLineHeight() + */ + public function setLineHeight(float $height): FontInterface + { + $this->lineHeight = $height; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::lineHeight() + */ + public function lineHeight(): float + { + return $this->lineHeight; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::setWrapWidth() + */ + public function setWrapWidth(?int $width): FontInterface + { + $this->wrapWidth = $width; + + return $this; + } + + /** + * {@inheritdoc} + * + * @see FontInterface::wrapWidth() + */ + public function wrapWidth(): ?int + { + return $this->wrapWidth; + } +} diff --git a/vendor/intervention/image/src/Typography/FontFactory.php b/vendor/intervention/image/src/Typography/FontFactory.php new file mode 100644 index 000000000..d6738f41e --- /dev/null +++ b/vendor/intervention/image/src/Typography/FontFactory.php @@ -0,0 +1,169 @@ +font = is_a($init, FontInterface::class) ? $init : new Font(); + + if (is_callable($init)) { + $init($this); + } + } + + /** + * Build font + * + * @return FontInterface + */ + public function __invoke(): FontInterface + { + return $this->font; + } + + /** + * Set the filename of the font to be built + * + * @param string $value + * @return FontFactory + */ + public function filename(string $value): self + { + $this->font->setFilename($value); + + return $this; + } + + /** + * {@inheritdoc} + * + * @see self::filename() + */ + public function file(string $value): self + { + return $this->filename($value); + } + + /** + * Set outline stroke effect for the font to be built + * + * @param mixed $color + * @param int $width + * @throws FontException + * @return FontFactory + */ + public function stroke(mixed $color, int $width = 1): self + { + $this->font->setStrokeWidth($width); + $this->font->setStrokeColor($color); + + return $this; + } + + /** + * Set color for the font to be built + * + * @param mixed $value + * @return FontFactory + */ + public function color(mixed $value): self + { + $this->font->setColor($value); + + return $this; + } + + /** + * Set the size for the font to be built + * + * @param float $value + * @return FontFactory + */ + public function size(float $value): self + { + $this->font->setSize($value); + + return $this; + } + + /** + * Set the horizontal alignment of the font to be built + * + * @param string $value + * @return FontFactory + */ + public function align(string $value): self + { + $this->font->setAlignment($value); + + return $this; + } + + /** + * Set the vertical alignment of the font to be built + * + * @param string $value + * @return FontFactory + */ + public function valign(string $value): self + { + $this->font->setValignment($value); + + return $this; + } + + /** + * Set the line height of the font to be built + * + * @param float $value + * @return FontFactory + */ + public function lineHeight(float $value): self + { + $this->font->setLineHeight($value); + + return $this; + } + + /** + * Set the rotation angle of the font to be built + * + * @param float $value + * @return FontFactory + */ + public function angle(float $value): self + { + $this->font->setAngle($value); + + return $this; + } + + /** + * Set the maximum width of the text block to be built + * + * @param int $width + * @return FontFactory + */ + public function wrap(int $width): self + { + $this->font->setWrapWidth($width); + + return $this; + } +} diff --git a/vendor/intervention/image/src/Typography/Line.php b/vendor/intervention/image/src/Typography/Line.php new file mode 100644 index 000000000..555acd911 --- /dev/null +++ b/vendor/intervention/image/src/Typography/Line.php @@ -0,0 +1,117 @@ + + */ +class Line implements IteratorAggregate, Countable +{ + /** + * Segments (usually individual words including punctuation marks) of the line + * + * @var array + */ + protected array $segments = []; + + /** + * Create new text line object with given text & position + * + * @param string $text + * @param PointInterface $position + * @return void + */ + public function __construct( + ?string $text = null, + protected PointInterface $position = new Point() + ) { + if (is_string($text)) { + $this->segments = explode(" ", $text); + } + } + + /** + * Add word to current line + * + * @param string $word + * @return Line + */ + public function add(string $word): self + { + $this->segments[] = $word; + + return $this; + } + + /** + * Returns Iterator + * + * @return Traversable + */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->segments); + } + + /** + * Get Position of line + * + * @return PointInterface + */ + public function position(): PointInterface + { + return $this->position; + } + + /** + * Set position of current line + * + * @param Point $point + * @return Line + */ + public function setPosition(Point $point): self + { + $this->position = $point; + + return $this; + } + + /** + * Count segments (individual words including punctuation marks) of line + * + * @return int + */ + public function count(): int + { + return count($this->segments); + } + + /** + * Count characters of line + * + * @return int + */ + public function length(): int + { + return mb_strlen((string) $this); + } + + /** + * Cast line to string + * + * @return string + */ + public function __toString(): string + { + return implode(" ", $this->segments); + } +} diff --git a/vendor/intervention/image/src/Typography/TextBlock.php b/vendor/intervention/image/src/Typography/TextBlock.php new file mode 100644 index 000000000..d7f648dda --- /dev/null +++ b/vendor/intervention/image/src/Typography/TextBlock.php @@ -0,0 +1,73 @@ +push(new Line($line)); + } + } + + /** + * Return array of lines in text block + * + * @return array + */ + public function lines(): array + { + return $this->items; + } + + /** + * Set lines of the text block + * + * @param array $lines + * @return self + */ + public function setLines(array $lines): self + { + $this->items = $lines; + + return $this; + } + + /** + * Get line by given key + * + * @param mixed $key + * @return null|Line + */ + public function line($key): ?Line + { + if (!array_key_exists($key, $this->lines())) { + return null; + } + + return $this->lines()[$key]; + } + + /** + * Return line with most characters of text block + * + * @return Line + */ + public function longestLine(): Line + { + $lines = $this->lines(); + usort($lines, function (Line $a, Line $b) { + if ($a->length() === $b->length()) { + return 0; + } + return $a->length() > $b->length() ? -1 : 1; + }); + + return $lines[0]; + } +}