上下文:多用户应用(node.js) - 1位画家,n位客户
画布尺寸:650x400像素(= 260,000像素)
对于经常更新的画布(我考虑每秒10次),我需要尽可能减小数据大小,特别是在考虑上传速率时。
返回base64字符串的toDataURL()
方法很好,但它包含我甚至不需要的大量数据(每像素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
命令。有时缺少运动的一部分(直线而不是圆形),有时事件甚至不被脚本客户端识别,因为它似乎被已经触发的大量事件“淹没”。 p>
所以我必须最终使用带有8.1 KB的base64字符串。我不想为此担心 - 但即使与增量更新异步完成,真实服务器也会出现重大延迟,更不用说偶尔会出现带宽溢出。
我使用的唯一颜色是#000和#FFF,所以我只考虑带有增量更新的 1位数据结构。这基本上就足够了,我不介意任何“颜色”精度损失(毕竟它是黑色)。
由于大多数画布都是白色的,你可以考虑使用额外的霍夫曼游程编码来进一步减小尺寸。就像尺寸为50x2像素的画布和(26,2)处的单个黑色像素一样,将返回以下字符串:75W1B74W
(50 + 25个白色像素,然后是1个黑色像素,然后是24个白色像素)< / p>
如果画布由这样的1位字符串组成,它甚至会有所帮助:
00000000000000000000000000000000000000000000000000
00000000000000000000000001000000000000000000000000
这已经有很多帮助了。
我的第一个问题是:如何编写算法以有效地获取此数据?
第二个是:我如何将纯二进制画布数据传递给客户端(通过节点服务器)?如何将1位数据结构发送到服务器?我是否必须将我的位转换为十六进制(或更多)数字并重新解析?
是否可以使用this作为数据结构?
提前致谢,
哈尔蒂
答案 0 :(得分:2)
我需要保持数据量尽可能小
然后不要发送整个数据。只发送更改,接近您自己的建议。
使框架成为每个用户只能执行“操作”,例如“从X1,Y1到X2,Y2绘制黑色笔画宽度2”。
我不会打扰一些纯二进制的东西。如果只有两种颜色,则很容易发送为字符串“1,2,x,y,x2,y2”,其他人将以与本地客户端相同的方式解析,并且它将以相同的方式绘制
我不会想到这个。在您担心任何聪明的编码之前,让它使用简单的字符串。首先尝试简单的事情是值得的。如果没有遇到很多麻烦,表现可能会非常好!
答案 1 :(得分:1)
我终于把它整理出来了。我使用算法获取指定区域(即当前绘制的区域)内的图像数据,然后将图像数据粘贴到相同的坐标。
在绘图时,我随时向应用程序通知修改区域的大小和开始位置(存储在currentDrawingCoords
中)。
pixels
是通过使用存储的绘图坐标调用context.getImageData(left, top, width, height)
获得的ImageData数组。
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;
}
image
是一个包含标题部分(x,y,width,height|...
)和数据正文部分(...|w,b,w,b,w,[...]
)
结果是字符串少于base64字符串的字符串(与8k字符串相反,delta更新有1k-6k字符,具体取决于已经将多少内容绘制到修改区域)
该字符串被发送到服务器,推送到所有其他客户端并使用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};
}
致电context.putImageData(data.image, data.where.x, data.where.y);
将该区域置于所有内容之上!
如前所述,由于修改区域仅提交onmouseup
,因此这可能不适合各种单色画布绘图应用。但是,我可以忍受这种权衡,因为它对服务器的压力远小于问题中提出的所有其他方法。
我希望我能够帮助人们关注这个问题。