最小化canvas“位图”数据大小

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

帆布尺寸: 650×400像素(= 260,000像素)

为了更频繁地更新canvas(我每秒思考10次),我需要保持尽可能小的数据大小,特别是在考虑上传速率时。

返回一个base64string的toDataURL()方法很好,但它包含了大量我不需要的数据(每像素23位)。 它的长度是8,088(没有前面的MIME信息),假设JavaScriptstring有8位编码,即8.1千字节的数据,每秒10次。

我接下来的尝试是使用JS对象来处理moveTo(x, y)lineTo(x, y)等不同的上下文动作,并将它们发送到服务器,让客户端通过delta更新(通过时间戳)接收数据。 但是,结果是比base64string效率更低。

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

它并不stream畅,也不准确,因为当你刷你的画笔时已经有近300条lineTo命令了。 有时会有一部分运动缺失(使一条直线而不是四舍五入),有时事件甚至不能被脚本客户端识别,因为它似乎已经被已经触发的大量事件“淹没”了。

所以我必须最终使用8.1 KB的base64string。 我不想担心这一点 – 但即使与增量更新asynchronous完成,实际服务器上也会出现严重滞后,更不用说偶尔带宽超限。

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

大部分的canvas都是白色的,你可以考虑额外的霍夫曼游程编码来进一步减小尺寸。 像一个尺寸为50×2像素的canvas,在(26,2)处的单个黑色像素将返回以下string: 75W1B74W (50 + 25白色像素,然后是1黑色像素,然后是24白色像素)

如果canvas由如下的1位string组成,那甚至会有所帮助:

 00000000000000000000000000000000000000000000000000 00000000000000000000000001000000000000000000000000 

这将有助于很多。

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

第二个是:我怎样才能将纯二进制canvas数据传递给客户端(通过节点服务器 )? 我怎样才能发送一个1位的数据结构到服务器? 我需要将我的位转换为hex(或更多)的数字并重新parsing吗?

有可能将其用作数据结构吗?

提前致谢,

哈尔蒂

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

那么不要发送整个数据。 只发送更改,接近你自己提出的build议。

制作框架,使每个用户只能做“行动”,如“从X1,Y1到X2,Y2绘制黑色笔画宽度2”。

我不会打扰一些纯粹的二进制的东西。 如果只有两种颜色,那么很容易以string“1,2,x,y,x2,y2”发送,其他人将以与本地客户端完全相同的方式进行parsing,并且将以相同方式绘制。

我不会推翻这一点。 在你担心任何聪明的编码之前,先用简单的string来处理它。 首先尝试简单的事情是值得的。 也许performance会相当不错,不会经历很多麻烦!

我最后把它整理出来。 我使用一种algorithm来获取指定区域(即当前绘制的区域)内的图像数据,然后将图像数据粘贴到相同的坐标上。

  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,[...] )的string

  5. 结果是string比base64string的字符less(与8k字符string相反,delta更新有1k-6k字符,取决于修改区域有多less东西)

  6. 该string被发送到服务器,推送到所有其他客户端,并通过使用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 ,因此这可能不是适用于各种单色canvas绘制应用的最佳select。 不过,我可以忍受这种折衷,因为服务器的压力远远小于问题中提出的所有其他方法。

我希望我能帮助人们跟随这个问题。