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:
@@ -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;
|
||||
}
|
||||
|
||||
48
vnc.js
48
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"></li>';
|
||||
html += ' <li>Encrypt: <input id="VNC_encrypt"';
|
||||
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 += ' value="Loading" disabled></li>';
|
||||
html += ' </ul>';
|
||||
|
||||
Reference in New Issue
Block a user