zrle and zywrle support

This commit is contained in:
Max Furtuna 2019-08-18 22:40:26 +03:00
parent e1d50c8c10
commit 3ee84fb333
4 changed files with 239 additions and 0 deletions

194
core/decoders/zrle.js Normal file
View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 + "]";
} }
} }

View File

@ -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);
} }