小程序中canvas实现图片拼接生成新图片

2,108 阅读4分钟

今天设计师给了一张背景图和一个头像框图标和头像图标,要求通过几个图标画成一张新图并在头像框中写上获奖者的名字

实现思路:由于canvas生成后样式比较难调,所以通过canvas生成图片,然后隐藏canvas,生成的图片通过image标签在页面中显示,通过imgage去调节定位间距等样式 image.png

canvas生成:

  • canvas的宽度固定,高度按照图片宽高比剩宽度得出
  • 如果canvas宽度设置750px 图片宽高比为1 则canvas高度为750px
  • 生成的图片根据image的使用小程序提供的widthFix样式,宽度不变,高度自适应
  • canvas作用只是负责生成一个原图比例相同的图片
<canvas  style="width: {{canvasX}}px; height: {{canvasY}}px" canvas-id="canvas"></canvas>
  • 由于设计稿给的大小是750px,那么我将canvas的宽度设置为750px
data: {
    file:'https://qfd.oss-cn-guangzhou.aliyuncs.com/2021/static/UI/20220320002.png',
    file1:'https://qfd.oss-cn-guangzhou.aliyuncs.com/2021/static/UI/20220320004.png',
    avatar:'https://qfd.oss-cn-guangzhou.aliyuncs.com/static/faceframe.png'
    ,avatarImg: '/image/student.png',
    direction:true//true为纵向,false为横向
},
onLoad: function (e) {  
    this.setData({
      canvasX:375,//画布宽度固定为375
    })
    this.setTempFile()
}
  • 直接使用网络图片无法绘制canvas的背景(不会显示没有效果)下图是网络图片设置背景,大家可以试一下
    canvas.drawImage('https://baidu.png',0, 0,150, 150);
  • 由于背景图和头像框是网络图片,无法通过canvas直接画出,要转成临时图片,因为getImageInfo是异步请求,所以需要
setTempFile:async function(){
    let bgImg = await this.getFile()
    let avatar = await this.getFile1()
    this.setData({
      bgImg:bgImg,//背景图临时地址
      avatar:avatar//头像框临时地址
    })
    this.drawImg()
}
  • 将请求放在一个promise中,等请求结束后再将路径返回设置到上面data中,通过返回的res获得图片的宽高,再根据宽高比,生成canvas的高度。
getFile:function(){
    return new Promise((resolve,reject)=>{
      let bgImg = this.data.file//网络图片
      let that = this
      wx.getImageInfo({ // 根据地址下载并存为临时路径,网络地址的路径无法画到画布中
        src: bgImg,
        success: res => {
          that.setData({
            canvasY:that.data.canvasX*(res.height/res.width),//设置canvas的高度
          })
          resolve(res.path)
        }
      }) 
    })
  },
  • 从上面的步骤已经拿到了背景图和头像框的临时图片,头像图片是本地地址

image.png

  • 下面开始绘制到画布上 drawImage参数为(地址,起点left,起点top,拉伸宽度,拉伸长度)
  • 由于画布的比例和背景图比例一样,所以从(0,0)起点开始,然后拉伸画布的长度宽度
  • 效果是铺满整个画布,比例相同的条件下能够保证图片不被拉伸

image.png

//将临时图片绘制到画布上
  drawImg:function(){
    let canvas = wx.createCanvasContext('canvas');
    canvas.drawImage(this.data.bgImg,0, 0, this.data.canvasX, this.data.canvasY);
    canvas.save();//绘制第二张图片应该先保存一下之前绘制的图片
    
    // //绘制头像定位
    let imgiconW = 70//头像高度
    let imgiconH = 70//头像宽度
    let imgiconX = (this.data.canvasX - 70) / 2//画布宽度减去自身宽度除2,得出左边距离
    let imgiconY = 100
    canvas.drawImage(this.data.avatarImg,imgiconX,imgiconY,imgiconW,imgiconH);

    // //绘制头像框定位
    let iconW = 150
    let iconH = 95
    let iconX = (this.data.canvasX - 150) / 2
    let iconY = 100
    canvas.drawImage(this.data.avatar,iconX,iconY,iconW,iconH);
    
    // //绘制文字定位 先改变中心 保持居中
    let fontVal = 15+"px serif"
    let fontX = 375/2//取画布x轴中心,画出通过下面文字居中,能达到文字在画布x轴居中的效果
    let fontY = 175
    canvas.font= fontVal;
    canvas.textAlign = 'center';
    canvas.fillText("Hello World",fontX,fontY);
    let that = this
    //将生成好的图片保存到本地,需要延迟一会,绘制期间耗时
    canvas.draw(true, setTimeout(function () {
      that.toImport()
    }, 1000));
  },

  • 需要注意的是
  • 先画头像,再画头像框,drawImage的调用顺序决定了图片的层级,越慢调用层级越高 image.png
  • 图片居中定位使用画布宽度减去自身宽度除2,得出左边距离 image.png
  • 文字居中定位使用画布宽度除2,得出中间点距离,再用textalign中间点居中
  • 将生成好的图片保存到本地,需要延迟一会,绘制期间耗时 image.png
  • 最后通过将画布的图片导出图片路径,放到image标签中进行显示,之前的canvas直接隐藏掉,这里笔者是通过定位的方式隐藏画布,有更好的方法可以评论区提出
  • 如果通过hidden的话会导致获取不到canvas所以不能用这个额方法
<view >
  <canvas  style="width: {{canvasX}}px; height: {{canvasY}}px;position: fixed;top:-800px;" canvas-id="canvas"></canvas>
  <view>
    <image class="imgClass" mode='widthFix' src="{{imgSrc}}"></image>
  </view>
</view>
toImport:function (params) {
    let that = this
    wx.canvasToTempFilePath({
      x: 0,
      y: 0,
      canvasId: 'canvas',
      fileType: 'png', //设置图片类型,否则图片无背景色
      success(res) {
        that.setData({
          imgSrc:res.tempFilePath
        })
      }
    })
  }
  • 最后实现效果如下 image.png