Commit d41c33e4 authored by Joel Martin's avatar Joel Martin

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).
parent 507b473a
...@@ -14,6 +14,9 @@ var Canvas = { ...@@ -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) { ...@@ -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, ...@@ -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 () { ...@@ -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) { ...@@ -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) { ...@@ -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;
} }
......
...@@ -77,6 +77,10 @@ FBU : { ...@@ -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 : '', ...@@ -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 () { ...@@ -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 () { ...@@ -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 () { ...@@ -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 () { ...@@ -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() { ...@@ -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 () { ...@@ -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 () { ...@@ -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) { ...@@ -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) { ...@@ -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>';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment