收藏
回答

安卓手机用canvasToTempFilePath提示exceed size limit

框架类型 问题类型 操作系统 操作系统版本 手机型号 微信版本
小程序 Bug Android 安卓9 funtouch OS_9 vivo Z5X 7.0.13

使用canvas2d的方法,安卓手机vivo z5x 红米note8 pro等用canvasToTempFilePath生成图片提示canvasToTempFilePath:fail:convert native buffer parameter fail. native buffer exceed size limit,初步怀疑是宽度设置太大原因,设置了180vw。

<canvas id="canvas" canvas-id="signCanvas" type="2d" binderror="drawError" bindtouchstart="drawStart" 
bindtouchmove="drawMove" bindtouchend="drawEnd" 
style="width:{{width}}vw;height:100%;border:1px solid #ddd;margin:auto;margin-left:{{marginLeft}}vw;">
</canvas>
  data: {
    marginLeft: 0,
    width: 180,
    demoSrc: '',
    title: '',
    isShowCanvas: true,
    // 默认一开始只有一个页面 
    isOnlyOnePage: true
  },

    init(res) {
      console.log(res)
      const width = res[0].width
      const height = res[0].height
      const canvas = res[0].node
      console.log('初始化宽高度', width, height)
      canvasWidth = width;
      canvasHeight = height
      signCanvas = canvas.getContext('2d')
      canvas.width = width * dpr
      canvas.height = height * dpr
      canvasObj = canvas
      signCanvas.scale(dpr, dpr)
      signCanvas.lineWidth = 8;
      signCanvas.lineCap = "round";
      signCanvas.strokeStyle = "#222222";
    },
    // 保存确定
    saveSign() {
      let that = this;
      let canHeight = 90;
      let canWidth = this.data.width;
      if (handData.length) {
        console.log('canvas宽高', canvasWidth, canvasHeight)
        console.log('dest图片宽度高度', canWidth, canHeight)
        wx.canvasToTempFilePath({
          x: 0,
          y: 0,
          destHeight: canHeight,
          destWidth: canWidth,
          canvas: canvasObj,
          success: function (res) {
            console.log('res', res)
            that.setData({
              demoSrc: res.tempFilePath
            })
            wx.downloadFile({
              url: res.tempFilePath,
              success: (res) => {
                console.log(res)
              },
              fail: (err) => {
                console.log(err)
              }
            })
          },
          fail: function (err) {
            console.log('生成图片失败', err)
            if (err && err.errMsg.indexOf('native buffer exceed size limit') >= 0) {
              util.showModel('生成图片失败''生成图片过大,请重新进入该页面,减少签名文字大小,降低右移次数')
            } else if (err && err.errMsg) {
              util.showModel('生成图片失败', err.errMsg)
            } else {
              util.showModel('生成图片失败''请重新打开页面签名')
            }
          }
        })
      }


    }
回答关注问题邀请回答
收藏

