ImageMagick 文字写入图片时,变问号的解决方法

使用ImageMagick做海报时,如果需要写入用户微信昵称,比如 ‘张三👏’,图片内的文字会变成 ‘张三?’,因为本质上,emoji不是文字,在常规入Pingfang字体中,是不存在对应关系的。查遍24史(github和stackoverflow),都说ImageMagick并不支持emoji表情,php自带的gd库就更不支持了。

说明

首先,emoji对应的unicode码在不同平台解析出的样式不同。所以即使mysql使用字符集UTF8mb4格式将emoji存储起来,拿出来后,发给前端(andr,ios,浏览器),同一个unicode的图并不一样。

思路

所以,想让‘张三👏’完美的在海报上写出来。需要转换思考的方式,将‘张三👏’拆分成‘张三’+‘👏’,前者当成文字,后者当成图片。所以,能找到对应unicode对应的图片素材。就能使用ImageMagick的写入文字和合并图片的方法分治处理了。

emoji素材

https://www.unicode.org/emoji/charts/full-emojilist.html

以上链接为unicode对应的emoji在各大平台的图像映射关系

分析一下页面源代码,发现,除了绿框内的。其他的都是base64的格式发出的图片数据流,如下:

所以,直接control + s 保存该页面,重命名为emoji.html,同时mkdir 建立emojis文件夹,编写处理图片的php代码,如下:

<?php

$str = file_get_contents('./emoji.html');
$regx = '/<td class=\"code\">.*?<a href=.*?>(.*?)<\/a>.*?<img.*? class=\"imga\" src=\"(.*?)\">.*?<\/td>/is';
preg_match_all($regx, $str, $matches, PREG_PATTERN_ORDER);
$codes = $matches[1];
$stream = $matches[2];
unset($matches);
function get_img_type($str){
    $regx = '/^(data:\s*image\/(\w+);base64,)/';
    preg_match($regx, $str, $result);
    return $result;
}
foreach($codes as $k => $code){
    $code = str_replace(' ', '', $code);
    $code = str_replace('U+', '', $code);
    $info = get_img_type($stream[$k]);
    $mime = $info[2];
    $prefix = $info[1];
    $streamInfo = base64_decode(str_replace($prefix, '', $stream[$k]));
    file_put_contents('./emojis/'.$code.'.'.$mime, $streamInfo);
    unset($stream[$k]);
}

运行后,拿出所有图片,如下,素材已经取出来了

那么,如何找到‘?’对应的unicode码呢

如下php函数

function utf8ToUnicode($utf8) 
{
    $i = 0;
    $l = strlen($utf8);
    $out = '';
    while ($i < $l) {
        if ((ord($utf8[$i]) & 0x80) === 0x00) {
            $n = ord($utf8[$i++]);
        } elseif ((ord($utf8[$i]) & 0xE0) === 0xC0) {
            $n =
                ((ord($utf8[$i++]) & 0x1F) <<  6) |
                ((ord($utf8[$i++]) & 0x3F) <<  0);
        } elseif ((ord($utf8[$i]) & 0xF0) === 0xE0) {
            $n =
                ((ord($utf8[$i++]) & 0x0F) << 12) |
                ((ord($utf8[$i++]) & 0x3F) <<  6) |
                ((ord($utf8[$i++]) & 0x3F) <<  0)
            ;
        } elseif ((ord($utf8[$i]) & 0xF8) === 0xF0) {
            $n =
                ((ord($utf8[$i++]) & 0x07) << 18) |
                ((ord($utf8[$i++]) & 0x3F) << 12) |
                ((ord($utf8[$i++]) & 0x3F) <<  6) |
                ((ord($utf8[$i++]) & 0x3F) <<  0)
            ;
        } elseif ((ord($utf8[$i]) & 0xFC) === 0xF8) {
            $n =
                ((ord($utf8[$i++]) & 0x03) << 24) |
                ((ord($utf8[$i++]) & 0x3F) << 18) |
                ((ord($utf8[$i++]) & 0x3F) << 12) |
                ((ord($utf8[$i++]) & 0x3F) <<  6) |
                ((ord($utf8[$i++]) & 0x3F) <<  0);
        } elseif ((ord($utf8[$i]) & 0xFE) === 0xFC) {
            $n =
                ((ord($utf8[$i++]) & 0x01) << 30) |
                ((ord($utf8[$i++]) & 0x3F) << 24) |
                ((ord($utf8[$i++]) & 0x3F) << 18) |
                ((ord($utf8[$i++]) & 0x3F) << 12) |
                ((ord($utf8[$i++]) & 0x3F) <<  6) |
                ((ord($utf8[$i++]) & 0x3F) <<  0);
        } else {
            throw new \Exception('Invalid utf-8 code point');
        }
        $n = strtoupper(dechex($n));
        $pad = strlen($n) <= 4 ? strlen($n) + strlen($n) %2 : 0;
        $n = str_pad($n, $pad, "0", STR_PAD_LEFT);

        $out .= $n;
        //$out .= sprintf("\u%s", $n);

    }
    return $out;
}

