This commit is contained in:
全栈小学生 2024-08-06 17:48:00 +08:00
parent c1e13b7b46
commit 333877eca2
497 changed files with 18958 additions and 8 deletions

View File

@ -98,7 +98,7 @@ class InstalledVersions
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
@ -119,7 +119,7 @@ class InstalledVersions
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
@ -328,7 +328,9 @@ class InstalledVersions
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
@ -340,12 +342,17 @@ class InstalledVersions
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}

View File

@ -68,6 +68,7 @@ return array(
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Kkokk\\Poster\\' => array($vendorDir . '/kkokk/poster/src'),
'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'Intervention\\Image\\' => array($vendorDir . '/intervention/image/src/Intervention/Image'),

View File

@ -147,6 +147,10 @@ class ComposerStaticInitf082efa3600aae2b847c3e8b4e641a4e
'League\\Flysystem\\' => 17,
'Laravel\\SerializableClosure\\' => 28,
),
'K' =>
array (
'Kkokk\\Poster\\' => 13,
),
'J' =>
array (
'JmesPath\\' => 9,
@ -456,6 +460,10 @@ class ComposerStaticInitf082efa3600aae2b847c3e8b4e641a4e
array (
0 => __DIR__ . '/..' . '/laravel/serializable-closure/src',
),
'Kkokk\\Poster\\' =>
array (
0 => __DIR__ . '/..' . '/kkokk/poster/src',
),
'JmesPath\\' =>
array (
0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src',

View File

@ -1677,6 +1677,59 @@
],
"install-path": "../intervention/image"
},
{
"name": "kkokk/poster",
"version": "v2.3.2",
"version_normalized": "2.3.2.0",
"source": {
"type": "git",
"url": "https://github.com/kkokk/poster.git",
"reference": "6fa26bb225d0d7bcd654c93d342abf5284803203"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kkokk/poster/zipball/6fa26bb225d0d7bcd654c93d342abf5284803203",
"reference": "6fa26bb225d0d7bcd654c93d342abf5284803203",
"shasum": "",
"mirrors": [
{
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
"preferred": true
}
]
},
"require": {
"ext-gd": "*",
"ext-iconv": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": ">=5.6.0"
},
"time": "2024-04-02T09:33:45+00:00",
"type": "project",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Kkokk\\Poster\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "lang",
"email": "732853989@qq.com"
}
],
"description": "PHP生成海报",
"support": {
"issues": "https://github.com/kkokk/poster/issues",
"source": "https://github.com/kkokk/poster/tree/v2.3.2"
},
"install-path": "../kkokk/poster"
},
{
"name": "kosinix/grafika",
"version": "dev-master",

View File

@ -3,7 +3,7 @@
'name' => 'topthink/think',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '783127440ec6e7540af2a2126eefb22fad7895ff',
'reference' => 'ce5a9834a9b2c9f70dbc00270fad24bd80a0c025',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -208,6 +208,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'kkokk/poster' => array(
'pretty_version' => 'v2.3.2',
'version' => '2.3.2.0',
'reference' => '6fa26bb225d0d7bcd654c93d342abf5284803203',
'type' => 'project',
'install_path' => __DIR__ . '/../kkokk/poster',
'aliases' => array(),
'dev_requirement' => false,
),
'kosinix/grafika' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
@ -783,7 +792,7 @@
'topthink/think' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '783127440ec6e7540af2a2126eefb22fad7895ff',
'reference' => 'ce5a9834a9b2c9f70dbc00270fad24bd80a0c025',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@ -0,0 +1,6 @@
tests/crop/
tests/poster/
tests/server/static/
vendor/
composer.lock
.idea

201
niucloud/vendor/kkokk/poster/LICENSE vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1015
niucloud/vendor/kkokk/poster/README.md vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "kkokk/poster",
"description": "PHP生成海报",
"type": "project",
"require": {
"php": ">=5.6.0",
"ext-gd": "*",
"ext-iconv": "*",
"ext-mbstring": "*",
"ext-json": "*"
},
"license": "MIT",
"authors": [
{
"name": "lang",
"email": "732853989@qq.com"
}
],
"minimum-stability": "dev",
"autoload":{
"psr-4":{
"Kkokk\\Poster\\":"src/"
}
},
"autoload-dev": {
"psr-4": {
"Kkokk\\Tests\\": "tests/"
}
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 14:37
*/
namespace Kkokk\Poster\Cache;
use Kkokk\Poster\Exception\PosterException;
use Illuminate\Support\Facades\Cache as LaravelCache;
use think\Cache as ThinkCache5;
use think\facade\Cache as ThinkCache6;
class Repository
{
function __call($method, $params)
{
if (class_exists(LaravelCache::class)) {
$connector = LaravelCache::class;
} elseif (class_exists(ThinkCache6::class)) {
$connector = ThinkCache6::class;
$method = str_replace('put', 'set', $method);
} elseif (class_exists(ThinkCache5::class)) {
$connector = ThinkCache5::class;
$method = str_replace('put', 'set', $method);
} else {
throw new PosterException('no cacheDriver');
}
return call_user_func_array([$connector, $method], $params);
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:14
*/
namespace Kkokk\Poster\Captcha;
use Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Exception\PosterException;
class CaptchaGeneratorFactory
{
public function make($name)
{
return $this->createGenerator($name);
}
protected function createGenerator($name)
{
switch ($name) {
case 'input':
return new Generators\InputGenerator(); // 输入类验证
case 'click':
return new Generators\ClickGenerator(); // 点击验证
case 'rotate':
return new Generators\RotateGenerator(); // 旋转验证
case 'slider':
return new Generators\SliderGenerator(); // 滑块验证
}
throw new PosterException("Unsupported Captcha Generator [{$name}].");
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 11:21
*/
namespace Kkokk\Poster\Captcha;
use Kkokk\Poster\Captcha\Generators\CaptchaGenerator;
interface CaptchaGeneratorInterface
{
/**
* 基础配置
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 11:53
* @param array $params
* @return CaptchaGenerator
*/
public function config($params = []);
/**
* 检查密码是否正确
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 11:53
* @param string $key key值
* @param string|int|array $value 比对值
* @param int $leeway 误差
* @param null $secret 没有缓存的时候,传用户自行储存的密码
* @return boolean
*/
public function check($key, $value, $leeway = 0, $secret = null);
/**
* 获取图片验证参数
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 11:55
* @param int $expire 设置有效时间(前提:有缓存才能生效)
* @return array
*/
public function get($expire = 0);
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:43
*/
namespace Kkokk\Poster\Captcha;
use Kkokk\Poster\Captcha\Generators\CaptchaGenerator;
class CaptchaManager
{
protected $channels = [];
protected $factory;
function __construct()
{
$this->factory = new CaptchaGeneratorFactory();
}
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:58
* @param null $name
* @return CaptchaGenerator
*/
public function type($name = null)
{
$name = $this->parseChannelName($name);
if (!isset($this->extensions[$name])) {
$this->channels[$name] = $this->configure($this->makeGenerator($name));
}
return $this->channels[$name];
}
protected function configure(CaptchaGenerator $generator)
{
return $generator;
}
protected function parseChannelName($name)
{
if (empty($name)) return $this->supportedGenerators()[0];
return $name;
}
protected function makeGenerator($name)
{
return $this->factory->make($name);
}
/**
* 获取所有支持方法。
*
* @return array
*/
public function supportedGenerators()
{
return ['slider', 'click', 'rotate', 'input'];
}
/**
* 将方法动态传递给默认方法。
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->type()->$method(...$parameters);
}
}

View File

@ -0,0 +1,240 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/6
* Time: 18:10
*/
namespace Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Common\Common;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Facades\Cache;
use Kkokk\Poster\Image\Drivers\GdDriver;
class CaptchaGenerator
{
protected $PosterDriver; // GdDriver
protected $Common; // Common
protected $im; // im
protected $expire = 180; // 过期时间
protected $leeway = 5; // 误差值
function __construct()
{
$this->PosterDriver = new GdDriver();
$this->Common = new Common;
}
/**
* 转base64
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:38
* @param $im
* @param string $type
* @return string
*/
protected function baseData($im, $type = 'png')
{
return $this->Common->baseData($im, $type);
}
public function imOutput($im, $type = 'png', $quality = 75, $filename = 'im')
{
$yes = 0; // 控制是否生成图片,测试时方便查看
$dir = __DIR__ . '/../../../tests/poster/' . $filename . '.' . $this->configs['im_type'];
return $yes && $this->Common->imOutput($im, $dir, $type, $quality);
}
public function getCross($p1, $p2, $p)
{
return $this->Common->getCross($p1, $p2, $p);
}
// 获取缓存
public function getCache($key)
{
try {
$contents = Cache::pull($key);
} catch (PosterException $e) {
// 如果未定义缓存器则返回false, 需要传递自行保存的密码进行比对
return false;
} catch (\Exception $e) {
// 报错则返回false, 需要传递自行保存的密码进行比对
return false;
}
return $contents;
}
// 设置缓存
public function setCache($key, $value, $expire)
{
try {
Cache::put($key, $value, $expire ?: $this->expire);
} catch (PosterException $e) {
// 未查询到缓存器,则返回密码,自行保存
return false;
} catch (\Exception $e) {
// 报错,则返回密码,自行保存
return false;
}
return true;
}
/**
* 画布填充图片
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:37
* @param string $src
* @param false $resize
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param int $src_width
* @param int $src_height
* @throws PosterException
*/
protected function drawImage($src = '', $resize = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $src_width = 0, $src_height = 0)
{
$src = $src ?: $this->getImBg();
list($Width, $Height, $bgType) = @getimagesize($src);
$Width = $src_width ?: $Width;
$Height = $src_height ?: $Height;
$bgType = image_type_to_extension($bgType, false);
if (empty($bgType)) throw new PosterException('image resources cannot be empty (' . $src . ')');
$getGdVersion = preg_match('~\((.*) ~', gd_info()['GD Version'], $matches);
if ($getGdVersion && (float)$matches[1] < 2 && $bgType == 'gif') {
$pic = imagecreatefromstring(file_get_contents($src));
} else {
$fun = 'imagecreatefrom' . $bgType;
$pic = @$fun($src);
}
if ($resize) {
imagecopyresized($this->im, $pic, $dst_x, $dst_y, $src_x, $src_y, $this->configs['im_width'], $this->configs['im_height'], $Width, $Height);
} else {
imagecopy($this->im, $pic, $dst_x, $dst_y, $src_x, $src_y, $Width, $Height);
}
$this->destroyImage($pic);
}
/**
* 干扰线
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:37
* @param int $width
* @param int $height
* @throws PosterException
*/
public function drawLine($width = 0, $height = 0)
{
$lineCount = $this->configs['line_count'];
if ($lineCount > 0) {
$im_width = $width ?: $this->configs['im_width'];
$im_height = $height ?: $this->configs['im_height'];
for ($i = 0; $i <= $lineCount; $i++) {
$color = $this->PosterDriver->createColorAlpha($this->im, [mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), 1]);
$x1 = mt_rand(0, $im_width);
$y1 = mt_rand(0, $im_height);
$x2 = mt_rand(0, $im_width);
$y2 = mt_rand(0, $im_height);
imageline($this->im, $x1, $y1, $x2, $y2, $color);
}
imageantialias($this->im, true);
}
}
/**
* 干扰文字
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:37
* @param int $width
* @param int $height
* @throws PosterException
*/
public function drawChar($width = 0, $height = 0)
{
$charCount = $this->configs['char_count'];
if ($charCount > 0) {
$font_family = $this->configs['font_family'];
$font = $this->configs['font_size'] / 2;
$contents = $this->getChar($this->configs['type']);
$color = $this->PosterDriver->createColorAlpha($this->im, [255, 255, 255, 1]);
$im_width = $width ?: $this->configs['im_width'];
$im_height = $height ?: $this->configs['im_height'];
for ($i = 0; $i < $charCount; $i++) {
$content = mb_substr($contents, mt_rand(0, mb_strlen($contents) - 1), 1);
$x = mt_rand($font, $im_width - $font);
$y = mt_rand($font, $im_height - $font);
$angle = mt_rand(0, 45);
imagettftext($this->im, $font, $angle, $x, $y, $color, $font_family, $content);
}
}
}
public function getChar($type)
{
switch ($type) {
case 'text':
$str = '的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该冯价严龙飞';
break;
case 'alpha_num':
$str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
break;
case 'math':
case 'number':
$str = '1234567890';
break;
default:
$str = 'abcdefghijklmnopqrstuvwxyz';
break;
}
return $str;
}
/**
* 释放resource
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:38
* @param $Resource
*/
protected function destroyImage($Resource)
{
!is_resource($Resource) || imagedestroy($Resource);
}
/**
* 析构方法,用于销毁 im 图像资源
*/
public function __destruct()
{
empty($this->im) || !is_resource($this->im) || imagedestroy($this->im);
}
}

View File

@ -0,0 +1,129 @@
<?php
/**
* @Author lang
* @Email: 732853989@qq.com
* Date: 2022/12/11
* Time: 下午9:40
*/
namespace Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Captcha\CaptchaGeneratorInterface;
use Kkokk\Poster\Captcha\Traits\ClickTrait;
use Kkokk\Poster\Exception\PosterException;
class ClickGenerator extends CaptchaGenerator implements CaptchaGeneratorInterface
{
use ClickTrait;
protected $configs = [
'src' => '',
'im_width' => 256,
'im_height' => 306,
'im_type' => 'png', // png 默认 jpg quality 质量
'quality' => 80, // jpg quality 质量
'bg_width' => 256,
'bg_height' => 256,
'type' => 'text', // text 汉字 number 数字 alpha_num 字母和数字
'font_family' => __DIR__ . DIRECTORY_SEPARATOR .'..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'zhankukuheiti.ttf', // 感谢站酷提供免费商用站酷库黑体、可自定义炫酷字体文件(绝对路径)
'contents' => '', // 自定义文字
'font_size' => 42, // 字体大小
'font_count' => 0, // 字体大小
'line_count' => 0, // 干扰线数量
'char_count' => 0, // 干扰字符数量
]; // 验证码图片配置
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:06
* @param array $params
* @return $this|CaptchaGenerator
*/
public function config($params = [])
{
if (empty($params)) return $this;
$this->configs['src'] = isset($params['src']) ? $params['src'] : $this->configs['src'];
$this->configs['im_type'] = isset($params['im_type']) ? $params['im_type'] : $this->configs['im_type'];
$this->configs['quality'] = isset($params['quality']) ? $params['quality'] : $this->configs['quality'];
$this->configs['contents'] = isset($params['contents']) ? $params['contents'] : $this->configs['contents'];
$this->configs['font_family'] = isset($params['font_family']) ? $params['font_family'] : $this->configs['font_family'];
$this->configs['font_size'] = isset($params['font_size']) ? $params['font_size'] : $this->configs['font_size'];
$this->configs['font_count'] = isset($params['font_count']) ? $params['font_count'] : $this->configs['font_count'];
$this->configs['line_count'] = isset($params['line_count']) ? $params['line_count'] : $this->configs['line_count'];
$this->configs['char_count'] = isset($params['char_count']) ? $params['line_count'] : $this->configs['char_count'];
if ($this->configs['contents']) $this->configs['font_count'] = mb_strlen($this->configs['contents']);
return $this;
}
public function check($key, $value, $leeway = 0, $secret = null)
{
if (!is_array($value)) throw new PosterException('array format required');
$contents = $this->getCache($key) ?: $secret;
if (!$contents) return false;
if (!is_array($contents)) {
$points = json_decode($contents, true);
} else {
$points = $contents;
}
if (count($points) != count($value)) return false;
foreach ($points as $k => $v) {
$point = $v['point'];
// 任意坐标点
$p = [$value[$k]['x'], $value[$k]['y']];
$p1 = [$point[0], $point[1]]; // 左下
$p2 = [$point[2], $point[3]]; // 右下
$p3 = [$point[4], $point[5]]; // 右上
$p4 = [$point[6], $point[7]]; // 左上
// 叉积计算 点在四条平行线内部则是在矩形内 p1->p2 p1->p3 参考点 p1 叉积大于0点p3在p2逆时针方向 等于0 三点一线 小于0 点p3在p2顺时针防线
$isCross = $this->getCross($p1, $p2, $p) * $this->getCross($p3, $p4, $p) >= 0 && $this->getCross($p2, $p3, $p) * $this->getCross($p4, $p1, $p) >= 0;
if ($isCross) {
continue;
} else {
return false;
}
}
return true;
}
public function get($expire = 0)
{
$data = $this->draw();
$this->imOutput(
$this->im,
$this->configs['im_type'],
$this->configs['quality'],
'click'
);
$baseData = $this->baseData($this->im, $this->configs['im_type']);
$key = uniqid('click' . mt_rand(0, 9999), true);
$res = [
'key' => $key,
'img' => $baseData,
'content_width' => $data['content_width'],
'content_height' => $data['content_height'],
'x' => $data['x'],
'y' => $data['y'],
];
$setCache = $this->setCache($key, json_encode($data['contents']), $expire);
if (!$setCache) $res['secret'] = json_encode($data['contents']);
return $res;
}
}

View File

@ -0,0 +1,91 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 11:47
*/
namespace Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Captcha\CaptchaGeneratorInterface;
use Kkokk\Poster\Captcha\Traits\InputTrait;
class InputGenerator extends CaptchaGenerator implements CaptchaGeneratorInterface
{
use InputTrait;
protected $configs = [
'src' => '',
'im_width' => 256,
'im_height' => 64,
'im_type' => 'png', // png 默认 jpg quality 质量
'quality' => 80, // jpg quality 质量
'type' => 'number', // type = number 数字 alpha_num 字母和数字 math 计算 text 文字
'font_family' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'simkai.ttf', // 感谢站酷提供免费商用站酷库黑体、可自定义炫酷字体文件
'font_size' => 32, // 字体大小
'font_count' => 4, // 字体长度
'line_count' => 5, // 干扰线数量
'char_count' => 10, // 干扰字符数量
];
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:06
* @param array $params
* @return $this|CaptchaGenerator
*/
public function config($params = [])
{
if (empty($params)) return $this;
$this->configs['src'] = isset($params['src']) ? $params['src'] : $this->configs['src'];
$this->configs['im_width'] = isset($params['im_width']) ? $params['im_width'] : $this->configs['im_width'];
$this->configs['im_height'] = isset($params['im_height']) ? $params['im_height'] : $this->configs['im_height'];
$this->configs['im_type'] = isset($params['im_type']) ? $params['im_type'] : $this->configs['im_type'];
$this->configs['quality'] = isset($params['quality']) ? $params['quality'] : $this->configs['quality'];
$this->configs['type'] = isset($params['type']) ? $params['type'] : $this->configs['type'];
$this->configs['font_family'] = isset($params['font_family']) ? $params['font_family'] : $this->configs['font_family'];
$this->configs['font_size'] = isset($params['font_size']) ? $params['font_size'] : $this->configs['font_size'];
$this->configs['font_count'] = isset($params['font_count']) ? $params['font_count'] : $this->configs['font_count'];
$this->configs['line_count'] = isset($params['line_count']) ? $params['line_count'] : $this->configs['line_count'];
$this->configs['char_count'] = isset($params['char_count']) ? $params['char_count'] : $this->configs['char_count'];
return $this;
}
public function check($key, $value, $leeway = 0, $secret = null)
{
$x = $this->getCache($key) ?: $secret;
if (empty($x)) return false;
return $x == $value;
}
public function get($expire = 0)
{
$data = $this->draw();
$this->imOutput(
$this->im,
$this->configs['im_type'],
$this->configs['quality'],
'input'
);
$baseData = $this->baseData($this->im, $this->configs['im_type']);
$key = uniqid('input:' . $this->configs['type'] . mt_rand(0, 9999), true);
$res = [
'key' => $key,
'img' => $baseData,
];
$setCache = $this->setCache($key, $data['value'], $expire);
if (!$setCache) $res['secret'] = $data['value'];
return $res;
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/9
* Time: 15:44
*/
namespace Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Captcha\CaptchaGeneratorInterface;
use Kkokk\Poster\Captcha\Traits\RotateTrait;
class RotateGenerator extends CaptchaGenerator implements CaptchaGeneratorInterface
{
use RotateTrait;
protected $configs = [
'src' => '',
'im_width' => 350,
'im_height' => 350,
'im_type' => 'png', // png 默认 jpg quality 质量
'quality' => 80, // jpg quality 质量
]; // 验证码图片配置
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:05
* @param array $params
* @return $this|CaptchaGenerator
*/
public function config($params = [])
{
if (empty($params)) return $this;
$this->configs['src'] = isset($params['src']) ? $params['src'] : $this->configs['src'];
$this->configs['im_width'] = isset($params['im_width']) ? $params['im_width'] : $this->configs['im_width'];
$this->configs['im_height'] = isset($params['im_height']) ? $params['im_height'] : $this->configs['im_height'];
$this->configs['im_type'] = isset($params['im_type']) ? $params['im_type'] : $this->configs['im_type'];
$this->configs['quality'] = isset($params['quality']) ? $params['quality'] : $this->configs['quality'];
return $this;
}
public function check($key, $value, $leeway = 3, $secret = null)
{
$x = $this->getCache($key) ?: $secret;
if (empty($x)) return false;
$leeway = $leeway ?: $this->leeway;
return $x >= ($value - $leeway) && $x <= ($value + $leeway);
}
public function get($expire = 0)
{
$data = $this->draw();
$this->imOutput(
$this->im,
$this->configs['im_type'],
$this->configs['quality'],
'rotate'
);
$baseData = $this->baseData($this->im, $this->configs['im_type']);
$key = uniqid('rotate' . mt_rand(0, 9999), true);
$res = [
'img' => $baseData,
'key' => $key,
];
$setCache = $this->setCache($key, $data['angle'], $expire);
if (!$setCache) $res['secret'] = $data['angle'];
return $res;
}
}

View File

@ -0,0 +1,111 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/7
* Time: 10:55
*/
namespace Kkokk\Poster\Captcha\Generators;
use Kkokk\Poster\Captcha\CaptchaGeneratorInterface;
use Kkokk\Poster\Captcha\Traits\SliderTrait;
class SliderGenerator extends CaptchaGenerator implements CaptchaGeneratorInterface
{
use SliderTrait;
protected $configs = [
'src' => '',
'im_width' => 340,
'im_height' => 251,
'im_type' => 'png', // png 默认 jpg quality 质量
'quality' => 80, // jpg quality 质量
'type' => '4', // 默认四边形 3 三角形 5 五边形 6 六边形
'bg_width' => 340,
'bg_height' => 191,
'slider_width' => 50,
'slider_height' => 50,
'slider_border' => 2,
'slider_bg' => 1,
]; // 验证码图片配置
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:06
* @param array $params
* @return $this|CaptchaGenerator
*/
public function config($params = [])
{
if (empty($params)) return $this;
$this->configs['src'] = isset($params['src']) ? $params['src'] : $this->configs['src'];
$this->configs['im_width'] = isset($params['im_width']) ? $params['im_width'] : $this->configs['im_width'];
$this->configs['im_height'] = isset($params['im_height']) ? $params['im_height'] : $this->configs['im_height'];
$this->configs['im_type'] = isset($params['im_type']) ? $params['im_type'] : $this->configs['im_type'];
$this->configs['quality'] = isset($params['quality']) ? $params['quality'] : $this->configs['quality'];
$this->configs['bg_width'] = isset($params['bg_width']) ? $params['bg_width'] : $this->configs['bg_width'];
$this->configs['bg_height'] = isset($params['bg_height']) ? $params['bg_height'] : $this->configs['bg_height'];
$this->configs['slider_width'] = isset($params['slider_width']) ? $params['slider_width'] : $this->configs['slider_width'];
$this->configs['slider_height'] = isset($params['slider_height']) ? $params['slider_height'] : $this->configs['slider_height'];
$this->configs['slider_border'] = isset($params['slider_border']) ? $params['slider_border'] : $this->configs['slider_border'];
$this->configs['slider_bg'] = isset($params['slider_bg']) ? $params['slider_bg'] : $this->configs['slider_bg'];
$this->configs['type'] = isset($params['type']) ? $params['type'] : $this->configs['type'];
return $this;
}
public function get($expire = 0)
{
$data = $this->draw();
$this->imOutput(
$this->im,
$this->configs['im_type'],
$this->configs['quality'],
'slider'
);
$baseData = $this->baseData($this->im, $this->configs['im_type']);
$key = uniqid('slider' . mt_rand(0, 9999), true);
$res = [
'img' => $baseData,
'key' => $key,
'y' => $data['y'],
];
$setCache = $this->setCache($key, $data['x'], $expire);
if (!$setCache) $res['secret'] = $data['x'];
return $res;
}
/**
* 判断是否正确
* 目前使用的是 laravel cache
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/7
* Time: 11:44
* @param $key
* @param $value
* @param int $leeway
* @return bool
*/
public function check($key, $value, $leeway = 0, $secret = null)
{
$x = $this->getCache($key) ?: $secret;
if (empty($x)) return false;
$leeway = $leeway ?: $this->leeway;
return $x >= ($value - $leeway) && $x <= ($value + $leeway);
}
}

View File

@ -0,0 +1,196 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:50
*/
namespace Kkokk\Poster\Captcha\Traits;
trait ClickTrait
{
public function draw()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_height'];
$this->im = $this->PosterDriver->createIm($bg_width, $bg_height, [], true);
$bg = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src'], true);
imagecopy($bg, $this->im, 0, 0, 0, 0, $bg_width, $bg_height);
$this->im = $bg;
$this->drawLine($bg_width, $bg_height); // 干扰线
$this->drawChar($bg_width, $bg_height); // 干扰字符
$data = $this->drawText(); // 字
return $data;
}
// 计算 三个点的叉乘 |p1 p2| X |p1 p|
public function getCross($p1, $p2, $p)
{
// (p2.x - p1.x) * (p.y - p1.y) -(p.x - p1.x) * (p2.y - p1.y);
return ($p1[0] - $p[0]) * ($p2[1] - $p[1]) - ($p2[0] - $p[0]) * ($p1[1] - $p[1]);
}
public function getContents($contentsLen)
{
$contents = [];
if ($this->configs['contents']) {
for ($i = 0; $i < $contentsLen; $i++) {
$contents[$i]['contents'] = mb_substr($this->configs['contents'], $i, 1);
}
} else {
$str = $this->getChar('text');
for ($i = 0; $i < $contentsLen; $i++) {
$contents[$i]['contents'] = mb_substr($str, mt_rand(0, 299), 1);
}
}
return $contents;
}
public function getSpace($contentsLen)
{
$font = $this->configs['font_size'] + 15;
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_width'];
switch ($contentsLen) {
case 2:
$space[] = [
mt_rand($font, $bg_width / 2 - $font),
mt_rand($font, $bg_height - $font / 2 - 12),
];
$space[] = [
mt_rand($bg_width / 2, $bg_width - $font),
mt_rand($font, $bg_height - $font / 2 - 12),
];
break;
case 3:
$space[] = [
mt_rand($font, $bg_width / 2 - $font),
mt_rand($font, $bg_height / 2),
];
$space[] = [
mt_rand($bg_width / 2, $bg_width - $font),
mt_rand($font, $bg_height / 2),
];
$space[] = [
mt_rand($font, $bg_width - $font),
mt_rand($bg_height / 2 + $font, $bg_height - $font / 2 - 12),
];
break;
default:
$space[] = [
mt_rand($font, $bg_width / 2 - $font),
mt_rand($font, $bg_height / 2),
];
$space[] = [
mt_rand($bg_width / 2, $bg_width - $font),
mt_rand($font, $bg_height / 2),
];
$space[] = [
mt_rand($font, $bg_width / 2 - $font),
mt_rand($bg_height / 2 + $font, $bg_height - $font / 2 - 12),
];
$space[] = [
mt_rand($bg_width / 2, $bg_width - $font),
mt_rand($bg_height / 2 + $font, $bg_height - $font / 2 - 12),
];
break;
}
return $space;
}
public function drawText()
{
$font_family = $this->configs['font_family'];
$font = $this->configs['font_size'];
$contentsLen = $this->configs['font_count'] ?: mt_rand(2, 4);
$contentsLen = $contentsLen < 2 ? 2 : ($contentsLen > 4 ? 4 : $contentsLen);
$contents = $this->getContents($contentsLen);
$color = $this->PosterDriver->createColorAlpha($this->im, [255, 255, 255, 1]);
$spaces = $this->getSpace($contentsLen);
$content = "";
foreach ($contents as $k => $v) {
$content .= $v['contents'];
// 随机获取位置
$spaceKey = mt_rand(0, count($spaces) - 1);
$space = array_splice($spaces, $spaceKey, 1);
$angle = mt_rand(-80, 80); // 旋转角度
$fontBox = imagettfbbox($font, $angle, $font_family, $v['contents']); // 计算文字方框坐标
$x = $space[0][0]; // 起始x坐标
$y = $space[0][1]; // 起始y坐标
$contents[$k]['point'] = [
$x + $fontBox[0], // 左下角,X 位置
$y + $fontBox[1], // 左下角Y 位置
$x + $fontBox[2], // 右下角X 位置
$y + $fontBox[3], // 右下角Y 位置
$x + $fontBox[4], // 右上角X 位置
$y + $fontBox[5], // 右上角Y 位置
$x + $fontBox[6], // 左上角X 位置
$y + $fontBox[7], // 左上角Y 位置
$angle, // 旋转角度
];
imagettftext($this->im, $font, $angle, $x, $y, $color, $font_family, $v['contents']);
// 加粗字体
$ttfCount = 6;
for ($j = 1; $j <= $ttfCount; $j++) {
// 随机颜色
$ttfColor = $this->PosterDriver->createColorAlpha($this->im, [mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), 1]);
imagettftext($this->im, $font - ($j * 2), $angle, $x + $j, $y - $j, $ttfColor, $font_family, $v['contents']);
}
}
// 显示字体为黑色
$color = $this->PosterDriver->createColorAlpha($this->im, [0, 0, 0, 1]);
$viewFont = 22; // 显示字体大小
$fontBox = imagettfbbox($viewFont, 0, $font_family, $content); // 计算文字长宽
$viewHeight = 296; // 显示字体y坐标
imagettftext($this->im, $viewFont, 0, 10, $viewHeight, $color, $font_family, $content);
$content_height = abs($fontBox[7]) + 1;
return [
'content' => $content,
'content_width' => $fontBox[2],
'content_height' => $content_height,
'x' => 10,
'y' => $viewHeight - $content_height,
'contents' => $contents,
];
}
protected function getImBg()
{
return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'rotate_bg' . DIRECTORY_SEPARATOR . 'rotate0' . mt_rand(1, 5) . '.jpg';
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:51
*/
namespace Kkokk\Poster\Captcha\Traits;
trait InputTrait
{
public function draw()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [mt_rand(125, 255), 255, mt_rand(125, 255), 1], false);
if ($this->configs['src']) { // 如果指定背景则用背景
$this->drawImage($this->configs['src'], false, 0, 0, 0, 0, $im_width, $im_height);
}
$this->drawLine(); // 干扰线
$this->drawChar(); // 干扰字
$res = $this->getContents();
$this->drawText($res['contents']); // 字
return $res;
}
public function getContents()
{
// type = number 数字 alpha_num 字母和数字 math 计算 text 文字
$fontCount = $this->configs['font_count'];
$contents = '';
switch ($this->configs['type']) {
case 'math':
$formula = '+-x';
$a = mt_rand(0, 20);
$b = mt_rand(0, 20);
$formula = substr($formula, mt_rand(0, 2), 1);
if ($formula == '+') $mul = $a + $b;
if ($formula == '-') $mul = $a - $b;
if ($formula == 'x') $mul = $a * $b;
$res = [
'contents' => [
$a,
$formula,
$b,
'='
],
'value' => $mul,
];
break;
case 'text':
$str = $this->getChar($this->configs['type']);
for ($i = 0; $i < $fontCount; $i++) {
$contents .= mb_substr($str, mt_rand(0, 499), 1);
}
$res = [
'contents' => $contents,
'value' => $contents,
];
break;
case 'alpha_num':
$str = $this->getChar($this->configs['type']);
for ($i = 0; $i < $fontCount; $i++) {
$contents .= substr($str, mt_rand(0, 61), 1);
}
$res = [
'contents' => $contents,
'value' => $contents,
];
break;
default:
$str = $this->getChar($this->configs['type']);
for ($i = 0; $i < $fontCount; $i++) {
$contents .= substr($str, mt_rand(0, 9), 1);
}
$res = [
'contents' => $contents,
'value' => $contents,
];
break;
}
return $res;
}
public function drawText($contents)
{
$font_family = $this->configs['font_family'];
$font = $this->configs['font_size'];
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
if (is_array($contents)) {
$equally = $im_width / count($contents);
foreach ($contents as $k => $v) {
$color = $this->PosterDriver->createColorAlpha($this->im, [mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), 1]);
$content = $v;
$x = mt_rand($k * $equally + 10, ($k + 1) * $equally - $font);
$y = mt_rand($font + 10, $im_height);
$angle = $this->configs['type'] === 'math' ? 0 : mt_rand(0, 45);
imagettftext($this->im, $font, $angle, $x, $y, $color, $font_family, $content);
}
} else {
$equally = $im_width / mb_strlen($contents);
for ($i = 0; $i < mb_strlen($contents); $i++) {
$color = $this->PosterDriver->createColorAlpha($this->im, [mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), 1]);
$content = mb_substr($contents, $i, 1);
$x = mt_rand($i * $equally + 10, ($i + 1) * $equally - $font);
$y = mt_rand($font + 10, $im_height);
$angle = mt_rand(0, 45);
imagettftext($this->im, $font, $angle, $x, $y, $color, $font_family, $content);
}
}
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:52
*/
namespace Kkokk\Poster\Captcha\Traits;
trait RotateTrait
{
public function draw()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src'], true); // 背景图
// 旋转角度
$angle = mt_rand(45, 315);
$this->im = imagerotate($this->im, $angle, 0);
$this->drawRotate();
return [
'angle' => $angle
];
}
protected function drawRotate()
{
$Width = imagesx($this->im);
$rotateBg = $this->im; // 旋转后的背景
$bgWidth = $this->configs['im_width'];
$bgHeight = $this->configs['im_height'];
$circle = $this->PosterDriver->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = false);
$this->im = $this->PosterDriver->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true); // 最后输出的jpg
$surplusR = ($Width - $bgWidth) / 2;
$r = ($bgWidth / 2) - 2; //圆半径
for ($x = 0; $x < $bgWidth; $x++) {
for ($y = 0; $y < $bgHeight; $y++) {
if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) {
$rgbColor = imagecolorat($rotateBg, $x + 2 + $surplusR, $y + 2 + $surplusR);
imagesetpixel($circle, $x + 2, $y + 2, $rgbColor);
}
}
}
imagecopyresampled($this->im, $circle, 0, 0, 2, 2, $bgWidth, $bgHeight, $bgWidth - 3, $bgHeight - 3);
$this->destroyImage($rotateBg);
$this->destroyImage($circle);
}
protected function getImBg()
{
return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'rotate_bg' . DIRECTORY_SEPARATOR . 'rotate0' . mt_rand(1, 5) . '.jpg';
}
}

View File

@ -0,0 +1,451 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:52
*/
namespace Kkokk\Poster\Captcha\Traits;
trait SliderTrait
{
public function draw()
{
$func = 'draw' . $this->configs['type'];
return $this->$func();
}
// 实现图片绘制
public function draw3()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src']); // 添加bg图片
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_height'];
$slider_width = $this->configs['slider_width'];
$slider_height = $this->configs['slider_height'];
$border = $this->configs['slider_border'];
$w = $slider_width;
$h = $slider_height;
$bgColor = $this->PosterDriver->createColorAlpha($this->im, [0, 0, 0, 60]);
$ims = $this->PosterDriver->createIm($slider_width, $slider_height, [], true); // 创建抠图背景
$x1 = mt_rand($slider_width * 2, $bg_width - $w - 10);
$x2 = $x1 + $w;
$y1 = mt_rand(0, $bg_height - $h);
$y2 = $y1 + $h;
$halfBorder = $border / 2;
$borderColor = $this->PosterDriver->createColor($this->im, [255, 255, 255, 1]);
$points = [
$x1 + $w / 2, $y1,
$x2, $y2,
$x1, $y2,
];
// 三角形
$p1 = [$points[0], $points[1]];
$p2 = [$points[2], $points[3]];
$p3 = [$points[4], $points[5]];
for ($i = 0; $i < $bg_width; $i++) {
for ($j = 0; $j < $bg_height; $j++) {
// 利用叉积抠图 p1 p2 p3 可以抠多边形
// 任意坐标点
$p = [$i, $j];
$cross1 = $this->getCross($p1, $p2, $p);
$cross2 = $this->getCross($p2, $p3, $p);
$cross3 = $this->getCross($p3, $p1, $p);
$isCross = $cross1 > 0 && $cross2 > 0 && $cross3 > 0;
if ($isCross) {
$rgbColor = imagecolorat($this->im, $i, $j);
imagesetpixel($ims, $i - $x1, $j - $y1, $rgbColor); // 抠图
}
// $isCross1 = $cross1 * $cross2 * $cross3 == 0;
// if($isCross1) {
// imagesetpixel($ims, $i - $x1, $j - $y1, $rgbColor); // 边框
// }
}
}
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$borderPoints = [
$w / 2, 0,
$w, $h - $halfBorder / 2,
0, $h - $halfBorder / 2,
];
imagesetthickness($ims, $halfBorder); // 划线的线宽加粗
imagepolygon($ims, $borderPoints, count($borderPoints) / 2, $borderColor);
$bgCount = 1;
$maxCount = min($this->configs['slider_bg'], 4);
$maxCount = max($maxCount, 1);
while ($bgCount < $maxCount) {
// 额外滑块背景
$x = mt_rand($slider_width * 2, $bg_width - $w);
$y = mt_rand(0, $bg_height - $h);
$points = [
$x + $w / 2, $y,
$x + $w, $y + $h,
$x, $y + $h,
];
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$bgCount++;
}
imagecopy($this->im, $ims, 5, 196, 0, 0, imagesx($ims), imagesy($ims));
$this->destroyImage($ims);
return [
'x' => $x1,
'y' => $y1,
];
}
public function draw4()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src']); // 添加bg图片
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_height'];
$slider_width = $this->configs['slider_width'];
$slider_height = $this->configs['slider_height'];
$border = $this->configs['slider_border'];
$w = $slider_width - $border;
$h = $slider_height - $border;
$bgColor = $this->PosterDriver->createColorAlpha($this->im, [0, 0, 0, 60]);
$ims = $this->PosterDriver->createIm($slider_width, $slider_height, [], false); // 创建抠图背景
$x1 = mt_rand($slider_width * 2, $bg_width - $w - 10);
$x2 = $x1 + $w;
$y1 = mt_rand(0, $bg_height - $h);
$y2 = $y1 + $h;
$halfBorder = $border / 2;
// 矩形
$p1 = [$x1 + $halfBorder - 1, $y2 + $halfBorder]; // 左下
$p2 = [$x2 + $halfBorder, $y2 + $halfBorder]; // 右下
$p3 = [$x2 + $halfBorder, $y1 + $halfBorder - 1]; // 右上
$p4 = [$x1 + $halfBorder - 1, $y1 + $halfBorder - 1]; // 左上
for ($i = 0; $i < $bg_width; $i++) {
for ($j = 0; $j < $bg_height; $j++) {
// 矩形抠图
// if (($i < $x2 && $i >= $x1) && ($j < $y2 && $j >= $y1)) {
// $rgbColor = imagecolorat($this->im, $i, $j);
// imagesetpixel($ims, $i - $x1 + $border / 2, $j - $y1 + $border / 2, $rgbColor); // 抠图
// }
// 利用叉积抠图 p1 p2 p3 可以抠多边形
// 任意坐标点
$p = [$i, $j];
// 叉积计算 点在四条平行线内部则是在矩形内 p1->p2 p1->p3 参考点 p1 叉积大于0点p3在p2逆时针方向 等于0 三点一线 小于0 点p3在p2顺时针防线
$isCross = $this->getCross($p1, $p2, $p) * $this->getCross($p3, $p4, $p) > 0 && $this->getCross($p2, $p3, $p) * $this->getCross($p4, $p1, $p) > 0;
if ($isCross) {
$rgbColor = imagecolorat($this->im, $i, $j);
imagesetpixel($ims, $i - $x1, $j - $y1, $rgbColor); // 抠图
}
}
}
imagefilledrectangle($this->im, $x1, $y1, $x1 + $slider_width, $y1 + $slider_height, $bgColor);
$bgCount = 1;
$maxCount = min($this->configs['slider_bg'], 4);
$maxCount = max($maxCount, 1);
while ($bgCount < $maxCount) {
// 额外滑块背景
$x = mt_rand(30, $bg_width - $w);
$y = mt_rand(0, $bg_height - $h);
imagefilledrectangle($this->im, $x, $y, $x + $slider_width, $y + $slider_height, $bgColor);
$bgCount++;
}
imagecopy($this->im, $ims, 5, 196, 0, 0, $slider_width, $slider_width);
$this->destroyImage($ims);
return [
'x' => $x1,
'y' => $y1,
];
}
public function draw5()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src']); // 添加bg图片
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_height'];
$slider_width = $this->configs['slider_width'];
$slider_height = $this->configs['slider_height'];
$border = $this->configs['slider_border'];
$w = $slider_width;
$h = $slider_height;
$bgColor = $this->PosterDriver->createColorAlpha($this->im, [0, 0, 0, 60]);
$ims = $this->PosterDriver->createIm($slider_width, $slider_height, [], true); // 创建抠图背景
$x1 = mt_rand($slider_width * 2, $bg_width - $w - 10);
$x2 = $x1 + $w;
$y1 = mt_rand(0, $bg_height - $h);
$y2 = $y1 + $h;
$halfBorder = $border / 2;
$borderColor = $this->PosterDriver->createColor($this->im, [255, 255, 255, 1]);
$points = [
$x1 + $w / 2, $y1,
$x2, $y1 + $h / 2,
$x1 + $w * 3 / 4, $y2,
$x1 + $w / 4, $y2,
$x1, $y1 + $h / 2,
];
// 五边形
$p1 = [$points[0], $points[1]];
$p2 = [$points[2], $points[3]];
$p3 = [$points[4], $points[5]];
$p4 = [$points[6], $points[7]];
$p5 = [$points[8], $points[9]];
for ($i = 0; $i < $bg_width; $i++) {
for ($j = 0; $j < $bg_height; $j++) {
// 利用叉积抠图 p1 p2 p3 可以抠多边形
// 任意坐标点
$p = [$i, $j];
$cross1 = $this->getCross($p1, $p2, $p);
$cross2 = $this->getCross($p2, $p3, $p);
$cross3 = $this->getCross($p3, $p4, $p);
$cross4 = $this->getCross($p4, $p5, $p);
$cross5 = $this->getCross($p5, $p1, $p);
$isCross = $cross1 > 0 && $cross2 > 0 && $cross3 > 0 && $cross4 > 0 && $cross5 > 0;
if ($isCross) {
$rgbColor = imagecolorat($this->im, $i, $j);
imagesetpixel($ims, $i - $x1, $j - $y1, $rgbColor); // 抠图
}
}
}
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$borderPoints = [
$w / 2, 0,
$w, $h / 2,
$w * 3 / 4, $h - $halfBorder / 2,
$w * 1 / 4, $h - $halfBorder / 2,
0, $h / 2,
];
imagesetthickness($ims, $halfBorder); // 划线的线宽加粗
imagepolygon($ims, $borderPoints, count($borderPoints) / 2, $borderColor);
$bgCount = 1;
$maxCount = min($this->configs['slider_bg'], 4);
$maxCount = max($maxCount, 1);
while ($bgCount < $maxCount) {
// 额外滑块背景
$x = mt_rand($slider_width * 2, $bg_width - $w);
$y = mt_rand(0, $bg_height - $h);
$points = [
$x + $w / 2, $y,
$x + $w, $y + $h / 2,
$x + $w * 3 / 4, $y + $h,
$x + $w / 4, $y + $h,
$x, $y + $h / 2,
];
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$bgCount++;
}
imagecopy($this->im, $ims, 5, 196, 0, 0, imagesx($ims), imagesy($ims));
$this->destroyImage($ims);
return [
'x' => $x1,
'y' => $y1,
];
}
public function draw6()
{
$im_width = $this->configs['im_width'];
$im_height = $this->configs['im_height'];
$this->im = $this->PosterDriver->createIm($im_width, $im_height, [], true);
$this->drawImage($this->configs['src']); // 添加bg图片
$bg_width = $this->configs['bg_width'];
$bg_height = $this->configs['bg_height'];
$slider_width = $this->configs['slider_width'];
$slider_height = $this->configs['slider_height'];
$border = $this->configs['slider_border'];
$w = $slider_width;
$h = $slider_height;
$bgColor = $this->PosterDriver->createColorAlpha($this->im, [0, 0, 0, 60]);
$ims = $this->PosterDriver->createIm($slider_width, $slider_height, [], true); // 创建抠图背景
$x1 = mt_rand($slider_width * 2, $bg_width - $w - 10);
$x2 = $x1 + $w;
$y1 = mt_rand(0, $bg_height - $h);
$y2 = $y1 + $h;
$halfBorder = $border / 2;
$borderColor = $this->PosterDriver->createColor($this->im, [255, 255, 255, 1]);
$points = [
$x1 + $w / 4, $y1,
$x1 + $w * 3 / 4, $y1,
$x2, $y1 + $h / 2,
$x1 + $w * 3 / 4, $y2,
$x1 + $w / 4, $y2,
$x1, $y1 + $h / 2,
];
// 五边形
$p1 = [$points[0], $points[1]];
$p2 = [$points[2], $points[3]];
$p3 = [$points[4], $points[5]];
$p4 = [$points[6], $points[7]];
$p5 = [$points[8], $points[9]];
$p6 = [$points[10], $points[11]];
for ($i = 0; $i < $bg_width; $i++) {
for ($j = 0; $j < $bg_height; $j++) {
// 利用叉积抠图 p1 p2 p3 可以抠多边形
// 任意坐标点
$p = [$i, $j];
$cross1 = $this->getCross($p1, $p2, $p);
$cross2 = $this->getCross($p2, $p3, $p);
$cross3 = $this->getCross($p3, $p4, $p);
$cross4 = $this->getCross($p4, $p5, $p);
$cross5 = $this->getCross($p5, $p6, $p);
$cross6 = $this->getCross($p6, $p1, $p);
$isCross = $cross1 > 0 && $cross2 > 0 && $cross3 > 0 && $cross4 > 0 && $cross5 > 0 && $cross6 > 0;
if ($isCross) {
$rgbColor = imagecolorat($this->im, $i, $j);
imagesetpixel($ims, $i - $x1, $j - $y1, $rgbColor); // 抠图
}
}
}
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$borderPoints = [
$w / 4, 0,
$w * 3 / 4, 0,
$w, $h / 2,
$w * 3 / 4, $h - $halfBorder / 2,
$w * 1 / 4, $h - $halfBorder / 2,
0, $h / 2,
];
imagesetthickness($ims, $halfBorder); // 划线的线宽加粗
imagepolygon($ims, $borderPoints, count($borderPoints) / 2, $borderColor);
$bgCount = 1;
$maxCount = min($this->configs['slider_bg'], 4);
$maxCount = max($maxCount, 1);
while ($bgCount < $maxCount) {
// 额外滑块背景
$x = mt_rand($slider_width * 2, $bg_width - $w);
$y = mt_rand(0, $bg_height - $h);
$points = [
$x + $w / 4, $y,
$x + $w * 3 / 4, $y,
$x + $w, $y + $h / 2,
$x + $w * 3 / 4, $y + $h,
$x + $w / 4, $y + $h,
$x, $y + $h / 2,
];
$this->drawImageFilledPolygon($this->im, $points, count($points) / 2, $bgColor);
$bgCount++;
}
imagecopy($this->im, $ims, 5, 196, 0, 0, imagesx($ims), imagesy($ims));
$this->destroyImage($ims);
return [
'x' => $x1,
'y' => $y1,
];
}
/**
* php 8.1 废弃参数 $points_count
* User: lang
* Date: 2023/7/17
* Time: 10:45
* @param $im
* @param $points
* @param $points_count
* @param $color
* @return void
*/
protected function drawImageFilledPolygon($im, $points, $points_count, $color){
if(PHP_VERSION < 8.1)
imagefilledpolygon($im, $points, $points_count, $color);
else
imagefilledpolygon($im, $points, $color);
}
protected function getImBg()
{
return __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'slider_bg' . DIRECTORY_SEPARATOR . 'layer0' . mt_rand(1, 3) . '.jpg';
}
}

