小程序利用Canvas绘制图片和竖排文字

引言

闲暇时间抽个空写了个三国杀武将手册的小程序,中间有个需求设计的是合成武将皮肤图、竖排的武将姓名、以及小程序码,然后提供保存图片到相册,最终让用户可以分享到朋友圈或其他平台。合成图片应该按照Canvas的文档来做都没什么问题,主要是有个竖排文字的需求,这里和大家分享一下。

正文

下面让我们一步一步来看看如何实现的吧。

整个图片分为三个部分:

  1. 武将图片
  2. 小程序码
  3. 武将文字信息

先来看一下wxml里面的代码,主要是放了一个canvas标签,控制了一下高度和宽度属性。

<view>
  <canvas class='share-canvas' style="width:100%;height:{{canvasHeight}}px" canvas-id="share_canvas"></canvas>
</view>

武将图片

drawHeroImage: function (path) {
    var that = this;
    // 拿到canvas context
    let ctx = wx.createCanvasContext('share_canvas');
    // 为了保证图片比例以及绘制的位置,先要拿到图片的大小
    wx.getImageInfo({
      src: path,
      success: function (res) {
            
          // 计算图片占比信息   
        let maxWidth = Math.min(res.width, that.data.canvasWidth * 0.65);
        let radio = maxWidth / res.width;

        let offsetY = (that.data.canvasHeight - res.height * radio) / 2;
        console.log('offsetY=' + offsetY);
        that.setData({
          imageWidth: res.width * radio,
          imageHeight: res.height * radio,
          offsetY: offsetY,
        });
        
        // 绘制canvas背景,不属于绘制图片部分
        ctx.setFillStyle('white')
        ctx.fillRect(0, 0, that.data.canvasWidth, that.data.canvasHeight);
        // 绘制武将图片,path是本地路径,不可以传网络url,如果是网络图片需要先下载
        ctx.drawImage(path, 10, offsetY, res.width * radio, res.height * radio)
        // 绘制小程序码
        that.drawQrCodeImage(ctx);
        // 绘制势力汉字:吴
        that.drawInfluence(ctx, that.data.hero.HERO.INFLUENCE);
        // 绘制武将姓名:陆逊
        that.drawName(ctx, that.data.hero.HERO.NAME);
        // 绘制武将称号:江陵侯
        that.drawHorner(ctx, that.data.hero.HERO.HORNER);
        // 最终调用draw函数,生成预览图
        // 一个坑点:只能调用一次,否则后面的会覆盖前面的
        ctx.draw();
      }
    });
  }

小程序码

小程序码和武将图片是一个类型,无非就是需要计算绘制的位置,这里就不再展示相关代码了。

武将文字信息

从刚刚的代码可以看出,我分了3个部分来绘制,其实陆逊应该是可以放到一起的,但是我在绘制的时候发现,空格在绘制的时候会引起异常,导致空格后面的文字无法绘制出来,所以我这里陆逊中间的空白是靠位置偏移来做的。

这里就展示一下如何绘制武将称号的。

// 绘制武将称号:江陵侯
drawHorner: function (ctx, text) {
    // 设置字号
    ctx.setFontSize(26);
    // 设置字体颜色
    ctx.setFillStyle("#000000");
    // 计算绘制起点
    let x = this.data.offsetX + 35;
    let y = this.data.offsetY + 10;
    console.log('drawHorner' + text);
    console.log(x);
    console.log(y);
    // 绘制竖排文字,这里是个Util函数,具体实现请继续看
    Canvas.drawTextVertical(ctx, text, x, y);
  }

绘制竖排文字从网上找了个开源的代码,需要看原理的请看这里

当然我这里为了适用小程序做了些改动,函数原型是这样子的:

CanvasRenderingContext2D.prototype.letterSpacingText = function (text, x, y, letterSpacing)

原谅我不是很会js,完全不懂这是个什么语法,看了一会没弄懂,感觉像是给类添加新的属性,不管他。

不管白猫黑猫,能抓到耗子就是好猫

改造后的函数像下面的样子:

canvas.js

/**
* @author zhangxinxu(.com)
* @licence MIT
* @description http://www.zhangxinxu.com/wordpress/?p=7362
*/
function drawTextVertical(context, text, x, y) {
  var arrText = text.split('');
  var arrWidth = arrText.map(function (letter) {
    return 26;
    // 这里为了找到那个空格的 bug 做了许多努力,不过似乎是白费力了
    // const metrics = context.measureText(letter);
    // console.log(metrics);
    // const width = metrics.width;
    // return width;
  });
  
  var align = context.textAlign;
  var baseline = context.textBaseline;

  if (align == 'left') {
    x = x + Math.max.apply(null, arrWidth) / 2;
  } else if (align == 'right') {
    x = x - Math.max.apply(null, arrWidth) / 2;
  }
  if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
    y = y - arrWidth[0] / 2;
  } else if (baseline == 'top' || baseline == 'hanging') {
    y = y + arrWidth[0] / 2;
  }

  context.textAlign = 'center';
  context.textBaseline = 'middle';

  // 开始逐字绘制
  arrText.forEach(function (letter, index) {
    // 确定下一个字符的纵坐标位置
    var letterWidth = arrWidth[index];
    // 是否需要旋转判断
    var code = letter.charCodeAt(0);
    if (code <= 256) {
      context.translate(x, y);
      // 英文字符,旋转90°
      context.rotate(90 * Math.PI / 180);
      context.translate(-x, -y);
    } else if (index > 0 && text.charCodeAt(index - 1) < 256) {
      // y修正
      y = y + arrWidth[index - 1] / 2;
    }
    context.fillText(letter, x, y);
    // 旋转坐标系还原成初始态
    context.setTransform(1, 0, 0, 1, 0, 0);
    // 确定下一个字符的纵坐标位置
    var letterWidth = arrWidth[index];
    y = y + letterWidth;
  });
  // 水平垂直对齐方式还原
  context.textAlign = align;
  context.textBaseline = baseline;
}

module.exports = {
  drawTextVertical: drawTextVertical
}

绘制网络图片

由于网络图片无法直接绘制,所以需要先下载到本地,然后再按住本地图片绘制的流程走一遍。

downloadHeroImage: function () {
    // 微信不支持非https的图片下载,这里了个替换
    let url = this.data.hero.HERO.ICON.replace(/http/, "https");
    var that = this;
    wx.downloadFile({
      url: url,
      success: function (res) {
        // 下载成功后拿到图片的路径,然后开始绘制
        var path = res.tempFilePath;
        that.drawHeroImage(path);
      }, fail: function (res) {
        console.log(res)
      }
    });
  }

保存图片

说了这么多,自然少不了最终的一步,将绘制到canvas的图片保存到手机相册,这里需要用户授权,你需要自己处理。

用的是微信给我们提供的接口wx.canvasToTempFilePath。需要我们传入起点坐标(x, y)和画布大小(width, height)以及canvasId

saveShareImage: function () {
    wx.showLoading({
      title: '正在保存图片..',
    });
    let that = this;
    wx.canvasToTempFilePath({
      x: 0,
      y: 0,
      width: that.data.canvasWidth,
      height: that.data.canvasHeight,
      canvasId: 'share_canvas',
      success: function (res) {
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success(res) {
            console.log(res);
            wx.showToast({
              title: '保存到相册成功',
              duration: 1500,
            })
          },
          fail(res) {
            console.log(res)
            wx.showToast({
              title: '保存到相册失败',
              icon: 'fail'
            })
          },
          complete(res) {
            console.log(res)
          }
        })
      }
    })
  }

开源

本着开源的精神,源码已经放在Github上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容