微信小程序 - 从 webgl 画布中截图放到 2d布中再保存成图片

本文探讨如何将WebGL画布的内容转换到2D画布并保存为图片。针对WebGL无法直接保存的问题,提出了两种方案:普通canvas和离屏canvas,但iOS设备可能不支持。在实现过程中,遇到readPixels读取像素失败的问题,并提供了解决方案,如在requestAnimationFrame回调中执行,关闭抗锯齿等。最后,提到了2d画布需要隐藏并引用了MDN的相关Canvas API文档。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

微信小程序 - 从 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

注意:iOSwx.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); }
      });
    }

问题

  1. readPixels 读取像素失败。
    1.1. 方案一:readPixels 在 Canvas.requestAnimationFrame(function callback) 的回调中执行。
    1.2. 方案二:canvas.getContext 时是开启 preserveDrawingBuffer
    1.3. 如果是 iOS 还要关闭抗锯齿 antialias

  2. 隐藏2d画布
    我用来做中转的 2d canvas 需要隐藏起来。方法是使用这样的样式

.canvas2d { 
	position: absolute;
	right: 100vw;
}

参考资料

MDN:Web 开发技术 > Web API 接口参考 > Canvas
MDN:Web 开发技术 > Web API 接口参考 > CanvasRenderingContext2D

wx.canvasToTempFilePath(Object object, Object this)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑虾

多情黯叹痴情癫。情癫苦笑多情难

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值