From d41c33e4b7b3a20784c26c93927b9c864498e4e5 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 1 Jun 2010 14:34:27 -0500 Subject: [PATCH] Add colour map support (non-true-color). In colourMap mode there are 256 colours in a colour palette sent from the server via the SetColourMapEntries message. This reduces the bandwidth by about 1/4. However, appearance can be somewhat less than ideal (pinks instead of gray, etc). It also increases client side rendering performance especially on firefox. Rendering a full 800x600 update takes about 950ms in firefox on my system compared to about 1400ms. Round-trip time for a full frame buffer update is even better on firefox (due to performance of the flash WebSocket emulator). Reduced from about 1800ms to 1100ms on firefox (for 800x600 full update). --- include/canvas.js | 75 +++++++++++++++++++++++++++++++++++++---------- vnc.js | 48 +++++++++++++++++++++++------- 2 files changed, 96 insertions(+), 27 deletions(-) diff --git a/include/canvas.js b/include/canvas.js index e1f1863d..2213b604 100644 --- a/include/canvas.js +++ b/include/canvas.js @@ -14,6 +14,9 @@ var Canvas = { prefer_js : false, +true_color : false, +colourMap : [], + c_x : 0, c_y : 0, c_wx : 0, @@ -74,7 +77,7 @@ ctxDisable: function (e) { }, -init: function (id, width, height, keyDown, keyUp, +init: function (id, width, height, true_color, keyDown, keyUp, mouseDown, mouseUp, mouseMove, mouseWheel) { console.log(">> Canvas.init"); @@ -105,6 +108,8 @@ init: function (id, width, height, keyDown, keyUp, Canvas.c_y = c.getPosition().y; Canvas.c_wx = c.getSize().x; Canvas.c_wy = c.getSize().y; + Canvas.true_color = true_color; + Canvas.colourMap = []; if (! c.getContext) { return; } Canvas.ctx = c.getContext('2d'); @@ -147,21 +152,26 @@ stop: function () { * gecko, Javascript array handling is much slower. */ getTile: function(x, y, width, height, color) { - var img, data, p, red, green, blue, j, i; + var img, data, p, rgb, red, green, blue, j, i; img = {'x': x, 'y': y, 'width': width, 'height': height, 'data': []}; if (Canvas.prefer_js) { data = img.data; - red = color[0]; - green = color[1]; - blue = color[2]; + if (Canvas.true_color) { + rgb = color; + } else { + rgb = Canvas.colourMap[color[0]]; + } + red = rgb[0]; + green = rgb[1]; + blue = rgb[2]; for (j = 0; j < height; j++) { for (i = 0; i < width; i++) { p = (i + (j * width) ) * 4; - img.data[p + 0] = red; - img.data[p + 1] = green; - img.data[p + 2] = blue; - //img.data[p + 3] = 255; // Set Alpha + data[p + 0] = red; + data[p + 1] = green; + data[p + 2] = blue; + //data[p + 3] = 255; // Set Alpha } } } else { @@ -171,13 +181,18 @@ getTile: function(x, y, width, height, color) { }, setTile: function(img, x, y, w, h, color) { - var data, p, red, green, blue, width, j, i; + var data, p, rgb, red, green, blue, width, j, i; if (Canvas.prefer_js) { data = img.data; width = img.width; - red = color[0]; - green = color[1]; - blue = color[2]; + if (Canvas.true_color) { + rgb = color; + } else { + rgb = Canvas.colourMap[color[0]]; + } + red = rgb[0]; + green = rgb[1]; + blue = rgb[2]; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { p = (x + i + ((y + j) * width) ) * 4; @@ -208,20 +223,48 @@ rgbxImage: function(x, y, width, height, arr, offset) { /* Old firefox and Opera don't support createImageData */ img = Canvas.ctx.getImageData(0, 0, width, height); data = img.data; - for (i=0; i < (width * height * 4); i=i+4) { - j=i+offset; + for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { data[i + 0] = arr[j + 0]; data[i + 1] = arr[j + 1]; data[i + 2] = arr[j + 2]; data[i + 3] = 255; // Set Alpha } Canvas.ctx.putImageData(img, x, y); +}, +cmapImage: function(x, y, width, height, arr, offset) { + var img, i, j, k, data, rgb, cmap; + img = Canvas.ctx.getImageData(0, 0, width, height); + data = img.data; + cmap = Canvas.colourMap; + //console.log("cmapImage x: " + x + ", y: " + y + "arr.slice(0,20): " + arr.slice(0,20)); + for (i=0, j=offset; i < (width * height * 4); i=i+4, j++) { + rgb = cmap[arr[j]]; + data[i + 0] = rgb[0]; + data[i + 1] = rgb[1]; + data[i + 2] = rgb[2]; + data[i + 3] = 255; // Set Alpha + } + Canvas.ctx.putImageData(img, x, y); +}, + +blitImage: function(x, y, width, height, arr, offset) { + if (Canvas.true_color) { + Canvas.rgbxImage(x, y, width, height, arr, offset); + } else { + Canvas.cmapImage(x, y, width, height, arr, offset); + } }, fillRect: function(x, y, width, height, color) { - var newStyle = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ")"; + var rgb, newStyle; + if (Canvas.true_color) { + rgb = color; + } else { + rgb = Canvas.colourMap[color[0]]; + } if (newStyle !== Canvas.prevStyle) { + newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; Canvas.ctx.fillStyle = newStyle; Canvas.prevStyle = newStyle; } diff --git a/vnc.js b/vnc.js index 31044f47..074559bd 100644 --- a/vnc.js +++ b/vnc.js @@ -77,6 +77,10 @@ FBU : { background : null }, +true_color : false, +fb_Bpp : 4, +fb_depth : 3, + // DOM objects statusLine : null, connectBtn : null, @@ -102,7 +106,6 @@ password : '', fb_width : 0, fb_height : 0, fb_name : "", -fb_Bpp : 4, rre_chunk : 100, timing : { @@ -293,10 +296,18 @@ init_msg: function () { name_length = RQ.shift32(); RFB.fb_name = RQ.shiftStr(name_length); - Canvas.init('VNC_canvas', RFB.fb_width, RFB.fb_height, + Canvas.init('VNC_canvas', RFB.fb_width, RFB.fb_height, RFB.true_color, RFB.keyDown, RFB.keyUp, RFB.mouseDown, RFB.mouseUp, RFB.mouseMove, RFB.mouseWheel); + if (RFB.true_color) { + RFB.fb_Bpp = 4; + RFB.fb_depth = 3; + } else { + RFB.fb_Bpp = 1; + RFB.fb_depth = 1; + } + response = RFB.pixelFormat(); response = response.concat(RFB.encodings()); response = response.concat(RFB.fbUpdateRequest(0)); @@ -318,7 +329,8 @@ normal_msg: function () { //console.log(">> normal_msg"); var RQ = RFB.RQ, FBU = RFB.FBU, now, fbu_rt_diff, - ret = true, msg_type, num_colours, msg; + ret = true, msg_type, msg, + c, first_colour, num_colours, red, green, blue; if (FBU.rects > 0) { msg_type = 0; @@ -414,11 +426,21 @@ normal_msg: function () { break; case 1: // SetColourMapEntries - console.log("SetColourMapEntries (unsupported)"); + console.log("SetColourMapEntries"); RQ.shift8(); // Padding - RQ.shift16(); // First colour + first_colour = RQ.shift16(); // First colour num_colours = RQ.shift16(); - RQ.shiftBytes(num_colours * 6); + for (c=0; c < num_colours; c++) { + red = RQ.shift16(); + //console.log("red before: " + red); + red = parseInt(red / 256, 10); + //console.log("red after: " + red); + green = parseInt(RQ.shift16() / 256, 10); + blue = parseInt(RQ.shift16() / 256, 10); + Canvas.colourMap[first_colour + c] = [red, green, blue]; + } + console.log("Registered " + num_colours + " colourMap entries"); + //console.log("colourMap: " + Canvas.colourMap); break; case 2: // Bell console.log("Bell (unsupported)"); @@ -477,7 +499,7 @@ display_raw: function () { cur_y = FBU.y + (FBU.height - FBU.lines); cur_height = Math.min(FBU.lines, Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp))); - Canvas.rgbxImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); + Canvas.blitImage(FBU.x, cur_y, FBU.width, cur_height, RQ, 0); RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp); FBU.lines -= cur_height; @@ -629,7 +651,7 @@ display_hextile: function() { Canvas.fillRect(x, y, w, h, FBU.background); } } else if (FBU.subencoding & 0x01) { // Raw - Canvas.rgbxImage(x, y, w, h, RQ, idx); + Canvas.blitImage(x, y, w, h, RQ, idx); } else { if (FBU.subencoding & 0x02) { // Background FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); @@ -694,9 +716,9 @@ pixelFormat: function () { arr.push8(0); // padding arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel - arr.push8(24); // depth + arr.push8(RFB.fb_depth * 8); // depth arr.push8(0); // little-endian - arr.push8(1); // true-color + arr.push8(RFB.true_color); // true-color arr.push16(255); // red-max arr.push16(255); // green-max @@ -1187,7 +1209,7 @@ init_vars: function () { }, -connect: function (host, port, password, encrypt) { +connect: function (host, port, password, encrypt, true_color) { console.log(">> connect"); RFB.host = (host !== undefined) ? host : @@ -1198,6 +1220,8 @@ connect: function (host, port, password, encrypt) { $('VNC_password').value; RFB.encrypt = (encrypt !== undefined) ? encrypt : $('VNC_encrypt').checked; + RFB.true_color = (true_color !== undefined) ? true_color: + $('VNC_true_color').checked; if ((!RFB.host) || (!RFB.port)) { alert("Must set host and port"); return; @@ -1253,6 +1277,8 @@ load: function (target) { html += ' type="password">'; html += '
  • Encrypt: