微信小程序canvas绘制

近日接触到微信小程序canvas绘制海报,经过一番折腾,算是能保存了,记录一下吧。

新版本Api接口

 <!-- canvas.wxml -->
<!-- width,height是根据设计图及rpx与px像素比计算出来的宽高-->
 <!-- wx.getSystemInfo({
      success: (res) => {
        if(res.model.indexOf("iPhone X") > -1){
          this.globalData.distance = 36
        }
        this.globalData.rpx = res.windowWidth / 750
        this.globalData.width = 635 * (res.windowWidth / 750)
        this.globalData.height = 974 * (res.windowWidth / 750)
      },
    })-->
<canvas type="2d" id="myCan" style="width: {{width}}px;height:{{height}}px"></canvas>

生成海报

 saveImg(res) {
    const index = res.detail
    wx.showLoading({
      title: '海报生成中'
    })
    // 新接口 canvas2d
    query.select('#myCan')
      .fields({
        node: true,
        size: true
      })
      .exec((res) => {
        console.log(res[0].node)
        const canvas = res[0].node
        const ctx = canvas.getContext('2d')
        const dpr = wx.getSystemInfoSync().pixelRatio
        canvas.width = res[0].width * dpr
        canvas.height = res[0].height * dpr
        ctx.scale(dpr, dpr)
        saveCanvers(canvas, ctx, this.data.images[index], this.data.postersInfo.title, this.data.postersInfo.desc, this.data.postersInfo.desc1, this.data.avatar, '入秋的番薯', this.data.miniCode)
      })

  },

方法封装

async function saveCanvers(canvas, ctx, headImg, title, desc, desc1, codeImg) {
  const App = getApp()
  let rpx = App.globalData.rpx
  // 海报解释下面向上移动的距离 如果两行解释  那么不上移  如果一行解释  那么上移35
  let upMove = desc1 ? 0 : 35
  // 背景色
  ctx.fillStyle = '#ffffff'
  // canvas大小
  ctx.fillRect(0, 0, 638 * rpx, (974 - upMove) * rpx)
  // 绘制主图
  await downLoadDrow(canvas, ctx, headImg, 0, 0, 638 * rpx, 558 * rpx)
  // 标题
  ctx.font = 36 * rpx + 'px'
  ctx.fillStyle = '#373B3E'
  ctx.fillText(title, 30 * rpx, 608 * rpx, 589 * rpx)
  // 解释
  ctx.font = 24 * rpx + 'px'
  ctx.fillStyle = '#9D9D9D'
  ctx.fillText(desc, 30 * rpx, 648 * rpx, 589 * rpx)
  desc1 && ctx.fillText(desc1, 30 * rpx, 684 * rpx, 589 * rpx)
  // 虚线
  ctx.strokeStyle = '#DEDEDE'
  ctx.setLineDash([3, 3]);
  ctx.beginPath();
  ctx.moveTo(30 * rpx, (708 - upMove) * rpx);
  ctx.lineTo(610 * rpx, (708 - upMove) * rpx);
  ctx.stroke();
  // 圆形头像
  ctx.save()
  ctx.beginPath()
  ctx.arc(72 * rpx, (808 - upMove) * rpx, 42 * rpx, 0, 2 * Math.PI)
  // 保证原型填充无bug
  ctx.fill()
  ctx.clip()
  await downLoadDrow(canvas, ctx, userInfo.avatarUrl, 30 * rpx, (766 - upMove) * rpx, 84 * rpx, 84 * rpx)
  ctx.restore()
  // 用户昵称
  ctx.font = 32 * rpx + 'px'
  ctx.fillStyle = '#000000'
  ctx.fillText(userInfo.nickName, 134 * rpx, (813 - upMove) * rpx, 200 * rpx)
  // 邀请扫码
  ctx.font = 24 * rpx + 'px'
  ctx.fillText('邀您扫码体验', 30 * rpx, (888 - upMove) * rpx, 200 * rpx)
  // 房车小程序
  ctx.fillText('上汽大通原厂房车小程序', 30 * rpx, (930 - upMove) * rpx, 300 * rpx)
  // 小程序码
  await downLoadDrow(canvas, ctx, codeImg, 400 * rpx, (738 - upMove) * rpx, 206 * rpx, 206 * rpx)
  setTimeout(() => {
    wx.canvasToTempFilePath({
      canvas: canvas,
      success: (can) => {
        wx.getSetting({
          success(res) {
            if (!res.authSetting['scope.writePhotosAlbum']) { //判断权限
              wx.authorize({ //获取权限
                scope: 'scope.writePhotosAlbum',
                success() {
                  saveImg(can.tempFilePath)
                }
              })
            } else {
              saveImg(can.tempFilePath)
            }
          }
        })
      },
      fail: (err) => {
        wx.showToast({
          title: '保存失败,请稍后重试',
          icon: 'none'
        })
        return false
      }
    })
  }, 500)
}
function downLoadDrow(canvas, ctx, url, x, y, w, h) {
  return new Promise((resolve, reject) => {
    wx.getImageInfo({
      src: url,
      success: (res) => {
        const img = canvas.createImage();
        img.src = res.path; //微信请求返回头像
        img.onload = () => {
          console.log(img)
          ctx.drawImage(img, x, y, w, h);
          img.src = ""
          resolve()
        }
      },
      fail: (err) => {
        wx.hideLoading()
        wx.showToast({
          title: '下载图片失败,请稍后重试',
          icon: 'none'
        })
        reject()
        return false
      }
    })
  })
}
function saveImg(path) {
  wx.hideLoading()
  wx.saveImageToPhotosAlbum({
    filePath: path,
    success: (res) => {
      wx.showToast({
        title: '已保存到相册',
        icon: 'success',
        duration: 2000
      })
    },
    fail: (err) => {
      wx.showToast({
        title: '保存失败',
        icon: 'none'
      });
    }
  })
}

