thinkphp8生成海报

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);
            }
        );
    }
相关推荐
云游云记1 个月前
FastAdmin 路由完全开启教程:去掉 index 前缀 + 优雅路由配置
thinkphp
kertag1 个月前
ThinkPHP 8 多应用入口绑定:BIND_MODULE vs $http->name() 全面解析
php·thinkphp
妙码生花1 个月前
全新的 TP8+Workerman+BuildAdmin 整合方案,已有近 2000 次下载使用。
websocket·php·thinkphp
quweiie1 个月前
在php8.3下签到、签退打卡的实现
thinkphp·签到·nesbot/carbon
天宁3 个月前
Workerman + ThinkPHP 8 结合使用
php·thinkphp
云游云记3 个月前
ThinkPHP 队列扩展 (topthink/think-queue) 使用笔记
php·thinkphp·think-queue
用户14644605033794 个月前
PHP 多维数组处理利器:array_column() 用法详解
php·thinkphp
用户3074596982074 个月前
ThinkPHP 6.0 多应用模式下的中间件机制详解
后端·thinkphp
行思理4 个月前
小游戏系统提供二开服务
layui·游戏程序·小游戏·thinkphp