Merge remote-tracking branch 'origin/main'

This commit is contained in:
liu 2024-06-05 18:39:21 +08:00
commit cf4fc46757
408 changed files with 23130 additions and 25 deletions

View File

@ -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 = ""
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'); // 设置字体文件路径,这里以微软雅黑字体为例
});

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -26,7 +26,8 @@ class SystemStoreLists extends BaseAdminDataLists implements ListsSearchInterfac
public function setSearch(): array
{
return [
'=' => ['name', 'phone'],
'=' => ['phone'],
'%like%'=>['name']
];
}

View File

@ -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();
}
//等级处理

View File

@ -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)

View File

@ -0,0 +1,51 @@
<?php
namespace app\store\controller\finance;
use app\admin\lists\store_finance_flow\StoreFinanceFlowLists;
use app\common\controller\Definitions;
use app\store\controller\BaseAdminController;
use hg\apidoc\annotation as ApiDoc;
#[ApiDoc\title('财务')]
class FinanceController extends BaseAdminController
{
#[
ApiDoc\Title('财务流水'),
ApiDoc\url('/store/finance/finance/lists'),
ApiDoc\Method('GET'),
ApiDoc\NotHeaders(),
ApiDoc\Author('中国队长'),
ApiDoc\Query(name: 'keyword', type: 'string', require: false, desc: '订单编号'),
ApiDoc\Query(name: 'staff_id', type: 'int', require: false, desc: '店员id'),
ApiDoc\Query(name: 'start_time', type: 'string', require: false, desc: '开始时间'),
ApiDoc\Query(name: 'end_time', type: 'string', require: false, desc: '结束时间'),
ApiDoc\Header(ref: [Definitions::class, "token"]),
ApiDoc\Query(ref: [Definitions::class, "page"]),
ApiDoc\ResponseSuccess("data", type: "array", children: [
['name' => '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());
}
}

View File

@ -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. "

152
composer.lock generated
View File

@ -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"
}

View File

@ -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'),

View File

@ -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',

View File

@ -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",

View File

@ -1,9 +1,9 @@
<?php return array(
'root' => 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(),

View File

@ -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');

21
vendor/intervention/gif/LICENSE vendored Normal file
View File

@ -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.

100
vendor/intervention/gif/README.md vendored Normal file
View File

@ -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).

44
vendor/intervention/gif/composer.json vendored Normal file
View File

@ -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
}
}
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" beStrictAboutTestsThatDoNotTestAnything="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<testsuites>
<testsuite name="Unit Tests">
<directory suffix=".php">./tests/Unit/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
use Intervention\Gif\Traits\CanDecode;
use Intervention\Gif\Traits\CanEncode;
use ReflectionClass;
abstract class AbstractEntity
{
use CanEncode;
use CanDecode;
public const TERMINATOR = "\x00";
/**
* Get short classname of current instance
*
* @return string
*/
public static function getShortClassname(): string
{
return (new ReflectionClass(static::class))->getShortName();
}
/**
* Cast object to string
*
* @return string
*/
public function __toString(): string
{
return $this->encode();
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
abstract class AbstractExtension extends AbstractEntity
{
public const MARKER = "\x21";
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractExtension;
class ApplicationExtension extends AbstractExtension
{
public const LABEL = "\xFF";
/**
* Application Identifier & Auth Code
*
* @var string
*/
protected string $application = '';
/**
* Data Sub Blocks
*
* @var array
*/
protected array $blocks = [];
public function getBlockSize(): int
{
return strlen($this->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;
}
}

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class Color extends AbstractEntity
{
/**
* Create new instance
*
* @param int $r
* @param int $g
* @param int $b
*/
public function __construct(
protected int $r = 0,
protected int $g = 0,
protected int $b = 0
) {
}
/**
* Get red value
*
* @return int
*/
public function getRed()
{
return $this->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);
}
}

View File

@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class ColorTable extends AbstractEntity
{
/**
* Create new instance
*
* @param array $colors
* @return void
*/
public function __construct(protected array $colors = [])
{
}
/**
* Return array of current colors
*
* @return array
*/
public function getColors(): array
{
return array_values($this->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);
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractExtension;
class CommentExtension extends AbstractExtension
{
public const LABEL = "\xFE";
/**
* Comment blocks
*
* @var array
*/
protected array $comments = [];
/**
* Get all or one comment
*
* @return mixed
*/
public function getComments()
{
return $this->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;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
use Intervention\Gif\Exceptions\FormatException;
class DataSubBlock extends AbstractEntity
{
public function __construct(protected string $value)
{
if ($this->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;
}
}

View File

@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class FrameBlock extends AbstractEntity
{
protected ?GraphicControlExtension $graphicControlExtension = null;
protected ?ColorTable $colorTable = null;
protected ?PlainTextExtension $plainTextExtension = null;
protected array $applicationExtensions = [];
protected array $commentExtensions = [];
public function __construct(
protected ImageDescriptor $imageDescriptor = new ImageDescriptor(),
protected ImageData $imageData = new ImageData()
) {
}
public function addEntity(AbstractEntity $entity): self
{
switch (true) {
case $entity instanceof TableBasedImage:
$this->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;
}
}

View File

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractExtension;
use Intervention\Gif\DisposalMethod;
class GraphicControlExtension extends AbstractExtension
{
public const LABEL = "\xF9";
public const BLOCKSIZE = "\x04";
/**
* Existance flag of transparent color
*
* @var bool
*/
protected bool $transparentColorExistance = false;
/**
* Transparent color index
*
* @var int
*/
protected int $transparentColorIndex = 0;
/**
* User input flag
*
* @var bool
*/
protected bool $userInput = false;
/**
* Create new instance
*
* @param int $delay
* @param DisposalMethod $disposalMethod
* @return void
*/
public function __construct(
protected int $delay = 0,
protected DisposalMethod $disposalMethod = DisposalMethod::UNDEFINED,
) {
}
/**
* Set delay time (1/100 second)
*
* @param int $value
*/
public function setDelay(int $value): self
{
$this->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;
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class Header extends AbstractEntity
{
/**
* Header signature
*/
public const SIGNATURE = 'GIF';
/**
* Current GIF version
*/
protected string $version = '89a';
/**
* Set GIF version
*
* @param string $value
*/
public function setVersion(string $value): self
{
$this->version = $value;
return $this;
}
/**
* Return current version
*
* @return string
*/
public function getVersion(): string
{
return $this->version;
}
}

View File

@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class ImageData extends AbstractEntity
{
/**
* LZW min. code size
*
* @var int
*/
protected int $lzw_min_code_size;
/**
* Sub blocks
*
* @var array
*/
protected array $blocks = [];
/**
* Get LZW min. code size
*
* @return int
*/
public function getLzwMinCodeSize(): int
{
return $this->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;
}
}

View File

@ -0,0 +1,246 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class ImageDescriptor extends AbstractEntity
{
public const SEPARATOR = "\x2C";
/**
* Width of frame
*
* @var int
*/
protected int $width = 0;
/**
* Height of frame
*
* @var int
*/
protected int $height = 0;
/**
* Left position of frame
*
* @var int
*/
protected int $left = 0;
/**
* Top position of frame
*
* @var int
*/
protected int $top = 0;
/**
* Determine if frame is interlaced
*
* @var bool
*/
protected bool $interlaced = false;
/**
* Local color table flag
*
* @var bool
*/
protected bool $localColorTableExistance = false;
/**
* Sort flag of local color table
*
* @var bool
*/
protected bool $localColorTableSorted = false;
/**
* Size of local color table
*
* @var int
*/
protected int $localColorTableSize = 0;
/**
* Get current width
*
* @return int
*/
public function getWidth(): int
{
return intval($this->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;
}
}

View File

@ -0,0 +1,254 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class LogicalScreenDescriptor extends AbstractEntity
{
/**
* Width
*
* @var int
*/
protected int $width;
/**
* Height
*
* @var int
*/
protected int $height;
/**
* Global color table flag
*
* @var bool
*/
protected bool $globalColorTableExistance = false;
/**
* Sort flag of global color table
*
* @var bool
*/
protected bool $globalColorTableSorted = false;
/**
* Size of global color table
*
* @var int
*/
protected int $globalColorTableSize = 0;
/**
* Background color index
*
* @var int
*/
protected int $backgroundColorIndex = 0;
/**
* Color resolution
*
* @var int
*/
protected int $bitsPerPixel = 8;
/**
* Pixel aspect ration
*
* @var int
*/
protected int $pixelAspectRatio = 0;
/**
* Set size
*
* @param int $width
* @param int $height
*/
public function setSize(int $width, int $height): self
{
$this->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;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
class NetscapeApplicationExtension extends ApplicationExtension
{
public const IDENTIFIER = "NETSCAPE";
public const AUTH_CODE = "2.0";
public const SUB_BLOCK_PREFIX = "\x01";
public function __construct()
{
$this->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;
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractExtension;
class PlainTextExtension extends AbstractExtension
{
public const LABEL = "\x01";
/**
* Array of text
*
* @var array
*/
protected array $text = [];
/**
* Get current text
*
* @return array
*/
public function getText(): array
{
return $this->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;
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class TableBasedImage extends AbstractEntity
{
protected ImageDescriptor $imageDescriptor;
protected ?ColorTable $colorTable = null;
protected ImageData $imageData;
public function getImageDescriptor(): ImageDescriptor
{
return $this->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;
}
}

View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Blocks;
use Intervention\Gif\AbstractEntity;
class Trailer extends AbstractEntity
{
public const MARKER = "\x3b";
}

215
vendor/intervention/gif/src/Builder.php vendored Normal file
View File

@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
use Exception;
use Intervention\Gif\Blocks\FrameBlock;
use Intervention\Gif\Blocks\GraphicControlExtension;
use Intervention\Gif\Blocks\ImageDescriptor;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
use Intervention\Gif\Blocks\TableBasedImage;
use Intervention\Gif\Traits\CanHandleFiles;
class Builder
{
use CanHandleFiles;
/**
* Create new instance
*
* @param GifDataStream $gif
* @return void
*/
public function __construct(protected GifDataStream $gif = new GifDataStream())
{
}
/**
* Get GifDataStream object we're currently building
*
* @return GifDataStream
*/
public function getGifDataStream(): GifDataStream
{
return $this->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();
}
}

30
vendor/intervention/gif/src/Decoder.php vendored Normal file
View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
use Intervention\Gif\Exceptions\DecoderException;
use Intervention\Gif\Traits\CanHandleFiles;
class Decoder
{
use CanHandleFiles;
/**
* Decode given input
*
* @param mixed $input
* @return GifDataStream
*/
public static function decode(mixed $input): GifDataStream
{
return GifDataStream::decode(
match (true) {
self::isFilePath($input) => self::getHandleFromFilePath($input),
is_string($input) => self::getHandleFromData($input),
default => throw new DecoderException('Decoder input must be either file path or binary data.')
}
);
}
}

View File

@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
abstract class AbstractDecoder
{
/**
* Decode current source
*
* @return mixed
*/
abstract public function decode(): mixed;
/**
* Create new instance
*
* @param resource $handle
* @param null|int $length
*/
public function __construct(protected $handle, protected ?int $length = null)
{
}
/**
* Set source to decode
*
* @param resource $handle
*/
public function setHandle($handle): self
{
$this->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);
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
abstract class AbstractPackedBitDecoder extends AbstractDecoder
{
/**
* Decode packed byte
*
* @return int
*/
protected function decodePackedByte(string $byte): int
{
return unpack('C', $byte)[1];
}
/**
* Determine if packed bit is set
*
* @param int $num from left to right, starting with 0
* @return bool
*/
protected function hasPackedBit(string $byte, int $num): bool
{
return (bool) $this->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);
}
}

View File

@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\ApplicationExtension;
use Intervention\Gif\Blocks\DataSubBlock;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
class ApplicationExtensionDecoder extends AbstractDecoder
{
/**
* Decode current source
*
* @return ApplicationExtension
*/
public function decode(): ApplicationExtension
{
$result = new ApplicationExtension();
$this->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];
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\Color;
class ColorDecoder extends AbstractDecoder
{
/**
* Decode current source to Color
*
* @return Color
*/
public function decode(): Color
{
$color = new Color();
$color->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];
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\Color;
use Intervention\Gif\Blocks\ColorTable;
class ColorTableDecoder extends AbstractDecoder
{
/**
* Decode given string to ColorTable
*
* @return ColorTable
*/
public function decode(): ColorTable
{
$table = new ColorTable();
for ($i = 0; $i < ($this->getLength() / 3); $i++) {
$table->addColor(Color::decode($this->handle));
}
return $table;
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\CommentExtension;
class CommentExtensionDecoder extends AbstractDecoder
{
/**
* Decode current source
*
* @return CommentExtension
*/
public function decode(): CommentExtension
{
$this->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];
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\DataSubBlock;
class DataSubBlockDecoder extends AbstractDecoder
{
/**
* Decode current sourc
*
* @return DataSubBlock
*/
public function decode(): DataSubBlock
{
$char = $this->getNextByte();
$size = (int) unpack('C', $char)[1];
return new DataSubBlock($this->getNextBytes($size));
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\AbstractExtension;
use Intervention\Gif\Blocks\ApplicationExtension;
use Intervention\Gif\Blocks\CommentExtension;
use Intervention\Gif\Blocks\FrameBlock;
use Intervention\Gif\Blocks\GraphicControlExtension;
use Intervention\Gif\Blocks\ImageDescriptor;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
use Intervention\Gif\Blocks\PlainTextExtension;
use Intervention\Gif\Blocks\TableBasedImage;
use Intervention\Gif\Exceptions\DecoderException;
class FrameBlockDecoder extends AbstractDecoder
{
public function decode(): FrameBlock
{
$frame = new FrameBlock();
do {
$block = match ($this->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;
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\AbstractExtension;
use Intervention\Gif\Blocks\ColorTable;
use Intervention\Gif\Blocks\CommentExtension;
use Intervention\Gif\Blocks\FrameBlock;
use Intervention\Gif\Blocks\Header;
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
use Intervention\Gif\Blocks\Trailer;
use Intervention\Gif\GifDataStream;
class GifDataStreamDecoder extends AbstractDecoder
{
/**
* Decode current source to GifDataStream
*
* @return GifDataStream
*/
public function decode(): GifDataStream
{
$gif = new GifDataStream();
$gif->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;
}
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\GraphicControlExtension;
use Intervention\Gif\DisposalMethod;
class GraphicControlExtensionDecoder extends AbstractPackedBitDecoder
{
/**
* Decode given string to current instance
*
* @return GraphicControlExtension
*/
public function decode(): GraphicControlExtension
{
$result = new GraphicControlExtension();
// bytes 1-3
$this->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];
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Exceptions\DecoderException;
use Intervention\Gif\Blocks\Header;
class HeaderDecoder extends AbstractDecoder
{
/**
* Decode current sourc
*
* @return Header
*/
public function decode(): Header
{
$header = new Header();
$header->setVersion($this->decodeVersion());
return $header;
}
/**
* Decode version string
*
* @return string
*/
protected function decodeVersion(): string
{
$parsed = (bool) preg_match("/^GIF(?P<version>[0-9]{2}[a-z])$/", $this->getNextBytes(6), $matches);
if ($parsed === false) {
throw new DecoderException('Unable to parse file header.');
}
return $matches['version'];
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\AbstractEntity;
use Intervention\Gif\Blocks\DataSubBlock;
use Intervention\Gif\Blocks\ImageData;
class ImageDataDecoder extends AbstractDecoder
{
/**
* Decode current source
*
* @return ImageData
*/
public function decode(): ImageData
{
$data = new ImageData();
// LZW min. code size
$char = $this->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;
}
}

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\ImageDescriptor;
class ImageDescriptorDecoder extends AbstractPackedBitDecoder
{
/**
* Decode given string to current instance
*
* @return ImageDescriptor
*/
public function decode(): ImageDescriptor
{
$descriptor = new ImageDescriptor();
$this->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);
}
}

View File

@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
class LogicalScreenDescriptorDecoder extends AbstractPackedBitDecoder
{
/**
* Decode given string to current instance
*
* @return LogicalScreenDescriptor
*/
public function decode(): LogicalScreenDescriptor
{
$logicalScreenDescriptor = new LogicalScreenDescriptor();
// bytes 1-4
$logicalScreenDescriptor->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];
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
class NetscapeApplicationExtensionDecoder extends ApplicationExtensionDecoder
{
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\PlainTextExtension;
class PlainTextExtensionDecoder extends AbstractDecoder
{
/**
* Decode current source
*
* @return PlainTextExtension
*/
public function decode(): PlainTextExtension
{
$extension = new PlainTextExtension();
// skip marker & label
$this->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;
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Decoders;
use Intervention\Gif\Blocks\ColorTable;
use Intervention\Gif\Blocks\ImageData;
use Intervention\Gif\Blocks\ImageDescriptor;
use Intervention\Gif\Blocks\TableBasedImage;
class TableBasedImageDecoder extends AbstractDecoder
{
public function decode(): TableBasedImage
{
$block = new TableBasedImage();
$block->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;
}
}

View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
enum DisposalMethod: int
{
case UNDEFINED = 0;
case NONE = 1; // overlay each frame in sequence
case BACKGROUND = 2; // clear to background (as indicated by the logical screen descriptor)
case PREVIOUS = 3; // restore the canvas to its previous state
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
abstract class AbstractEncoder
{
/**
* Create new instance
*
* @param mixed $source
*/
public function __construct(protected mixed $source)
{
}
/**
* Encode current source
*
* @return string
*/
abstract public function encode(): string;
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\ApplicationExtension;
class ApplicationExtensionEncoder extends AbstractEncoder
{
/**
* Create new decoder instance
*
* @param ApplicationExtension $source
*/
public function __construct(ApplicationExtension $source)
{
$this->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,
]);
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\Color;
class ColorEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param Color $source
*/
public function __construct(Color $source)
{
$this->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);
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\ColorTable;
class ColorTableEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param ColorTable $source
*/
public function __construct(ColorTable $source)
{
$this->source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return implode('', array_map(function ($color) {
return $color->encode();
}, $this->source->getColors()));
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\CommentExtension;
class CommentExtensionEncoder extends AbstractEncoder
{
/**
* Create new decoder instance
*
* @param CommentExtension $source
*/
public function __construct(CommentExtension $source)
{
$this->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()));
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\DataSubBlock;
class DataSubBlockEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param DataSubBlock $source
*/
public function __construct(DataSubBlock $source)
{
$this->source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return pack('C', $this->source->getSize()) . $this->source->getValue();
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\FrameBlock;
class FrameBlockEncoder extends AbstractEncoder
{
/**
* Create new decoder instance
*
* @param FrameBlock $source
*/
public function __construct(FrameBlock $source)
{
$this->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(),
]);
}
}

View File

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\GifDataStream;
class GifDataStreamEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param GifDataStream $source
*/
public function __construct(GifDataStream $source)
{
$this->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()));
}
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\GraphicControlExtension;
class GraphicControlExtensionEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param GraphicControlExtension $source
*/
public function __construct(GraphicControlExtension $source)
{
$this->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(),
])));
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\Header;
class HeaderEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param Header $source
*/
public function __construct(Header $source)
{
$this->source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return Header::SIGNATURE . $this->source->getVersion();
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\AbstractEntity;
use Intervention\Gif\Exceptions\EncoderException;
use Intervention\Gif\Blocks\ImageData;
class ImageDataEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param ImageData $source
*/
public function __construct(ImageData $source)
{
$this->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,
]);
}
}

View File

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\ImageDescriptor;
class ImageDescriptorEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param ImageDescriptor $source
*/
public function __construct(ImageDescriptor $source)
{
$this->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(),
])));
}
}

View File

@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
class LogicalScreenDescriptorEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param LogicalScreenDescriptor $source
*/
public function __construct(LogicalScreenDescriptor $source)
{
$this->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(),
])));
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\ApplicationExtension;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
class NetscapeApplicationExtensionEncoder extends ApplicationExtensionEncoder
{
/**
* Create new decoder instance
*
* @param NetscapeApplicationExtension $source
*/
public function __construct(NetscapeApplicationExtension $source)
{
$this->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,
]);
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\PlainTextExtension;
class PlainTextExtensionEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param PlainTextExtension $source
*/
public function __construct(PlainTextExtension $source)
{
$this->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()));
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\TableBasedImage;
class TableBasedImageEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param TableBasedImage $source
*/
public function __construct(TableBasedImage $source)
{
$this->source = $source;
}
public function encode(): string
{
return implode('', [
$this->source->getImageDescriptor()->encode(),
$this->source->getColorTable() ? $this->source->getColorTable()->encode() : '',
$this->source->getImageData()->encode(),
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Encoders;
use Intervention\Gif\Blocks\Trailer;
class TrailerEncoder extends AbstractEncoder
{
/**
* Create new instance
*
* @param Trailer $source
*/
public function __construct(Trailer $source)
{
$this->source = $source;
}
/**
* Encode current source
*
* @return string
*/
public function encode(): string
{
return Trailer::MARKER;
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Exceptions;
class DecoderException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Exceptions;
class EncoderException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Exceptions;
class FormatException extends \RuntimeException
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Exceptions;
class NotReadableException extends \RuntimeException
{
}

View File

@ -0,0 +1,218 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
use Intervention\Gif\Blocks\ColorTable;
use Intervention\Gif\Blocks\CommentExtension;
use Intervention\Gif\Blocks\FrameBlock;
use Intervention\Gif\Blocks\Header;
use Intervention\Gif\Blocks\LogicalScreenDescriptor;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
use Intervention\Gif\Blocks\Trailer;
class GifDataStream extends AbstractEntity
{
/**
* Create new instance
*/
public function __construct(
protected Header $header = new Header(),
protected LogicalScreenDescriptor $logicalScreenDescriptor = new LogicalScreenDescriptor(),
protected ?ColorTable $globalColorTable = null,
protected array $frames = [],
protected array $comments = []
) {
}
/**
* Get header
*
* @return Header
*/
public function getHeader(): Header
{
return $this->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);
}
}

277
vendor/intervention/gif/src/Splitter.php vendored Normal file
View File

@ -0,0 +1,277 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif;
use ArrayIterator;
use IteratorAggregate;
use Traversable;
class Splitter implements IteratorAggregate
{
/**
* Single frames
*
* @var array
*/
protected array $frames = [];
/**
* Delays of each frame
*
* @var array
*/
protected array $delays = [];
/**
* Create new instance
*
* @param GifDataStream $stream
*/
public function __construct(protected GifDataStream $stream)
{
}
/**
* Iterator
*
* @return Traversable
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->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();
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Traits;
use Intervention\Gif\Decoders\AbstractDecoder;
use Intervention\Gif\Exceptions\DecoderException;
trait CanDecode
{
/**
* Decode current instance
*
* @param resource $source
* @param null|int $length
* @return mixed
*/
public static function decode($source, ?int $length = null): mixed
{
return self::getDecoder($source, $length)->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());
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Traits;
use Intervention\Gif\Encoders\AbstractEncoder;
use Intervention\Gif\Exceptions\EncoderException;
trait CanEncode
{
/**
* Encode current entity
*
* @return string
*/
public function encode(): string
{
return $this->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());
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Intervention\Gif\Traits;
trait CanHandleFiles
{
/**
* Determines if input is file path
*
* @return bool
*/
private static function isFilePath($input): bool
{
return is_string($input) && !self::hasNullBytes($input) && @is_file($input);
}
/**
* Determine if given string contains null bytes
*
* @param string $string
* @return bool
*/
private static function hasNullBytes($string): bool
{
return strpos($string, chr(0)) !== false;
}
/**
* Create file pointer from given gif image data
*
* @param string $data
* @return resource
*/
private static function getHandleFromData($data)
{
$handle = fopen('php://memory', 'r+');
fwrite($handle, $data);
rewind($handle);
return $handle;
}
/**
* Create file pounter from given file path
*
* @param string $path
* @return resource
*/
private static function getHandleFromFilePath(string $path)
{
return fopen($path, 'rb');
}
}

21
vendor/intervention/image/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-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.

51
vendor/intervention/image/composer.json vendored Normal file
View File

@ -0,0 +1,51 @@
{
"name": "intervention/image",
"description": "PHP image manipulation",
"homepage": "https://image.intervention.io/",
"keywords": [
"image",
"gd",
"imagick",
"watermark",
"thumbnail",
"resize"
],
"license": "MIT",
"authors": [
{
"name": "Oliver Vogel",
"email": "oliver@intervention.io",
"homepage": "https://intervention.io/"
}
],
"require": {
"php": "^8.1",
"ext-mbstring": "*",
"intervention/gif": "^4.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^1",
"squizlabs/php_codesniffer": "^3.8",
"slevomat/coding-standard": "~8.0"
},
"suggest": {
"ext-exif": "Recommended to be able to read EXIF data properly."
},
"autoload": {
"psr-4": {
"Intervention\\Image\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Intervention\\Image\\Tests\\": "tests"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

97
vendor/intervention/image/readme.md vendored Normal file
View File

@ -0,0 +1,97 @@
# Intervention Image
## PHP Image Processing
[![Latest Version](https://img.shields.io/packagist/v/intervention/image.svg)](https://packagist.org/packages/intervention/image)
[![Build Status](https://github.com/Intervention/image/actions/workflows/run-tests.yml/badge.svg)](https://github.com/Intervention/image/actions)
[![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/image.svg)](https://packagist.org/packages/intervention/image/stats)
Intervention Image is a **PHP image processing library** that provides a simple
and expressive way to create, edit, and compose images. It features a unified
API for the two most popular image manipulation extensions. You can choose
between the GD library or Imagick as the base layer for all operations.
- Simple interface for common image editing tasks
- Interchangeable driver architecture
- Support for animated images
- Framework-agnostic
- PSR-12 compliant
## Installation
You can easily install this library using [Composer](https://getcomposer.org).
Just request the package with the following command:
```bash
composer require intervention/image
```
## Getting Started
Learn the [basics](https://image.intervention.io/v3/basics/instantiation/) on
how to use Intervention Image and more with the [official
documentation](https://image.intervention.io/v3/).
## Code Examples
```php
use Intervention\Image\ImageManager;
// create image manager with desired driver
$manager = new ImageManager(
new Intervention\Image\Drivers\Gd\Driver()
);
// open an image file
$image = $manager->read('images/example.gif');
// resize image instance
$image->resize(height: 300);
// insert a watermark
$image->place('images/watermark.png');
// encode edited image
$encoded = $image->toJpg();
// save encoded image
$encoded->save('images/example.jpg');
```
## Requirements
- PHP >= 8.1
## Supported Image Libraries
- GD Library
- Imagick PHP extension
## Development & Testing
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).

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class ColorspaceAnalyzer extends SpecializableAnalyzer
{
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class HeightAnalyzer extends SpecializableAnalyzer
{
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class PixelColorAnalyzer extends SpecializableAnalyzer
{
public function __construct(
public int $x,
public int $y,
public int $frame_key = 0
) {
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class PixelColorsAnalyzer extends SpecializableAnalyzer
{
public function __construct(
public int $x,
public int $y
) {
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class ProfileAnalyzer extends SpecializableAnalyzer
{
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class ResolutionAnalyzer extends SpecializableAnalyzer
{
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Analyzers;
use Intervention\Image\Drivers\SpecializableAnalyzer;
class WidthAnalyzer extends SpecializableAnalyzer
{
}

View File

@ -0,0 +1,229 @@
<?php
declare(strict_types=1);
namespace Intervention\Image;
use Intervention\Image\Interfaces\CollectionInterface;
use ArrayIterator;
use Countable;
use Traversable;
use IteratorAggregate;
/**
* @implements IteratorAggregate<int|string, mixed>
*/
class Collection implements CollectionInterface, IteratorAggregate, Countable
{
/**
* Create new collection object
*
* @param array<int|string, mixed> $items
* @return void
*/
public function __construct(protected array $items = [])
{
}
/**
* Static constructor
*
* @param array<int|string, mixed> $items
* @return self<int|string, mixed>
*/
public static function create(array $items = []): self
{
return new self($items);
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::has()
*/
public function has(int|string $key): bool
{
return array_key_exists($key, $this->items);
}
/**
* Returns Iterator
*
* @return Traversable<int|string, mixed>
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
/**
* {@inheritdoc}
*
* @see CollectionInterface::toArray()
*/
public function toArray(): array
{
return $this->items;
}
/**
* Count items in collection
*
* @return int
*/
public function count(): int
{
return count($this->items);
}
/**
* Append new item to collection
*
* @param mixed $item
* @return CollectionInterface<int|string, mixed>
*/
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;
}
}

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorChannelInterface;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
abstract class AbstractColor implements ColorInterface
{
/**
* Color channels
*
* @var array<ColorChannelInterface>
*/
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();
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorChannelInterface;
abstract class AbstractColorChannel implements ColorChannelInterface
{
protected int $value;
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::__construct()
*/
public function __construct(?int $value = null, ?float $normalized = null)
{
$this->value = $this->validate(
match (true) {
is_null($value) && is_numeric($normalized) => intval(round($normalized * $this->max())),
is_numeric($value) && is_null($normalized) => $value,
default => throw new ColorException('Color channels must either have a value or a normalized value')
}
);
}
/**
* Alias of value()
*
* @return int
*/
public function toInt(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::value()
*/
public function value(): int
{
return $this->value;
}
/**
* {@inheritdoc}
*
* @see ColorChannelInterface::normalize()
*/
public function normalize(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();
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk\Channels;
use Intervention\Image\Colors\AbstractColorChannel;
class Cyan extends AbstractColorChannel
{
public function min(): int
{
return 0;
}
public function max(): int
{
return 100;
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk\Channels;
class Key extends Cyan
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk\Channels;
class Magenta extends Cyan
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk\Channels;
class Yellow extends Cyan
{
}

View File

@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk;
use Intervention\Image\Colors\AbstractColor;
use Intervention\Image\Colors\Cmyk\Channels\Cyan;
use Intervention\Image\Colors\Cmyk\Channels\Magenta;
use Intervention\Image\Colors\Cmyk\Channels\Yellow;
use Intervention\Image\Colors\Cmyk\Channels\Key;
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
use Intervention\Image\Drivers\AbstractInputHandler;
use Intervention\Image\Interfaces\ColorChannelInterface;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
class Color extends AbstractColor
{
/**
* Create new instance
*
* @param int $c
* @param int $m
* @param int $y
* @param int $k
* @return void
*/
public function __construct(int $c, int $m, int $y, int $k)
{
/** @throws void */
$this->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;
}
}

View File

@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Intervention\Image\Colors\Cmyk;
use Intervention\Image\Colors\Rgb\Color as RgbColor;
use Intervention\Image\Colors\Cmyk\Color as CmykColor;
use Intervention\Image\Colors\Hsv\Color as HsvColor;
use Intervention\Image\Colors\Hsl\Color as HslColor;
use Intervention\Image\Colors\Rgb\Colorspace as RgbColorspace;
use Intervention\Image\Exceptions\ColorException;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
class Colorspace implements ColorspaceInterface
{
/**
* Channel class names of colorspace
*
* @var array<string>
*/
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);
}
}

Some files were not shown because too many files have changed in this diff Show More