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) {
|
||||
if (this._renderQ.length !== 0 && !from_queue) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
_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) {
|
||||
// NB(directxman12): arr must be an Type Array view
|
||||
let img;
|
||||
|
|
@ -625,6 +658,9 @@ export default class Display {
|
|||
case 'blitRgb':
|
||||
this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'blitBgr':
|
||||
this.blitBgrImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
case 'blitRgbx':
|
||||
this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export const encodings = {
|
|||
encodingRRE: 2,
|
||||
encodingHextile: 5,
|
||||
encodingTight: 7,
|
||||
encodingZRLE: 16,
|
||||
encodingZYWRLE: 17,
|
||||
encodingTightPNG: -260,
|
||||
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
|
|
@ -36,6 +38,8 @@ export function encodingName(num) {
|
|||
case encodings.encodingHextile: return "Hextile";
|
||||
case encodings.encodingTight: return "Tight";
|
||||
case encodings.encodingTightPNG: return "TightPNG";
|
||||
case encodings.encodingZRLE: return "ZRLE";
|
||||
case encodings.encodingZYWRLE: return "ZYWRLE";
|
||||
default: return "[unknown encoding " + num + "]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import RREDecoder from "./decoders/rre.js";
|
|||
import HextileDecoder from "./decoders/hextile.js";
|
||||
import TightDecoder from "./decoders/tight.js";
|
||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||
import ZRLEDecoder from "./decoders/zrle.js";
|
||||
|
||||
// How many seconds to wait for a disconnect to finish
|
||||
const DISCONNECT_TIMEOUT = 3;
|
||||
|
|
@ -163,6 +164,8 @@ export default class RFB extends EventTargetMixin {
|
|||
this._decoders[encodings.encodingHextile] = new HextileDecoder();
|
||||
this._decoders[encodings.encodingTight] = new TightDecoder();
|
||||
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
|
||||
// before this point, since this can throw an exception
|
||||
|
|
@ -1217,6 +1220,8 @@ export default class RFB extends EventTargetMixin {
|
|||
if (this._fb_depth == 24) {
|
||||
encs.push(encodings.encodingTight);
|
||||
encs.push(encodings.encodingTightPNG);
|
||||
encs.push(encodings.encodingZRLE);
|
||||
encs.push(encodings.encodingZYWRLE);
|
||||
encs.push(encodings.encodingHextile);
|
||||
encs.push(encodings.encodingRRE);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue