zrle and zywrle support
This commit is contained in:
parent
e1d50c8c10
commit
3ee84fb333
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* noVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2012 Joel Martin
|
||||||
|
* Copyright (C) 2018 Samuel Mannehed for Cendio AB
|
||||||
|
* Copyright (C) 2018 Pierre Ossman for Cendio AB
|
||||||
|
* Copyright (C) 2018 Maxim Furtuna for Skysilk inc.
|
||||||
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
*
|
||||||
|
* See README.md for usage and integration instructions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Inflate from "../inflator.js";
|
||||||
|
|
||||||
|
const ZRLE_TILE_WIDTH = 64;
|
||||||
|
const ZRLE_TILE_HEIGHT = 64;
|
||||||
|
|
||||||
|
|
||||||
|
export default class ZRLEDecoder {
|
||||||
|
constructor() {
|
||||||
|
this._length = 0;
|
||||||
|
this._offset = 0;
|
||||||
|
this._inflator = new Inflate();
|
||||||
|
|
||||||
|
this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeRect(x, y, width, height, sock, display, depth) {
|
||||||
|
if (this._length === 0) {
|
||||||
|
if (sock.rQwait("ZLib data length", 4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._length = sock.rQshift32();
|
||||||
|
}
|
||||||
|
if (sock.rQwait("Zlib data", this._length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const data = sock.rQshiftBytes(this._length);
|
||||||
|
|
||||||
|
const tiles_x = Math.ceil(width / ZRLE_TILE_WIDTH);
|
||||||
|
const tiles_y = Math.ceil(height / ZRLE_TILE_HEIGHT);
|
||||||
|
const total_tiles = tiles_x * tiles_y;
|
||||||
|
|
||||||
|
//this._inflator.reset();
|
||||||
|
this._uncompressed = this._inflator.inflate(data, true, width * height * 3 + total_tiles);
|
||||||
|
|
||||||
|
for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {
|
||||||
|
let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);
|
||||||
|
|
||||||
|
for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {
|
||||||
|
let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);
|
||||||
|
|
||||||
|
const tileSize = tw * th;
|
||||||
|
|
||||||
|
const subencoding = this._uncompressed[this._offset++];
|
||||||
|
if (subencoding === 0) {
|
||||||
|
// raw data
|
||||||
|
const data = this._readPixels(tileSize);
|
||||||
|
display.blitBgrImage(tx, ty, tw, th, data, 0, false);
|
||||||
|
|
||||||
|
} else if (subencoding === 1) {
|
||||||
|
// solid
|
||||||
|
const background = this._readPixels(1);
|
||||||
|
display.fillRect(tx, ty, tw, th, [background[2], background[1], background[0]]);
|
||||||
|
|
||||||
|
} else if (subencoding >= 2 && subencoding <= 16) {
|
||||||
|
// palette types
|
||||||
|
const data = this._decodePaletteTile(subencoding, tileSize);
|
||||||
|
display.blitBgrImage(tx, ty, tw, th, data, 0, false);
|
||||||
|
|
||||||
|
} else if (subencoding === 128) {
|
||||||
|
// run-length encoding
|
||||||
|
const data = this._decodeRLETile(tileSize);
|
||||||
|
display.blitBgrImage(tx, ty, tw, th, data, 0, false);
|
||||||
|
|
||||||
|
} else if (subencoding >= 130 && subencoding <= 255) {
|
||||||
|
const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);
|
||||||
|
display.blitBgrImage(tx, ty, tw, th, data, 0, false);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown subencoding: ' + subencoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._uncompressed = null;
|
||||||
|
this._length = 0;
|
||||||
|
this._offset = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getBitsPerPixelInPalette(paletteSize) {
|
||||||
|
if (paletteSize <= 2) {
|
||||||
|
return 1;
|
||||||
|
} else if (paletteSize <= 4) {
|
||||||
|
return 2;
|
||||||
|
} else if (paletteSize <= 16) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readPixels(pixels) {
|
||||||
|
const size = pixels * 3;
|
||||||
|
const data = new Uint8Array(this._uncompressed.buffer, this._offset, size);
|
||||||
|
this._offset += size;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
_decodePaletteTile(paletteSize, tileSize) {
|
||||||
|
const data = this._tileBuffer;
|
||||||
|
|
||||||
|
// palette
|
||||||
|
const palette = this._readPixels(paletteSize);
|
||||||
|
|
||||||
|
const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);
|
||||||
|
const mask = (1 << bitsPerPixel) - 1;
|
||||||
|
const encodedLength = Math.ceil(tileSize * bitsPerPixel / 8);
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (let j = 0; j < encodedLength; j++) {
|
||||||
|
let encoded = this._uncompressed[this._offset];
|
||||||
|
for (let i = 0; i < 8; i += bitsPerPixel) {
|
||||||
|
const indexInPalette = encoded & mask;
|
||||||
|
encoded = encoded >> bitsPerPixel;
|
||||||
|
|
||||||
|
data[offset] = palette[indexInPalette * 3];
|
||||||
|
data[offset + 1] = palette[indexInPalette * 3 + 1];
|
||||||
|
data[offset + 2] = palette[indexInPalette * 3 + 2];
|
||||||
|
|
||||||
|
offset += 3;
|
||||||
|
}
|
||||||
|
this._offset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
_decodeRLETile(tileSize) {
|
||||||
|
const data = this._tileBuffer;
|
||||||
|
let i = 0;
|
||||||
|
while (i < tileSize) {
|
||||||
|
const pixel = this._readPixels(1);
|
||||||
|
const length = this._readRLELength();
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
data[i * 3] = pixel[0];
|
||||||
|
data[i * 3 + 1] = pixel[1];
|
||||||
|
data[i * 3 + 2] = pixel[2];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
_decodeRLEPaletteTile(paletteSize, tileSize) {
|
||||||
|
const data = this._tileBuffer;
|
||||||
|
|
||||||
|
// palette
|
||||||
|
const palette = this._readPixels(paletteSize);
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
while (offset < tileSize) {
|
||||||
|
let indexInPalette = this._uncompressed[this._offset++];
|
||||||
|
let length = 1;
|
||||||
|
if (indexInPalette >= 128) {
|
||||||
|
indexInPalette -= 128;
|
||||||
|
length = this._readRLELength();
|
||||||
|
}
|
||||||
|
if (indexInPalette > paletteSize) {
|
||||||
|
throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);
|
||||||
|
}
|
||||||
|
if (offset + length > tileSize) {
|
||||||
|
throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));
|
||||||
|
}
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
data[offset * 3] = palette[indexInPalette * 3];
|
||||||
|
data[offset * 3 + 1] = palette[indexInPalette * 3 + 1];
|
||||||
|
data[offset * 3 + 2] = palette[indexInPalette * 3 + 2];
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
//offset += length;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
_readRLELength() {
|
||||||
|
let length = 0;
|
||||||
|
let current = 0;
|
||||||
|
do {
|
||||||
|
current = this._uncompressed[this._offset++];
|
||||||
|
length += current;
|
||||||
|
} while (current === 255);
|
||||||
|
return length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -475,6 +475,26 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blitBgrImage(x, y, width, height, arr, offset, from_queue) {
|
||||||
|
if (this._renderQ.length !== 0 && !from_queue) {
|
||||||
|
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||||
|
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
|
||||||
|
// this probably isn't getting called *nearly* as much
|
||||||
|
const new_arr = new Uint8Array(width * height * 3);
|
||||||
|
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
|
||||||
|
this._renderQ_push({
|
||||||
|
'type': 'blitBgr',
|
||||||
|
'data': new_arr,
|
||||||
|
'x': x,
|
||||||
|
'y': y,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._bgrImageData(x, y, width, height, arr, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
|
blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
|
||||||
if (this._renderQ.length !== 0 && !from_queue) {
|
if (this._renderQ.length !== 0 && !from_queue) {
|
||||||
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
// NB(directxman12): it's technically more performant here to use preallocated arrays,
|
||||||
|
|
@ -576,6 +596,19 @@ export default class Display {
|
||||||
this._damage(x, y, img.width, img.height);
|
this._damage(x, y, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_bgrImageData(x, y, width, height, arr, offset) {
|
||||||
|
const img = this._drawCtx.createImageData(width, height);
|
||||||
|
const data = img.data;
|
||||||
|
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
|
||||||
|
data[i] = arr[j + 2];
|
||||||
|
data[i + 1] = arr[j + 1];
|
||||||
|
data[i + 2] = arr[j];
|
||||||
|
data[i + 3] = 255; // Alpha
|
||||||
|
}
|
||||||
|
this._drawCtx.putImageData(img, x, y);
|
||||||
|
this._damage(x, y, img.width, img.height);
|
||||||
|
}
|
||||||
|
|
||||||
_rgbxImageData(x, y, width, height, arr, offset) {
|
_rgbxImageData(x, y, width, height, arr, offset) {
|
||||||
// NB(directxman12): arr must be an Type Array view
|
// NB(directxman12): arr must be an Type Array view
|
||||||
let img;
|
let img;
|
||||||
|
|
@ -625,6 +658,9 @@ export default class Display {
|
||||||
case 'blitRgb':
|
case 'blitRgb':
|
||||||
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||||
break;
|
break;
|
||||||
|
case 'blitBgr':
|
||||||
|
this.blitBgrImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||||
|
break;
|
||||||
case 'blitRgbx':
|
case 'blitRgbx':
|
||||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ export const encodings = {
|
||||||
encodingRRE: 2,
|
encodingRRE: 2,
|
||||||
encodingHextile: 5,
|
encodingHextile: 5,
|
||||||
encodingTight: 7,
|
encodingTight: 7,
|
||||||
|
encodingZRLE: 16,
|
||||||
|
encodingZYWRLE: 17,
|
||||||
encodingTightPNG: -260,
|
encodingTightPNG: -260,
|
||||||
|
|
||||||
pseudoEncodingQualityLevel9: -23,
|
pseudoEncodingQualityLevel9: -23,
|
||||||
|
|
@ -36,6 +38,8 @@ export function encodingName(num) {
|
||||||
case encodings.encodingHextile: return "Hextile";
|
case encodings.encodingHextile: return "Hextile";
|
||||||
case encodings.encodingTight: return "Tight";
|
case encodings.encodingTight: return "Tight";
|
||||||
case encodings.encodingTightPNG: return "TightPNG";
|
case encodings.encodingTightPNG: return "TightPNG";
|
||||||
|
case encodings.encodingZRLE: return "ZRLE";
|
||||||
|
case encodings.encodingZYWRLE: return "ZYWRLE";
|
||||||
default: return "[unknown encoding " + num + "]";
|
default: return "[unknown encoding " + num + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import RREDecoder from "./decoders/rre.js";
|
||||||
import HextileDecoder from "./decoders/hextile.js";
|
import HextileDecoder from "./decoders/hextile.js";
|
||||||
import TightDecoder from "./decoders/tight.js";
|
import TightDecoder from "./decoders/tight.js";
|
||||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||||
|
import ZRLEDecoder from "./decoders/zrle.js";
|
||||||
|
|
||||||
// How many seconds to wait for a disconnect to finish
|
// How many seconds to wait for a disconnect to finish
|
||||||
const DISCONNECT_TIMEOUT = 3;
|
const DISCONNECT_TIMEOUT = 3;
|
||||||
|
|
@ -163,6 +164,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
||||||
this._decoders[encodings.encodingTight] = new TightDecoder();
|
this._decoders[encodings.encodingTight] = new TightDecoder();
|
||||||
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
||||||
|
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
|
||||||
|
this._decoders[encodings.encodingZYWRLE] = new ZRLEDecoder();
|
||||||
|
|
||||||
// NB: nothing that needs explicit teardown should be done
|
// NB: nothing that needs explicit teardown should be done
|
||||||
// before this point, since this can throw an exception
|
// before this point, since this can throw an exception
|
||||||
|
|
@ -1217,6 +1220,8 @@ export default class RFB extends EventTargetMixin {
|
||||||
if (this._fb_depth == 24) {
|
if (this._fb_depth == 24) {
|
||||||
encs.push(encodings.encodingTight);
|
encs.push(encodings.encodingTight);
|
||||||
encs.push(encodings.encodingTightPNG);
|
encs.push(encodings.encodingTightPNG);
|
||||||
|
encs.push(encodings.encodingZRLE);
|
||||||
|
encs.push(encodings.encodingZYWRLE);
|
||||||
encs.push(encodings.encodingHextile);
|
encs.push(encodings.encodingHextile);
|
||||||
encs.push(encodings.encodingRRE);
|
encs.push(encodings.encodingRRE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue