减小画布“位图”数据大小

4

背景:多用户应用程序(node.js)- 1位画家,n个客户端

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

为了经常更新画布(我考虑每秒10次),我需要将数据大小尽可能地保持小,特别是在考虑上传速率时。

toDataURL()方法返回一个base64字符串,这很好,但它包含我甚至不需要的大量数据(每像素23位)。 它的长度为8,088(不包括前面的MIME信息),假设JavaScript字符串具有8位编码,则每秒将传输8.1千字节的数据,每秒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位数据结构。这基本上就足够了,我不介意任何“颜色”精度损失(毕竟它是黑色的)。
由于大部分画布都是白色的,您可以考虑使用额外的哈夫曼游程编码来进一步减小大小。例如,一个大小为50x2像素且在(26,2)处有一个单独的黑色像素的画布将返回以下字符串:75W1B74W(50个白色像素加上25个白色像素,然后是1个黑色像素,然后是24个白色像素)。
如果画布由像这样的1位字符串组成,甚至可以帮助减小大小:
00000000000000000000000000000000000000000000000000 
00000000000000000000000001000000000000000000000000

那已经非常有帮助了。
我的第一个问题是:如何编写一种算法来有效地获取这些数据?
第二个问题是:我如何将纯二进制画布数据通过节点服务器传递给客户端?我该如何发送1位数据结构到服务器?我需要将位转换为十六进制(或更多)数字并重新解析吗?
是否可以使用这个作为数据结构?
提前致谢,
Harti
2个回答

2

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

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

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

我不会费心去处理一些纯二进制的东西。如果只有两种颜色,那么可以将其作为字符串“1,2,x,y,x2,y2”发送,其他人将以与本地客户端完全相同的方式解析它,并以相同的方式绘制。

我不会过于思考这个问题。在担心任何聪明的编码之前,请先使用简单的字符串使其正常工作。也许性能很好,而无需经历许多麻烦!


是的,我一开始也是这么想的(我的方法大致相同)。我使用了以下类似的对象:{ "timestamp": 0, "what": { "name": LINE_TO, "args": {"x": x, "y": y} } }然而,用一个大约60x40像素的单个画笔线,你已经有超过300个这样的命令了。 不过用简单(更短)的字符串代替对象的想法听起来也很不错。用对象的方法对我来说没有很流畅(在本地主机上)。Base64 字符串可以流畅运行,但我担心这只适用于局域网节点服务器。 - Harti

1

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

当绘画时,我会让我的应用程序知道修改区域的大小和位置(存储在 currentDrawingCoords 中)。 pixels 是通过使用存储的绘图坐标调用context.getImageData(left, top, width, height)获得的ImageData数组。 getDeltaUpdate 在onmouseup时被调用(yeah, that's the drawback of the area idea): image是一个字符串,它有一个头部分(x,y,width,height|...)和一个数据主体部分(...|w,b,w,b,w,[...]) 结果是一个比base64字符串少字符的字符串(与8k字符字符串相反,delta更新有1k-6k个字符,具体取决于已经绘制了多少东西到修改区域中) 将该字符串发送到服务器,推送到所有其他客户端并使用getImageData恢复为ImageData: 调用context.putImageData(data.image, data.where.x, data.where.y);将该区域放置在所有内容的顶部。

如先前提到的,这种方法可能并不适用于每一种单色画布绘图应用程序,因为修改区域仅在提交onmouseup时生效。但是,我可以接受这个折衷方案,因为它比问题中介绍的所有其他方法对服务器的压力要小得多。

我希望我能够帮助那些遵循本问题的人们。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接