收藏
评论

这些 Canvas 小技巧,保证你新年用得上官方

来自「微信开发者」公众号,作者为微信小程序技术研发工程师binnie。
本文主要介绍了3个隐藏的 Canvas 小技巧:
- 绘制并生成图片
- Video 绘制 Canvas / webgl
- 视频解码并绘制到 webgl 
- 录制并导出 webgl 视频


一键加滤镜

快速合成音视频

轻松挑选视频封面

……

Canvas 能够做这些?

作为资深的开发者,相信大家对 Canvas 都不陌生。这项能力在绘制图形方面发挥着极大的作用,高效支持图片编辑、数据可视化等应用场景。但是只局限于一般能力应用,那格局就小了。

Canvas 的应用场景非常丰富!赶紧往下看看这些隐藏的 Canvas 小技巧,保证你新年用得上!还有手把手教程以及文末彩蛋哟。

-- • 绘制并生成图片 • --

示例:新年模板长按保存祝福

适用场景:图片分享海报

相关 API:RenderingContextCanvaswx.canvasToTempFilePath

Step 1: 创建实例获取对象

创建 Canvas 实例,获取 CanvasRenderingContext2D 对象(Canvas 绘图上下文)来绘制形状、文本、图像等。


const query = wx.createSelectorQuery()
let canvas = null
query.select('#myCanvas')
  .fields({ node: true, size: true })
  .exec((res) => {
    // 通过 wx.createSelectorQuery 获取到 canvas 实例
    canvas = res[0].node
  // 通过 canvas.getContext('2d') 获取 CanvasRenderingContext2D 对象
    const ctx = canvas.getContext('2d')
  })


Step 2: 设置宽高调整图片

获取 Canvas 绘图上下文后,将 Canvas 的宽高设置为节点宽高 * 设备像素比,绘制出来的图片更清晰

// 获取设备像素比
const dpr = wx.getSystemInfoSync().pixelRatio
// 将 canvas 宽高设置为
canvas.width = res[0].width * dpr
canvas.height = res[0].height * dpr


Step 3: 绘制内容

使用 CanvasRenderingContext2D 绘制,根据业务需要在画布中绘制头像、文字、背景等

// 矩形
ctx.fillStyle = '#FFFFFF'
ctx.fillRect(0, 0, canvas.width , canvas.height )


// 图片
var image = canvas.createImage()
himage.src = 'https://example.com/example.jpg'
headImage.onload = (res) => {
  ctx.drawImage(himage 0, 0, 32, 32;
}


// 文本
ctx.font = "18px SimHei";
ctx.textAlgin = "left"
ctx.fillStyle = "#07c160";
ctx.fillText("这是我的名字", 0, 0);


Step 4: 生成并保存本地

使用 wx.canvasToTempFilePath 将画布生成图片,wx.saveImageToPhotosAlbum 将图片保存到本地。

wx.canvasToTempFilePath({
  canvas: canvas, // canvas 实例
    success(res) {
    // canvas 生成图片成功
        wx.saveImageToPhotosAlbum({
          filePath: res.tempFilePath,
          success(res) {
              // 保存成功
          }
        })
    }
})



-- • Video 绘制 Canvas / webgl • --

示例:视频文件绘制 Canvas

适用场景:制作 Video 滤镜、挑选 Video 封面等

相关 API:RenderingContextCanvas

Step 1: 获取实例

通过 wx.createSelectorQuery 获取 VideoContext 实例

let video = null
wx.createSelectorQuery().select('#video').context(res => {
  // 通过 wx.createSelectorQuery 获取 VideoContext 实例
    video = res.context;   
})


Step 2: 绘制内容

获取 VideoContext 实例后,将 VideoContext 传递给 Canvas 进行绘制。开发者根据业务需求选择绘制类型:

  • Canvas 2d 写法:canvas.drawImage(video, ...)
  • webgl 写法:gl.texImage2D(..., video)


wx.createSelectorQuery().selectAll('#myCanvas,#webglCanvas').node(res => {


  const ctx = res[0].node.getContext('2d')
    const gl = res[1].node.getContext('webgl')


    setInterval(() => {
    // canvas 2d
    // 将 video 纹理对象传入 drawImage 进行绘制
      ctx1.drawImage(video, 0, 0, w * dpr, h * dpr);
    // 添加一个蒙层
      ctx1.fillStyle = 'rgba(0, 0, 0, 0.3)'
      ctx1.fillRect(0, 0, w * dpr, h * dpr);


    // webgl
      const render = createRenderer(res[1].node, w, h)
      render(new Uint8Array(ctx1.getImageData(0, 0, w * dpr, h * dpr).data), w * dpr, h * dpr)


    }, 1000 / 24)
}).exec()


function createRenderer(canvas, width, height) {
  const gl = canvas.getContext("webgl")
  ...
  return (arrayBuffer, width, height) => {
      ...
      // 指定二维纹理图像
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer)
      gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)
    }
}