View File

@ -0,0 +1,117 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/9
* Time: 15:53
*/
namespace Kkokk\Poster\Common;
use Kkokk\Poster\Exception\PosterException;
class Common
{
protected $imType = [
'gif' => 'imagegif',
'jpeg' => 'imagejpeg',
'jpg' => 'imagejpeg',
'png' => 'imagepng',
'wbmp' => 'imagewbmp'
];
// 转base64
public function baseData($image, $type = 'png')
{
$baseData = '';
if (is_resource($image) || is_object($image)) {
ob_start();
$this->imType[$type]($image);
$data = ob_get_contents();
ob_end_clean();
$baseData = 'data:image/' . $type . ';base64,' . base64_encode($data);
imagedestroy($image);
} elseif (is_string($image)) {
$baseData = 'data:image/' . $type . ';base64,' . base64_encode($image);
}
return $baseData;
}
/**
* 输出图片
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:44
* @param $im
* @param string $dir
* @param string $type
* @param int $quality
*/
public function imOutput($im, $dir = '', $type = 'png', $quality = 75)
{
if ($type == 'jpg' || $type == 'jpeg') {
$this->imType[$type]($im, $dir, $quality);
} else {
$this->imType[$type]($im, $dir);
}
return 1;
}
/**
* 计算 三个点的叉乘 |p1 p2| X |p1 p|
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 10:44
* @param int $p1
* @param int $p2
* @param int $p
* @return int
*/
public function getCross($p1, $p2, $p)
{
// (p2.x - p1.x) * (p.y - p1.y) -(p.x - p1.x) * (p2.y - p1.y);
return ($p1[0] - $p[0]) * ($p2[1] - $p[1]) - ($p2[0] - $p[0]) * ($p1[1] - $p[1]);
}
public function getNodeStyleColor($color)
{
if(strpos($color, 'rgb') !== false) {
return $this->styleToRgba($color);
} elseif(strpos($color, '#') !== false) {
return $this->hexToRgba($color);
} else {
throw new PosterException('only support rgb or hexadecimal');
}
}
public function hexToRgba($hexColor)
{
$rgb = [];
$color = str_replace('#', '', $hexColor);
if (strlen($color) > 3) {
$rgb = [
hexdec(substr($color, 0, 2)),
hexdec(substr($color, 2, 2)),
hexdec(substr($color, 4, 2)),
1,
];
}
return $rgb;
}
public function styleRgbToRgba($rgbColor)
{
preg_match('/rgb\((.*?)\)/', $rgbColor, $color);
print_r($color);exit;
}
public function styleRgbaToRgba($rgbaColor)
{
preg_match('/rgba\((.*?)\)/', $rgbaColor, $color);
print_r($color);exit;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Kkokk\Poster\Exception;
if (interface_exists(\Throwable::class)) {
class Exception extends \Exception
{
const SYSTEM_CODE = 1;
const ERROR_POSTER_CODE = 2;
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
} else {
class Exception extends \Exception
{
const SYSTEM_CODE = 1;
const ERROR_POSTER_CODE = 2;
public function __construct($message = "", $code = 0, $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Kkokk\Poster\Exception;
/**
* @Author: lang
* @Email: 732853989@qq.com
* @Date: 2020-08-14 09:56:51
* @Last Modified by: lang
* @Last Modified time: 2020-08-17 14:05:50
*/
class PosterException extends Exception
{
public function __construct($message = "", $code = null, $previous = null)
{
parent::__construct("PosterException : " . $message, $code ?: self::ERROR_POSTER_CODE, $previous);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Kkokk\Poster\Exception;
/**
* @Author: lang
* @Email: 732853989@qq.com
* @Date: 2020-08-14 09:56:51
* @Last Modified by: lang
* @Last Modified time: 2020-08-17 14:05:52
*/
class SystemException extends Exception
{
public function __construct($message = "", $code = null, $previous = null)
{
parent::__construct("SystemException : " . $message, $code ?: self::ERROR_POSTER_CODE, $previous);
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 14:11
*/
namespace Kkokk\Poster\Facades;
class Cache extends Facade
{
protected static function getFacadeModel()
{
return 'cache';
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 11:08
*/
namespace Kkokk\Poster\Facades;
use Kkokk\Poster\Captcha\Generators\CaptchaGenerator;
use Kkokk\Poster\Captcha\CaptchaGeneratorInterface;
use Kkokk\Poster\Captcha\CaptchaManager;
/**
* @method static CaptchaGenerator type(string $channel = null)
* @method static CaptchaGenerator config($params = [])
* @method static boolean check($key, $value, $leeway = 0, $secret = null)
* @method static array get($expire = 0)
*
* @see CaptchaManager
*/
class Captcha extends Facade
{
protected static function getFacadeModel()
{
return 'captcha';
}
}

View File

@ -0,0 +1,68 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/15
* Time: 14:21
*/
namespace Kkokk\Poster\Facades;
use Kkokk\Poster\Exception\PosterException;
abstract class Facade
{
protected static $resolvedInstance = [];
protected static $store = [
'cache' => \Kkokk\Poster\Cache\Repository::class,
'poster' => \Kkokk\Poster\Image\PosterManager::class,
'captcha' => \Kkokk\Poster\Lang\Captcha::class,
'html' => \Kkokk\Poster\Html\HtmlManager::class,
];
protected static function getInstance()
{
return static::setInstance(static::getFacadeModel());
}
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 14:14
* @return string
* @throws PosterException
*/
protected static function getFacadeModel()
{
throw new PosterException('未获取到模型');
}
// 设置实例
protected static function setInstance($model)
{
if (is_object($model)) {
return $model;
}
if (!isset($resolvedInstance[$model])) {
// 单例
static::$resolvedInstance = new self::$store[$model];
}
return static::$resolvedInstance;
}
public static function __callStatic($method, $args)
{
$instance = static::getInstance();
if (!$instance) {
throw new PosterException('未找到相关实例与方法');
}
return $instance->$method(...$args);
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 17:06
*/
namespace Kkokk\Poster\Facades;
use Kkokk\Poster\Html\Builder;
/**
* User: lang
* @method static Builder channel($channel = "");
* @method static Builder load($html);
* @package Kkokk\Poster\Facades
* @class Html
* @author 73285 2023-08-09
*
* @see Builder
*/
class Html extends Facade
{
protected static function getFacadeModel()
{
return 'html';
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/10
* Time: 10:58
*/
namespace Kkokk\Poster\Facades;
use Kkokk\Poster\Image\ExtensionInterface;
use Kkokk\Poster\Image\PosterManager;
use Kkokk\Poster\Image\Builder;
/**
* @method static ExtensionInterface extension($name = "");
* @method static Builder config($params = []);
* @method static Builder buildIm($w, $h, $rgba = [], $alpha = false);
* @method static Builder buildImDst($src, $w = 0, $h = 0);
* @method static Builder buildBg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $func = '');
* @method static array Qr($text, $outfile = false, $level = 'L', $size = 4, $margin = 1, $saveAndPrint = 0);
*
* @see \Kkokk\Poster\Image\PosterManager
* @see \Kkokk\Poster\Image\Extension
*/
class Poster extends Facade
{
protected static function getFacadeModel()
{
return 'poster';
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 18:04
*/
namespace Kkokk\Poster\Html;
use Kkokk\Poster\Html\Queries\Query;
class Builder
{
protected $channel;
protected $query;
function __construct(HtmlInterface $channel, Query $query)
{
$this->channel = $channel;
$this->query = $query;
}
public function load($html)
{
$this->query->buildQuery('load', [$html]);
return $this;
}
public function transparent($transparent = true)
{
$this->query->buildQuery('transparent', [$transparent]);
return $this;
}
public function size($width = 0, $height = 0)
{
$this->query->buildQuery('size', [$width, $height]);
return $this;
}
public function crop($crop_w = 0, $crop_h = 0, $crop_x = 0, $crop_y = 0)
{
$this->query->buildQuery('crop', [$crop_w, $crop_h, $crop_x, $crop_y]);
return $this;
}
public function type($type = 'png')
{
$this->query->buildQuery('type', [$type]);
return $this;
}
public function command($command = '')
{
$this->query->buildQuery('command', [$command]);
return $this;
}
public function output($path, $type = '')
{
$this->query->buildQuery('output', [$path, $type]);
return $this;
}
public function quality($quality)
{
$this->query->buildQuery('quality', [$quality]);
return $this;
}
public function render()
{
return $this->channel->render($this->query->getQuery());
}
}

View File

@ -0,0 +1,23 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 18:11
*/
namespace Kkokk\Poster\Html\Drivers;
class Driver
{
/**
* html内容
* @var string
*/
protected $html;
/**
* 输出地址
* @var string
*/
protected $output;
}

View File

@ -0,0 +1,17 @@
<?php
/**
* User: lang
* Date: 2023/8/10
* Time: 11:21
*/
namespace Kkokk\Poster\Html\Drivers;
interface DriverInterface
{
public function render($query);
public function getImageBlob();
public function getFilePath();
}

View File

@ -0,0 +1,204 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 18:09
*/
namespace Kkokk\Poster\Html\Drivers;
use Kkokk\Poster\Exception\PosterException;
/**
* User: lang
* 注意 使用的是 -webkit 语法
* @package Kkokk\Poster\Html\Drivers
* @class WkDriver
* @author 73285 2023-08-10
*/
class WkDriver extends Driver implements DriverInterface
{
/**
* 文件类型常规图片png、jpg等和pdf
* @var string
*/
private $type = 'png';
private $driver = "wkhtmltoimage";
private $size = "";
private $crop = "";
private $quality = '--quality 100';
private $transparent = '';
private $command = '';
private $tmpHtmlPath;
private $tmp = false;
public function load($html)
{
$this->html = $html;
}
public function size($width = 0, $height = 0)
{
if ($width) {
$this->size .= "--width " . $width;
}
if ($height) {
$this->size .= " --height " . $height;
}
}
public function crop($crop_w = 0, $crop_h = 0, $crop_x = 0, $crop_y = 0)
{
if ($crop_w) {
$this->crop .= " --crop-w " . $crop_w;
}
if ($crop_h) {
$this->crop .= " --crop-h " . $crop_h;
}
if ($crop_x) {
$this->crop .= " --crop-x " . $crop_x;
}
if ($crop_y) {
$this->crop .= " --crop-y " . $crop_y;
}
}
public function quality($quality)
{
$quality = max($quality, 0);
$quality = min($quality, 100);
$this->quality = '--quality ' . $quality;
}
public function setType($type)
{
$this->type = strtolower($type);
if ($this->type == 'pdf') {
$this->driver = 'wkhtmltopdf';
}
}
public function output($path, $type = '')
{
$this->output = $path;
if ($type) {
$this->setType($type);
}
}
public function setTransparent($transparent = true)
{
$this->transparent = $transparent ? '--transparent' : '';
}
/**
* 设置原生命令
* User: lang
* Date: 2023/8/10
* Time: 16:35
* @param $command
* @return void
*/
public function setCommand($command)
{
$this->command .= ' ' . $command;
}
private function tmp()
{
$this->output = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . uniqid('html') . '.png';
$this->tmp = true;
}
public function render($query)
{
foreach ($query as $v) {
switch ($v['type']) {
case "load":
$this->load(...$v['params']);
break;
case "crop":
$this->crop(...$v['params']);
break;
case "quality":
$this->quality(...$v['params']);
break;
case "type":
$this->setType(...$v['params']);
break;
case "tmp":
$this->tmp();
break;
case "output":
$this->output(...$v['params']);
break;
case "size":
$this->size(...$v['params']);
break;
case "transparent":
$this->setTransparent(...$v['params']);
break;
case "command":
$this->setCommand(...$v['params']);
break;
}
}
$check = exec($this->driver . ' --version');
if (empty($check)) {
throw new PosterException('Please install ' . $this->driver);
}
if (preg_match('/<[^>]*>/', $this->html)) {
$this->tmpHtmlPath = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . uniqid('html') . '.html';
file_put_contents($this->tmpHtmlPath, $this->html);
$this->html = $this->tmpHtmlPath;
}
if ($this->type == 'pdf') {
if (empty($this->output)) {
throw new PosterException('Save path cannot be empty');
}
$this->quality = '';
} else {
if (empty($this->output)) {
$this->tmp();
}
}
$command = sprintf("%s %s %s %s %s %s %s %s", $this->driver, $this->command, $this->transparent, $this->size,
$this->crop, $this->quality, $this->html, $this->output);
exec($command);
return $this;
}
public function getImageBlob()
{
return file_get_contents($this->output);
}
public function getFilePath()
{
return $this->output;
}
function __destruct()
{
$this->tmp && @unlink($this->output);
$this->tmpHtmlPath && @unlink($this->tmpHtmlPath);
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 17:00
*/
namespace Kkokk\Poster\Html;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Html\Drivers\DriverInterface;
class Html implements HtmlInterface
{
protected $driver;
function __construct(DriverInterface $driver)
{
$this->driver = $driver;
}
public function query()
{
return new Builder(
$this,
$this->getQueryInstance()
);
}
public function load($html)
{
return $this->query()->load($html);
}
public function render($query)
{
return $this->run($query, function ($query) {
return $this->driver->render($query);
});
}
protected function run($query, \Closure $callback)
{
try {
$result = $callback($query);
} catch (\Exception $e) {
throw new PosterException($e->getMessage(), 0, $e);
}
return $result;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 17:17
*/
namespace Kkokk\Poster\Html;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Html\Drivers\WkDriver;
class HtmlFactory
{
public function make($name)
{
return $this->createInstance($name);
}
protected function createDriver($name)
{
switch ($name) {
case 'wk':
return new WkDriver();
}
throw new PosterException("Unsupported driver [{$name}].");
}
private function createInstance($name)
{
switch ($name) {
case 'wk':
return new WkHtml($this->createDriver($name));
}
throw new PosterException("Unsupported channel [{$name}].");
}
}

View File

@ -0,0 +1,14 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 13:32
*/
namespace Kkokk\Poster\Html;
interface HtmlInterface
{
public function load($html);
public function render($query);
}

View File

@ -0,0 +1,67 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 16:55
*/
namespace Kkokk\Poster\Html;
class HtmlManager
{
protected $channels = [];
private $factory;
function __construct()
{
$this->factory = new HtmlFactory;
}
public function channel($name = null)
{
$name = $this->parseConnectionName($name);
if (!isset($this->channels[$name])) {
$this->channels[$name] = $this->configure($this->makeChannel($name));
}
return $this->channels[$name];
}
protected function configure(Html $Html)
{
return $Html;
}
protected function parseConnectionName($name)
{
if (empty($name)) return $this->supportedChannels()[0];
return $name;
}
protected function makeChannel($name)
{
return $this->factory->make($name);
}
/**
* 获取所有支持通道。
*
* @return array
*/
public function supportedChannels()
{
return ['wk'];
}
/**
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->channel()->$method(...$parameters);
}
}

View File

@ -0,0 +1,25 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 18:05
*/
namespace Kkokk\Poster\Html\Queries;
abstract class Query
{
protected $query = [];
abstract public function makeQuery();
public function getQuery()
{
return static::makeQuery();
}
public function buildQuery($type, $params)
{
$this->query[] = ['type' => $type, 'params' => $params];
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 18:06
*/
namespace Kkokk\Poster\Html\Queries;
class WkQuery extends Query
{
public function makeQuery()
{
return $this->query;
}
}

View File

@ -0,0 +1,18 @@
<?php
/**
* User: lang
* Date: 2023/8/9
* Time: 17:00
*/
namespace Kkokk\Poster\Html;
use Kkokk\Poster\Html\Queries\WkQuery;
class WkHtml extends Html
{
public function getQueryInstance()
{
return new WkQuery;
}
}

View File

@ -0,0 +1,261 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 18:10
*/
namespace Kkokk\Poster\Image;
use Kkokk\Poster\Image\Queries\Query;
class Builder
{
public $extension;
/** @var Query 合成参数 */
public $query;
// 画布
public $im = [];
/** @var array 基础配置 */
public $configs = [];
/** @var array 图片组 */
public $images = [];
/** @var array 文字组 */
public $texts = [];
/** @var array 二维码组 */
public $qrs = [];
/** @var array 背景组 */
public $bgs = [];
/** @var array 线组 */
public $lines = [];
/** @var array 圆组 */
public $arcs = [];
/** @var string */
public $path = null;
public function __construct(ExtensionInterface $extension, Query $query, $path = null)
{
$this->extension = $extension;
$this->query = $query;
$this->path = $path;
$this->query->setPath($path);
}
public function config($params = [])
{
$this->configs = $params;
$this->query->setQuery('config', $this->configs);
return $this;
}
public function buildIm($w, $h, $rgba = [255, 255, 255, 1], $alpha = false)
{
$this->im = [$w, $h, $rgba, $alpha];
$this->query->setQuery('im', $this->im);
return $this;
}
public function buildImDst($src, $w = 0, $h = 0)
{
$this->im = [$src, $w, $h];
$this->query->setQuery('imDst', $this->im);
return $this;
}
public function buildBg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, \Closure $callback = null)
{
$query = [];
if ($callback) {
$that = clone $this;
$that->query->clearQuery(); // 清理 query
$callback($that);
$query = $that->query->getQuery(); // 获取闭包内生成的query
unset($that);
}
$bg = [$w, $h, $rgba, $alpha, $dst_x, $dst_y, $src_x, $src_y, $query];
$this->query->setQuery('bg', $bg);
return $this;
}
public function buildImage($src, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $src_w = 0, $src_h = 0, $alpha = false, $type = 'normal')
{
$this->setImages($src, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $alpha, $type);
return $this;
}
public function buildImageMany($params = [])
{
foreach ($params as $value) {
$value['dst_x'] = isset($value['dst_x']) ? $value['dst_x'] : 0;
$value['dst_y'] = isset($value['dst_y']) ? $value['dst_y'] : 0;
$value['src_x'] = isset($value['src_x']) ? $value['src_x'] : 0;
$value['src_y'] = isset($value['src_y']) ? $value['src_y'] : 0;
$value['src_w'] = isset($value['src_w']) ? $value['src_w'] : 0;
$value['src_h'] = isset($value['src_h']) ? $value['src_h'] : 0;
$value['alpha'] = isset($value['alpha']) ?: false;
$value['type'] = isset($value['type']) ? $value['type'] : 'normal';
$this->setImages($value['src'], $value['dst_x'], $value['dst_y'], $value['src_x'], $value['src_y'], $value['src_w'], $value['src_h'], $value['alpha'], $value['type']);
}
return $this;
}
public function buildText($content, $dst_x = 0, $dst_y = 0, $fontSize = null, $rgba = null, $max_w = null, $font = null, $weight = null, $space = null, $angle = null)
{
$this->setTexts($content, $dst_x, $dst_y, $fontSize, $rgba, $max_w, $font, $weight, $space, $angle);
return $this;
}
public function buildTextMany($params = [])
{
foreach ($params as $value) {
$value['dst_x'] = isset($value['dst_x']) ? $value['dst_x'] : 0;
$value['dst_y'] = isset($value['dst_y']) ? $value['dst_y'] : 0;
$value['fontSize'] = isset($value['font']) ? $value['font'] : 0;
$value['rgba'] = isset($value['rgba']) ? $value['rgba'] : [];
$value['max_w'] = isset($value['max_w']) ? $value['max_w'] : 0;
$value['font'] = isset($value['font_family']) ? $value['font_family'] : '';
$value['weight'] = isset($value['weight']) ? $value['weight'] : 1;
$value['space'] = isset($value['space']) ? $value['space'] : 0;
$value['angle'] = isset($value['angle']) ? $value['angle'] : 0;
$this->setTexts($value['content'], $value['dst_x'], $value['dst_y'], $value['fontSize'], $value['rgba'], $value['max_w'], $value['font'], $value['weight'], $value['space'], $value['angle']);
}
return $this;
}
public function buildQr($text, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $src_w = 0, $src_h = 0, $size = 4, $margin = 1, $level = 'L')
{
$this->setQrs($text, $level, $size, $margin, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
return $this;
}
public function buildQrMany($params = [])
{
foreach ($params as $value) {
$value['dst_x'] = isset($value['dst_x']) ? $value['dst_x'] : 0;
$value['dst_y'] = isset($value['dst_y']) ? $value['dst_y'] : 0;
$value['src_x'] = isset($value['src_x']) ? $value['src_x'] : 0;
$value['src_y'] = isset($value['src_y']) ? $value['src_y'] : 0;
$value['src_w'] = isset($value['src_w']) ? $value['src_w'] : 0;
$value['src_h'] = isset($value['src_h']) ? $value['src_h'] : 0;
$value['size'] = isset($value['size']) ? $value['size'] : 4;
$value['margin'] = isset($value['margin']) ? $value['margin'] : 1;
$value['level'] = isset($value['level']) ? $value['level'] : 'L';
$this->setQrs($value['text'], $value['level'], $value['size'], $value['margin'], $value['dst_x'], $value['dst_y'], $value['src_x'], $value['src_y'], $value['src_w'], $value['src_h']);
}
return $this;
}
public function buildLine($x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $rgba = [], $type = '', $weight = 1)
{
$line = [$x1, $y1, $x2, $y2, $rgba, $type, $weight];
$this->lines[] = $line;
$this->query->setQuery('line', $line);
return $this;
}
public function buildArc($cx = 0, $cy = 0, $w = 0, $h = 0, $s = 0, $e = 0, $rgba = [], $type = '', $style = '', $weight = 1)
{
$arc = [$cx, $cy, $w, $h, $s, $e, $rgba, $type, $style, $weight];
$this->arcs = $arc;
$this->query->setQuery('arc', $arc);
return $this;
}
public function path($path)
{
$this->path = $path;
$this->query->setPath($path);
return $this;
}
public function getPoster($path = '')
{
$query = $this->query->getQuery();
return $this->extension->getPoster($query, $path);
}
public function setPoster()
{
$query = $this->query->getQuery();
return $this->extension->setPoster($query);
}
public function stream()
{
$query = $this->query->getQuery();
return $this->extension->stream($query);
}
public function baseData()
{
$query = $this->query->getQuery();
return $this->extension->baseData($query);
}
public function getIm()
{
$query = $this->query->getQuery();
return $this->extension->getIm($query);
}
public function getImInfo()
{
$query = $this->query->getQuery();
return $this->extension->getImInfo($query);
}
public function blob()
{
$query = $this->query->getQuery();
return $this->extension->blob($query);
}
public function tmp()
{
$query = $this->query->getQuery();
return $this->extension->tmp($query);
}
public function crop($x = 0, $y = 0, $width = 0, $height = 0)
{
$crop = [$x, $y, $width, $height];
$this->query->setQuery('crop', $crop);
return $this;
}
protected function setImages(...$params)
{
$this->images[] = $params;
$this->query->setQuery('image', $params);
}
protected function setTexts(...$params)
{
$this->texts[] = $params;
$this->query->setQuery('text', $params);
}
protected function setQrs(...$params)
{
$this->qrs[] = $params;
$this->query->setQuery('qrs', $params);
}
public function __clone()
{
$this->query = clone $this->query;
}
}

View File

@ -0,0 +1,758 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 11:28
*/
namespace Kkokk\Poster\Image\Drivers;
//require_once(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'PHPQrcode' . DIRECTORY_SEPARATOR . 'phpqrcode.php');
use Kkokk\Poster\Exception\PosterException;
class Driver
{
/** @var resource 画布 */
protected $im;
/** @var resource 资源文件 */
protected $source;
/** @var int 画布尺寸宽 */
protected $im_w;
/** @var int 画布尺寸高 */
protected $im_h;
/** @var int[] 默认 x y 分辨率 默认是 [72, 72] */
protected $dpi = [];
/** @var string 存储路径 */
protected $path;
/** @var string 设置字体 */
protected $font = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'style' . DIRECTORY_SEPARATOR . 'simkai.ttf';
/** @var string 字体系列 例如 Microsoft YaHei */
protected $font_family = '';
/** @var int 字体大小 */
protected $font_size = 16;
/** @var int[] 字体颜色 */
protected $font_rgba = [52, 52, 52, 1];
/** @var int 字体间距 */
protected $font_space = 0;
/** @var int 字体粗细 */
protected $font_weight = 1;
/** @var null 字体旋转角度 */
protected $font_angle = 0;
/** @var null 字体最大换行宽度 */
protected $font_max_w = 0;
/** @var string 默认目录 */
protected $pathname = 'poster';
/** @var string 文件名 */
protected $filename;
/** @var string 图片类型 */
protected $type = 'png';
/** @var string[] 图片类型 */
protected $poster_type = [
'gif' => 'imagegif',
'jpeg' => 'imagejpeg',
'jpg' => 'imagejpeg',
'png' => 'imagepng',
'wbmp' => 'imagewbmp'
];
/** @var array|null 返回结果 */
public $result = null;
/**
* 设置基本配置
* @Author lang
* @Email : 732853989@qq.com
* Date: 2023/2/12
* Time: 下午10:09
* @param array $params
* @throws PosterException
*/
public function setConfig($params = [])
{
isset($params['path']) && !empty($params['path']) && $this->setFilePath($params['path']);
isset($params['font_family']) && !empty($params['font_family']) && $this->font = $params['font_family'];
isset($params['font_size']) && !empty($params['font_size']) && $this->font_size = $params['font_size'];
isset($params['font_rgba']) && !empty($params['font_rgba']) && $this->font_rgba = $params['font_rgba'];
isset($params['font_space']) && !empty($params['font_space']) && $this->font_space = $params['font_space'];
isset($params['font_weight']) && !empty($params['font_weight']) && $this->font_weight = $params['font_weight'];
isset($params['font_angle']) && !empty($params['font_angle']) && $this->font_angle = $params['font_angle'];
isset($params['font_max_w']) && !empty($params['font_max_w']) && $this->font_max_w = $params['font_max_w'];
if (isset($params['dpi']) && !empty($params['dpi'])) {
$this->dpi = is_numeric($params['dpi']) ? [$params['dpi'], $params['dpi']] : $params['dpi'];
}
if (isset($params['font']) && !empty($params['font'])) {
$this->font = $this->getRealRoute($params['font']);
}
}
/**
* 设置文件路径
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 9:55
* @param $path
* @throws PosterException
*/
public function setFilePath($path)
{
$path = is_array($path) ? $path : [$path];
$pathFileName = isset($path[0]) ? $path[0] : '';
$pathFileName = str_replace(['\\', DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR, $pathFileName);
$fileName = $pathFileName ?: time();
if (strripos($pathFileName, DIRECTORY_SEPARATOR) !== false) {
$this->setPathName($pathFileName);
$fileName = substr($pathFileName, strripos($pathFileName, DIRECTORY_SEPARATOR) + 1);
}
$this->setFileName($fileName);
$this->setPath($pathFileName);
}
/**
* setFileName 设置文件名
* @Author lang
* @Date 2022-03-10T15:42:06+0800
* @param [type] $fileName [description]
*/
public function setFileName($fileName)
{
$this->filename = $fileName;
if (strripos($this->filename, '.') !== false) {
$this->type = substr($this->filename, strripos($this->filename, '.') + 1);
if (!in_array($this->type, ['jpeg', 'jpg', 'png', 'gif', 'wbmp'])) {
throw new PosterException('The file naming format is incorrect');
}
}
}
/**
* setPathName 设置目录名
* @Author lang
* @Date 2022-03-10T15:42:19+0800
* @param [type] $fileName [description]
*/
public function setPathName($pathFileName)
{
$this->pathname = substr($pathFileName, 0, strripos($pathFileName, DIRECTORY_SEPARATOR));
}
/**
* setPath 设置文件位置
* @Author lang
* @Date 2022-03-10T15:42:38+0800
* @param [type] $fileName [description]
*/
public function setPath($pathFileName)
{
// 绝对路径 or 相对路径
$absolute = $this->isAbsolute($pathFileName);
$this->path = $this->getDocumentRoot();
$this->path = $absolute ? '' : ($this->path ?: __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR);
}
/**
* 获取项目根目录
* @Author lang
* @Date 2022-03-10T15:42:38+0800
*/
public function getDocumentRoot()
{
$documentRoot = iconv('UTF-8', 'GBK', $_SERVER['DOCUMENT_ROOT']);
return $documentRoot ? $documentRoot . DIRECTORY_SEPARATOR : '';
}
/**
* 判断是否是绝对路径
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 9:54
* @param $pathFileName
* @return bool
*/
public function isAbsolute($pathFileName)
{
// 区分WIN系统绝对路径、暂时只区分linux win mac
switch (PHP_OS) {
case 'Darwin':
$absolute = stripos($pathFileName, DIRECTORY_SEPARATOR) === 0 ?: false;
break;
case 'linux':
default:
if (stripos(PHP_OS, 'WIN') !== false) {
$absolute = substr($pathFileName, 1, 1) === ':' ?: false;
} else {
$absolute = stripos($pathFileName, DIRECTORY_SEPARATOR) === 0 ?: false;
}
break;
}
return $absolute;
}
/**
* 获取真实路径
* @Author lang
* @Email : 732853989@qq.com
* Date: 2023/5/2
* Time: 上午9:35
* @param $path
* @return false|string
*/
public function getRealRoute($path)
{
$isAbsolute = $this->isAbsolute($path);
if ($this->isCli() && !$isAbsolute) {
throw new PosterException('For cli environment, please pass the absolute path');
}
return !$isAbsolute ? $this->getDocumentRoot() . $path : realpath($path);
}
public function isCli()
{
return php_sapi_name() === 'cli';
}
/**
* 检查文件是否存在并创建
* @Author lang
* @Date 2020-08-14T15:32:04+0800
* @param string $pathname 路径名称
*/
public function dirExists($pathname)
{
if (!file_exists($this->path . $pathname)) {
mkdir($this->path . $pathname, 0777, true);
}
}
/**
* 生成二维码
* @Author lang
* @Date 2020-10-14T10:59:28+0800
* @param [type] $text [二维码包含的内容可以是链接、文字、json字符串等等]
* @param [type] $outfile [默认为false不生成文件只将二维码图片返回输出否则需要给出存放生成二维码图片的文件名及路径]
* @param [type] $level [容错级别默认为L]
* 可传递的值分别是L(QR_ECLEVEL_L7%)、M(QR_ECLEVEL_M15%)、Q(QR_ECLEVEL_Q25%)、H(QR_ECLEVEL_H30%)
* 这个参数控制二维码容错率,不同的参数表示二维码可被覆盖的区域百分比,也就是被覆盖的区域还能识别
* @param [type] $size [控制生成图片的大小默认为4]
* @param [type] $margin [控制生成二维码的空白区域大小]
* @param [type] $saveAndPrint [保存二维码图片并显示出来,$outfile必须传递图片路径]
* @return array|void
*/
public function createQr($text, $outfile, $level, $size, $margin, $saveAndPrint)
{
if ($outfile) {
$this->setPath($outfile);
$this->setPathName($outfile);
$this->dirExists($this->pathname);
\QRcode::png($text, $this->path . $outfile, $level, $size, $margin, $saveAndPrint);
return ['url' => $outfile];
} else {
\QRcode::png($text, $outfile, $level, $size, $margin, $saveAndPrint);
}
}
/**
* 计算画布X轴位置
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/18
* Time: 16:10
* @return int
*/
protected function calcDstX($dst_x, $imWidth, $bgWidth)
{
if ($dst_x == '0') {
return $dst_x;
} elseif ($dst_x === 'center') {
$dst_x = ceil(($imWidth - $bgWidth) / 2);
} elseif (is_numeric($dst_x) && $dst_x < 0) {
$dst_x = ceil($imWidth + $dst_x);
} elseif (is_array($dst_x)) {
if ($dst_x[0] == 'center') {
$dst_x = ceil(($imWidth - $bgWidth) / 2) + $dst_x[1];
}
} elseif (strpos($dst_x, '%') !== false) {
if (substr($dst_x, 0, strpos($dst_x, '%')) < 0) {
$dst_x = ceil($imWidth + ($imWidth * substr($dst_x, 0, strpos($dst_x, '%')) / 100));
} else {
$dst_x = ceil($imWidth * substr($dst_x, 0, strpos($dst_x, '%')) / 100);
}
}
return $dst_x;
}
/**
* 计算画布Y轴位置
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/18
* Time: 16:10
* @return int
*/
protected function calcDstY($dst_y, $imHeight, $bgHeight)
{
if ($dst_y == '0') {
return $dst_y;
} elseif ($dst_y == 'center') {
$dst_y = ceil(($imHeight - $bgHeight) / 2);
} elseif (is_numeric($dst_y) && $dst_y < 0) {
$dst_y = ceil($imHeight + $dst_y);
} elseif (is_array($dst_y)) {
if ($dst_y[0] == 'center') {
$dst_y = ceil(($imHeight - $bgHeight) / 2) + $dst_y[1];
}
} elseif (strpos($dst_y, '%') !== false) {
if (substr($dst_y, 0, strpos($dst_y, '%')) < 0) {
$dst_y = ceil($imHeight + (($imHeight * substr($dst_y, 0, strpos($dst_y, '%'))) / 100));
} else {
$dst_y = ceil($imHeight * substr($dst_y, 0, strpos($dst_y, '%')) / 100);
}
}
return $dst_y;
}
/**
* 计算文字x轴坐标
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/2/13
* Time: 14:15
* @param $dst_x
* @param $fontBox
* @param null $x1
* @param null $x2
* @return false|float|int|mixed
*/
protected function calcTextDstX($dst_x, $calcFont, $x1 = null, $x2 = null)
{
$fontBoxWidth = $calcFont['text_width'];
$imWidth = ($x1 !== null && $x2 !== null) ?
($x2 - $x1)
: $this->im_w;
if ($dst_x === 'center') {
// 如果文字宽度大于 画布宽度 则为0
$dst_x = ceil(max(0, ($imWidth - $fontBoxWidth)) / 2);
} elseif (is_array($dst_x)) {
$dst_x[1] = isset($dst_x[1]) ? $dst_x[1] : 0;
$x1 = $x1 !== null ? $x1 : 0;
switch ($dst_x[0]) {
case 'center':
$dst_x = ceil(max(0, ($imWidth - $fontBoxWidth)) / 2) + $x1 + $dst_x[1];
break;
case 'left': // 左对齐 且 左右偏移
$dst_x = $x1 + $dst_x[1];
break;
case 'right': // 右对齐 且 左右偏移
$dst_x = ceil(($imWidth - $fontBoxWidth)) + $x1 + $dst_x[1];
break;
case 'custom': // 设置 自定义宽度居中 ['custom', 'center|top|bottom', $x1, $x2, $offset] $x1 区间起点宽度 $x2 区间终点宽度 $offset 偏移
$custom = [$dst_x[1], isset($dst_x[4]) ? $dst_x[4] : 0];
$dst_x = $this->calcTextDstX($custom, $calcFont, $dst_x[2], $dst_x[3]);
break;
default:
$dst_x = 0;
}
} elseif (strpos($dst_x, '%') !== false) {
if (substr($dst_x, 0, strpos($dst_x, '%')) < 0) {
$dst_x = ceil($imWidth + ($imWidth * substr($dst_x, 0, strpos($dst_x, '%')) / 100));
} else {
$dst_x = ceil($imWidth * substr($dst_x, 0, strpos($dst_x, '%')) / 100);
}
}
return $dst_x;
}
/**
* 计算文字y轴坐标
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/2/13
* Time: 14:14
* @param $dst_y
* @param $fontBox
* @param null $y1
* @param null $y2
* @return false|float|int|mixed
*/
protected function calcTextDstY($dst_y, $calcFont, $y1 = null, $y2 = null)
{
$fontBoxHeight = $calcFont['text_height']; // 文字加换行数的高度
$imHeight = ($y1 !== null && $y2 !== null) ?
($y2 - $y1)
: $this->im_h;
if ($dst_y === 'center') {
$dst_y = ceil(max(0, ($imHeight / 2) + ($fontBoxHeight / 2) - $fontBoxHeight));
} elseif (is_array($dst_y)) {
$dst_y[1] = isset($dst_y[1]) ? $dst_y[1] : 0;
$y1 = $y1 !== null ? $y1 : 0;
switch ($dst_y[0]) {
case 'center':
$dst_y = ceil(max(0, ($imHeight / 2) + ($fontBoxHeight / 2) - $fontBoxHeight)) + $y1 + $dst_y[1];
break;
case 'top': // 顶对齐 且 上下偏移
$dst_y = $y1 + $dst_y[1];
break;
case 'bottom': // 底对齐 且 上下偏移
$dst_y = ceil(($imHeight - $fontBoxHeight)) + $y1 + $dst_y[1];
break;
case 'custom': // 设置 自定义高度居中 ['custom', 'center|top|bottom', $y1, $y2, $offset] $y1 区间起点高度 $y2 区间终点高度 $offset 偏移
$custom = [$dst_y[1], isset($dst_y[4]) ? $dst_y[4] : 0];
$dst_y = $this->calcTextDstY($custom, $calcFont, $dst_y[2], $dst_y[3]);
break;
default:
$dst_y = 0;
}
} elseif (strpos($dst_y, '%') !== false) {
if (substr($dst_y, 0, strpos($dst_y, '%')) < 0) {
$dst_y = ceil($imHeight + (($imHeight * substr($dst_y, 0, strpos($dst_y, '%'))) / 100));
} else {
$dst_y = ceil($imHeight * substr($dst_y, 0, strpos($dst_y, '%')) / 100);
}
}
return $dst_y;
}
/**
* 计算加粗绘画
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 16:46
* @param int $i 当前循环次数
* @param int $weight 循环次数
* @param int $fontSize 字体大小
* @param int $dst_x x 位置
* @param int $dst_y y 位置
* @return array|float[]
*/
protected function calcWeight($i, $weight, $fontSize, $dst_x, $dst_y)
{
if ($weight % 2 == 0 && $i > 0) {
$really_dst_x = $dst_x + ($i * 0.25);
$really_dst_y = $dst_y + $fontSize;
} elseif ($weight % 2 != 0 && $i > 0) {
$really_dst_x = $dst_x;
$really_dst_y = $dst_y + $fontSize + ($i * 0.25);
} else {
$really_dst_x = $dst_x;
$really_dst_y = $dst_y + $fontSize;
}
return [$really_dst_x, $really_dst_y];
}
public function run($item, Driver $driver)
{
switch ($item['type']) {
case 'im':
$driver->Im(...$item['params']);
break;
case 'imDst':
$driver->ImDst(...$item['params']);
break;
case 'bg':
$driver->Bg(...$item['params']);
break;
case 'config':
$driver->setConfig($item['params']);
break;
case 'path':
$driver->setFilePath($item['params']);
break;
case 'image':
$driver->CopyImage(...$item['params']);
break;
case 'text':
$driver->CopyText(...$item['params']);
break;
case 'line':
$driver->CopyLine(...$item['params']);
break;
case 'arc':
$driver->CopyArc(...$item['params']);
break;
case 'qrs':
$driver->CopyQr(...$item['params']);
break;
case 'qr':
$driver->result = $driver->createQr(...$item['params']);
break;
case 'crop':
$driver->crop(...$item['params']);
break;
}
}
/**
* 根据传值类型获取四个角的半径
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/11/12
* Time: 15:39
* @param $radius string|array|integer '20 10' [20, 10] 10
* @param $len
* @return false[]|float[]
*/
protected function getRadiusType($radius, $len)
{
if (is_string($radius)) {
// 把字符串格式转数组
$radius = preg_replace('~\s+~', ' ', trim($radius, ' '));
$radius = explode(' ', $radius);
} elseif (is_numeric($radius)) {
// 整形转数组
$radius = [$radius, $radius, $radius, $radius];
} else {
if (!is_array($radius)) {
throw new PosterException('圆角参数类型错误');
}
}
// [20] 四个角
// [20,30] 第一个值 左上 右下 第二个值 右上 左下
// [20,30,20] 第一个值 左上 第二个值 右上 左下 第三个值 右下
// [20,30,20,10] 左上 右上 右下 左下
$radiusCount = count($radius);
if ($radiusCount == 1) {
$leftTopRadius = $this->getMaxRadius($len, $radius[0]);
$rightTopRadius = $this->getMaxRadius($len, $radius[0]);
$leftBottomRadius = $this->getMaxRadius($len, $radius[0]);
$rightBottomRadius = $this->getMaxRadius($len, $radius[0]);
} elseif ($radiusCount == 2) {
$leftTopRadius = $this->getMaxRadius($len, $radius[0]);
$rightBottomRadius = $this->getMaxRadius($len, $radius[0]);
$rightTopRadius = $this->getMaxRadius($len, $radius[1]);
$leftBottomRadius = $this->getMaxRadius($len, $radius[1]);
} elseif ($radiusCount == 3) {
$leftTopRadius = $this->getMaxRadius($len, $radius[0]);
$rightTopRadius = $this->getMaxRadius($len, $radius[1]);
$leftBottomRadius = $this->getMaxRadius($len, $radius[1]);
$rightBottomRadius = $this->getMaxRadius($len, $radius[2]);
} else {
$leftTopRadius = $this->getMaxRadius($len, $radius[0]);
$rightTopRadius = $this->getMaxRadius($len, $radius[1]);
$leftBottomRadius = $this->getMaxRadius($len, $radius[2]);
$rightBottomRadius = $this->getMaxRadius($len, $radius[3]);
}
return [$leftTopRadius, $rightTopRadius, $leftBottomRadius, $rightBottomRadius];
}
/**
* 获取最大圆角半径
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/11/12
* Time: 15:14
* @param $len
* @param $radius
* @return false|float
*/
protected function getMaxRadius($len, $radius)
{
return $radius < $len ? floor($radius) : floor($len);
}
/**
* 获取图片信息
* Author: lang
* Date: 2024/3/12
* Time: 11:08
* @return mixed
*/
public function getImInfo()
{
return [
'type' => $this->type,
'width' => $this->im_w,
'height' => $this->im_h,
];
}
public function domHtml($content)
{
$dom = new \DOMDocument();
$dom->loadHTML('<?xml encoding="UTF-8">' . $content);
$xpath = new \DOMXPath($dom);
// 获取所有的 h1 标签
$spanTags = $xpath->query('//span');
$textNodes = $xpath->query("//text()");
// 遍历 h1 标签并输出内容
foreach ($spanTags as $tag) {
// echo $tag->nodeValue; // 输出标签内的文本内容
$style = $tag->getAttribute('style'); // 获取 style 属性值
$styles = explode(';', $style);
foreach ($styles as $style) {
$styleParts = explode(':', $style);
$property = trim($styleParts[0]);
$value = trim($styleParts[1]);
if ($property === 'color') {
echo $value; // 输出 color 值
}
}
echo $style;
echo $tag->nodeValue;
}
// 输出选中节点的文本内容
foreach ($textNodes as $node) {
echo $node->nodeValue;
}
print_r($textNodes);
exit;
}
public function getStyleAttr($style, $type = 'color')
{
$attr = '';
$styles = explode(';', $style);
foreach ($styles as $style) {
$styleParts = explode(':', $style);
$property = trim($styleParts[0]);
if (isset($styleParts[1])) {
$value = trim($styleParts[1]);
} else {
$value = '';
}
if ($property === $type) {
$attr = $value; // 输出属性值
}
}
return $attr;
}
/**
* 获取单个字属性
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/6/2
* Time: 15:38
* @param string $content
* @param string $color
* @param int $w
* @return array
*/
public function getLetterArr($content = "\n", $color = '', $w = 0)
{
return [
'color' => $color,
'w' => $w,
'value' => $content
];
}
/**
* 获取内容
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/6/2
* Time: 15:37
* @param $letter
* @param $content
* @param string $color
*/
public function getNodeValue(&$letter, $content, $color = '')
{
$contents = $this->getBrNodeValue($content);
foreach ($contents as $k => $v) {
if ($v != '') {
if (isset($contents[$k - 1])) {
$letter[] = $this->getLetterArr();
}
for ($i = 0; $i < mb_strlen($v); $i++) {
$letter[] = $this->getLetterArr(mb_substr($v, $i, 1), $color);
}
} else {
if ($k != 0) {
$letter[] = $this->getLetterArr();
}
}
}
}
/**
* 匹配换行符
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/6/2
* Time: 15:37
* @param $content
* @return array|false|string[]
*/
public function getBrNodeValue($content)
{
$pattern = '/<br>|<br\/>/i';
// 分割字符串
return preg_split($pattern, $content, -1, 2);
}
}

View File

@ -0,0 +1,288 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:08
*/
namespace Kkokk\Poster\Image\Drivers;
interface DriverInterface
{
/**
* 获取文件路径
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:23
* @param string $path
* @return array
*/
public function getData($path = '');
/**
* 输出流
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:23
* @return resource
*/
public function getStream();
/**
* 获取base64文件
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:23
* @return string
*/
public function getBaseData();
/**
* 设置图片
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:24
* @return array|\Kkokk\Poster\Exception\PosterException
*/
public function setData();
/**
* 获取 Im 实例
* User: lang
* Date: 2023/8/10
* Time: 16:16
* @return mixed
*/
public function getIm();
/**
* 创建画布
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:30
* @param int $w
* @param int $h
* @param array $rgba
* @param boolean $alpha
* @return void
*/
public function Im($w, $h, $rgba = [255, 255, 255, 1], $alpha = false);
/**
* 以指定资源创建画布
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:31
* @param string $source
* @param int $w
* @param int $h
* @return void
*/
public function ImDst($source, $w = 0, $h = 0);
/**
* 创建背景
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:40
* @param int $w
* @param int $h
* @param array $rgba
* @param false $alpha
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param array $query
* @return void
*/
public function Bg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $query = []);
/**
* 复制图片
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:41
* @param string|array $src
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param int $src_w
* @param int $src_h
* @param false $alpha
* @param string $type
* @return void
*/
public function CopyImage(
$src,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0,
$alpha = false,
$type = 'normal'
);
/**
* 复制文字
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:41
* @param string $content
* @param int $dst_x
* @param int $dst_y
* @param int $fontSize
* @param array $rgba
* @param int $max_w
* @param string $font
* @param int $weight
* @param int $space
* @param int $angle
* @return void
*/
public function CopyText(
$content,
$dst_x = 0,
$dst_y = 0,
$fontSize = null,
$rgba = null,
$max_w = null,
$font = null,
$weight = null,
$space = null,
$angle = null
);
/**
* 复制线
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:44
* @param int $x1
* @param int $y1
* @param int $x2
* @param int $y2
* @param array $rgba
* @param string $type
* @param int $weight
* @return void
*/
public function CopyLine($x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $rgba = [], $type = '', $weight = 1);
/**
* 复制圆弧
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:45
* @param int $cx
* @param int $cy
* @param int $w
* @param int $h
* @param int $s
* @param int $e
* @param array $rgba
* @param string $type
* @param string $style
* @param int $weight
* @return void
*/
public function CopyArc(
$cx = 0,
$cy = 0,
$w = 0,
$h = 0,
$s = 0,
$e = 0,
$rgba = [],
$type = '',
$style = '',
$weight = 1
);
/**
* 复制二维码
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:47
* @param string $text
* @param int $size
* @param int $margin
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param int $src_w
* @param int $src_h
* @return mixed
*/
public function CopyQr(
$text,
$level = 'L',
$size = 4,
$margin = 1,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0
);
/**
* 执行画图
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:48
* @param array $query
* @param Driver|null $driver
* @return Driver
*/
public function execute($query = [], Driver $driver = null);
/**
* 裁剪
* Author: lang
* Date: 2024/3/12
* Time: 11:22
* @param $x
* @param $y
* @param $width
* @param $height
*/
public function crop($x = 0, $y = 0, $width = 0, $height = 0);
/**
* 获取流
* Author: lang
* Date: 2024/3/12
* Time: 15:11
* @return mixed
*/
public function blob();
/**
* 保存到临时文件并返回路径
* User: lang
* Date: 2024/3/12
* Time: 15:28
* @return mixed
*/
public function tmp();
}

View File

@ -0,0 +1,866 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 11:30
*/
namespace Kkokk\Poster\Image\Drivers;
use Kkokk\Poster\Common\Common;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Image\Traits\GdTrait;
class GdDriver extends Driver implements DriverInterface
{
use GdTrait;
private $common;
function __construct()
{
$this->common = new Common();
}
public function getData($path = '')
{
if ($path) {
$this->setFilePath($path);
}
return $this->returnImage($this->type);
}
public function getStream()
{
return $this->returnImage($this->type, false);
}
public function getBaseData()
{
return $this->common->baseData($this->im, $this->type);
}
public function setData()
{
return $this->setImage($this->source);
}
public function getIm()
{
return $this->im;
}
public function blob()
{
return $this->getBlob($this->type, $this->im);
}
public function tmp()
{
return $this->getTmp($this->type, $this->im);
}
/**
* 创建指定宽高,颜色,透明的画布
*/
public function Im($w, $h, $rgba = [255, 255, 255, 1], $alpha = false)
{
$this->im_w = $w;
$this->im_h = $h;
$this->im = $this->createIm($w, $h, $rgba, $alpha);
}
/**
* 创建指定图片为画布 宽高,颜色,透明的画布
*/
public function ImDst($source, $w = 0, $h = 0)
{
$this->source = $source;
list($cut, $bgWidth, $bgHeight) = $this->createImage($source);
//设定水印图像的混色模式
imagealphablending($cut, true);
if (!empty($w) && !empty($h)) {
$this->im_w = $w;
$this->im_h = $h;
$circle_new = $this->createIm($w, $h, [255, 255, 255, 127], true);
imagecopyresized($circle_new, $cut, 0, 0, 0, 0, $w, $h, $bgWidth, $bgHeight);
$cut = $circle_new;
// $this->destroyImage($circle_new);
} else {
$this->im_w = $bgWidth;
$this->im_h = $bgHeight;
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], true);
imagecopy($circle_new, $cut, 0, 0, 0, 0, $bgWidth, $bgHeight);
$cut = $circle_new;
}
$this->im = $cut;
}
/**
* 创建背景
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/25
* Time: 17:55
* @param int $w
* @param int $h
* @param array $rgba 背景颜色
* @param false $alpha 是否透明
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param array $query
*/
public function Bg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $query = [])
{
// 判断颜色是否渐变
$rgbaColor = isset($rgba['color']) ? $rgba['color'] : [[0, 0, 0]];
$alphas = isset($rgba['alpha']) ? $rgba['alpha'] : 1;
$to = isset($rgba['to']) ? $rgba['to'] : 'bottom';
$radius = isset($rgba['radius']) ? $rgba['radius'] : 0;
$contentAlpha = isset($rgba['content_alpha']) ? $rgba['content_alpha'] : false;
$rgbaCount = count($rgbaColor);
// im不存在则创建
if (empty($this->im)) {
$this->Im($w, $h, [], $alpha);
}
// 渐变处理->直接处理im
// 计算颜色方向
$pic = $this->createIm($w, $h, [], $alpha);
$this->calcColorDirection($pic, $rgbaColor, $rgbaCount, $to, $w, $h);
// 设置透明度,内容不透明
if ($alpha && !$contentAlpha) {
$pic = $this->setImageAlpha($pic, $w, $h, $alphas);
}
$dst_x = $this->calcDstX($dst_x, $this->im_w, $w);
$dst_y = $this->calcDstY($dst_y, $this->im_h, $h);
if (!empty($query)) {
$that = clone $this;
$that->im = $pic;
$that->im_w = $w;
$that->im_h = $h;
$that->execute($query, $that);
}
// 设置透明度内容也透明
if ($alpha && $contentAlpha) {
$pic = $this->setImageAlpha($pic, $w, $h, $alphas);
}
// 如果设置了圆角则画圆角
if ($radius) {
$pic = $this->setPixelRadius($pic, $w, $h, $radius);
}
imagecopy($this->im, $pic, $dst_x, $dst_y, $src_x, $src_y, $w, $h);
if (isset($pic) && is_resource($pic)) {
$this->destroyImage($pic);
}
if (isset($mask) && is_resource($mask)) {
$this->destroyImage($mask);
}
unset($rgbaCount);
unset($rgbaColor);
unset($alphas);
unset($to);
unset($w);
unset($h);
unset($rgba);
unset($alpha);
unset($dst_x);
unset($dst_y);
unset($src_x);
unset($src_y);
}
public function getRotatedPoints($x, $y, $angle)
{
$theta = deg2rad($angle);
$cs = cos($theta);
$sn = sin($theta);
$newX = $x * $cs - $y * $sn;
$newY = $x * $sn + $y * $cs;
return array($newX, $newY);
}
public function matrix_multiply($matrix1, $matrix2)
{
$result = array();
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
$sum = 0;
for ($k = 0; $k < 3; $k++) {
$sum += $matrix1[$i][$k] * $matrix2[$k][$j];
}
$result[$i][$j] = $sum;
}
}
return $result;
}
/**
* 创建图片,合并到画布,释放内存
*/
public function CopyImage(
$src,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0,
$alpha = false,
$type = 'normal'
) {
$angle = 0;
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
if (is_array($src)) {
$angle = isset($src['angle']) ? $src['angle'] : 0;
$src = isset($src['src']) ? $src['src'] : '';
if (empty($src)) {
throw new PosterException('image resources cannot be empty (' . $src . ')');
}
}
list($pic, $Width, $Height) = $this->createImage($src);
$bgWidth = !empty($src_w) ? $src_w : $Width;
$bgHeight = !empty($src_h) ? $src_h : $Height;
switch ($type) {
case 'normal':
# 自定义宽高的时候
if (!empty($src_w) && !empty($src_h)) {
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
// $circle_new_white = imagecolorallocatealpha($circle_new, 255, 255, 255, 127);
// imagecolortransparent($circle_new,$circle_new_white);
// imagefill($circle_new, 0, 0, $circle_new_white);
$w_circle_new = $bgWidth;
$h_circle_new = $bgHeight;
# 按比例缩放
imagecopyresized($circle_new, $pic, 0, 0, 0, 0, $w_circle_new, $h_circle_new, $Width, $Height);
$pic = $circle_new;
}
break;
case 'circle':
$circle = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
$w_circle = $bgWidth;
$h_circle = $bgHeight;
# 按比例缩放
imagecopyresized($circle_new, $pic, 0, 0, 0, 0, $w_circle, $h_circle, $Width, $Height);
$r = ($w_circle / 2); //圆半径
for ($x = 0; $x < $w_circle; $x++) {
for ($y = 0; $y < $h_circle; $y++) {
$rgbColor = imagecolorat($circle_new, $x, $y);
// $thisColor = imagecolorsforindex($circle_new, $rgbColor); // imagecolorallocatealpha
if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) {
imagesetpixel($circle, $x, $y, $rgbColor);
}
// $newR = $r - 0.5;
// if (((($x - $newR) * ($x - $newR) + ($y - $newR) * ($y - $newR)) == ($newR * $newR))) {
// imagesetpixel($circle, $x + 1, $y, $rgbColor);
// imagesetpixel($circle, $x, $y + 1, $rgbColor);
// imagesetpixel($circle, $x + 1, $y + 1, $rgbColor);
// }
}
}
$pic = $circle;
break;
default:
# code...
break;
}
# 处理目标 x 轴
$dst_x = $this->calcDstX($dst_x, $this->im_w, $bgWidth);
# 处理目标 y 轴
$dst_y = $this->calcDstY($dst_y, $this->im_h, $bgHeight);
# 处理旋转
if ($angle > 0) {
$pic = imagerotate($pic, abs($angle % 360 - 360), $this->createColorAlpha($this->im));
//获取旋转后的宽高
$newWidth = imagesx($pic);
$newHeight = imagesy($pic);
if (empty($src_w)) {
$bgWidth = $newWidth;
} else {
if ($newWidth != $newHeight) {
$bgWidth = $newWidth;
}
$src_x = ceil(($newWidth - $bgWidth) / 2);
}
if (empty($src_h)) {
$bgHeight = $newHeight;
} else {
if ($newWidth != $newHeight) {
$bgHeight = $newHeight;
}
$src_y = ceil(($newHeight - $bgHeight) / 2);
}
}
//整合海报
imagecopy($this->im, $pic, $dst_x, $dst_y, $src_x, $src_y, $bgWidth, $bgHeight);
if (isset($pic) && is_resource($pic)) {
$this->destroyImage($pic);
}
if (isset($circle) && is_resource($circle)) {
$this->destroyImage($circle);
}
if (isset($circle_new) && is_resource($circle_new)) {
$this->destroyImage($circle_new);
}
unset($path);
unset($bgWidth);
unset($bgHeight);
unset($bgType);
}
/**
* 合并图片
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 9:52
* @param $src
* @param $dst_x
* @param $dst_y
* @param $src_x
* @param $src_y
* @param $src_w
* @param $src_h
* @param false $alpha
* @param string $type
* @throws PosterException
*/
public function CopyMergeImage(
$src,
$dst_x,
$dst_y,
$src_x,
$src_y,
$src_w,
$src_h,
$alpha = false,
$type = 'normal'
) {
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
if (strpos($src, 'http') === false) {
$src = $this->getRealRoute($src);
}
list($Width, $Height, $bgType) = @getimagesize($src);
$bgType = image_type_to_extension($bgType, false);
if (empty($bgType)) {
throw new PosterException('image resources cannot be empty (' . $src . ')');
}
// if ($bgType == 'gif') {
// $pic = imagecreatefromstring(file_get_contents($src));
// } else {
//
// $fun = 'imagecreatefrom' . $bgType;
// $pic = @$fun($src);
// }
$fun = 'imagecreatefrom' . $bgType;
$pic = @$fun($src);
$bgWidth = !empty($src_w) ? $src_w : $Width;
$bgHeight = !empty($src_h) ? $src_h : $Height;
switch ($type) {
case 'normal':
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
//整合水印
imagecopy($circle_new, $pic, 0, 0, 0, 0, $bgWidth, $bgWidth);
# 自定义宽高的时候
if (!empty($src_w) && !empty($src_h)) {
// $circle_new_white = imagecolorallocatealpha($circle_new, 255, 255, 255, 127);
// imagecolortransparent($circle_new,$circle_new_white);
// imagefill($circle_new, 0, 0, $circle_new_white);
$w_circle_new = $bgWidth;
$h_circle_new = $bgHeight;
# 按比例缩放
imagecopyresized($circle_new, $pic, 0, 0, 0, 0, $w_circle_new, $h_circle_new, $Width, $Height);
$pic = $circle_new;
}
break;
case 'circle':
$circle = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
$w_circle = $bgWidth;
$h_circle = $bgHeight;
# 按比例缩放
imagecopyresized($circle_new, $pic, 0, 0, 0, 0, $w_circle, $h_circle, $Width, $Height);
$r = ($w_circle / 2); //圆半径
for ($x = 0; $x < $w_circle; $x++) {
for ($y = 0; $y < $h_circle; $y++) {
$rgbColor = imagecolorat($circle_new, $x, $y);
if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) {
imagesetpixel($circle, $x, $y, $rgbColor);
}
}
}
$pic = $circle;
break;
default:
# code...
break;
}
//整合水印
imagecopymerge($this->im, $pic, $dst_x, $dst_y, $src_x, $src_y, $bgWidth, $bgHeight, 100);
if (isset($circle) && is_resource($circle)) {
$this->destroyImage($circle);
}
if (isset($circle_new) && is_resource($circle_new)) {
$this->destroyImage($circle_new);
}
}
/**
* 合并文字
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/2/13
* Time: 15:33
* @param $content
* @param $dst_x
* @param $dst_y
* @param $fontSize
* @param $rgba
* @param int $max_w
* @param string $font
* @param int $weight
* @param int $space
* @return bool
* @throws PosterException
*/
public function CopyText(
$content,
$dst_x = 0,
$dst_y = 0,
$fontSize = null,
$rgba = null,
$max_w = null,
$font = null,
$weight = null,
$space = null,
$angle = null
) {
if ($content == '') {
return true;
}
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
$fontSize = $fontSize ?: $this->font_size;
$rgba = $rgba ?: $this->font_rgba;
$max_w = $max_w ?: $this->font_max_w;
$weight = $weight ?: $this->font_weight;
$space = $space ?: $this->font_space;
$angle = $angle ?: $this->font_angle;
if (!empty($font)) {
$font = $this->getRealRoute($font);
} else {
$font = $this->font;
}
$calcSpace = $space > $fontSize ? ($space - $fontSize) : 0; // 获取间距计算值
$fontSize = ($fontSize * 3) / 4; // px 转化为 pt
$color = $this->createColorAlpha($this->im, $rgba);
mb_internal_encoding('UTF-8'); // 设置编码
$max_ws = $this->im_w;
if (isset($max_w) && !empty($max_w)) {
$max_ws = $max_w;
}
// 这几个变量分别是 字体大小, 角度, 字体名称, 字符串, 预设宽度
$contents = '';
$contentsArr = [];
$letter = [];
$line = 1;
$calcSpaceRes = 0;
// 主动设置是否解析html标签
if (is_array($content)) {
if (!isset($content['type'])) {
throw new PosterException('type is required');
}
if (!isset($content['content'])) {
throw new PosterException('content is required');
}
$type = $content['type'];
$content = $content['content'];
// 确认包含才处理
if ($type == 'html' && preg_match('/<[^>]*>/', $content)) {
// 正则匹配 span 属性
$pattern = '/<span style="(.*?)">(.*?)<\/span>/i';
// 分割字符串
$matches = preg_split($pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i = 0; $i < count($matches); $i += 3) {
if (!empty($matches[$i])) {
$this->getNodeValue($letter, $matches[$i], $color);
}
if (isset($matches[$i + 1])) {
$style = $matches[$i + 1];
$colorValue = $this->getStyleAttr($style);
$colorCustom = $this->createColorAlpha($this->im,
$this->common->getNodeStyleColor($colorValue));
$this->getNodeValue($letter, $matches[$i + 2], $colorCustom);
}
}
} else {
$this->getNodeValue($letter, $content, $color);
}
$normalSize = imagettfbbox($fontSize, 0, $font, '好');
$punctuationSize = imagettfbbox($fontSize, 0, $font, '');
// 计算标点符号的水平偏移量
$horizontalOffset = abs($punctuationSize[2] - $normalSize[2]) / 2;
$textWidthArr = [];
foreach ($letter as $l) {
$textStr = $contents . $l['value'];
$fontBox = imagettfbbox($fontSize, $angle, $font, $textStr);
$textWidth = abs($fontBox[2]) + $calcSpaceRes + 2;
if (preg_match('/[\x{3002}\x{ff0c}\x{ff1f}\x{ff01}\x{ff1a}\x{ff1b}]/u', $l['value'])) {
$textWidth += $horizontalOffset;
}
if ($l['value'] == "\n") {
$contents = "";
$contentsArr[] = $this->getLetterArr();
$line++;
continue;
}
if (!isset($textWidthArr[$line])) {
$textWidthArr[$line] = -$space / 2;
}
if (($textWidth > $max_ws || $textWidthArr[$line] > $max_ws) && ($contents !== '')) {
// 判断拼接后的字符串是否超过预设的宽度
$contents = "";
$contentsArr[] = $this->getLetterArr();
$line++;
$textWidthArr[$line] = -$space / 2;
}
$contents .= $l['value'];
$fontBox1 = imagettfbbox($fontSize, $angle, $font, $l['value']);
$l['w'] = abs($fontBox1[2]) + $calcSpace + 2;
if (preg_match('/[\x{3002}\x{ff0c}\x{ff1f}\x{ff01}\x{ff1a}\x{ff1b}]/u', $l['value'])) {
$l['w'] += $horizontalOffset;
}
$textWidthArr[$line] += $l['w'];
$contentsArr[] = $l;
$line === 1 && $calcSpaceRes += $calcSpace;
}
$calcFont = [
'text_width' => max(array_values($textWidthArr)), // 取最宽行宽
'text_height' => abs($fontBox[1] - $fontBox[7]),
];
$dst_x = $this->calcTextDstX($dst_x, $calcFont);
$dst_y = $this->calcTextDstY($dst_y, $calcFont);
# 自定义间距
$this->fontWeightArr($weight, $fontSize, $angle, $dst_x, $dst_y, $color, $font, $contentsArr);
return true;
} else {
// 将字符串拆分成一个个单字 保存到数组 letter 中
for ($i = 0; $i < mb_strlen($content); $i++) {
$letter[] = mb_substr($content, $i, 1);
}
$textWidthArr = [];
$contentStr = '';
foreach ($letter as $l) {
$textStr = $contentStr . $l;
$fontBox = imagettfbbox($fontSize, $angle, $font, $textStr);
$textWidth = abs($fontBox[2]) + $calcSpaceRes;
$textWidthArr[$line] = $textWidth;
// 判断拼接后的字符串是否超过预设的宽度
if (($textWidth > $max_ws) && ($contents !== '')) {
$contents .= "\n";
$contentStr = "";
$line++;
}
$contents .= $l;
$contentStr .= $l;
$line === 1 && $calcSpaceRes += $calcSpace;
$calcFont = [
'text_width' => max(array_values($textWidthArr)),
'text_height' => abs($fontBox[1] - $fontBox[7]),
];
}
$dst_x = $this->calcTextDstX($dst_x, $calcFont);
$dst_y = $this->calcTextDstY($dst_y, $calcFont);
# 自定义间距
if ($space > 0) {
$dst_x_old = $dst_x;
for ($j = 0; $j < mb_strlen($contents); $j++) {
$spaceStr = mb_substr($contents, $j, 1);
if ($spaceStr == "\n") {
$dst_x = $dst_x_old;
$dst_y += 1.75 * $fontSize;
continue;
}
$this->fontWeight($weight, $fontSize, $angle, $dst_x, $dst_y, $color, $font, $spaceStr);
$dst_x += $space;
}
} else {
$this->fontWeight($weight, $fontSize, $angle, $dst_x, $dst_y, $color, $font, $contents);
}
return true;
}
}
public function CopyLine($x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $rgba = [], $type = '', $weight = 1)
{
imagesetthickness($this->im, $weight); // 划线的线宽加粗
$color = $this->createColorAlpha($this->im, $rgba);
switch ($type) {
case 'rectangle':
imagerectangle($this->im, $x1, $y1, $x2, $y2, $color);
break;
case 'filled_rectangle':
case 'filledRectangle':
imagerectangle($this->im, $x1, $y1, $x2, $y2, $color);
imagefilledrectangle($this->im, $x1, $y1, $x2, $y2, $color);
break;
default:
imageline($this->im, $x1, $y1, $x2, $y2, $color);
break;
}
}
public function CopyArc(
$cx = 0,
$cy = 0,
$w = 0,
$h = 0,
$s = 0,
$e = 0,
$rgba = [],
$type = '',
$style = '',
$weight = 1
) {
imagesetthickness($this->im, $weight); // 划线的线宽加粗
$color = $this->createColorAlpha($this->im, $rgba);
switch ($type) {
case 'filled_arc':
case 'filledArc':
imagearc($this->im, $cx, $cy, $w, $h, $s, $e, $color);
$style = $style ?: IMG_ARC_PIE;
// IMG_ARC_PIE
// IMG_ARC_CHORD
// IMG_ARC_NOFILL
// IMG_ARC_EDGED
imagefilledarc($this->im, $cx, $cy, $w, $h, $s, $e, $color, $style);
break;
default:
imagearc($this->im, $cx, $cy, $w, $h, $s, $e, $color);
break;
}
}
/**
* 合并二维码
* @Author lang
* @Date 2020-10-14T14:40:51+0800
* @param [type] $text [description]
* @param [type] $size [description]
* @param [type] $margin [description]
* @param [type] $dst_x [description]
* @param [type] $dst_y [description]
* @param [type] $src_x [description]
* @param [type] $src_y [description]
*/
public function CopyQr(
$text,
$level = 'L',
$size = 4,
$margin = 1,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0
) {
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
$result = \QRcode::re_png($text, $level, $size, $margin);
if ($src_w > 0) {
$bgWidth = $src_w;
$Width = imagesx($result);
} else {
$bgWidth = imagesx($result);
}
if ($src_h > 0) {
$bgHeight = $src_h;
$Height = imagesy($result);
} else {
$bgHeight = imagesy($result);
}
# 处理目标 x 轴
$dst_x = $this->calcDstX($dst_x, $this->im_w, $bgWidth);
# 处理目标 y 轴
$dst_y = $this->calcDstY($dst_y, $this->im_h, $bgHeight);
# 自定义宽高的时候
if (!empty($src_w) && !empty($src_h)) {
$circle_new = $this->createIm($bgWidth, $bgHeight, [255, 255, 255, 127], $alpha = true);
// $circle_new_white = imagecolorallocatealpha($circle_new, 255, 255, 255, 127);
// imagecolortransparent($circle_new,$circle_new_white);
// imagefill($circle_new, 0, 0, $circle_new_white);
$w_circle_new = $bgWidth;
$h_circle_new = $bgHeight;
# 按比例缩放
imagecopyresized($circle_new, $result, 0, 0, 0, 0, $w_circle_new, $h_circle_new, $Width, $Height);
$result = $circle_new;
}
//整合海报
imagecopy($this->im, $result, $dst_x, $dst_y, $src_x, $src_y, $bgWidth, $bgHeight);
if (isset($circle_new) && is_resource($circle_new)) {
$this->destroyImage($circle_new);
}
if (isset($result) && is_resource($result)) {
$this->destroyImage($result);
}
}
/**
* 裁剪
* Author: lang
* Date: 2024/3/12
* Time: 11:22
* @param $x
* @param $y
* @param $width
* @param $height
*/
public function crop($x = 0, $y = 0, $width = 0, $height = 0)
{
$this->im = imagecrop($this->im, ['x' => $x, 'y' => $y, 'width' => $width, 'height' => $height]);
$this->im_w = $width;
$this->im_h = $height;
}
public function execute($query = [], Driver $driver = null)
{
if (empty($driver)) {
$driver = $this;
}
foreach ($query as $item) {
$driver->run($item, $driver);
}
return $driver;
}
/**
* 释放资源
* @Author lang
* @Date 2020-08-14T14:29:46+0800
* @param Resource
*/
protected function destroyImage($Resource)
{
!is_resource($Resource) || imagedestroy($Resource);
}
/**
* 析构方法,用于销毁图像资源
*/
public function __destruct()
{
empty($this->im) || !is_resource($this->im) || imagedestroy($this->im);
}
}

View File

@ -0,0 +1,632 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 11:31
*/
namespace Kkokk\Poster\Image\Drivers;
use Kkokk\Poster\Common\Common;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Image\Traits\ImagickTrait;
class ImagickDriver extends Driver implements DriverInterface
{
use ImagickTrait;
protected $ImagickDraw;
function __construct()
{
}
public function getData($path = '')
{
if ($path) {
$this->setFilePath($path);
}
$this->setDPI();
return $this->returnImage($this->type);
}
public function getStream()
{
$this->setDPI();
return $this->returnImage($this->type, false);
}
public function getBaseData()
{
$this->setDPI();
$common = new Common();
return $common->baseData($this->im->getImageBlob(), $this->type);
}
public function getIm()
{
return $this->im;
}
public function blob()
{
$this->setDPI();
return $this->getBlob($this->im);
}
public function tmp()
{
return $this->getTmp($this->type, $this->im);
}
public function setData()
{
$this->setDPI();
return $this->setImage($this->source);
}
public function im($w, $h, $rgba = [255, 255, 255, 1], $alpha = false)
{
$this->im_w = $w;
$this->im_h = $h;
$this->im = $this->createIm($w, $h, $rgba, $alpha);
}
public function ImDst($source, $w = 0, $h = 0)
{
$this->source = $source;
$pic = $this->createImagick($source);
$bgWidth = $pic->getImageWidth();
$bgHeight = $pic->getImageHeight();
$this->type = strtolower($pic->getImageFormat());
if (!empty($w) && !empty($h)) {
$this->im_w = $w;
$this->im_h = $h;
$pic->resizeImage($w, $h, $pic::FILTER_LANCZOS, 1, true);
} else {
$this->im_w = $bgWidth;
$this->im_h = $bgHeight;
}
$this->im = $pic;
}
public function Bg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, $query = [])
{
// 判断颜色是否渐变
$rgbaColor = isset($rgba['color']) ? $rgba['color'] : [[0, 0, 0]];
$alphas = isset($rgba['alpha']) ? $rgba['alpha'] : 1;
$to = isset($rgba['to']) ? $rgba['to'] : 'bottom';
$radius = isset($rgba['radius']) ? $rgba['radius'] : 0;
$contentAlpha = isset($rgba['content_alpha']) ? $rgba['content_alpha'] : false;
$rgbaCount = count($rgbaColor);
// im不存在则创建
if (empty($this->im)) {
$this->im($w, $h, [], $alpha);
}
// 渐变处理->直接处理im
// 计算颜色方向
$pic = $this->createIm($w, $h, [], $alpha);
$this->calcColorDirection($pic, $rgbaColor, $rgbaCount, $to, $w, $h);
// 设置透明度,内容不透明
if ($alpha && !$contentAlpha) {
$this->setImageAlpha($pic, $alphas);
}
$dst_x = $this->calcDstX($dst_x, $this->im_w, $w);
$dst_y = $this->calcDstY($dst_y, $this->im_h, $h);
if (!empty($query)) {
$that = clone $this;
$that->im = $pic;
$that->im_w = $w;
$that->im_h = $h;
$that->execute($query, $that);
// 合并图片, 合并图片移到下方,这里不需要再合并
// $pic->compositeImage($that->im, ($that->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
// 设置透明度,内容也透明
if ($alpha && $contentAlpha) {
$this->setImageAlpha($pic, $alphas);
}
if ($radius) {
// 圆角处理
$pic = $this->setPixelRadius($pic, $w, $h, $radius);
}
// 裁剪图片
$this->cropImage($pic, $src_x, $src_y);
// 合并图片
if ($this->type == 'gif') {
// 每帧长宽不一致问题, 水印会不一致
foreach ($this->im as $frame) {
$frame->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
} else {
$this->im->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
if ($that) {
unset($that);
}
$this->destroyImage($pic);
}
public function CopyImage(
$src,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0,
$alpha = false,
$type = 'normal'
) {
$angle = 0;
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
if (is_array($src)) {
$angle = isset($src['angle']) ? $src['angle'] : 0;
$src = isset($src['src']) ? $src['src'] : '';
if (empty($src)) {
throw new PosterException('image resources cannot be empty (' . $src . ')');
}
}
$pic = $this->createImagick($src);
$Width = $pic->getImageWidth();
$Height = $pic->getImageHeight();
$bgWidth = !empty($src_w) ? $src_w : $Width;
$bgHeight = !empty($src_h) ? $src_h : $Height;
switch ($type) {
case 'normal':
# 自定义宽高的时候
if (!empty($src_w) && !empty($src_h)) {
// $pic->resizeImage($bgWidth, $bgHeight, $pic::FILTER_LANCZOS, 1, true); // 等比缩放
$pic->scaleImage($bgWidth, $bgHeight);
}
break;
case 'circle':
if (!empty($src_w) && !empty($src_h)) {
// $pic->resizeImage($bgWidth, $bgHeight, $pic::FILTER_LANCZOS, 1, true); // 等比缩放
$pic->scaleImage($bgWidth, $bgHeight);
}
$pic->setImageFormat("png");
$pic->setImageMatte(true); // 激活遮罩通道
// 创建一个圆形遮罩图片
$mask = $this->createImagick();
$mask->newImage($bgWidth, $bgHeight, $this->createColorAlpha([255, 255, 255, 127]));
$circle = $this->createImagickDraw();
$circle->setFillColor($this->createColorAlpha([255, 255, 255, 1]));
$circle->circle($bgWidth / 2, $bgHeight / 2, $bgWidth / 2, $bgHeight);
$mask->drawImage($circle);
// 合并原始图片和圆形遮罩图片
$pic->compositeImage($mask, $pic::COMPOSITE_DSTIN, 0, 0);
$this->destroyImage($circle);
$this->destroyImage($mask);
break;
default:
# code...
break;
}
# 处理目标 x 轴
$dst_x = $this->calcDstX($dst_x, $this->im_w, $bgWidth);
# 处理目标 y 轴
$dst_y = $this->calcDstY($dst_y, $this->im_h, $bgHeight);
// 裁剪图片
$this->cropImage($pic, $src_x, $src_y);
# 处理旋转
if ($angle > 0) {
$pic->rotateimage($this->createColorAlpha(), $angle);
}
// 合并图片
if ($this->type == 'gif') {
// 每帧长宽不一致问题, 水印会不一致
foreach ($this->im as $frame) {
$frame->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
} else {
$this->im->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
$this->destroyImage($pic);
}
public function CopyText(
$content,
$dst_x = 0,
$dst_y = 0,
$fontSize = null,
$rgba = null,
$max_w = null,
$font = null,
$weight = null,
$space = null,
$angle = null
) {
if ($content == '') {
return true;
}
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
$fontSize = $fontSize ?: $this->font_size;
$rgba = $rgba ?: $this->font_rgba;
$max_w = $max_w ?: $this->font_max_w;
$weight = $weight ?: $this->font_weight;
$space = $space ?: $this->font_space;
$angle = $angle ?: $this->font_angle;
if (!empty($font)) {
$font = $this->getRealRoute($font);
} else {
$font = $this->font;
}
$calcSpace = $space > $fontSize ? ($space - $fontSize) : 0; // 获取间距计算值
$color = $this->createColorAlpha($rgba);
$max_ws = $this->im_w;
if (isset($max_w) && !empty($max_w)) {
$max_ws = $max_w;
}
// 这几个变量分别是 字体大小, 角度, 字体名称, 字符串, 预设宽度
$contents = '';
$letter = [];
$line = 1;
$calcSpaceRes = 0;
$draw = $this->createTextImagickDraw();
$draw->setFont($font);
$draw->setFillColor($color);
$draw->setFontSize($fontSize);
$fontSize = ($fontSize * 3) / 4; // 使和gd一致
// 主动设置是否解析html标签
if (is_array($content)) {
if (!isset($content['type'])) {
throw new PosterException('type is required');
}
if (!isset($content['content'])) {
throw new PosterException('content is required');
}
$type = $content['type'];
$content = $content['content'];
// 确认包含才处理
if ($type == 'html' && preg_match('/<[^>]*>/', $content)) {
// 正则匹配 span 属性
$pattern = '/<span style="(.*?)">(.*?)<\/span>/i';
// 分割字符串
$matches = preg_split($pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
$common = new Common();
for ($i = 0; $i < count($matches); $i += 3) {
if (!empty($matches[$i])) {
$this->getNodeValue($letter, $matches[$i], $color);
}
if (isset($matches[$i + 1])) {
$style = $matches[$i + 1];
$colorValue = $this->getStyleAttr($style);
$colorCustom = $this->createColorAlpha($common->getNodeStyleColor($colorValue));
$this->getNodeValue($letter, $matches[$i + 2], $colorCustom);
}
}
} else {
$this->getNodeValue($letter, $content, $color);
}
$textWidthArr = [];
foreach ($letter as $l) {
$textStr = $contents . $l['value'];
$fontBox = $this->im->queryFontMetrics($draw, $textStr);
$textWidth = abs($fontBox['textWidth'] + $fontBox['descender']) + $calcSpaceRes;
if ($l['value'] == "\n") {
$contents = "";
$contentsArr[] = $this->getLetterArr();
$line++;
continue;
}
if (!isset($textWidthArr[$line])) {
$textWidthArr[$line] = -$space / 2;
}
// 判断拼接后的字符串是否超过预设的宽度
if (($textWidth > $max_ws || $textWidthArr[$line] > $max_ws) && ($contents !== '')) {
$contents .= "\n";
$contentsArr[] = $this->getLetterArr();
$line++;
}
$contents .= $l['value'];
$fontBox1 = $this->im->queryFontMetrics($draw, $l['value']);
$l['w'] = abs($fontBox1['textWidth'] + $fontBox1['descender']) + $calcSpace;
$textWidthArr[$line] += $l['w'];
$contentsArr[] = $l;
$line === 1 && $calcSpaceRes += $calcSpace;
}
$calcFont = [
'text_width' => max(array_values($textWidthArr)), // 取最宽行宽
'text_height' => abs($fontBox[1] - $fontBox[7]),
];
$dst_x = $this->calcTextDstX($dst_x, $calcFont);
$dst_y = $this->calcTextDstY($dst_y, $calcFont);
# 自定义间距
$this->fontWeightArr($draw, $weight, $fontSize, $angle, $dst_x - 5, $dst_y, $contentsArr, $color);
return true;
} else {
// 将字符串拆分成一个个单字 保存到数组 letter 中
for ($i = 0; $i < mb_strlen($content); $i++) {
$letter[] = mb_substr($content, $i, 1);
}
foreach ($letter as $l) {
$textStr = $contents . $l;
$fontBox = $this->im->queryFontMetrics($draw, $textStr);
$textWidth = abs($fontBox['textWidth'] + $fontBox['descender']) + $calcSpaceRes;
// 判断拼接后的字符串是否超过预设的宽度
if (($textWidth > $max_ws) && ($contents !== '')) {
$contents .= "\n";
$line++;
}
$contents .= $l;
$line === 1 && $calcSpaceRes += $calcSpace;
}
$calcFont = [
'text_width' => $textWidth,
'text_height' => abs($fontBox['textHeight'] + $fontBox['descender']),
];
$dst_x = $this->calcTextDstX($dst_x, $calcFont) - $fontBox['descender']; // 调整和 gd 的误差值
$dst_y = $this->calcTextDstY($dst_y, $calcFont);
# 自定义间距
if ($space > 0) {
$dst_x_old = $dst_x;
for ($j = 0; $j < mb_strlen($contents); $j++) {
$spaceStr = mb_substr($contents, $j, 1);
if ($spaceStr == "\n") {
$dst_x = $dst_x_old;
$dst_y += 1.75 * $fontSize;
continue;
}
$this->fontWeight($draw, $weight, $fontSize, $angle, $dst_x - 5, $dst_y, $spaceStr);
$dst_x += $space;
}
} else {
$this->fontWeight($draw, $weight, $fontSize, $angle, $dst_x - 5, $dst_y, $contents);
}
}
}
public function CopyLine($x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $rgba = [], $type = '', $weight = 1)
{
$color = $this->createColorAlpha($rgba);
$draw = $this->createImagickDraw();
$draw->setStrokeColor($color);
$draw->setStrokeWidth($weight);
switch ($type) {
case 'rectangle':
$draw->setFillColor($this->createColorAlpha());
$draw->rectangle($x1, $y1, $x2, $y2);
break;
case 'filled_rectangle':
case 'filledRectangle':
$draw->rectangle($x1, $y1, $x2, $y2);
break;
default:
$draw->line($x1, $y1, $x2, $y2);
break;
}
if ($this->type == 'gif') {
// 每帧长宽不一致问题, 水印会不一致
foreach ($this->im as $frame) {
$frame->drawImage($draw);
}
} else {
$this->im->drawImage($draw);
}
}
public function CopyArc(
$cx = 0,
$cy = 0,
$w = 0,
$h = 0,
$s = 0,
$e = 0,
$rgba = [],
$type = '',
$style = '',
$weight = 1
) {
$color = $this->createColorAlpha($rgba);
$draw = $this->createImagickDraw();
$draw->setStrokeColor($color);
$draw->setStrokeWidth($weight);
$wr = $w / 2;
$hr = $h / 2;
switch ($type) {
case 'filled_arc':
case 'filledArc':
$draw->arc($cx - $wr, $cy - $hr, $cx + $wr, $cy + $hr, $s, $e);
break;
default:
$draw->setFillColor($this->createColorAlpha());
$draw->arc($cx - $wr, $cy - $hr, $cx + $wr, $cy + $hr, $s, $e);
break;
}
if ($this->type == 'gif') {
// 每帧长宽不一致问题, 水印会不一致
foreach ($this->im as $frame) {
$frame->drawImage($draw);
}
} else {
$this->im->drawImage($draw);
}
}
public function CopyQr(
$text,
$level = 'L',
$size = 4,
$margin = 1,
$dst_x = 0,
$dst_y = 0,
$src_x = 0,
$src_y = 0,
$src_w = 0,
$src_h = 0
) {
if (empty($this->im)) {
throw new PosterException('im resources not be found');
}
$qr = \QRcode::re_png($text, $level, $size, $margin);
$bgWidth = imagesx($qr);
$bgHeight = imagesy($qr);
ob_start(); // 打开一个输出缓冲区
$this->poster_type['png']($qr); // 将 GD 图像输出到缓冲区
$imageData = ob_get_contents(); // 从缓冲区中读取图像数据
ob_end_clean();
$pic = $this->createImagick();
$pic->readImageBlob($imageData);
if ($src_w > 0) {
$bgWidth = $src_w;
}
if ($src_h > 0) {
$bgHeight = $src_h;
}
# 处理目标 x 轴
$dst_x = $this->calcDstX($dst_x, $this->im_w, $bgWidth);
# 处理目标 y 轴
$dst_y = $this->calcDstY($dst_y, $this->im_h, $bgHeight);
# 自定义宽高的时候
if (!empty($src_w) && !empty($src_h)) {
$pic->resizeImage($bgWidth, $bgHeight, $pic::FILTER_LANCZOS, 1, true);
}
// 裁剪图片
$this->cropImage($pic, $src_x, $src_y);
// 合并图片
if ($this->type == 'gif') {
// 每帧长宽不一致问题, 水印会不一致
foreach ($this->im as $frame) {
$frame->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
} else {
$this->im->compositeImage($pic, ($this->im)::COMPOSITE_DEFAULT, $dst_x, $dst_y);
}
!is_resource($qr) || imagedestroy($qr);
$this->destroyImage($pic);
}
/**
* 裁剪
* Author: lang
* Date: 2024/3/12
* Time: 11:22
* @param $x
* @param $y
* @param $width
* @param $height
*/
public function crop($x = 0, $y = 0, $width = 0, $height = 0)
{
$this->im->cropImage($width, $height, $x, $y);
$this->im_w = $width;
$this->im_h = $height;
}
public function execute($query = [], Driver $driver = null)
{
if (empty($driver)) {
$driver = $this;
}
foreach ($query as $item) {
$driver->run($item, $driver);
}
return $driver;
}
public function destroyImage($Imagick)
{
empty($Imagick) || $Imagick->destroy();
}
/**
* 析构方法,用于销毁图像资源
*/
public function __destruct()
{
empty($this->im) || $this->im->destroy();
empty($this->ImagickDraw) || $this->ImagickDraw->destroy();
}
}

View File

@ -0,0 +1,135 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 18:01
*/
namespace Kkokk\Poster\Image;
use Kkokk\Poster\Exception\PosterException;
class Extension implements ExtensionInterface
{
/**
* @var \Kkokk\Poster\Image\Drivers\DriverInterface $driver
*/
protected $driver;
/**
* @var string
*/
protected $path;
function __construct($driver, $path)
{
$this->driver = $driver;
$this->path = $path;
}
public function config($params = [])
{
return $this->query()->config($params);
}
public function buildIm($w, $h, $rgba = [], $alpha = false)
{
return $this->query()->buildIm($w, $h, $rgba, $alpha);
}
public function buildImDst($src, $w = 0, $h = 0)
{
return $this->query()->buildImDst($src, $w, $h);
}
public function buildBg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, \Closure $callback = null)
{
return $this->query()->buildBg($w, $h, $rgba, $alpha, $dst_x, $dst_y, $src_x, $src_y, $callback);
}
public function Qr($text, $outfile = false, $level = 'L', $size = 4, $margin = 1, $saveAndPrint = 0)
{
return $this->getDriverInstance()->createQr($text, $outfile, $level, $size, $margin, $saveAndPrint);
}
public function getPoster($query, $path)
{
return $this->getDriverInstance($query)->getData($path);
}
public function setPoster($query)
{
return $this->getDriverInstance($query)->setData();
}
public function stream($query)
{
return $this->getDriverInstance($query)->getStream();
}
public function baseData($query)
{
return $this->getDriverInstance($query)->getBaseData();
}
public function getIm($query)
{
return $this->getDriverInstance($query)->getIm();
}
public function getImInfo($query){
return $this->getDriverInstance($query)->getImInfo();
}
public function blob($query)
{
return $this->getDriverInstance($query)->blob();
}
public function tmp($query)
{
return $this->getDriverInstance($query)->tmp();
}
public function crop($x = 0, $y = 0, $width = 0, $height = 0) {
return $this->query()->crop($x, $y, $width, $height);
}
public function query()
{
return new Builder(
$this,
$this->getQueryInstance(),
$this->path
);
}
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 15:45
* @param $query
* @return \Kkokk\Poster\Image\Drivers\Driver
* @throws PosterException
*/
protected function getDriverInstance($query = [])
{
return $this->run($query, function ($query) {
return $this->driver->execute($query);
});
}
protected function run($query, \Closure $callback)
{
try {
$result = $callback($query);
} catch (\Exception $e) {
throw new PosterException($e->getMessage(), 0, $e);
}
return $result;
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 14:27
*/
namespace Kkokk\Poster\Image;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Image\Drivers\GdDriver;
use Kkokk\Poster\Image\Drivers\ImagickDriver;
class ExtensionFactory
{
protected $path;
public function make($name, $path = null)
{
$this->path = $path;
return $this->createExtension($name);
}
protected function createDriver($name)
{
switch ($name) {
case 'gd':
return new GdDriver();
case 'imagick':
return new ImagickDriver();
}
throw new PosterException("Unsupported driver [{$name}].");
}
protected function createExtension($name)
{
switch ($name) {
case 'gd':
return new GdExtension($this->createDriver($name), $this->path);
case 'imagick':
return new ImagickExtension($this->createDriver($name), $this->path);
}
throw new PosterException("Unsupported extension [{$name}].");
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 18:10
*/
namespace Kkokk\Poster\Image;
interface ExtensionInterface
{
/**
* 配置基础参数
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 14:53
* @param array $params
* @return Builder
*/
public function config($params = []);
/**
* 创建画布
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:56
* @param $w
* @param $h
* @param array $rgba
* @param false $alpha
* @return Builder
*/
public function buildIm($w, $h, $rgba = [], $alpha = false);
/**
* 以图像资源创建画布
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 14:57
* @param $src
* @param int $w
* @param int $h
* @return Builder
*/
public function buildImDst($src, $w = 0, $h = 0);
/**
* 创建背景
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:34
* @param $w
* @param $h
* @param array $rgba
* @param false $alpha
* @param int $dst_x
* @param int $dst_y
* @param int $src_x
* @param int $src_y
* @param \Closure|null $callback
* @return Builder
*/
public function buildBg($w, $h, $rgba = [], $alpha = false, $dst_x = 0, $dst_y = 0, $src_x = 0, $src_y = 0, \Closure $callback = null);
/**
* 生成二维码
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 14:48
* @param string $text 二维码内容
* @param false|string $outfile false 直接输出 或者填写输出路径
* @param string $level 容错级别默认为L
* 可传递的值分别是L(QR_ECLEVEL_L7%)、M(QR_ECLEVEL_M15%)、Q(QR_ECLEVEL_Q25%)、H(QR_ECLEVEL_H30%)
* 这个参数控制二维码容错率,不同的参数表示二维码可被覆盖的区域百分比,也就是被覆盖的区域还能识别
* @param int $size 控制生成图片的大小默认为4
* @param int $margin 控制生成二维码的空白区域大小
* @param int $saveAndPrint 保存二维码图片并显示出来,$outfile 必须传递图片路径
* @return mixed
*/
public function Qr($text, $outfile = false, $level = 'L', $size = 4, $margin = 1, $saveAndPrint = 0);
/**
* 获取海报
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:35
* @param $query
* @param $path
* @return array
*/
public function getPoster($query, $path);
/**
* 设置海报
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:35
* @param $query
* @return boolean
*/
public function setPoster($query);
/**
* 返回流文件
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:37
* @param $query
* @return resource
*/
public function stream($query);
/**
* 获取 base64 字符串
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/29
* Time: 15:38
* @param $query
* @return string
*/
public function baseData($query);
/**
* 获取 im 对象
* Author: lang
* Date: 2023/8/10
* Time: 16:00
* @param $query
* @return mixed
*/
public function getIm($query);
/**
* 获取 im 对象 图片类型,宽高
* Author: lang
* Date: 2023/8/10
* Time: 16:00
* @param $query
* @return mixed
*/
public function getImInfo($query);
/**
* 获取流
* Author: lang
* Date: 2024/3/12
* Time: 15:03
* @param $query
* @return mixed
*/
public function blob($query);
/**
* 获取临时路径
* Author: lang
* Date: 2024/3/12
* Time: 15:34
* @param $query
* @return mixed
*/
public function tmp($query);
/**
* 裁剪
* Author: lang
* Date: 2024/3/12
* Time: 11:22
* @param $x
* @param $y
* @param $width
* @param $height
*/
public function crop($x = 0, $y = 0, $width = 0, $height = 0);
}

View File

@ -0,0 +1,19 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/23
* Time: 17:43
*/
namespace Kkokk\Poster\Image;
use Kkokk\Poster\Image\Queries\GdQuery;
class GdExtension extends Extension
{
public function getQueryInstance()
{
return new GdQuery;
}
}

View File

@ -0,0 +1,19 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/23
* Time: 17:44
*/
namespace Kkokk\Poster\Image;
use Kkokk\Poster\Image\Queries\ImagickQuery;
class ImagickExtension extends Extension
{
public function getQueryInstance()
{
return new ImagickQuery;
}
}

View File

@ -0,0 +1,81 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 13:43
*/
namespace Kkokk\Poster\Image;
class PosterManager
{
protected $extensions = [];
protected $factory;
protected $path;
function __construct($path = null) // 兼容老版本设置路径
{
$this->path = $path;
$this->factory = new ExtensionFactory;
}
public function extension($name = null)
{
$name = $this->parseConnectionName($name);
if (!isset($this->extensions[$name])) {
$this->extensions[$name] = $this->configure($this->makeExtension($name));
}
return $this->extensions[$name];
}
protected function configure(Extension $extension)
{
return $extension;
}
protected function parseConnectionName($name)
{
if (empty($name)) return $this->supportedExtensions()[0];
return $name;
}
/**
* 创建一个拓展实例
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/22
* Time: 13:58
* @param $name
*/
protected function makeExtension($name)
{
return $this->factory->make($name, $this->path);
}
/**
* 获取所有支持拓展。
*
* @return array
*/
public function supportedExtensions()
{
return ['gd', 'imagick'];
}
/**
* 将方法动态传递给默认拓展。
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->extension()->$method(...$parameters);
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/23
* Time: 18:49
*/
namespace Kkokk\Poster\Image\Queries;
class GdQuery extends Query
{
}

View File

@ -0,0 +1,15 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 14:45
*/
namespace Kkokk\Poster\Image\Queries;
class ImagickQuery extends Query
{
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/24
* Time: 10:56
*/
namespace Kkokk\Poster\Image\Queries;
class Query
{
protected $query = [];
public function clearQuery()
{
$this->query = [];
}
public function getQuery()
{
return $this->query;
}
public function setQuery($type, $params)
{
$this->query[] = ['type' => $type, 'params' => $params];
}
public function setPath($path)
{
$this->query[] = ['type' => 'path', 'params' => $path];
}
}

View File

@ -0,0 +1,768 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:10
*/
namespace Kkokk\Poster\Image\Traits;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Html\Drivers\DriverInterface;
trait GdTrait
{
/**
* 返回图片流或者图片
* User: lang
* Date: 2023/8/4
* Time: 17:53
* @param $type
* @param $outfile
* @return false|string|string[]|void
* @throws PosterException
*/
protected function returnImage($type, $outfile = true)
{
if (!isset($this->im) || empty($this->im)) throw new PosterException('没有创建任何资源');
if ($outfile) {
$this->dirExists($this->pathname);
if (strripos($this->filename, '.') === false) {
$this->filename = $this->filename . '.' . $this->type;
}
$this->poster_type[$type]($this->im, $this->path . $this->pathname . DIRECTORY_SEPARATOR . $this->filename);
return ['url' => $this->pathname . DIRECTORY_SEPARATOR . $this->filename];
}
if(PHP_SAPI === 'cli') {
return $this->getBlob($type, $this->im);
}
header('Content-Type:Image/' . $this->type);
$this->poster_type[$type]($this->im);
}
protected function getBlob($type, $im)
{
ob_start();
$this->poster_type[$type]($im);
return ob_get_clean();
}
protected function getTmp($type, $im){
$output = tempnam(sys_get_temp_dir(), uniqid('gdImage'));
$this->poster_type[$type]($im, $output);
return $output;
}
protected function setImage($source)
{
if (strpos($source, 'http') === 0) {
throw new PosterException("unable to set the remote source {$source}");
}
if (!empty($source)) {
return $this->poster_type[$this->type]($this->im, $source);
}
throw new PosterException("source not found {$source}");
}
public function createImage($src = '')
{
if($src instanceof DriverInterface) {
return $this->returnImageInfoByBlob($src->getImageBlob());
} elseif(strpos($src, 'http') === 0 || file_exists($src)) {
return $this->returnImageInfoBySrc($src);
} else {
return $this->returnImageInfoByBlob($src);
}
}
public function returnImageInfoByBlob($blob)
{
list($width, $height) = @getimagesizefromstring($blob);
$pic = imagecreatefromstring($blob);
return [$pic, $width, $height];
}
public function returnImageInfoBySrc($src)
{
list($width, $height, $bgType) = @getimagesize($src);
if (empty($bgType)) throw new PosterException('image resources cannot be empty (' . $src . ')');
$bgType = image_type_to_extension($bgType, false);
$getGdVersion = preg_match('~\((.*) ~', gd_info()['GD Version'], $matches);
if ($getGdVersion && (float)$matches[1] < 2 && $bgType == 'gif') {
$pic = imagecreatefromstring(file_get_contents($src));
} else {
$fun = 'imagecreatefrom' . $bgType;
$pic = @$fun($src);
}
return [$pic, $width, $height];
}
/**
* 创建画布
*/
public function createIm($w, $h, $rgba, $alpha = false)
{
$cut = imagecreatetruecolor($w, $h);
$color = $alpha ? $this->createColorAlpha($cut, $rgba) : $this->createColor($cut, $rgba);
if ($alpha) {
// imagecolortransparent($cut, $color);
imagesavealpha($cut, true);
}
imagefill($cut, 0, 0, $color);
return $cut;
}
/**
* 获取颜色值,可设置透明度
*/
public function createColorAlpha($cut, $rgba = [255, 255, 255, 127])
{
if (empty($rgba)) $rgba = [255, 255, 255, 127];
if (count($rgba) != 4) throw new PosterException('The length of the rgba parameter is 4');
foreach ($rgba as $k => $value) {
if (!is_int($rgba[$k])) {
throw new PosterException('The value must be an integer');
} elseif ($k < 3 && ($rgba[$k] > 255 || $rgba[$k] < 0)) {
throw new PosterException('The color value is between 0-255');
} elseif ($k == 3 && ($rgba[$k] > 127 || $rgba[$k] < 0)) {
throw new PosterException('The alpha value is between 0-127');
}
}
return imagecolorallocatealpha($cut, $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
}
/**
* 获取颜色值,没有透明度
*/
public function createColor($cut, $rgba = [255, 255, 255, 1])
{
if (empty($rgba)) $rgba = [255, 255, 255, 1];
if (count($rgba) < 4) throw new PosterException('The length of the rgba parameter is 4');
foreach ($rgba as $k => $value) {
if (!is_int($rgba[$k])) {
throw new PosterException('The text value must be an integer');
} elseif ($k < 3 && ($rgba[$k] > 255 || $rgba[$k] < 0)) {
throw new PosterException('The text color value is between 0-255');
}
}
return imagecolorallocate($cut, $rgba[0], $rgba[1], $rgba[2]);
}
/**
* 计算渐变颜色值
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/18
* Time: 16:08
* @param $h
* @param $i
* @param $c1
* @param $c2
* @return array
*/
protected function calcColor($h, $i, $c1, $c2)
{
$res = [];
$r = abs($c2[0] - $c1[0]);
$rr = $c2[0] - $c1[0];
$b = abs($c2[1] - $c1[1]);
$bb = $c2[1] - $c1[1];
$g = abs($c2[2] - $c1[2]);
$gg = $c2[2] - $c1[2];
if ($r == 0) {
$res[] = $c2[0];
} else {
$res[] = $rr > 0 ? ($c1[0] + $r / $h * $i) : ($c1[0] - $r / $h * $i);
}
if ($b == 0) {
$res[] = $c2[1];
} else {
$res[] = $bb > 0 ? ($c1[1] + $b / $h * $i) : ($c1[1] - $b / $h * $i);
}
if ($g == 0) {
$res[] = $c2[2];
} else {
$res[] = $gg > 0 ? ($c1[2] + $g / $h * $i) : ($c1[2] - $g / $h * $i);
}
return $res;
}
/**
* 计算颜色渐变区块->等比分配
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/18
* Time: 10:56
* @param $rgba
* @param $h
* @param $i
* @return array
*/
protected function calcColorArea($rgbaColor, $rgbaCount, $h, $i)
{
// 单色
if ($rgbaCount == 1) {
$colorRgb = $this->calcColor($h, $i, $rgbaColor[0], $rgbaColor[0]);
} elseif ($rgbaCount == 2) {
// 两种颜色
$colorRgb = $this->calcColor($h, $i, $rgbaColor[0], $rgbaColor[1]);
} else {
// 多种颜色 计算距离并分配颜色
$rgbaCount = $rgbaCount - 1;
$d = ceil($h / $rgbaCount);
$index1 = 0;
$index2 = 1;
$di = 0;
for ($j = $rgbaCount; $j > 0; $j--) {
if ($i >= ceil((($j - 1) * $d)) && $i <= ceil($j * $d)) {
$index1 = $j - 1;
$index2 = $j;
$di = $i - (($j - 1) * $d);
break;
}
}
$colorRgb = $this->calcColor($d, $di, $rgbaColor[$index1], $rgbaColor[$index2]);
}
return $colorRgb;
}
/**
* 计算颜色渐变方向
* @Author lang
* @Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 上午12:17
* @param $im
* @param $rgbaColor
* @param $rgbaCount
* @param $to
* @param $w
* @param $h
* @return mixed
*/
public function calcColorDirection($im, $rgbaColor, $rgbaCount, $to, $w, $h)
{
$to = preg_replace('~\s+~', ' ', trim($to, ' '));
switch ($to) {
case '':
case 'bottom':
$toi = $h;
$toj = $w;
$im = $this->linearGradient($im, $toi, $toj, $rgbaColor, $rgbaCount);
break;
case 'top':
$toi = $h;
$toj = $w;
$rgbaColor = array_reverse($rgbaColor);
$im = $this->linearGradient($im, $toi, $toj, $rgbaColor, $rgbaCount);
break;
case 'left':
$toi = $w;
$toj = $h;
$rgbaColor = array_reverse($rgbaColor);
$im = $this->linearGradient($im, $toi, $toj, $rgbaColor, $rgbaCount, 'j', 'i');
break;
case 'right':
$toi = $w;
$toj = $h;
$im = $this->linearGradient($im, $toi, $toj, $rgbaColor, $rgbaCount, 'j', 'i');
break;
case 'right bottom':
case 'bottom right':
$toi = $w;
$toj = $h;
$rgbaColor = array_reverse($rgbaColor);
$im = $this->linearGradientLeftTopRightBottomDiagonal($im, $toi, $toj, $rgbaColor, $rgbaCount);
break;
case 'right top':
case 'top right':
$toi = $w;
$toj = $h;
$rgbaColor = array_reverse($rgbaColor);
$im = $this->linearGradientLeftTopRightBottomDiagonal($im, $toi, $toj, $rgbaColor, $rgbaCount, 0, $toj);
break;
case 'left bottom':
case 'bottom left':
$toi = $w;
$toj = $h;
$im = $this->linearGradientLeftTopRightBottomDiagonal($im, $toi, $toj, $rgbaColor, $rgbaCount, 0, $toj);
break;
case 'left top':
case 'top left':
$toi = $w;
$toj = $h;
$im = $this->linearGradientLeftTopRightBottomDiagonal($im, $toi, $toj, $rgbaColor, $rgbaCount);
break;
default:
// code...
break;
}
return $im;
}
/**
* 获取渐变颜色值
* @Author lang
* @Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 上午12:15
* @param $im
* @param $rgbaColor
* @param $rgbaCount
* @param $length
* @param $i
* @return false|int
*/
public function getColor($im, $rgbaColor, $rgbaCount, $length, $i)
{
$colorRgb = $this->calcColorArea($rgbaColor, $rgbaCount, $length, $i);
$color = imagecolorallocate($im, $colorRgb[0], $colorRgb[1], $colorRgb[2]);
return $color;
}
/**
* 获取透明颜色值
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 9:37
* @param $im
* @param $colorRgb
* @param $alphas
* @return false|int
*/
public function getAlphasColor($im, $colorRgb, $alphas)
{
$color = imagecolorallocatealpha($im, $colorRgb[0], $colorRgb[1], $colorRgb[2], $alphas);
return $color;
}
/**
* 渐变处理方法
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 10:04
* @param $im resource 画布资源
* @param $toi double 宽或高
* @param $toj double 高或宽
* @param $rgbaColor array 渐变色值
* @param $rgbaCount int 渐变色数量
* @param int $radius int 圆角
* @param string $ii string x,y 变量取值替换
* @param string $jj string x,y 变量取值替换
* @return mixed|resource
*/
protected function linearGradient($im, $toi, $toj, $rgbaColor, $rgbaCount, $ii = 'i', $jj = 'j')
{
for ($i = $toi; $i >= 0; $i--) {
// 获取颜色
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $toi, $i);
// imagefilledrectangle($this->im, 0, $i, $w, 0, $color); // 填充颜色
// $color = ($colorRgb[0] << 16) + ($colorRgb[1] << 8) + $colorRgb[2]; // 获取颜色参数
for ($j = 0; $j < $toj; $j++) {
imagesetpixel($im, $$jj, $$ii, $color);
}
}
return $im;
}
/**
* 画圆角
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 9:55
* @param $im resource 画布资源
* @param $w double
* @param $h double
* @param $radius int 圆角
* @return mixed|resource
*/
protected function setPixelRadius($im, $w, $h, $radius)
{
$newIm = $this->createIm($w, $h, [], true);;
$len = $w > $h ? $h / 2 : $w / 2;
list($leftTopRadius, $rightTopRadius, $leftBottomRadius, $rightBottomRadius) = $this->getRadiusType($radius, $len);
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
$color = imagecolorat($im, $x, $y);
if (($x >= $leftTopRadius || $y >= $leftTopRadius)
&& (($x <= ($w - $rightTopRadius) || $y >= $rightTopRadius))
&& (($x >= $leftBottomRadius || $y <= ($h - $leftBottomRadius)))
&& (($x <= ($w - $rightBottomRadius)) || $y <= ($h - $rightBottomRadius))) {
//不在四角的范围内,直接画
imagesetpixel($newIm, $x, $y, $color);
} else {
// 上左
$y_x = $leftTopRadius;
$y_y = $leftTopRadius;
if (((($x - $y_x) * ($x - $y_x) + ($y - $y_y) * ($y - $y_y)) <= ($leftTopRadius * $leftTopRadius))) {
imagesetpixel($newIm, $x, $y, $color);
}
// 上右
$y_x = $w - $rightTopRadius;
$y_y = $rightTopRadius;
if (((($x - $y_x) * ($x - $y_x) + ($y - $y_y) * ($y - $y_y)) <= ($rightTopRadius * $rightTopRadius))) {
imagesetpixel($newIm, $x, $y, $color);
}
//下左
$y_x = $leftBottomRadius;
$y_y = $h - $leftBottomRadius;
if (((($x - $y_x) * ($x - $y_x) + ($y - $y_y) * ($y - $y_y)) <= ($leftBottomRadius * $leftBottomRadius))) {
imagesetpixel($newIm, $x, $y, $color);
}
//下右
$y_x = $w - $rightBottomRadius;
$y_y = $h - $rightBottomRadius;
if (((($x - $y_x) * ($x - $y_x) + ($y - $y_y) * ($y - $y_y)) <= ($rightBottomRadius * $rightBottomRadius))) {
imagesetpixel($newIm, $x, $y, $color);
}
}
}
}
return $newIm;
}
/**
* 渐变处理方法 对角 分两段循环
* @Author lang
* @Email: 732853989@qq.com
* Date: 2022/10/20
* Time: 上午12:13
* @param $im
* @param $toi
* @param $toj
* @param $rgbaColor
* @param $rgbaCount
* @param int $x
* @param int $y
* @return mixed
*/
protected function linearGradientLeftTopRightBottom($im, $toi, $toj, $rgbaColor, $rgbaCount, $x = 0, $y = 0)
{
$toLen = $toi >= $toj ? $toi : $toj;
$len = $toi + $toj;
$ii = $len - 1;
if ($x == 0 && $y == 0) {
// 从 0,0 开始
for ($i = 0; $i < $toLen + 1; $i++) {
//设$i为y轴坐标
$f = 0;
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $len, $ii--);
for ($j = 0; $j <= $i; $j++) {
if ($j <= $toi && ($i - $j) <= $toj) {
if (!$f) {
$x = $j;
$y = $i - $j;
$f = 1;
}
imagesetpixel($im, $j, $i - $j, $color);
}
}
}
//加入右半段
for ($i = $x + 1; $i <= $toi; $i++) {
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $len, $ii--);
for ($j = 0; $j <= $y; $j++) {
if (($i + $j) <= $toi && ($y - $j) <= $toj) {
imagesetpixel($im, $i + $j, $y - $j, $color);
}
}
}
} else {
// 从 0,y 开始
for ($i = 0; $i < $toLen + 1; $i++) {
//设$i为y轴坐标
$f = false;
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $len, $ii--);
for ($j = 0; $j <= $i; $j++) {
if ($j <= $toi && ($i - $j) <= $toj) {
if (!$f) {
$x = $j;
$y = $i - $j;
$f = true;
}
imagesetpixel($im, $j, $toj - ($i - $j), $color);
}
}
}
//加入后半段
for ($i = $x + 1; $i <= $toi; $i++) {
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $len, $ii--);
for ($j = 0; $j <= $y; $j++) {
if (($i + $j) <= $toi && ($y - $j) <= $toj) {
imagesetpixel($im, $i + $j, $j, $color);
}
}
}
}
return $im;
}
/**
* 根据对角线长度循环
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/11/23
* Time: 11:35
* @param $im
* @param $toi
* @param $toj
* @param $rgbaColor
* @param $rgbaCount
* @param int $x
* @param int $y
* @return mixed
*/
protected function linearGradientLeftTopRightBottomDiagonal($im, $toi, $toj, $rgbaColor, $rgbaCount, $x = 0, $y = 0)
{
$total = $toi + $toj + 1; // 对角线最大循环次数
$isRectangle = $toi != $toj; // 判断是否是长方形
// 获取中间位置数值
$centerNum = !$isRectangle ? $this->centerNumSquare($total) : $this->centerNumRectangle($toi, $toj);
$ii = $total; // 颜色计算递减数值
$toiTag = 'ii'; // 默认宽大于长
$tojTag = 'jj'; // 默认宽大于长
if ($toj > $toi) { // 长大于宽
$toiTag = 'jj';
$tojTag = 'ii';
}
if ($isRectangle) { // 长方形
for ($i = 0; $i < $total; $i++) {
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $total, $ii--);
$im = $this->getPointRectangle($im, $i, $centerNum, $total, $color, $toiTag, $tojTag, $x, $y);
}
} else {
// 正方形
for ($i = 0; $i < $total; $i++) {
$color = $this->getColor($im, $rgbaColor, $rgbaCount, $total, $ii--);
$im = $this->getPointSquare($im, $i, $centerNum, $color, $x, $y);
}
}
return $im;
}
/**
* 正方形获取中间位置数值
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/11/23
* Time: 10:38
* @param $num
* @return float|int
*/
protected function centerNumSquare($num)
{
return $num / 2;
}
/**
* 长方形获取中间位置数值
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/11/23
* Time: 10:39
* @param $x
* @param $y
* @return int
*/
protected function centerNumRectangle($x, $y)
{
return $x > $y ? $y + 1 : $x + 1;
}
/**
* 长方形通过对角线循环绘画
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 9:51
* @param $im
* @param $num
* @param $centerNum
* @param $total
* @param $color
* @param $toiTag
* @param $tojTag
* @param int $x
* @param int $y
* @return mixed
*/
protected function getPointRectangle($im, $num, $centerNum, $total, $color, $toiTag, $tojTag, $x = 0, $y = 0)
{
$len = $total - $centerNum * 2; // 求取对角线上相交线坐标到边的最大宽度数量
$min = $centerNum; // 从第几次循环开始保持最大宽度
$max = $min + $len; // 到第几结束
if ($num >= $min && $num <= $max) {
$ii = $num - $centerNum + 1;
for ($jj = $centerNum - 1; $jj >= 0; $jj--) {
imagesetpixel($im, ceil($$toiTag), abs($y - floor($$tojTag)), $color);
$ii++;
}
} elseif ($num > $max) {
$num = $num - $centerNum;
$ii = $num + 1;
$jj = $max - $len - 1;
for ($i = $max; $i > $num; $i--) {
imagesetpixel($im, ceil($$toiTag), abs($y - floor($$tojTag)), $color);
$ii++;
$jj--;
}
} else {
for ($i = 0; $i <= $num; $i++) {
imagesetpixel($im, $i, abs($y - ($num - $i)), $color);
}
}
return $im;
}
/**
* 正方形通过对角线循环绘画
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/12
* Time: 9:52
* @param $im
* @param $num
* @param $centerNum
* @param $color
* @param int $x
* @param int $y
* @return mixed
*/
protected function getPointSquare($im, $num, $centerNum, $color, $x = 0, $y = 0)
{
if ($num > $centerNum) {
$num = $num - $centerNum;
$ii = $num;
for ($i = $centerNum; $i >= $num; $i--) {
// $arr[] = [ceil($ii) , floor($i)];
imagesetpixel($im, ceil($ii), abs($y - floor($i)), $color);
$ii++;
}
} else {
for ($i = 0; $i <= $num; $i++) {
// $arr[] = [$i , $num-$i];
imagesetpixel($im, $i, abs($y - ($num - $i)), $color);
}
}
return $im;
}
/**
* 字体加粗
*/
protected function fontWeight($weight, $fontSize, $angle, $dst_x, $dst_y, $color, $font, $contents)
{
for ($i = 0; $i < $weight; $i++) {
list($really_dst_x, $really_dst_y) = $this->calcWeight($i, $weight, $fontSize, $dst_x, $dst_y);
imagettftext($this->im, $fontSize, $angle, intval($really_dst_x), intval($really_dst_y), $color, $font, $contents);
}
}
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/6/2
* Time: 14:26
*/
protected function fontWeightArr($weight, $fontSize, $angle, $dst_x, $dst_y, $color, $font, $contentsArr)
{
$dst_x_old = $dst_x;
// $max = max(array_column($contentsArr, 'w')); // 取最大宽度
foreach ($contentsArr as $v) {
$contents = $v['value'];
if ($contents == "\n") {
$dst_x = $dst_x_old;
$dst_y += 1.75 * $fontSize;
continue;
}
$customColor = !empty($v['color']) ? $v['color'] : $color;
$this->fontWeight($weight, $fontSize, $angle, $dst_x, $dst_y, $customColor, $font, $contents);
// $dst_x += $max;
$dst_x += $v['w'];
}
}
/** 设置背景透明 */
public function setImageAlpha($pic, $w, $h, $alphas)
{
$mask = $this->createIm($w, $h, [], $alphas > 1);
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
$rgbColor = imagecolorat($pic, $x, $y);
$thisColor = imagecolorsforindex($pic, $rgbColor);
$color = $this->createColorAlpha($pic, [$thisColor['red'], $thisColor['green'], $thisColor['blue'], $alphas]);
imagesetpixel($mask, $x, $y, $color);
}
}
return $mask;
}
}

View File

@ -0,0 +1,400 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 11:48
*/
namespace Kkokk\Poster\Image\Traits;
use Kkokk\Poster\Exception\PosterException;
use Kkokk\Poster\Html\Drivers\DriverInterface;
trait ImagickTrait
{
protected function setDPI()
{
if (!isset($this->im) || empty($this->im)) throw new PosterException('没有创建任何资源');
if (!empty($this->dpi)) {
$this->im->resampleImage($this->dpi[0], $this->dpi[1], ($this->im)::RESOLUTION_PIXELSPERINCH, 0); //设置画布的dpi
}
}
/**
* 返回图片流或者图片
* @Author lang
* @Date 2020-08-14T14:29:57+0800
* @return void|array
*/
protected function returnImage($type, $outfile = true)
{
if ($outfile) {
$this->dirExists($this->pathname);
if (strripos($this->filename, '.') === false) {
$this->filename = $this->filename . '.' . $type;
}
if ($type == 'gif') {
$this->im->writeImages($this->path . $this->pathname . DIRECTORY_SEPARATOR . $this->filename, true);
} else {
$this->im->writeImage($this->path . $this->pathname . DIRECTORY_SEPARATOR . $this->filename);
}
return ['url' => $this->pathname . DIRECTORY_SEPARATOR . $this->filename];
}
$imageBlob = $this->im->getImageBlob();
if (PHP_SAPI === 'cli') {
return $imageBlob;
}
header('Content-Type:Image/' . $type);
echo $imageBlob;
}
protected function getBlob($im)
{
return $im->getImageBlob();
}
protected function getTmp($type, $im)
{
$output = tempnam(sys_get_temp_dir(), uniqid('imagickImage'));
if ($type == 'gif') {
$im->writeImages($output, true);
} else {
$im->writeImage($output);
}
return $output;
}
protected function setImage($source)
{
if (strpos($source, 'http') === 0) {
throw new PosterException("unable to set the remote source {$source}");
}
if (!empty($source)) {
if ($this->type == 'gif') return $this->im->writeImages($source, true);
return $this->im->writeImage($source);
}
throw new PosterException("source not found {$source}");
}
/**
* 创建文字绘画类
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/27
* Time: 16:58
* @return \ImagickDraw
*/
public function createTextImagickDraw()
{
if (empty($this->ImagickDraw)) {
$this->ImagickDraw = new \ImagickDraw();
$this->ImagickDraw->settextencoding('UTF-8');
}
return $this->ImagickDraw;
}
public function createImagickDraw()
{
return new \ImagickDraw();
}
public function createImagick($src = '')
{
$Imagick = new \Imagick();
if ($src) {
if ($src instanceof DriverInterface) {
$Imagick->readImageBlob($src->getImageBlob());
} elseif (strpos($src, 'http') === 0 ) {
$stream = @file_get_contents($src, NULL);
if (empty($stream)) throw new PosterException('image resources cannot be empty (' . $src . ')');
$Imagick->readImageBlob($stream);
} elseif(file_exists($src)) {
$Imagick->readImage($this->getRealRoute($src));
} else {
$Imagick->readImageBlob($src);
}
}
return $Imagick;
}
/**
* 创建画布
*/
public function createIm($w, $h, $rgba, $alpha = false, $type = null)
{
$color = $alpha ? $this->createColorAlpha($rgba) : $this->createColor($rgba);
$image = $this->createImagick();
$image->newImage($w, $h, $color, $type ?: $this->type);//设置画布的信息以及画布的格式
return $image;
}
/**
* 获取颜色值,可设置透明度
*/
public function createColorAlpha($rgba = [255, 255, 255, 127])
{
if (empty($rgba)) $rgba = [255, 255, 255, 127];
if (count($rgba) != 4) throw new PosterException('The length of the rgba parameter is 4');
foreach ($rgba as $k => $value) {
if (!is_int($rgba[$k])) {
throw new PosterException('The value must be an integer');
} elseif ($k < 3 && ($rgba[$k] > 255 || $rgba[$k] < 0)) {
throw new PosterException('The color value is between 0-255');
} elseif ($k == 3 && ($rgba[$k] > 127 || $rgba[$k] < 0)) {
throw new PosterException('The alpha value is between 0-127');
}
}
$rgba[3] = sprintf("%.2f", (128 - $rgba[3]) / 127);
return new \ImagickPixel("rgba($rgba[0], $rgba[1], $rgba[2], $rgba[3])");
}
/**
* 获取颜色值,没有透明度
*/
public function createColor($rgba = [255, 255, 255, 1])
{
if (empty($rgba)) $rgba = [255, 255, 255, 1];
if (count($rgba) < 4) throw new PosterException('The length of the rgba parameter is 4');
foreach ($rgba as $k => $value) {
if (!is_int($rgba[$k])) {
throw new PosterException('The text value must be an integer');
} elseif ($k < 3 && ($rgba[$k] > 255 || $rgba[$k] < 0)) {
throw new PosterException('The text color value is between 0-255');
}
}
return new \ImagickPixel("rgb($rgba[0], $rgba[1], $rgba[2])");
}
/**
* 剪裁图片并复制(复制指定坐标内容)
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/28
* Time: 11:44
* @param mixed $source 读取原图片
*/
public function cropImage(\Imagick $source, $src_x, $src_y)
{
// 裁剪原图片,仅保留指定坐标的内容
if ($src_x > 0 || $src_y > 0) {
$width = $source->getImageWidth();
$height = $source->getImageHeight();
$source->cropImage($width - $src_x, $height - $src_y, $src_x, $src_y);
}
}
/**
* 渐变色,目前只支持两种颜色渐变,暂时只支持从上往下
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/3/28
* Time: 17:04
* @param \Imagick $source
* @param $rgbaColor
* @param $rgbaCount
* @param $to
* @param $w
* @param $h
* @throws \ImagickException
*/
public function calcColorDirection(\Imagick $source, $rgbaColor, $rgbaCount, $to, $w, $h)
{
switch ($to) {
case '':
case 'bottom':
break;
case 'top':
$rgbaColor = array_reverse($rgbaColor);
break;
case 'left':
break;
case 'right':
break;
case 'right bottom':
case 'bottom right':
break;
case 'right top':
case 'top right':
break;
case 'left bottom':
case 'bottom left':
break;
case 'left top':
case 'top left':
break;
default:
// code...
break;
}
if ($rgbaCount < 3) {
$this->linearGradient($source, $rgbaColor, $rgbaCount, $w, $h);
} else {
$picKey = 0;
$chunk = ceil($h / ($rgbaCount - 1));
foreach ($rgbaColor as $k => $v) {
if ($k == $rgbaCount - 1) break;
$picsC = $this->createIm($w, $chunk, [], true);
$this->linearGradient($picsC, [$rgbaColor[$k], $rgbaColor[$k + 1]], $rgbaCount, $w, $chunk);
$source->compositeImage($picsC, ($this->im)::COMPOSITE_DEFAULT, 0, $k * $chunk);
$picKey++;
}
}
}
public function linearGradient(\Imagick $source, $rgbaColor, $rgbaCount, $w, $h)
{
if ($rgbaCount == 1) {
$rgb1 = "rgb(" . $rgbaColor[0][0] . "," . $rgbaColor[0][1] . "," . $rgbaColor[0][2] . ")";
$source->newPseudoImage($w, $h, "gradient:$rgb1-$rgb1");
} elseif ($rgbaCount > 1) {
$rgb1 = "rgb(" . $rgbaColor[0][0] . "," . $rgbaColor[0][1] . "," . $rgbaColor[0][2] . ")";
$rgb2 = "rgb(" . $rgbaColor[1][0] . "," . $rgbaColor[1][1] . "," . $rgbaColor[1][2] . ")";
$source->newPseudoImage($w, $h, "gradient:$rgb1-$rgb2");
}
}
protected function fontWeight($draw, $weight, $fontSize, $angle, $dst_x, $dst_y, $contents)
{
for ($i = 0; $i < $weight; $i++) {
list($really_dst_x, $really_dst_y) = $this->calcWeight($i, $weight, $fontSize, $dst_x, $dst_y);
if ($this->type == 'gif') {
foreach ($this->im as $frame) {
$frame->annotateImage($draw, $really_dst_x, $really_dst_y, $angle, $contents);
// $this->im->nextImage();
}
} else {
$this->im->annotateImage($draw, $really_dst_x, $really_dst_y, $angle, $contents);
}
}
}
public function fontWeightArr($draw, $weight, $fontSize, $angle, $dst_x, $dst_y, $contentsArr, $color)
{
$dst_x_old = $dst_x;
foreach ($contentsArr as $v) {
$contents = $v['value'];
if ($contents == "\n") {
$dst_x = $dst_x_old;
$dst_y += 1.75 * $fontSize;
continue;
}
if (!empty($v['color'])) {
$draw->setFillColor($v['color']);
} else {
$draw->setFillColor($color);
}
$this->fontWeight($draw, $weight, $fontSize, $angle, $dst_x, $dst_y, $contents);
$dst_x += $v['w'];
}
}
/**
* 画圆角
* Author: lang
* Email: 732853989@qq.com
* Date: 2023/5/19
* Time: 15:34
* @param \Imagick $pic
* @param $w double
* @param $h double
* @param $radius int 圆角
* @return mixed
* @throws PosterException
* @throws \ImagickDrawException
* @throws \ImagickException
*/
protected function setPixelRadius($pic, $w, $h, $radius)
{
// 圆角处理
$len = $w > $h ? $h / 2 : $w / 2;
list($leftTopRadius, $rightTopRadius, $leftBottomRadius, $rightBottomRadius) = $this->getRadiusType($radius, $len);
// 四个角一样
$mask = $this->createImagick();
$mask->newImage($w, $h, $this->createColorAlpha([255, 255, 255, 127]));
$draw = $this->createImagickDraw();
$draw->setFillColor($this->createColorAlpha([255, 255, 255, 1]));
if ($leftTopRadius == $rightTopRadius && $leftTopRadius == $leftBottomRadius && $leftTopRadius == $rightBottomRadius) {
// 搞一个长方形
$draw->roundRectangle(0, 0, $w, $h, $leftTopRadius, $leftTopRadius);
} else {
// 中间两个长方形填满
$draw->rectangle(max($leftTopRadius, $leftBottomRadius) * 2, 0, $w - max($rightTopRadius, $rightBottomRadius) * 2, $h);
$draw->rectangle(0, max($leftTopRadius, $rightTopRadius) * 2, $w, $h - max($leftBottomRadius, $rightBottomRadius) * 2);
// 左上角为 圆角
$draw->rectangle(0, $leftTopRadius, max($leftTopRadius, $leftBottomRadius) * 2, max($leftTopRadius, $rightTopRadius) * 2);
$draw->rectangle($leftTopRadius, 0, max($leftTopRadius, $leftBottomRadius) * 2, max($leftTopRadius, $rightTopRadius) * 2);
$draw->ellipse($leftTopRadius, $leftTopRadius, $leftTopRadius, $leftTopRadius, 0, 360);
// 右上角为 圆角
$draw->rectangle($w - max($rightTopRadius, $rightBottomRadius) * 2, 0, $w - $rightTopRadius, max($leftTopRadius, $rightTopRadius) * 2);
$draw->rectangle($w - max($rightTopRadius, $rightBottomRadius) * 2, $rightTopRadius, $w, max($leftTopRadius, $rightTopRadius) * 2);
$draw->ellipse($w - $rightTopRadius, $rightTopRadius, $rightTopRadius, $rightTopRadius, 0, 360);
// 右下角为 圆角
$draw->rectangle($w - max($rightBottomRadius, $rightTopRadius) * 2, $h - max($rightBottomRadius, $leftBottomRadius) * 2, $w, $h - $rightBottomRadius);
$draw->rectangle($w - max($rightBottomRadius, $rightTopRadius) * 2, $h - max($rightBottomRadius, $leftBottomRadius) * 2, $w - $rightBottomRadius, $h);
$draw->ellipse($w - $rightBottomRadius, $h - $rightBottomRadius, $rightBottomRadius, $rightBottomRadius, 0, 360);
// 左下角为 圆角
$draw->rectangle(0, $h - max($rightBottomRadius, $leftBottomRadius) * 2, max($leftTopRadius, $leftBottomRadius) * 2, $h - $leftBottomRadius);
$draw->rectangle($leftBottomRadius, $h - max($rightBottomRadius, $leftBottomRadius) * 2, max($leftTopRadius, $leftBottomRadius) * 2, $h);
$draw->ellipse($leftBottomRadius, $h - $leftBottomRadius, $leftBottomRadius, $leftBottomRadius, 0, 360);
}
$mask->drawImage($draw);
$pic->compositeImage($mask, ($this->im)::COMPOSITE_DSTIN, 0, 0);
return $pic;
}
/**
* 设置透明度
* User: lang
* Date: 2023/9/22
* Time: 11:00
* @param $pic
* @param $alphas
* @return void
*/
protected function setImageAlpha($pic, $alphas)
{
if(method_exists($pic,'setImageOpacity')) {
$pic->setImageOpacity(floor((128 - $alphas) / 127 * 100) / 100); // 透明度
} elseif (method_exists($pic,'setImageAlpha')) {
$pic->setImageAlpha(floor((128 - $alphas) / 127 * 100) / 100); // 透明度
}
}
}

View File

@ -0,0 +1,16 @@
<?php
/**
* Author: lang
* Email: 732853989@qq.com
* Date: 2022/12/6
* Time: 18:10
*/
namespace Kkokk\Poster\Lang;
use Kkokk\Poster\Captcha\CaptchaManager;
class Captcha extends CaptchaManager
{
}

View File

@ -0,0 +1,19 @@
<?php
namespace Kkokk\Poster\Lang;
use Kkokk\Poster\Image\PosterManager;
/**
* @Author: lang
* @Email: 732853989@qq.com
* @Date: 2020-08-14 11:18:03
* @Last Modified by: lang
* @Last Modified time: 2021-09-09 10:33:59
* 接口模式
*
*/
class Poster extends PosterManager
{
}

View File

@ -0,0 +1,38 @@
* 1.0.0 build 2010031920
- first public release
- help in readme, install
- cleanup ans separation of QRtools and QRspec
- now TCPDF binding requires minimal changes in TCPDF, having most of job
done in QRtools tcpdfBarcodeArray
- nicer QRtools::timeBenchmark output
- license and copyright notices in files
- indent cleanup - from tab to 4spc, keep it that way please :)
- sf project, repository, wiki
- simple code generator in index.php
* 1.1.0 build 2010032113
- added merge tool wich generate merged version of code
located in phpqrcode.php
- splited qrconst.php from qrlib.php
* 1.1.1 build 2010032405
- patch by Rick Seymour allowing saving PNG and displaying it at the same time
- added version info in VERSION file
- modified merge tool to include version info into generated file
- fixed e-mail in almost all head comments
* 1.1.2 build 2010032722
- full integration with TCPDF thanks to Nicola Asuni, it's author
- fixed bug with alphanumeric encoding detection
* 1.1.3 build 2010081807
- short opening tags replaced with standard ones
* 1.1.4 build 2010100721
- added missing static keyword QRinput::check (found by Luke Brookhart, Onjax LLC)

View File

@ -0,0 +1,67 @@
== REQUIREMENTS ==
* PHP5
* PHP GD2 extension with JPEG and PNG support
== INSTALLATION ==
If you want to recreate cache by yourself make sure cache directory is
writable and you have permisions to write into it. Also make sure you are
able to read files in it if you have cache option enabled
== CONFIGURATION ==
Feel free to modify config constants in qrconfig.php file. Read about it in
provided comments and project wiki page (links in README file)
== QUICK START ==
Notice: probably you should'nt use all of this in same script :)
<?phpb
//include only that one, rest required files will be included from it
include "qrlib.php"
//write code into file, Error corection lecer is lowest, L (one form: L,M,Q,H)
//each code square will be 4x4 pixels (4x zoom)
//code will have 2 code squares white boundary around
QRcode::png('PHP QR Code :)', 'test.png', 'L', 4, 2);
//same as above but outputs file directly into browser (with appr. header etc.)
//all other settings are default
//WARNING! it should be FIRST and ONLY output generated by script, otherwise
//rest of output will land inside PNG binary, breaking it for sure
QRcode::png('PHP QR Code :)');
//show benchmark
QRtools::timeBenchmark();
//rebuild cache
QRtools::buildCache();
//code generated in text mode - as a binary table
//then displayed out as HTML using Unicode block building chars :)
$tab = $qr->encode('PHP QR Code :)');
QRspec::debug($tab, true);
== TCPDF INTEGRATION ==
Inside bindings/tcpdf you will find slightly modified 2dbarcodes.php.
Instal phpqrcode liblaty inside tcpdf folder, then overwrite (or merge)
2dbarcodes.php
Then use similar as example #50 from TCPDF examples:
<?php
$style = array(
'border' => true,
'padding' => 4,
'fgcolor' => array(0,0,0),
'bgcolor' => false, //array(255,255,255)
);
//code name: QR, specify error correction level after semicolon (L,M,Q,H)
$pdf->write2DBarcode('PHP QR Code :)', 'QR,L', '', '', 30, 30, $style, 'N');

View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,45 @@
This is PHP implementation of QR Code 2-D barcode generator. It is pure-php
LGPL-licensed implementation based on C libqrencode by Kentaro Fukuchi.
== LICENSING ==
Copyright (C) 2010 by Dominik Dzienia
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Lesser General Public License (LICENSE file)
for more details.
You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 51
Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
== INSTALATION AND USAGE ==
* INSTALL file
* http://sourceforge.net/apps/mediawiki/phpqrcode/index.php?title=Main_Page
== CONTACT ==
Fell free to contact me via e-mail (deltalab at poczta dot fm) or using
folowing project pages:
* http://sourceforge.net/projects/phpqrcode/
* http://phpqrcode.sourceforge.net/
== ACKNOWLEDGMENTS ==
Based on C libqrencode library (ver. 3.1.1)
Copyright (C) 2006-2010 by Kentaro Fukuchi
http://megaui.net/fukuchi/works/qrencode/index.en.html
QR Code is registered trademarks of DENSO WAVE INCORPORATED in JAPAN and other
countries.
Reed-Solomon code encoder is written by Phil Karn, KA9Q.
Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q

View File

@ -0,0 +1,2 @@
1.1.4
2010100721

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
<EFBFBD><EFBFBD>Á À E9³u<06><>`³"PÅ„CÛ牗T!0$
E•ɲQ™<EFBFBD>Ém½úhÛ¾9{kI" 9Ln)Ap¤åÖ¾Ë>ß^‡Õz³mënÅ´mßn†ú¦Ë

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

View File

@ -0,0 +1 @@
xÚí™A E]sëIX´;¸Ün6€È`q”êêW6ñ奚`Œ%A/3!¢°‚¢Š!gÈÌ¡1N) éE¢Ï|;®—>6â¸<C3A2>Þ97$ëÄôëc]kkö<6B>wé1Öü[·m­CÍœcÊRºÄê¹>¦èµ¾šE,•hʼnp„#áxF<1C>yWÏÇVWGçòÕ3¼Õ+шþàË“úSŽâ}Äž<C384>#áG8b^c^cÏÀŽp„c&3YQ"ñŽ÷çÌvµù…ñàÎþþ¼¹kÞ9ŠÜ‡÷}”¹³ï×ú ¢Ä¿<C384>QäÿL—/ÝÔÀÏ

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View File

@ -0,0 +1,2 @@
xÚí™A
ƒ0E]çÖ…,2;sƒä&ÉÍšh¥ÛêO¡ôÝÈàã1&09OIv@DDÒ Ì&§Ù‰K<E280B0>XÈÕFv•<Ádqò9Ö<%h•¹ Yïs !(d¥²ës;~||b(ÏøYůg#µ`œK ±S¼Åô¹Ä¶˜ùsàidß<64>Lg:Ó™Îtþ/gmª<6D>™ƒkÅMâ3³{­4rTÈQýÿe¥·s·>ó<Ó™Ît¦3<C2A6>éÌ;ïH¼#Ñ™Ît¦3<C2A6>ÍYœ+og©hù¶óµÙ½¬lnðûF>Øi^»#awm;gè~pÛgìNs{6z»ãºïÞäp¾Ê'

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

@ -0,0 +1,3 @@
xÚíšA
Ä E»öÖ.ĚNo Ť¶iiRÚN2áW%đxÁ@ÚÚśę'­
u<EFBFBD>6×ę<EFBFBD>.ť*S;}<7D>«ŇĂ ĎT účĚztąď%ç,ŇĹÚâÎ}ç;“âç)ąź<C485>âÝZÚîLĺčą÷¬Pçç$Ż×÷ĎqËgśLÂôdJ‡;Üáw¸Ăý.]z#źľ«[Íť˝ďOg­Ćô"ĐË áBíî¦}Ç}‡;Üáw¸Ăî<>#1GbŽ„;Üáw¸Ăý_ÝC+w˘@Dfî÷ďç™uťř2™ĹÚÉNţű9R7|pWßkďű®ż“ßßkşöżşú»ĽÎÓ

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

View File

@ -0,0 +1 @@
xÚÍÍ À F{vë&  à&°Y+?Z1öÐSŸ'y!¢ŸÌÁa<C381>815&£•Û´ŽÙHå£Ùžc³•l«ÏFÆè1º#é6 fÊÖü©§6Äø•O7ˆ¨†C¦«ðÖ<C3B0>ž<EFBFBD>Ï8gI®ÏöfB¦ÃÄÿæ\DÔ»(

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

View File

@ -0,0 +1 @@
xÚíšA E]sëIX´Ün6Up<13>“в™ÿ]Ù˜þ< i-eWö˜)×äÅ•¼ÉÂ…H\jvqÙHL\6šÝÐ…rI¢LܹÜÕ%ÅÓ@´þ±V—vÆÂúý¤(ÏP4|ÎXnÒgÉ<>ß¼~]D¾ÉÕ×u1Us S\À°€,ÿÅ2Þ¢N§Ã?DKºüF-:“eJ]p_À°€,˜a0Ã`†ÁÝ °`†Á ƒw,` X´]˜ˆ¹˜°5 ‰®Y4{屿ñ2íûåvçJs†±Ûí9±˜í)õu±Û¹êÏØ,«]¸“‹Ù^_§7$ƒ_Í

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1,3 @@
xÚíšA
„0 E]{ë<>.]{{{³©Z¥BepÆÞwe@<1F>VERZ3»Á"*2o€4¦y‰)i#dÒbdFÒ…´ŒI"ú‘—4ž½W­IíuŠÓ45ßx«.Z­SÙ{ÁŸ¯8åËÿk={o.±qÊÙ£[œÍ:å¸q»õƒy
)t#á„N8ádCj<43>-O<>OG}¼:/Ÿ:s<>z!Å)^<ùe½·S·uâ{ 'œp 'ú=ú=ú=¾'œp 'œp¢ß£ß£ßã<1F>N8á„Óÿ9©ªˆôpQQõ]HÔpz¾<7A>ØGœ^æ½Qº˜I|¾ß³<C39F>u;9™ÎïÕëd;“X~$ËÙÑÉt¶ÊÛédy

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

View File

@ -0,0 +1,3 @@
xÚíšA
à E³öÖfo U<E280BA>) %M!ΔÂûYu(<šð“sK²“TœÓ
É&§IÚ\i+¥Ðª™(m®´FQ¡¹¯h±æöüèv~n1„oÏ]sëçÖï¤_ÞŸÊ3`î_w2õȹ•lc[¼•;·Ûc֟ˤNóª4Üpà 7ÜpÃímTÿ¸œÝêrÞiñä_ƒç¿pS=7Þ7Üpà 7ÜpÃ<70>>IŸ¤Oò-Á 7Üpà 7ú$}>É·7Üpà ·tss‰Órs §åVÍÎÜÆ÷mýï¡Ò¹ò‡<C3B2>Þñ}R~7ôà&¾÷º?7ù<37>Þý<C39E>Ô¦Iïbhâ{æ»<ÀMi-

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

View File

@ -0,0 +1 @@
νAƒ E»φΦMX0;Έ<>άnVP4ΪHSS»xίU3±/O΄ύ LiJ4<4A><34>±Vβ JC<4A>%ύ‰6VR&ΓήDB<E28098>HjDωJΟ??™―κBl­cΗ±ρ½§'σU­λXοUοή<CEBF>0ζΓywΝΔ―χj¬ιλ<CEB9>³€3ΕΎλ<CE8E>cj†ω£{¨¥½:GG<1C>έρψ<CF81>ϋΪ°N†v;Ή¶η¬“J ‡ΔΠ<ϋ‡Ι]<5D>κλΘσ<CE98>#<23><38>#<23>8βH'§“ΣΙωΝΑGGιδtr:9Ο#<23><38>#<23>8βΨ“h­<68>―NΤt”<74>΄Φ_έΨ>tΉeλμS­―¦ζ<C2A6>ω^<5E>\g―υΞQe?ωvΜoοΥ;<3B>ο<>*οwlςΧmΡ

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

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