【问题标题】:Minimizing canvas "bitmap" data size最小化画布“位图”数据大小
【发布时间】:2012-03-23 02:22:43
【问题描述】:

上下文: 多用户应用 (node.js) - 1 个画家,n 个客户端

画布尺寸: 650x400 像素(= 260,000 像素)

对于要频繁更新的画布(我在考虑每秒 10 次),我需要保持数据大小尽可能小,尤其是在考虑上传速率时。

toDataURL() 方法返回一个 base64 字符串很好,但它包含我什至不需要的大量数据(每像素 23 位)。它的长度是 8,088(没有前面的 MIME 信息),假设 JavaScript 字符串有 8 位编码,这将是 8.1 KB 的数据,每秒 10 次。

我的下一个尝试是将 JS 对象用于不同的上下文操作,例如 moveTo(x, y)lineTo(x, y),将它们发送到服务器并让客户端接收增量更新中的数据(通过时间戳)。然而,结果证明这比 base64 字符串效率更低。

{ 
    "timestamp": 0, 
    "what": { 
       "name": LINE_TO, 
       "args": {"x": x, "y": y}  
    } 
}

它不能流畅地工作,也不能精确地工作,因为当您快速刷一下画笔时,已经有近 300 个lineTo 命令。有时会丢失一部分运动(直线而不是圆形),有时脚本客户端甚至无法识别事件,因为它似乎被已经触发的大量事件“淹没”了。

所以我最终必须使用 8.1 KB 的 base64 字符串。我不想担心这么多——但即使使用增量更新异步完成,真实服务器上也会有很大的延迟,更不用说偶尔的带宽溢出了。

我使用的唯一颜色是#000 和#FFF,所以我正在考虑一个仅具有增量更新的1 位数据结构。这基本上就足够了,我不介意任何“颜色”精度损失(毕竟是黑色)。

由于大部分画布都是白色的,您也可以考虑额外的 Huffman 游程编码以进一步减小尺寸。就像大小为 50x2 像素和 (26, 2) 处的单个黑色像素的画布一样,将返回以下字符串:75W1B74W(50 + 25 个白色像素,然后是 1 个黑色像素,然后再是 24 个白色像素)

如果画布由这样的 1 位字符串组成,甚至会有所帮助:

00000000000000000000000000000000000000000000000000 
00000000000000000000000001000000000000000000000000

这已经很有帮助了。

我的第一个问题是:如何编写一个算法来有效地获取这些数据

第二个是:我如何传递纯二进制画布数据给客户端(通过节点服务器)?我什至如何将 1 位数据结构发送到服务器?我是否必须将我的位转换为十六进制(或更多)数字并重新解析?

是否可以使用this 作为数据结构?

提前致谢,

哈蒂

