1、环境:centOS stream 9, php8.3, thinkphp8.1 这是基础环境;
2、安装了imagick3.8, guzzlehttp7.10, intervention/image4.0.3;
3、在使用时,一定要根据我的版本号,使用我的代码;特别是第二步;
php
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Imagick\Driver;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Alignment;
use Intervention\Image\Format;
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\RoundBlockSizeMode;
use Endroid\QrCode\Writer\WebPWriter;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use think\response\View;
use think\response\Json;
use think\facade\Db;
use think\facade\Log;
public function generate(): Json
{
try {
$userId = 11;
$nickname = '云淡风轻';
$avatarUrl = 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLyaziaiaBkMjL0FQofdSMYetsEzFMnYpibbUxHvbHSmj3OOyJ1icmjCriaRyTianY6OWIT4AnIiaibloOYhA/132';
$qrcodeContent = 'https://m.hajslm.com/index/share/reg/?userId=' . $userId;
\Imagick::setResourceLimit(\Imagick::RESOURCETYPE_MEMORY, 256);
$manager = new ImageManager(new Driver());
// ======================
// 背景
// ======================
$bgImagePath = public_path('static/api/images') . 'fenx.jpg';
$poster = $manager->decodePath($bgImagePath);
// ======================
// 二维码
// ======================
$qrCodeImage = $this->createQrCode($manager, $qrcodeContent, 315);
$poster->insert($qrCodeImage, 210, 783, Alignment::TOP_LEFT);
// ======================
// 头像
// ======================
if (!empty($avatarUrl)) {
$avatarImage = $this->createCircleAvatar($manager, $avatarUrl, 100);
if ($avatarImage) {
$avatarX = 50;
$avatarY = 50;
$poster->insert(
$avatarImage,
(int)$avatarX,
(int)$avatarY,
Alignment::TOP_LEFT,
1
);
}
}
// ======================
// 昵称
// ======================
$this->addNicknameText($poster, $nickname);
// ======================
// 保存
// ======================
$saveDir = public_path('storage/uploads/poster');
$dateDir = date('Ymd');
$fullDir = $saveDir . '/' . $dateDir;
if (!is_dir($fullDir)) {
mkdir($fullDir, 0775, true);
}
$filename = uniqid('', true) . '_' . $userId . '.webp';
$savePath = $fullDir . '/' . $filename;
// ======================
// WebP 输出(v4正确方式)
// ======================
$encoded = $poster->encodeUsingFormat(Format::WEBP,quality: 90);
$encoded->save($savePath);
// file_put_contents($savePath,(string) $poster->encode(new WebpEncoder(quality: 90)));
unset($poster);
gc_collect_cycles();
return json([
'code' => 1,
'msg' => '生成成功',
'data' => [
'img_url' => request()->domain() . '/storage/uploads/poster/' . $dateDir . '/' . $filename
]
]);
} catch (\Throwable $e) {
Log::error($e->getMessage());
return json([
'code' => 0,
'msg' => $e->getMessage()
]);
}
}
/**
* 创建二维码
*/
private function createQrCode(ImageManager $manager, string $content, int $size = 315)
{
$builder = new Builder(
writer: new WebPWriter(),
writerOptions: [
'quality' => 90
],
validateResult: false,
data: $content,
encoding: new Encoding('UTF-8'),
errorCorrectionLevel: ErrorCorrectionLevel::High,
size: $size,
margin: 10,
roundBlockSizeMode: RoundBlockSizeMode::Margin
);
$result = $builder->build();
return $manager->decodeBinary($result->getString());
}
/**
* 创建圆形头像
*/
private function createCircleAvatar(ImageManager $manager, string $avatarUrl, int $size = 100)
{
try {
$avatarBinary = $this->fetchRemoteImage($avatarUrl);
if (!$avatarBinary) return null;
// =========================
// 用 Imagick 处理圆形(核心)
// =========================
$imagick = new \Imagick();
$imagick->readImageBlob($avatarBinary);
$imagick->setImageFormat('png');
$w = $imagick->getImageWidth();
$h = $imagick->getImageHeight();
$min = min($w, $h);
// 裁剪正方形
$imagick->cropImage(
$min,
$min,
intval(($w - $min) / 2),
intval(($h - $min) / 2)
);
// 缩放
$imagick->resizeImage($size, $size, \Imagick::FILTER_LANCZOS, 1);
// =========================
// 创建圆形遮罩(关键)
// =========================
$mask = new \Imagick();
$mask->newImage($size, $size, 'transparent');
$mask->setImageFormat('png');
$draw = new \ImagickDraw();
$draw->setFillColor('white');
$draw->circle(
$size / 2,
$size / 2,
$size / 2,
0
);
$mask->drawImage($draw);
// =========================
// 应用 alpha mask
// =========================
$imagick->setImageMatte(true);
$imagick->compositeImage($mask, \Imagick::COMPOSITE_DSTIN, 0, 0);
// =========================
// 转回 Intervention Image
// =========================
return $manager->decodeBinary($imagick->getImageBlob());
} catch (\Throwable $e) {
Log::error('头像失败:' . $e->getMessage());
return null;
}
}
/**
* 下载远程图片
*/
private function fetchRemoteImage(string $url): ?string
{
try {
$client = new Client([
'timeout' => 5,
'connect_timeout' => 3,
'verify' => false, // 对应你原来的 SSL 关闭
'headers' => [
'User-Agent' => 'Mozilla/5.0',
'Accept' => 'image/webp,image/apng,image/*,*/*;q=0.8',
]
]);
$response = $client->get($url);
if ($response->getStatusCode() !== 200) {
Log::error('头像下载HTTP异常:' . $response->getStatusCode());
return null;
}
return (string) $response->getBody();
} catch (GuzzleException $e) {
Log::error('头像下载失败:' . $e->getMessage());
return null;
} catch (\Throwable $e) {
Log::error('远程图片异常:' . $e->getMessage());
return null;
}
}
/**
* 添加昵称文字
*/
private function addNicknameText($poster, string $nickname): void
{
$fontPath = public_path('static/fonts') . 'NotoSansSC-Regular.ttf';
if (!is_file($fontPath)) return;
$textX = 210;
$textY = 91;
$nickname = mb_substr($nickname, 0, 10);
$poster->text(
$nickname,
$textX,
$textY,
function ($font) use ($fontPath) {
$font->filename($fontPath);
$font->size(26);
$font->color('#333333');
$font->align(Alignment::CENTER,Alignment::CENTER);
}
);
}