Generating posters is a frequent requirement in applets development, and HERE I summarize the common steps:

  • There are mainly text and background drawing, user profile picture and small program TWO-DIMENSIONAL code
  • Note: The implementation of this case is based on mpVue framework, if the use of small program own framework or UNIAPP framework need to make a small amount of adjustment

Resources to prepare

Small program TWO-DIMENSIONAL code

Before generating the poster, you first need to obtain the small program QR code, which is usually generated on the server side. In this case, we will use cloud development instead. The advantage of cloud development is FAAS = Functions as a Service, which can bypass tedious back-end construction and allow development to focus on business logic (for the front end, it is a step towards all-dry engineer).

Citing a picture of Dashuai teacher, the following traditional deployment can be omitted under the cloud development mode

Developers.weixin.qq.com/miniprogram…

  • Interface A can accept pages/index/index? Param =128 bytes (100 thousand bytes)? userid=xxx&from=123213
  • Interface C, officially not recommended use, square is not recommended.
  • Interface B must be the page where the published applet exists (otherwise an error will be reported). For example, do not add/before the root path of Pages /index/index, and do not carry parameters (please put the parameters in the scene field). If this field is not filled in, the main page will be skipped by default

The cloud function code is as follows: getMpCode/index.js

// Cloud function entry file
const cloud = require('wx-server-sdk')

cloud.init()

// Cloud function entry function

exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()
  try {
  // wechat official interface, generate small program code (interface A is used here for demonstration, interface B is recommended for the actual project)
    const result = await cloud.openapi.wxacode.get({
        "path": 'page/index/index'."width": 430."isHyaline":true
      })
    return result
  } catch (err) {
    return err
  }
}

Copy the code

Call the cloud function to get the QR code:

// The promise wrapper is mainly used to avoid callback hell when used later.
 getQrcode(){
    return new Promise((resolve,reject) = >{
         wx.cloud.callFunction({
          name:"getMpCode".// The data to be retrieved is of type buffer
          success:(res) = >{
            const src="data:image/jpg; base64,"+ wx.arrayBufferToBase64(res.result.buffer);
            resolve(src)
          },
          fail:(err) = >{ reject(err) } }); })},Copy the code

The avatars

Obtaining a user profile picture requires user authorization. My approach is to save the obtained user information locally. If there is no local user profile picture information when drawing posters, the popup box will guide users to authorize.

  // Check whether the local user profile picture exists
  checkUserInfo(){
      const userInfoProfile = wx.getStorageSync("userProfile");
      if(! userInfoProfile){return false
      }
      return true
  },
  
  // Check if there is a profile picture before drawing a poster
   drawPoster(){
    const self=this;
    if(! self.checkUserInfo()){// If the user information does not exist, a dialog box is displayed to guide the user to obtain the information
      return wx.showModal({
            title: 'tip'.content: 'Please first authorize access to profile picture information'.async success (res) {
              if (res.confirm) {
                // Obtain user information after user authorization
                await self.getUserprofile()
                self.drawPoster();
              } else if (res.cancel) {
              }
            }
          })
    };
    / /...
   }
   
  // Get user information
  getUserprofile(){
    return new Promise((resolve,reject) = >{
          // This interface needs to be triggered by a button and cannot be used directly in the lifecycle
          wx.getUserProfile({
          desc:'Get your nickname, avatar'.success:res= >{
          // Save the file to the local directory
              wx.setStorage({
                  key: "userProfile".data: res.userInfo
              });
              resolve()
          },
          fail: (res) = >{
            console.log(`fail`,res)
            reject()
          }
        })

    })
  },
  
  // Obtain the user profile picture from the local directory
   getHeadImage(){
    return new Promise((resolve,reject) = >{
        // Obtain it from storage
        const userProfile=wx.getStorageSync(`userProfile`);
        if(! userProfile){return reject()
        }
        let {avatarUrl=""}=userProfile;
        resolve(avatarUrl)
     })
   },
Copy the code

background

Here, the image is stored in the cloud storage in advance, and then call wx.getImageInfo to download the network image to the local for subsequent mapping

  getBgImg(){
    const self=this
    return new Promise((resolve,reject) = >{
      // Get the image in the cloud storage
      self.getImgInfo('cloud://cloud1-8gp38tt58f1cad0e.636c-cloud1-8gp38tt58f1cad0e-1306912889/mdata/img/weiwuxian2.jpg').then(res= >{
        console.log(`cloudImg`,res)
        //{errMsg: "getImageInfo:ok", width: 794, height: 794, type: "jpeg", orientation: "up",path: "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg",type:"jpeg",width:794}
        if(res && res.path){
          resolve(res.path)
        }else{
          reject()
        }
      })
    })
  },
  
  getImgInfo(src){
    return new Promise((resolve, reject) = > {
      wx.getImageInfo({
        src: src,
        success: resolve,
        fail: reject
      })
    })
  },

Copy the code

Canvas style

Since the picture saved with a doubled canvas will be very fuzzy when obtaining temporary road force to save the picture, we need to process the canvas canvas in multiple times. Pixel ratio can be used as the multiplier, which is easier to process. Pixel ratio is used here, as shown below

