来自「微信开发者」公众号,作者为微信小程序技术研发工程师binnie。
本文主要介绍了3个隐藏的 Canvas 小技巧:
- 绘制并生成图片
- Video 绘制 Canvas / webgl
- 视频解码并绘制到 webgl
- 录制并导出 webgl 视频
一键加滤镜
快速合成音视频
轻松挑选视频封面
……
Canvas 能够做这些?
作为资深的开发者,相信大家对 Canvas 都不陌生。这项能力在绘制图形方面发挥着极大的作用,高效支持图片编辑、数据可视化等应用场景。但是只局限于一般能力应用,那格局就小了。
Canvas 的应用场景非常丰富!赶紧往下看看这些隐藏的 Canvas 小技巧,保证你新年用得上!还有手把手教程以及文末彩蛋哟。
-- • 绘制并生成图片 • --
示例:新年模板长按保存祝福
适用场景:图片分享海报
相关 API:RenderingContext/Canvas/wx.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:RenderingContext/Canvas
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.createVideoDecoder/VideoDecoder/RenderingContext/Canvas.requestAnimationFrame/wx.createMediaAudioPlayer/MediaAudioPlayer
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.createMediaRecorder/MediaRecorder/wx.createMediaContainer/MediaContainer/MediaTrack
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 在手,红包一直有!
有没有demo
最后一个导出视频,华为的手机不支持啊
请问怎么把文字渲染到webgl画布上?
请问怎么录制canvas并通过video实时播放出来?
请问微信小程序ctx.lineTo()画直线能选择颜色吗?
能不能在现实环境中绘制视频,比如ar扫描识别后绘制视频