老是在绘制的时候闪退,内存溢出,也不知道怎么解决,网上找了点方法,但好像都不行,也可能没找到重点,不得已,再来一套老版本canvas

老版本canvas

<canvas canvas-id="myCan" style="width: {{width}}px;height:{{height}}px"></canvas>

生成图片

 saveImg(res) {
    // 组件里面传递的图片索引  不在细说 可忽略
    const index = res.detail
    wx.showLoading({
      title: '海报生成中'
    })
    let ctx = wx.createCanvasContext("myCan")
    saveCanversOld(ctx, this.data.postersInfo.swiper[index], this.data.postersInfo.title, this.data.postersInfo.desc, this.data.postersInfo.desc1, this.data.postersInfo.buffer)
  },

方法封装

async function saveCanversOld(ctx, headImg, title, desc, desc1, codeImg) {
  let userInfo = wx.getStorageSync('userInfo')
  const App = getApp()
  let rpx = App.globalData.rpx
  let upMove = desc1 ? 0 : 35
  // 背景色
  ctx.setFillStyle('#ffffff')
  // canvas大小
  ctx.fillRect(0, 0, 638 * rpx, (974 - upMove) * rpx)
  // 绘制主图
  await downLoadDrowOld(ctx, headImg, 0, 0, 638 * rpx, 558 * rpx, 'headImg')
  // 标题
  ctx.setFontSize(36 * rpx)
  ctx.setFillStyle('#373B3E')
  ctx.fillText(title, 30 * rpx, 608 * rpx, 589 * rpx)
  // 解释
  ctx.setFontSize(24 * rpx)
  ctx.setFillStyle('#9D9D9D')
  ctx.fillText(desc, 30 * rpx, 648 * rpx, 589 * rpx)
  desc1 && ctx.fillText(desc1, 30 * rpx, 684 * rpx, 589 * rpx)
  // 虚线
  ctx.setStrokeStyle('#DEDEDE')
  ctx.setLineDash([6, 5]);
  ctx.beginPath();
  ctx.moveTo(30 * rpx, (708 - upMove) * rpx);
  ctx.lineTo(610 * rpx, (708 - upMove) * rpx);
  ctx.stroke();
  // 圆形头像
  ctx.save()
  ctx.beginPath()
  ctx.arc(72 * rpx, (808 - upMove) * rpx, 42 * rpx, 0, 2 * Math.PI)
  // 保证原型填充无bug
  ctx.fill()
  ctx.clip()
  await downLoadDrowOld(ctx, userInfo && userInfo.avatarUrl || config.defaultImg, 30 * rpx, (766 - upMove) * rpx, 84 * rpx, 84 * rpx)
  ctx.restore()
  // 用户昵称
  ctx.setFontSize(32 * rpx)
  ctx.setFillStyle('#000000')
  ctx.fillText(userInfo && userInfo.nickName || config.defaultUser, 134 * rpx, (813 - upMove) * rpx, 200 * rpx)
  // 邀请扫码
  ctx.setFontSize(24 * rpx)
  ctx.fillText('邀您扫码体验', 30 * rpx, (888 - upMove) * rpx, 200 * rpx)
  // 房车小程序
  ctx.fillText('上汽大通原厂房车小程序', 30 * rpx, (930 - upMove) * rpx, 300 * rpx)
  // 小程序码
  await downLoadDrowOld(ctx, codeImg, 400 * rpx, (738 - upMove) * rpx, 206 * rpx, 206 * rpx, 'miniCode')
  ctx.draw(false, () => {
    wx.canvasToTempFilePath({
      fileType: 'jpg',
      canvasId: 'myCan',
      success: (res) => {
        console.log(res.tempFilePath)
        saveImg(res.tempFilePath)
      },
      fail: (err) => {
        console.log(err)
      }
    })
  })
}
// 下载图片
function downLoadDrowOld(ctx, url, x, y, w, h, type = '') {
  return new Promise((resolve, reject) => {
    if (type == 'miniCode') {
      //声明文件系统
      const fs = wx.getFileSystemManager();
      //随机定义路径名称
      var times = new Date().getTime();
      var codeimg = wx.env.USER_DATA_PATH + '/' + times + '.png';
      //将base64图片写入
      fs.writeFile({
        filePath: codeimg,
        data: url,
        encoding: 'base64',
        success: (res) => {
          //写入成功了的话,新的图片路径就能用了
          ctx.drawImage(codeimg, x, y, w, h)
          resolve()
          // 绘制成功之后要释放 内存
          setTimeout(() => {
            fs.unlink({
              filePath: codeimg,
              success: (res) => {
                console.log('清除文件成功', res)
              },
              err: (err) => {
                wx.hideLoading()
                wx.showToast({
                  title: '释放文件内存失败',
                  icon: 'none'
                })
              }
            })
          }, 2000)
        }
      });
    } else if (type == 'headImg') {
      // 获取图片信息进行截取 截取的起止位置也需要计算  然后绘制  x, y, w, h只代表绘制到canvas上的范围
      wx.getImageInfo({
        src: url,
        success: (res) => {
          // 根据ui算出来的要展示的比例  width / height
          const rat = 1.143
          // 图片的宽度
          const imgWidth = res.width
          // 图片的高度
          const imgHeight = res.height
          // 剪切图片的宽度, 高度, 剪切X起始位置, 剪切Y起始位置
          let cutWidth, cutHeight, cutStartX, cutStartY
          if (imgWidth <= imgHeight) {
            cutWidth = imgWidth
            cutHeight = imgWidth / rat
            cutStartX = 0
            cutStartY = (imgHeight - cutHeight) / 2
          } else {
            cutHeight = imgHeight
            cutWidth = imgHeight * rat
            cutStartX = (imgWidth - cutWidth) / 2
            cutStartY = 0
          }
          ctx.drawImage(res.path, cutStartX, cutStartY, cutWidth, cutHeight, x, y, w, h)
          resolve()
        },
        fail: (err) => {
          console.log(err)
          wx.hideLoading()
          wx.showToast({
            title: '获取图片信息失败',
            icon: 'none'
          })
          reject()
          return false
        }
      })
    } else {
      wx.downloadFile({
        url,
        success: (res) => {
          ctx.drawImage(res.tempFilePath, x, y, w, h)
          resolve()
        },
        fail: (err) => {
          wx.hideLoading()
          wx.showToast({
            title: '下载图片失败,请稍后重试',
            icon: 'none'
          })
          reject()
          return false
        }
      })
    }
  })
}
function saveImg(path) {
  wx.hideLoading()
  wx.saveImageToPhotosAlbum({
    filePath: path,
    success: (res) => {
      wx.showToast({
        title: '已保存到相册',
        icon: 'success',
        duration: 2000
      })
    },
    fail: (err) => {
      wx.showToast({
        title: '保存失败',
        icon: 'none'
      });
    }
  })
}

里面有一些buffer文件的转换,绘制图片时比例大小的裁剪,分享图的后台配置愈合与获取等,都是比较常规的业务,就不记了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容