-- • 视频解码并绘制到 webgl • --

示例:视频一键解码并绘制到 webgl

适用场景:添加特效、贴图等视频编辑场景

相关 API:wx.createVideoDecoderVideoDecoderRenderingContextCanvas.requestAnimationFramewx.createMediaAudioPlayerMediaAudioPlayer

Step 1: 创建视频解码器进行解码

1. 调用 createVideoDecoder 对视频进行解码

2. 使用 videodecoder.start 启动解码,视频源文件不限制本地或远程路径

3. 通过 videodecoder.on('start', res => {}) 监听解码,通过 videodecoder.getFrameData() 获取到解码数据

// 获取视频解码器
getVideoDecoder(source, abortAudio) {
  return new Promise((resolve, reject) => {
  // 创建视频解码器
    videodecoder = wx.createVideoDecoder()
    // 开始解码
    videodecoder.start({
      abortAudio: abortAudio,
      source: source, // 视频源文件,支持本地路径&远程路径
      mode: 0 // 按pts解码,保证音画同步
    })
    // 监听解码 开始
    videodecoder.on('start', res => {
      console.log('videodecoder start', res)
      // 状态初始化
      isStop = false
      resolve(videodecoder)
    })
    // 监听解码 结束
    videodecoder.on('ended', res => {
      // 状态设置为结束,停止画面录制器
      isStop = true
    })
  })
},


Step 2: 解码数据绘制到 webgl

1. 通过 gl.texImage2D(..., image) 将解码数据绘制到 webgl

2. 使用 webgl.requestAnimationFrame 继续绘制,效果更加流畅


// 将解码数据绘制到 webgl 中
const query = wx.createSelectorQuery()
query.select('#webglCanvas').node().exec((res) => {
  const webgl  = res[0].node
  const requestAnimationFrame = webgl.requestAnimationFrame;
  // 初始化webgl
  let render = null
  if (!render) {
    render = createRenderer(webgl, 600, 400)
  }


  /**
   * 绘制视频帧到 canvas
   */
  let i = 1
  let loop = () => {
    // 解码结束,停止循环
    if (isStop) {
      return
    }
    // 获取解码数据,绘制到 webgl 中
    const imageData = videodecoder.getFrameData()
    if (imageData) {
      // render 的高宽需要设置为图片的宽高才可以绘制出来
      render(new Uint8Array(imageData.data), imageData.width, imageData.height)
    }
    // 继续绘制
    console.log('绘制帧数:', i++)
    requestAnimationFrame(loop)
  }  
  // 启动录制循环 
  requestAnimationFrame(loop)
})


Step 3: 添加音频播放器同步播放音频

完成 Step2 后,webgl 只有视频播放,缺少音频。因此使用 wx.createMediaAudioPlayer(),支持 addAudioSource 传入 videodecoder,保证视频帧渲染音画同步

/**
 * 创建媒体音频播放器
 */ 
let mediaAudioPlayer = null
let addAudio = () => {
  if (mediaAudioPlayer) return
  mediaAudioPlayer = wx.createMediaAudioPlayer()
  mediaAudioPlayer.start().then(() => {
    // 添加播放器音频来源
    mediaAudioPlayer.addAudioSource(videodecoder).then(res => {
      console.log('add mediaAudioPlayer: ',)
    })
  })
}


// render 绘制视频同时添加音频
render(new Uint8Array(imageData.data), imageData.width, imageData.height)
addAudio()



-- • 录制并导出 webgl 视频 • --

示例:录制并一键导出 webgl 视频

适用场景:将动画、编辑过的视频导出视频文件保存

相关 API:wx.createMediaRecorderMediaRecorderwx.createMediaContainerMediaContainerMediaTrack

Step 1: 创建 webgl 画面录制器进行录制

通过 createMediaRecorder 创建页面录制器,并且绑定 webgl(建议离屏状态,效果更好)进行录制