6 个回答

  • Karl
    Karl
    2021-09-14

    点赞,我说怎么忽然就内存不足,还是新机型!

    2021-09-14
    有用
    回复
  • Schnuffel
    Schnuffel
    2021-05-24

    我的解决方案是:

    1. 在 `wxml` 用一个假的 `view` 容器展示海报给用户,然后 `canvas` 使用 `opacity: 0` 隐藏起来(不要用 visibility: hidden 或 hidden),之前遇到某个不生效问题现在印象还深刻,具体已经不知道了,因为我用的是 `wxml-to-canvas` 这个小程序拓展库,所以也必须用 `wxml` 作为一个容器(建议不要用,还不如自己写 FUCK)
    2. `canvas` 一般不会超过屏幕宽度,但是会超过评估高度,超过评估高度就会出现上面的问题,所以要判断该 `容器` 的高度是否已经超过屏幕(使用获取元素的 `api` 获取高度),如果超过了,那就按比例缩放(这个经过个人实践,就算缩放了截出来的图还是很清晰的,由于手机相册的特性,就算你保存一张高度超级高的图片,你默认展示的效果跟 `canvas` 缩放是一样的,相册也是全屏展示图片,宽度和高度被等比例缩放)


    以下是我实现这个需求的笔记,由于微信开发工具展示太丑,建议复制到 `markdown` 查看:

    实现思路与步骤
    
    
      一般的海报都带有小程序码,而小程序码是需要服务端生成之后返回一个图片地址给前端,之后再绘制出来
    
    
      由于生成海报是动态的,如文字数量不是固定的,或是生成多种海报类型,海报的高度可能不一样,而初始化组件时还需要传入 `width` 和 `height`,并且渲染元素也需要 `width` 和 `height` 属性
    
    
      1. 显示生成海报中的样式
      2. 写好一个和海报样式一模一样的容器,并使用 `visibility: hidden` 进行隐藏 
      3. 获取到该容器的宽度和高度之后再初始化 `wxml-to-canvans` 组件且把带有 `wxml-to-canvans` 组件的容器使用 `opacity: 0` 隐藏起来
      4. 获取到容器宽高之后获取小程序码
      5. 获取小程序码之后获取到 `wxml-to-canvans` 实例并调用 `renderToCanvas` 方法,这个过程需要传入 `wxml` 和 `style` 对象
      6. 渲染成功之后调用 `wxml-to-canvans` 实例的 `canvasToTempFilePath` 方法将 `canvas` 转为图片,返回图片的临时地址
      7. 拿到临时地址之后把海报的容器的 `visibility` 改为 `visible`
      8. 点击保存按钮之后需要调用 `wx.saveImageToPhotosAlbum` 的 `api`,把生成的临时地址传入即可。该 `api` 需要进行相册授权,且该授权拒绝过一次之后不再弹出授权框,需要做拒绝授权处理,如在 `fail` 函数中诱导用户打开 `wx.openSetting` 设置相册授权
      9.  保存成功之后进行成功操作即可
    注意事项
    
    
      - 小程序对原生组件使用 `visibility: hidden` 不能达到效果,在 `ios` 中 `canvas` 依然有问题,所以使用 `opacity: 0` 进行替代(开发工具没效果)
      - `canvas` 在容器里面滚动时会跟着屏幕一起滚动(微信开发工具),在手机里面不会
      - `canvas` 的层级比较高,但是新版本可以设置 `canvas` 的层级
      - `wxml-to-canvas` 组件初始化需要一段时间,才能获取其组件并调用里面的方法
      - `wxml-to-canvans` 放在带有加载渲染逻辑的 `sort` 里面不会渲染出来,即在 `sort` 使用 `if` 操作,然后 `if` 的条件最后使用 `this.setData` 然后进行显示,这个在微信开发工具不会渲染,在手机设备没有测试过
      - `wxml-to-canvas` 第一次初始化了宽高之后,如果再对该 `width` 和 `height` 属性使用 `this.setData` 改变,这个组件的宽高有效果,但是这个组件里面的 `canvas` 不会改变,可以使用 `if` 等获取到宽高之后再渲染出来
      - `wxml-to-canvas` 调用 `renderToCanvas` 方法时,如果绘制的内容带有图片且图片是在线链接,则需要在微信公众平台设置小程序的域名配置
      - `canvas` 的宽高不能设置太大,安卓手机会出现闪退问题,`ios` 则会出现绘制白图问题
      - `canvas` 宽度或高度过高时,需要按比例缩放,否则安卓机型生成不了图片
    `canvasToTempFilePath` 的报错记录
    
    
      - `write file fail`
    
    
        `wxml-to-canvas` 在手机上调用 `canvasToTempFilePath` 方法时报 `write file fail`,原因是因为 `wxml-to-canvas` 的宽度或高度大于该组件的容器的宽度或高度,把 `wxml-to-canvas` 的 `width` 或 `height` 减去 `1` 或 `2` 就能解决问题
    
    
      - `convert native buffer parameter fail.native buffer exceed size limit`
    
    
        调用 `canvasToTempFilePath` 方法时报这个错误是因为 `canvas` 的宽度或高度太大,安卓机型的内存占用过高导致失败,需要把 `canvas` 绘制按比例缩放
    重要实现过程
    
    
      - 获取海报样式容器的高度
    
    
        获取到容器的高度之后再初始化 `wxml-to-canvas` 组件
    
    
        ```html
        <wxml-to-canvas 
          wx:if="{{isPosterShow}}" 
          class="widget" 
          width="{{canvasWidth}}" 
          height="{{canvasHeight > screenHeight ? screenHeight : canvasHeight}}"
        ></wxml-to-canvas>
        ```
    
    
        ```js
        /**
         * 获取容器高度
         * @method getContainerHeight
         */
        getContainerHeight() {
          return new Promise((resolve, reject) => {
            const query = wx.createSelectorQuery().in(this);
            query.select('.poster-target').boundingClientRect();
            query.exec((res) => {
              this.setData({
                isPosterShow: true,
                canvasHeight: res[0].height
              })
              resolve();
            })
          })
        }
        ```
    
    
      - 自适应设备以及缩放
    
    
        不能超过屏幕高度,如果超过则需要设置为最大高度
    
    
        ```html
        <wxml-to-canvas 
          wx:if="{{isPosterShow}}" 
          class="widget" 
          width="{{canvasWidth}}" 
          height="{{canvasHeight > screenHeight ? screenHeight : canvasHeight}}"
        ></wxml-to-canvas>
        ```
    
    
        ```js
        class DprClass {
          constructor(curHeight, maxHeight) {
            this.curHeight = curHeight; // 当前容器高度
            this.maxHeight = maxHeight; // 最大高度
            this.sysInfo = wx.getSystemInfoSync();
          }
    
    
          /**
          * 处理像素比例
          * @method handlePxToRpx
          * @params {Number} num 当前设置的像素
          * @params {Boolean} isNotToRpx 不需要转换成 rpx
          * @return {Number} num 当前设置的像素
          */ 
          handlePxToRpx(num, isNotToRpx = false) {
            const w = this.sysInfo.windowWidth;
            const mw = 750; // 设计稿宽度
    
    
            // 如果是通过DOM获取的值不需要做适配
            let diffNum = isNotToRpx ? num : (w / 750 * num);
    
    
            // 如果当前高度大于最大高度,则做缩放处理
            if (this.curHeight > this.maxHeight) {
              const ratio = this.maxHeight / this.curHeight;
              diffNum = Number((diffNum * ratio).toFixed(3));
            }
    
    
            return diffNum;
          }
        }
    
    
        function renderCanvas() {
          /**
           * 必须等 wxml-to-canvas 初始化一段时间才能获取成功
           */
          this.widget = this.selectComponent('.widget');
          const dprObj = new DprClass(containerHeight, windowHeight);
          const wxml = `<text class="user-name">帅</text>`
          const style = {
            userName: {
              width: dprObj.handlePxToRpx(20),
              height: dprObj.handlePxToRpx(20),
              lineHeight: dprObj.handlePxToRpx(20),
              fontSize: dprObj.handlePxToRpx(18),
              color: '#000000'
            }
          }
    
    
          const p1 = this.widget.renderToCanvas({
            wxml: poster.wxml,
            style: poster.style
          });
          p1.then((res) => {
            this.extraImage()
          }).catch((err) => {
            console.log(err)
            this._handiePosterErr();
          })
        }
        ```
    
    2021-05-24
    有用
    回复 1
    • Schnuffel
      Schnuffel
      2021-05-24
      左边那张图是小程序看到的效果图,右边那个是保存到相册的效果图(由于手机相册机制,会先完整显示一张图片,黑边就是缩放的效果),这张图已经超长了,之前测试还有更恐怖的长图,高度超过几千了,但是相册缩放看还是不怎么模糊的
      2021-05-24
      回复
  • 李梓聪
    李梓聪
    2020-06-17

    这个后面定义了x和y,从有滑动的点开始,重新设置width和height,尽量减少width和height,减少右移次数,完成需求的。

    2020-06-17
    有用
    回复
  • ᥬᥬ[大笑]᭄᭄
    ᥬᥬ[大笑]᭄᭄
    2020-06-17

    这个问题解决了吗?


    2020-06-17
    有用
    回复
  • 涛々
    涛々
    2020-06-03

    请问,解决了吗?

    2020-06-03
    有用
    回复
  • 是小白啊
    是小白啊
    2020-04-27

    试下设置小点正常吗?

    2020-04-27
    有用
    回复 8
    • Schnuffel
      Schnuffel
      2020-04-30
      canvas 宽度或高度太大都会报这个错误,我生成一张小图就可以,测试用了长图来生产就出这个错误,看了社区都是说部分机型 DPR 是 3 的机型都会有这个问题


      能不能出个解决方案?
      2020-04-30
      1
      回复
    • Schnuffel
      Schnuffel
      2020-04-30
      IOS 机型测了没有问题
      2020-04-30
      1
      回复
    • 2021-05-10回复Schnuffel
      解决了吗?
      我在用canvas画长图,IOS设备,高度超过4096也报这个错。
      安卓小米10,高度1795.5时canvas就不正常了,不渲染,但是不报错
      2021-05-10
      回复
    • 2021-05-10
      解决了吗?
      我在用canvas画长图,IOS设备,高度超过4096也报这个错。
      安卓小米10,高度1795.5时canvas就不正常了,不渲染,但是不报错
      IOS的分辨率要比安卓差很多,看图
      2021-05-10
      回复
    • 2021-05-10
      减小高度两端都能渲染,但是IOS的分辨率一直不正常
      2021-05-10
      回复
    查看更多(3)
登录 后发表内容
问题标签