echo utf8ToUnicode('👏');   //输出1F44F

从emojis文件夹中可以找到1F44F.png,就是对应的素材。

最后,怎么用ImageMagick插入图片到另一张图片上,这就不在此多做赘述了。

效果图如下:

小程序推送

前言

小程序推送限制性较强,用户活跃度低几乎就无法推送,如张小龙设计的那般,用完就走(该接口将于2020/01/10下线,详见新的订阅消息文章)

代码

<?php

class PushService
{
    protected $touser;

    protected $template_id;

    protected $page;

    protected $form_id;

    protected $data;

    protected $emphasis_keyword;

    public function __construct($userOpenid, $formId, $page, $data, $emphasisKeyword = '')
    {
        $this->touser      = $userOpenid;
        $this->page        = $page;
        $this->form_id     = $formId
        $this->data        = $this->paramsDeal($data);
        $this->emphasis_keyword = $emphasisKeyword;
    }

    /**
     * 通知
     */
    public function someMsg($tmpId){
        $this->template_id = $tmpId;
        return $this->doPush();
    }

    /**
     * 发送推送
     * @return bool|mixed
     */
    public function doPush(){
        if(!$this->form_id){
            return false;
        }
        $pushData = [
            'touser' => $this->touser,
            'template_id' => $this->template_id,
            'page' => $this->page,
            'form_id' => $this->form_id,
            'data' => $this->data,
        ];

        if($this->emphasis_keyword){
            $pushData['emphasis_keyword'] = $this->emphasis_keyword;
        }

        $wx = new WeiXinService();
        $result = $wx->pushToUser(json_encode($pushData));

        return $result;
    }

    /**
     * 处理模版内容
     * @param $data
     * @return array
     */
    public function paramsDeal($data){
        $i = 1;
        $result = [];
        foreach ($data as $v){
            $result['keyword'.$i] = array(
                'value' => $v
            );
            $i++;
        }
        return $result;
    }
}

<?php

class WeiXinService
{
    /**
     * @var String appID 微信小程序AppID
     */
    protected $appID;

    /**
     * @var String AppSecret 微信小程序AppSecret
     */
    protected $appSecret;

    public function __construct()
    {
        $this->appID          = $this->config['appID'];
        $this->appSecret      = $this->config['appSecret'];
    }

    /**
    * 获取accessToken
    */
    public function getAccessToken(){
        $hashKey = md5($this->appID);

        $access_token = UtilCache::getCache($hashKey);
        if($access_token){
            return $access_token;
        }

        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$this->appID."&secret=".$this->appSecret;

        $result = json_decode(NnCommon::curlPost($url, []), true);
        if(!$result['access_token']){
            return '';
        }
        UtilCache::setCache($hashKey, $result['access_token'], 7000);
        return $result['access_token'];
    }

    /**
     * 小程序消息推送
     * @param $data
     * @return mixed
    */
    public function pushToUser($data){
        $url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=".$this->getAccessToken();
        $res = json_decode(NnCommon::curlPost($url, $data, 1), true);
        return $res;
    }
}

$params = [];
$push = new PushService(****);
$res = push->someMsg($tplId);

小程序授权

前言

小程序授权分2步,第一步,前端获取code给后段,获得用户openid+sessionkey,第二步,用户点击授权按钮,拿到解密向量iv,加密数据encryptedData。后端通过sessionkey+iv+encryptedData,然后使用官方的解密方法(还需要小程序appid)。解密出用户的信息,如头像,昵称,unionid等