The structure style

   
<template>
   <view class="sharePoster">
     <view>
        <view class="canvasWrap">
          <canvas id="myCanvas" type="2d" style="height:450px; width:300px;"></canvas>
          <img :src="poster" class="poster-img" style="height:450px; width:300px; margin-top:20rpx;">
        </view>
        <button type="primary" style="width:500rpx" @click="drawPoster">Draw the posters</button>
        <button type="primary" @click="btnSavePoster" style="width:500rpx; margin-top:20rpx">Save the posters</button>
        
     </view>
   </view>
</template>

    <style scoped lang="less">
     .canvasWrap{
       display:flex;
       flex-direction:column;
       align-items:center;
       padding-bottom:20rpx;
       #myCanvas{
         position:absolute;
         left: -1000px; //canvasHide out of view}}</style>
Copy the code

The canvas adaptation

 onReady(){
    this.drawPoster();
 },

drawPoster(){
    const query = wx.createSelectorQuery()
    const self=this;
    / / /... User authorization to obtain profile picture information
    // Get canvas instance and context object
    query.select('#myCanvas')
      .fields({ node: true.size: true })
      .exec(async (res) => {
        self.canvas = res[0].node
        self.ctx = self.canvas.getContext('2d')
        // Screen pixel ratio
        const dpr = wx.getSystemInfoSync().pixelRatio
        self.canvas.width = res[0].width * dpr
        self.canvas.height = res[0].height * dpr
        // After the canvas is enlarged, the drawing scale is enlarged accordingly, so that the drawing can be calculated according to the original size
        self.ctx.scale(dpr, dpr)
        / /... Draw the background text, get the avatar and the QR code, draw the avatar and the QR code})},Copy the code

Draw the background and text

drawPoster(){
    const query = wx.createSelectorQuery()
    const self=this;
    / /... Judge the local information and guide the user to obtain the profile picture

    query.select('#myCanvas')
      .fields({ node: true.size: true })
      .exec(async (res) => {
        / /... Canvas fit
        / / the background
        self.ctx.fillStyle='#F5F5F5'
        self.ctx.fillRect(0.0.300.450)
        self.ctx.fillStyle='pink'
        self.ctx.fillRect(0.0.300.300)
        
        / / text
        self.ctx.textBaseline='top'
        self.ctx.textAlign='left'
        self.ctx.fillStyle='# 000'
        self.ctx.fontSize=120+'px'
        self.ctx.fillText("I'm writing an article in nuggets, please give me a thumbs up!".20.320)
        // Draw the dividing line
        self.ctx.moveTo(20.340)
        self.ctx.lineTo(280.340)
        self.ctx.strokeStyle = '# 333'
        self.ctx.stroke()
        / /... Get the profile picture and qr code, draw the profile picture and QR code, and convert it into a picture})}Copy the code

Draw profile pictures, QR codes and background images

Get profile picture, QR code and background image

As described above, the code is as follows

  drawPoster(){
    const query = wx.createSelectorQuery()
    const self=this;
    / /... Judge the local information and guide the user to obtain the profile picture

    query.select('#myCanvas')
      .fields({ node: true.size: true })
      .exec(async (res) => {
        / /... Canvas fit
        / /... Background and text drawing part
        wx.showLoading({
          title: 'Poster in production'
        });
        // Get the profile picture, QR code and background image
        const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
        console.log(result) 
        / / [" https://thirdwx.qlogo.cn/mmopen/vi_32/Al8jy0dq1soJ... OiczAdv0PBXCt5erf1WZU90csaMsXjR3ERib9BIFJskTQ / 132 ", "data: image/JPG; base64, iVBORw0KGgoAAAANSUhEUgAAAa4A... ra2vrIXKqra2tra2th8j/Cwh3EyS5PLNGAAAAAElFTkSuQmCC", "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg"]
        / /... Draw profile pictures and QR codes and convert them into pictures})}// Get the user profile picture
   getHeadImage(){
    return new Promise((resolve,reject) = >{
        // Obtain it from storage
        const userProfile=wx.getStorageSync(`userProfile`);
        if(! userProfile){return reject()
        }
        let {avatarUrl=""}=userProfile;
        resolve(avatarUrl)
     })
   },
   
   // Get the code of the applet
   getQrcode(){
    return new Promise((resolve,reject) = >{
         wx.cloud.callFunction({
          name:"getMpCode".success:(res) = >{
            const src="data:image/jpg; base64,"+ wx.arrayBufferToBase64(res.result.buffer);
            console.log(`scr`,src)
            resolve(src)
          },
          fail:(err) = >{ reject(err) } }); })},// Get the background image
   getBgImg(){
    const self=this
    return new Promise((resolve,reject) = >{
    // Get the image in the cloud storage
      self.getImgInfo('cloud://cloud1-8gp38tt58f1cad0e.636c-cloud1-8gp38tt58f1cad0e-1306912889/mdata/img/weiwuxian2.jpg').then(res= >{
        console.log(`cloudImg`,res)
        //{errMsg: "getImageInfo:ok", width: 794, height: 794, type: "jpeg", orientation: "up",path: "http://tmp/wsz3Rlfyo5yj279d60b79b9c747d19a192f79e7e9cb8.jpg",type:"jpeg",width:794}
        if(res && res.path){
          resolve(res.path)
        }else{
          reject()
        }
      })
    })
  },
Copy the code

Draw profile pictures, QR codes and background images

  drawPoster(){
    const query = wx.createSelectorQuery()
    const self=this;
    / /... Judge the local information and guide the user to obtain the profile picture

    query.select('#myCanvas')
      .fields({ node: true.size: true })
      .exec(async (res) => {
        / /... Canvas fit
        / /... Background and text drawing part
        wx.showLoading({
          title: 'Poster in production'
        });
        // Get the profile picture, QR code and background image
       const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
        / /... Draw profile picture and QR code and background picture
        await Promise.all([self.drawHeadImg(result[0])], self.drawQRcode2(result[1]),self.drawBgImg(result[2])) wx.hideLoading(); })}// Draw the avatar
   drawHeadImg(img){
    const self=this
    return new Promise((resolve,reject) = >{
       Wx. getImageInfo = wx.getImageInfo = wx.getImageInfo
        self.getImgInfo(img).then(res= >{
            let image=self.canvas.createImage();
            image.src=res.path;
            image.onload=() = >{
              self.ctx.save()
              self.ctx.fillStyle='#fff'
              // To draw a round avatar, first make a round clipping area and then draw a rectangular avatar. The final effect is a round avatar
              self.ctx.beginPath()
              self.ctx.arc(70.400.50.0.2 * Math.PI)
              self.ctx.clip()
              self.ctx.drawImage(image, 20.350.100.100);
              self.ctx.restore()
              resolve()
            }
        })
     })
   },
   
   getImgInfo(src){
    return new Promise((resolve, reject) = > {
      wx.getImageInfo({
        src: src,
        success: resolve,
        fail: reject
      })
    })
  },
  
  // Draw a qr code image
  drawQRcode2(bufferData){
    const self=this;
    return new Promise((resolve,reject) = >{
       let image=self.canvas.createImage();
            // Load buffer data
            image.src=bufferData;
            image.onload=() = >{
              self.ctx.drawImage(image, 160.350.100.100);
              resolve()
            }
    })
  },
  // Draw the background image
 drawBgImg(src){
    const self=this;
    return new Promise((resolve,reject) = >{
       let image=self.canvas.createImage();
            image.src=src;
            image.onload=() = >{
              self.ctx.drawImage(image, 0.0.300.300);
              resolve()
            }
    })
  },
  
Copy the code

Convert canvas image to image

  drawPoster(){
    const query = wx.createSelectorQuery()
    const self=this;
    / /... Judge the local information and guide the user to obtain the profile picture

    query.select('#myCanvas')
      .fields({ node: true.size: true })
      .exec(async (res) => {
        / /... Canvas fit
        / /... Background and text drawing part
        wx.showLoading({
          title: 'Poster in production'
        });
        // Get the profile picture, QR code and background image
        const result=await Promise.all([self.getHeadImage(),self.getQrcode(),self.getBgImg()]);
        / /... Draw profile picture and QR code and background picture
        await Promise.all([self.drawHeadImg(result[0])], self.drawQRcode2(result[1]),self.drawBgImg(result[2]))
        wx.hideLoading();
        // Convert the canvas into a temporary path and use IMGE to load the generated image to render on the page
        self.canvas2Img()
        
      })
   }
   
   canvas2Img(){
        const self=this
        setTimeout(() = > {
          // Save the canvas as a temporary path for display
          wx.canvasToTempFilePath({
            // The entire canvas node needs to be passed in because it is 2D
            canvas:self.canvas,
            success: (res) = > {
              // Save the path to display on the page through image
              self.poster = res.tempFilePath;
            },
            fail:(res) = >{
              console.log(`fail`,res)
            }
          },self)
        }, 200)}Copy the code

Trigger button to save poster locally

  btnSavePoster(){
    const self=this;
    // Save the image from the local path to the album
    wx.saveImageToPhotosAlbum({
        filePath: self.poster,
        success: (result) = > {
          wx.showToast({
            title: 'Poster saved, go share it with your friends. '.icon: 'none'})},// Saving an image to an album requires user authorization. If saving fails, pull up the authorization dialog box
        fail:(err) = >{
          console.log(err)
            if(err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg === "saveImageToPhotosAlbum:fail auth deny" || err.errMsg === "saveImageToPhotosAlbum:fail authorize no response") {
                wx.showModal({
                  title: 'tip'.content: 'Need your authorization to save album'.showCancel: false.success: modalSuccess= > {
                  wx.openSetting({
                    success(settingdata) {
                      if (settingdata.authSetting['scope.writePhotosAlbum']) {
                                console.log('Obtain permission successfully, give again click the picture to save to the album prompt. ')}else {
                                console.log('Permission failed to be obtained. Warning is given that it cannot be used properly without permission')}}})}})}})}})},Copy the code