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).
This commit is contained in:
Joel Martin 2010-06-01 14:34:27 -05:00
parent 507b473a2e
commit d41c33e4b7
2 changed files with 96 additions and 27 deletions

View File

@ -14,6 +14,9 @@ var Canvas = {
prefer_js : false, prefer_js : false,
true_color : false,
colourMap : [],
c_x : 0, c_x : 0,
c_y : 0, c_y : 0,
c_wx : 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) { mouseDown, mouseUp, mouseMove, mouseWheel) {
console.log(">> Canvas.init"); console.log(">> Canvas.init");
@ -105,6 +108,8 @@ init: function (id, width, height, keyDown, keyUp,
Canvas.c_y = c.getPosition().y; Canvas.c_y = c.getPosition().y;
Canvas.c_wx = c.getSize().x; Canvas.c_wx = c.getSize().x;
Canvas.c_wy = c.getSize().y; Canvas.c_wy = c.getSize().y;
Canvas.true_color = true_color;
Canvas.colourMap = [];
if (! c.getContext) { return; } if (! c.getContext) { return; }
Canvas.ctx = c.getContext('2d'); Canvas.ctx = c.getContext('2d');
@ -147,21 +152,26 @@ stop: function () {
* gecko, Javascript array handling is much slower. * gecko, Javascript array handling is much slower.
*/ */
getTile: function(x, y, width, height, color) { 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, img = {'x': x, 'y': y, 'width': width, 'height': height,
'data': []}; 'data': []};
if (Canvas.prefer_js) { if (Canvas.prefer_js) {
data = img.data; data = img.data;
red = color[0]; if (Canvas.true_color) {
green = color[1]; rgb = color;
blue = color[2]; } else {
rgb = Canvas.colourMap[color[0]];
}
red = rgb[0];
green = rgb[1];
blue = rgb[2];
for (j = 0; j < height; j++) { for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) { for (i = 0; i < width; i++) {
p = (i + (j * width) ) * 4; p = (i + (j * width) ) * 4;
img.data[p + 0] = red; data[p + 0] = red;
img.data[p + 1] = green; data[p + 1] = green;
img.data[p + 2] = blue; data[p + 2] = blue;
//img.data[p + 3] = 255; // Set Alpha //data[p + 3] = 255; // Set Alpha
} }
} }
} else { } else {
@ -171,13 +181,18 @@ getTile: function(x, y, width, height, color) {
}, },
setTile: function(img, x, y, w, h, 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) { if (Canvas.prefer_js) {
data = img.data; data = img.data;
width = img.width; width = img.width;
red = color[0]; if (Canvas.true_color) {
green = color[1]; rgb = color;
blue = color[2]; } else {
rgb = Canvas.colourMap[color[0]];
}
red = rgb[0];
green = rgb[1];
blue = rgb[2];
for (j = 0; j < h; j++) { for (j = 0; j < h; j++) {
for (i = 0; i < w; i++) { for (i = 0; i < w; i++) {
p = (x + i + ((y + j) * width) ) * 4; 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 */ /* Old firefox and Opera don't support createImageData */
img = Canvas.ctx.getImageData(0, 0, width, height); img = Canvas.ctx.getImageData(0, 0, width, height);
data = img.data; data = img.data;
for (i=0; i < (width * height * 4); i=i+4) { for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
j=i+offset;
data[i + 0] = arr[j + 0]; data[i + 0] = arr[j + 0];
data[i + 1] = arr[j + 1]; data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2]; data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Set Alpha data[i + 3] = 255; // Set Alpha
} }
Canvas.ctx.putImageData(img, x, y); 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) { 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) { if (newStyle !== Canvas.prevStyle) {
newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
Canvas.ctx.fillStyle = newStyle; Canvas.ctx.fillStyle = newStyle;
Canvas.prevStyle = newStyle; Canvas.prevStyle = newStyle;
} }

48
vnc.js
View File

@ -77,6 +77,10 @@ FBU : {
background : null background : null
}, },
true_color : false,
fb_Bpp : 4,
fb_depth : 3,
// DOM objects // DOM objects
statusLine : null, statusLine : null,
connectBtn : null, connectBtn : null,
@ -102,7 +106,6 @@ password : '',
fb_width : 0, fb_width : 0,
fb_height : 0, fb_height : 0,
fb_name : "", fb_name : "",
fb_Bpp : 4,
rre_chunk : 100, rre_chunk : 100,
timing : { timing : {
@ -293,10 +296,18 @@ init_msg: function () {
name_length = RQ.shift32(); name_length = RQ.shift32();
RFB.fb_name = RQ.shiftStr(name_length); 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.keyDown, RFB.keyUp, RFB.mouseDown, RFB.mouseUp,
RFB.mouseMove, RFB.mouseWheel); 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 = RFB.pixelFormat();
response = response.concat(RFB.encodings()); response = response.concat(RFB.encodings());
response = response.concat(RFB.fbUpdateRequest(0)); response = response.concat(RFB.fbUpdateRequest(0));
@ -318,7 +329,8 @@ normal_msg: function () {
//console.log(">> normal_msg"); //console.log(">> normal_msg");
var RQ = RFB.RQ, FBU = RFB.FBU, now, fbu_rt_diff, 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) { if (FBU.rects > 0) {
msg_type = 0; msg_type = 0;
@ -414,11 +426,21 @@ normal_msg: function () {
break; break;
case 1: // SetColourMapEntries case 1: // SetColourMapEntries
console.log("SetColourMapEntries (unsupported)"); console.log("SetColourMapEntries");
RQ.shift8(); // Padding RQ.shift8(); // Padding
RQ.shift16(); // First colour first_colour = RQ.shift16(); // First colour
num_colours = RQ.shift16(); 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; break;
case 2: // Bell case 2: // Bell
console.log("Bell (unsupported)"); console.log("Bell (unsupported)");
@ -477,7 +499,7 @@ display_raw: function () {
cur_y = FBU.y + (FBU.height - FBU.lines); cur_y = FBU.y + (FBU.height - FBU.lines);
cur_height = Math.min(FBU.lines, cur_height = Math.min(FBU.lines,
Math.floor(RQ.length/(FBU.width * RFB.fb_Bpp))); 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); RQ.shiftBytes(FBU.width * cur_height * RFB.fb_Bpp);
FBU.lines -= cur_height; FBU.lines -= cur_height;
@ -629,7 +651,7 @@ display_hextile: function() {
Canvas.fillRect(x, y, w, h, FBU.background); Canvas.fillRect(x, y, w, h, FBU.background);
} }
} else if (FBU.subencoding & 0x01) { // Raw } else if (FBU.subencoding & 0x01) { // Raw
Canvas.rgbxImage(x, y, w, h, RQ, idx); Canvas.blitImage(x, y, w, h, RQ, idx);
} else { } else {
if (FBU.subencoding & 0x02) { // Background if (FBU.subencoding & 0x02) { // Background
FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp); FBU.background = RQ.slice(idx, idx + RFB.fb_Bpp);
@ -694,9 +716,9 @@ pixelFormat: function () {
arr.push8(0); // padding arr.push8(0); // padding
arr.push8(RFB.fb_Bpp * 8); // bits-per-pixel 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(0); // little-endian
arr.push8(1); // true-color arr.push8(RFB.true_color); // true-color
arr.push16(255); // red-max arr.push16(255); // red-max
arr.push16(255); // green-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"); console.log(">> connect");
RFB.host = (host !== undefined) ? host : RFB.host = (host !== undefined) ? host :
@ -1198,6 +1220,8 @@ connect: function (host, port, password, encrypt) {
$('VNC_password').value; $('VNC_password').value;
RFB.encrypt = (encrypt !== undefined) ? encrypt : RFB.encrypt = (encrypt !== undefined) ? encrypt :
$('VNC_encrypt').checked; $('VNC_encrypt').checked;
RFB.true_color = (true_color !== undefined) ? true_color:
$('VNC_true_color').checked;
if ((!RFB.host) || (!RFB.port)) { if ((!RFB.host) || (!RFB.port)) {
alert("Must set host and port"); alert("Must set host and port");
return; return;
@ -1253,6 +1277,8 @@ load: function (target) {
html += ' type="password"></li>'; html += ' type="password"></li>';
html += ' <li>Encrypt: <input id="VNC_encrypt"'; html += ' <li>Encrypt: <input id="VNC_encrypt"';
html += ' type="checkbox"></li>'; html += ' type="checkbox"></li>';
html += ' <li>True Color: <input id="VNC_true_color"';
html += ' type="checkbox" checked></li>';
html += ' <li><input id="VNC_connect_button" type="button"'; html += ' <li><input id="VNC_connect_button" type="button"';
html += ' value="Loading" disabled></li>'; html += ' value="Loading" disabled></li>';
html += ' </ul>'; html += ' </ul>';