微信小程序 - 从 webgl 画布中截图放到 2d布中再保存成图片
前言
webgl 画布无法直接保存为图片,所以要转一手。
先从 webgl 中取出像素,填充到 2d 画布。
然后再从 2d画布保存图片。
获取 webgl 上下文
var config = {
alpha: true, // 透明通道开启
antialias: false, // 抗锯齿开启,开启后iOS 的 readPixels 取不到数据
premultipliedAlpha: true, // 预乘 Alpha(配合导出时的参数,解决黑边问题)
preserveDrawingBuffer: true // 打开缓冲区才能 readPixels 读到数据,但会影响性能
};
const gl = this.canvas.getContext("webgl", config) || this.canvas.getContext("experimental-webgl", config);
方案一:使用普通 canvas
async test(){
wx.showToast({ title: '点击 test', });
// 1. 用 webgl 从缓存中读像素信息。(取 ctx 时的 config 要打开 preserveDrawingBuffer: true)
let gl = 全局对象.gl; // 取出之前存好的 gl
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const len = width * height * 4; // 用画布尺寸算出像素容器的大小。(每个像素RGBA点4字节)
const row = width * 4; // 每行大小
const end = (height - 1) * row; // 结束位置(翻转Y转时用到)
let arr = new Uint8Array(len); // 临时容器,用来存从 webgl 中取的像素
let pixels = new Uint8Array(len); // 用来存最终的像素
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, arr);
// 直接处理像素数据:反转 y 转(坐标系不同,需要转换一下,不然图像是上下倒转的)
for (let i = 0; i < len; i += row) {
pixels.set(arr.subarray(i, i + row), end - i);
}
// 2. 从页面上获取 canvas (这个 canvas 是 2d 的藏在角落用来做中转)
const query = wx.createSelectorQuery()
const canvas = await new Promise((resolve,reject)=>{
query.select('#canvas2d').node().exec( (res) => resolve( res[0].node ) );
});
const ctx = canvas.getContext('2d'); // 获取2d上下文
const imgData = canvas.createImageData(pixels, width, height); // 创建 ImageData 对象
canvas.height = imgData.height; // 根据图片大小更新画布尺寸
canvas.width = imgData.width; // 根据图片大小更新画布尺寸
ctx.putImageData(imgData, 0, 0); // 向画布坐标 0,0 处开始填充全部像素(因为省略了宽高)
// 3. 画布保存到图片
wx.canvasToTempFilePath({
x: 0, //指定的画布区域的左上角横坐标
y: 0, //指定的画布区域的左上角纵坐标
fileType: 'png',
destWidth: width,
destHeight: height,
canvas: canvas,
canvasId: query,
success(res) {
debugger
console.log({path: res.tempFilePath, width, height})
// 3.1 图片保存到相册
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log(res);
wx.showToast({ title: '保存成功' })
},
fail(err){ console.error(err) }
})
},
fail(err){ console.error(err); }
});
}
方案二:使用离屏 canvas
注意:iOS
上 wx.canvasToTempFilePath
不支持 离屏canvas
。所以iOS不适合此方案。
(没有详细测试,起码我手上的iOS设备不行)
async test(){
wx.showToast({ title: '点击 test', });
debugger
// 1. 用 webgl 从缓存中读像素信息。(取 ctx 时的 config 要打开 preserveDrawingBuffer: true)
let gl = wx.polyfill.spinePlayer.ctx.gl;
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const len = width * height * 4;
const row = width * 4;
const end = (height - 1) * row;
let arr = new Uint8Array(len);
let pixels = new Uint8Array(len);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, arr);
// 直接处理像素数据(反转 y 转)
for (let i = 0; i < len; i += row) {
pixels.set(arr.subarray(i, i + row), end - i);
}
// 2. 借助离屏画布处理图片
const canvas = wx.createOffscreenCanvas({type: '2d', width, height})
const ctx = canvas.getContext('2d');
const imgData = canvas.createImageData(pixels, width, height);
canvas.height = imgData.height;
canvas.width = imgData.width;
ctx.putImageData(imgData, 0, 0);
// 3. 画布保存到图片
wx.canvasToTempFilePath({
x: 0, //指定的画布区域的左上角横坐标
y: 0, //指定的画布区域的左上角纵坐标
fileType: 'png',
destWidth: width,
destHeight: height,
canvas: canvas,
success(res) {
debugger
console.log({path: res.tempFilePath, width, height})
// 3.1 图片保存到相册
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res) {
console.log(res);
wx.showToast({ title: '保存成功' })
},
fail(err){ console.error(err) }
})
},
fail(err){ console.error(err); }
});
}
问题
-
readPixels 读取像素失败。
1.1. 方案一:readPixels 在 Canvas.requestAnimationFrame(function callback) 的回调中执行。
1.2. 方案二:canvas.getContext 时是开启preserveDrawingBuffer
1.3. 如果是iOS
还要关闭抗锯齿antialias
-
隐藏2d画布
我用来做中转的 2d canvas 需要隐藏起来。方法是使用这样的样式
.canvas2d {
position: absolute;
right: 100vw;
}
参考资料
MDN:Web 开发技术 > Web API 接口参考 > Canvas
MDN:Web 开发技术 > Web API 接口参考 > CanvasRenderingContext2D