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:
parent
507b473a2e
commit
d41c33e4b7
|
@ -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
48
vnc.js
|
@ -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>';
|
||||||
|
|
Loading…
Reference in New Issue