/**
 * 获取画面录制器
 */
getRecorder() {
  let canvas = this.getMainCanvasNode()


  let recorder = wx.createMediaRecorder(canvas, {
    fps: choosedVideoInfo.fps, // 实际视频的 fps
    videoBitsPerSecond: choosedVideoInfo.bitrate, // 实际视频的 bitrate
    gop: 12
  })
  // 监听录制事件
  recorder.on("timeupdate", (res) => {
    console.log('recorder 录制中,当前时间:', res.currentTime)
  })
  recorder.on("stop", (res) => {
    console.log('recorder停止')
    this.saveMedia(res.tempFilePath)
  })
  // 开始录制
  recorder.start()
  this.recorder = recorder
  return recorder
},


// 初始化 画面录制器 并进行录制
await this.initRenderer()
this.getDecoder().then((decoder) => {
  let recorder = this.getRecorder()
  var self = this
  function loop() {
    if (self.stopped) {
      return
    }
    let frameData = decoder.getFrameData()
    if (!frameData) {
      console.log('没取到帧')
      setTimeout(() => {
        loop()
      }, 1000/60)
    } else {
      self.renderFrame(frameData)
      recorder.requestFrame(() => {
        console.log('录制帧数:', i++)
        loop()
      })
    }
  }
  loop()
})


Step 2: 添加音频合成音视频

1. 通过 createMediaContainer 创建音视频处理容器来合成音视频

2. 通过 MediaContainer.extractDataSource 将视频源分离出视频轨道和音频轨道,将需要的轨道通过 MediaContainer.addTrack 添加到容器中

3. 通过 MediaContainer.export 导出即可获得合成后的视频文件

/**
 * 将视频和音频合到一起并保存到本地
 * @param {*} videoTempFilePath 
 */
saveMedia(videoTempFilePath) {
  const self = this
  let choosedFile = this.choosedFile
  const MediaContainer = wx.createMediaContainer()
  // webgl的取视频
  MediaContainer.extractDataSource({
    source: videoTempFilePath,
    success(res) {
      MediaContainer.addTrack(res.tracks[0])


      // 源视频取音频
      MediaContainer.extractDataSource({
        source: choosedFile,
        success(res) {
          // 拿到音频轨道并加入到容器
          res.tracks[0].kind == 'audio' && MediaContainer.addTrack(res.tracks[0])
          res.tracks[1].kind == 'audio' && MediaContainer.addTrack(res.tracks[1])


          // 合成视频并导出视频文件
          MediaContainer.export({
            success(res) {
              // 保存视频到本地
              wx.saveVideoToPhotosAlbum({
                filePath: res.tempFilePath,
                success() {
                  wx.showToast({
                    title: '导出成功',
                    icon: 'success',
                    duration: 2000
                  })
                  self.destroy()
                }
              })
            }
          })
        }
      })
    }
  })


},



-- •高效图像处理彩蛋 • --

学会以上这些 Canvas 小技巧,还担心新年的美图美照美视频处理不过来?赶紧码下这个 Canvas 代码包,保证你就是家里最闪耀的靓女靓仔。

预祝大家新的一年 Canvas 在手,红包一直有!

最后一次编辑于  2022-03-24
赞 7
收藏

7 个评论

  • 眰恦🥤
    眰恦🥤
    2023-11-03

    有没有demo


    2023-11-03
    赞同
    回复
  • 三文Lxy
    三文Lxy
    2023-11-03

    最后一个导出视频,华为的手机不支持啊

    2023-11-03
    赞同
    回复
  • huangwx
    huangwx
    2023-06-05

    请问怎么把文字渲染到webgl画布上?

    2023-06-05
    赞同
    回复
  • 其实我在看你
    其实我在看你
    2023-03-02

    请问怎么录制canvas并通过video实时播放出来?

    2023-03-02
    赞同
    回复
  • V.Steel
    V.Steel
    2022-09-14

    请问微信小程序ctx.lineTo()画直线能选择颜色吗?

    2022-09-14
    赞同
    回复
  • 偏执狂
    偏执狂
    2022-08-24

    能不能在现实环境中绘制视频,比如ar扫描识别后绘制视频

    2022-08-24
    赞同
    回复
  • ZLY
    ZLY
    2022-07-14

    牛牛牛

    2022-07-14
    赞同
    回复
登录 后发表内容
课程标签