【问题讨论】:

    标签: javascript algorithm node.js canvas base64


    【解决方案1】:

    我需要保持数据大小尽可能小

    然后不要发送整个数据。仅发送更改,接近您自己提出的建议。

    使框架使每个用户只能执行“操作”,例如“从 X1,Y1 到 X2,Y2 绘制黑色 strokeWidth 2”。

    我不会为一些纯二进制的东西而烦恼。如果只有两种颜色,那么很容易以字符串“1,2,x,y,x2,y2”的形式发送,其他人会以与本地客户端相同的方式解析它,并且会以相同的方式绘制.

    我不会想太多。在您担心任何聪明的编码之前,让它使用简单的字符串。值得先尝试简单的事情。不费吹灰之力,或许性能会相当不错!

    【讨论】:

    • 是的,这也是我首先想到的(我也差不多。同样的方式)。我使用了如下对象:{ "timestamp": 0, "what": { "name": LINE_TO, "args": {"x": x, "y": y} } } 但是,使用大约 60x40 像素的单个画笔线,您已经拥有 300 多个这样的命令。但是使用简单(较短)字符串而不是对象的想法听起来也不错。对象的东西对我来说不是很流畅(在本地主机上)。 Base64 字符串确实可以流畅地工作,但恐怕只有 LAN 节点服务器才会出现这种情况。
    【解决方案2】:

    我终于解决了。我使用算法获取指定区域(即当前绘制的区域)内的图像数据,然后将图像数据粘贴到相同的坐标。

    1. 在绘图时,我会通知我的应用程序修改区域有多大以及它从哪里开始(存储在currentDrawingCoords)。

    2. pixels是一个ImageData数组,通过存储的绘图坐标调用context.getImageData(left, top, width, height)获得。

    3. getDeltaUpdate 被调用 onmouseup (是的,这就是区域概念的缺点)

      getDeltaUpdate = function(pixels, currentDrawingCoords) {   
          var image = "" + 
              currentDrawingCoords.left + "," + // x
              currentDrawingCoords.top + "," + // y
              (currentDrawingCoords.right - currentDrawingCoords.left) + "," + // width
              (currentDrawingCoords.bottom - currentDrawingCoords.top) + "";  // height
          var blk = 0, wht = 0, d = "|";
      
          // http://stackoverflow.com/questions/667045/getpixel-from-html-canvas
          for (var i=0, n=pixels.length; i < n; i += 4) {
                  if(
                      pixels[i]   > 0 ||
                      pixels[i+1] > 0 ||
                      pixels[i+2] > 0 ||
                      pixels[i+3] > 0
                  ) {
                      // pixel is black
                      if(wht > 0 || (i == 0 && wht == 0)) {
                          image = image + d + wht;        
                          wht = 0;
                          d = ",";
                      }
                      blk++;
      
                      //console.log("Pixel " + i + " is BLACK (" + blk + "-th in a row)");
                  } else {
                      // pixel is white 
                      if(blk > 0) {
                          image = image + d + blk;        
                          blk = 0;
                          d = ",";
                      }
                      wht++;
      
                      //console.log("Pixel " + i + " is WHITE (" + blk + "-th in a row)");
                  }
          }
      
          return image;   
      }
      
    4. image 是一个带有标题部分 (x,y,width,height|...) 和数据正文部分 (...|w,b,w,b,w,[...]) 的字符串

    5. 结果是一个字符串的字符数少于 base64 字符串(与 8k 字符的字符串相反,增量更新有 1k-6k 个字符,具体取决于已将多少东西绘制到修改区域)

    6. 该字符串被发送到服务器,推送到所有其他客户端并使用 getImageData 恢复为 ImageData:

      getImageData = function(imagestring) {
          var data = imagestring.split("|");
          var header = data[0].split(",");
          var body = data[1].split(",");
      
          var where = {"x": header[0], "y": header[1]};
          var image = context.createImageData(header[2], header[3]); // create ImageData object (width, height)
      
          var currentpixel = 0,
          pos = 0,
          until = 0,
          alpha = 0,
          white = true;
      
          for(var i=0, n=body.length; i < n; i++) {
              var pixelamount = parseInt(body[i]); // amount of pixels with the same color in a row
      
              if(pixelamount > 0) {
                  pos = (currentpixel * 4);
                  until = pos + (pixelamount * 4); // exclude
      
                  if(white) alpha = 0;
                  else alpha = 255;
      
                  while(pos < until) {
                      image.data[pos] = 0;
                      image.data[pos+1] = 0;
                      image.data[pos+2] = 0;
                      image.data[pos+3] = alpha;                  
                      pos += 4;
                  }
                  currentpixel += pixelamount;
                  white = (white ? false : true);
              } else {
                  white = false;  
              }
          }
      
          return {"image": image, "where": where};
      }
      
    7. 致电context.putImageData(data.image, data.where.x, data.where.y); 将该区域置于一切之上!

    如前所述,由于修改区域仅提交onmouseup,这可能并不适合所有类型的单色画布绘图应用程序。但是,我可以接受这种权衡,因为与问题中提出的所有其他方法相比,它对服务器的压力要小得多。

    我希望我能够帮助人们关注这个问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-07
      • 2011-07-30
      • 1970-01-01
      • 2011-01-21
      • 1970-01-01
      • 2011-10-03
      • 1970-01-01
      • 2020-12-23
      相关资源
      最近更新 更多