PHP-在图片上叠加二维码

很多场景下,我们需要用户分享活动海报,并且海报上面需要包含用户信息,例如一个携带了用户ID的链接,这时我们就要使用动态生成二维码的技术。

1、准备php环境

我们使用Dockerfile构建一个Apache+PHP的镜像,并且配置安装GD库用于图片处理。

创建项目文件夹:mkdir -p /data/docker/qrcode-demo

创建存放php脚本的目录:mkdir -p /data/docker/qrcode-demo/src

创建存放php依赖的目录:mkdir -p /data/docker/qrcode-demo/src/lib

进入项目文件夹:cd /data/docker/qrcode-demo

创建Dockerfile文件,内容如下:

FROM php:7.0-apache
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng-dev \
&& docker-php-ext-install -j$(nproc) iconv mcrypt \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install -j$(nproc) gd

RUN rm -rf /var/www/html/*
RUN mkdir /var/www/html/images && chmod -R 777 /var/www/html/images
RUN mkdir /var/www/html/qrcode && chmod -R 777 /var/www/html/qrcode
COPY src/ /var/www/html/

然后使用以下命令构建镜像:

docker build -t qrcode-demo:1.0 .

首次构建过程可能会等待比较长一段时间,因为构建过程需要安装GD库,默认从国外下载源码进行构建,视网络状况等待10分钟左右即可。

然后执行以下命令运行这个镜像:

docker run -d -rm -p 81:80 -v /data/docker/qrcode-demo/src:/var/www/html qrcode-demo:1.0

为什么要挂载src目录呢?因为刚刚构建时我们的src目录是空的,我们的php代码还没写出来呢,现在挂载了src目录,我们只要在src目录编写代码然后在浏览器访问就能看到效果,方便调试。

2、编写php代码

首先我们准备两个依赖:

phpqrcode – 用于生成二维码的库 (传送门)

下载好之后,找到phpqrcode.php文件复制到本项目src/lib目录下

resizeImage – 用于调整图片大小的一个函数

由于这个函数是自己写的,不需要下载。

将以下内容保存到当前项目src/lib/文件夹,命名为resizeImage.php

<?php
function resizeImage($im,$maxX=100,$maxY=100) {
    $width = imagesx($im);
    //获取宽度 
    $height = imagesy($im);
    //获取高度
    //计算缩放比例
    $scale = ($maxX/$width) > ($maxY/$height) ? $maxY / $height : $maxX / $width;
    //计算缩放后的尺寸
    $sWidth = floor($width*$scale);
    $sHeight = floor($height*$scale);
    //创建目标图像资源
    $nim = imagecreatetruecolor($sWidth,$sHeight);
    //等比缩放
    imagecopyresampled($nim,$im,0,0,0,0,$sWidth,$sHeight,$width,$height);
    //释放图片资源
    imagedestroy($im);
    return $nim;
}

依赖准备好之后,我们就要开始编写主要代码了。

在项目文件夹的src目录创建gen.php文件,内容如下:

<?php
include('./lib/phpqrcode.php');
include('./lib/resizeImage.php');

$IMG_URL = empty($_GET['img'])?null:$_GET['img'];
$LINK = empty($_GET['link'])?null:$_GET['link'];
$POS = empty($_GET['pos'])?null:$_GET['pos'];
$SIZE = empty($_GET['size'])?235:$_GET['size'];
#数据有效性检查 $POS = explode(',',$POS);
if(count($POS) != 2 || !is_numeric($POS[0]) || !is_numeric($POS[1])){
    die(10);
}
if(!$IMG_URL || !$LINK){
    die(13);
}
//TODO检查文件是否已生成过
function download($url, $path = './images/'){
    $filePath = $path.pathinfo($url, PATHINFO_BASENAME);
    if(!file_exists($filePath)){
        #文件不存在,从网络上下载 $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
        $file = curl_exec($ch);
        curl_close($ch);
        $resource = fopen($filePath, 'a');
        fwrite($resource, $file);
        fclose($resource);
    }
    #文件存在,从本地加载
    return imagecreatefrompng($filePath);
}
#生成二维码
$qrFilePath = './qrcode/'.md5($LINK).'.png';
$qrImg = null;
if(!file_exists($qrFilePath)){
    QRcode::png($LINK,$qrFilePath,QR_ECLEVEL_L,6,3,false);
    $qrImg = imagecreatefrompng($qrFilePath);
    #调整大小
    $qrImg = resizeImage($qrImg,$SIZE,$SIZE);
    #存入本地
    ImagePng($qrImg,$qrFilePath);
}
if($qrImg == null){
    $qrImg = imagecreatefrompng($qrFilePath);
}
#下载并加载背景图
$bgImg = download($IMG_URL);

#合并 imagecopymerge($bgImg,$qrImg,$POS[0],$POS[1],0,0,imagesx($qrImg),imagesy($qrImg),100);
ob_clean();
header('Content-type: image/png');
#输出
imagepng($bgImg);

#释放资源
imagedestroy($bgImg);
imagedestroy($qrImg);

保存之后,放一张png格式的图片到项目文件夹的src目录下,取名为bg.png

然后在浏览器打开:

http://你的IP:81/gen.php?img=http%3a%2f%2flocalhost%2fbg.png&link=https%3a%2f%2fitmx.xyz&pos=0%2c0&size=200

看到上面的url不要慌,我来如果你能看懂上面的php代码,那就不用听我解释了,如果不太明白,那么我大概解释一下:

url中为什么有这么多%号?那是参数经过urlencode之后产生的

img参数:指定一张背景图片,php代码会在这张图片上画一个二维码,上面这条链接编码之前是:http://localhost/bg.png

link参数:生成的二维码的文本内容,上面url中link参数编码前为:https://itmx.xyz

pos参数:将二维码画在背景图的哪个位置,上面url中pos参数编码前为:0,0 ,也就是生成的二维码会在背景图片的左上角。格式:x位置,y位置

size参数:生成的二维码的大小

在测试的过程中,这段代码默认是有缓存二维码的功能的,如果要调试二维码大小,需要注释if(!file_exists($qrFilePath)) 这一个判断,调试完成之后记得解除注释。

如果是用在生产环境中,视情况定时清理二维码缓存。

3、投入使用

代码编写调试完之后,需要重新构建镜像以免挂载目录运行,执行以下命令构建:

docker build -t qrcode-demo:1.0 .

运行镜像:

docker run -d -p 81:80 -rm qrcode-demo:1.0

使用编排文件和nginx-proxy

web:
  image: qrcode-demo:1.0
  expose:
    - "80"
  environment:
    - VIRTUAL_HOST=qrdemo.itmx.xyz
  restart: always

跑起来:

docker-compose up -d

现在,你可以为你的项目提供一个二维码合成接口了。

后来发现这个二维码合成的PHP代码存在内存泄漏的问题,就用java实现了这个功能然后集成到项目代码中了