最小化画布“位图”数据大小

时间:2012-03-05 15:48:56

标签: javascript algorithm node.js canvas base64

上下文:多用户应用(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作为数据结构?

提前致谢,

哈尔蒂

2 个答案:

答案 0 :(得分:2)

  

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

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

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

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

我不会想到这个。在您担心任何聪明的编码之前,让它使用简单的字符串。首先尝试简单的事情是值得的。如果没有遇到很多麻烦,表现可能会非常好!

答案 1 :(得分:1)

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

  1. 在绘图时,我随时向应用程序通知修改区域的大小和开始位置(存储在currentDrawingCoords中)。

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

  3. getDeltaUpdateonmouseup 上被调用(是的,这是区域理念的缺点)

    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. 的字符串
  6. 结果是字符串少于base64字符串的字符串(与8k字符串相反,delta更新有1k-6k字符,具体取决于已经将多少内容绘制到修改区域)

  7. 该字符串被发送到服务器,推送到所有其他客户端并使用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};
    }
    
  8. 致电context.putImageData(data.image, data.where.x, data.where.y);将该区域置于所有内容之上!

  9. 如前所述,由于修改区域仅提交onmouseup,因此这可能不适合各种单色画布绘图应用。但是,我可以忍受这种权衡,因为它对服务器的压力远小于问题中提出的所有其他方法。

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