195 lines
6.3 KiB
JavaScript
195 lines
6.3 KiB
JavaScript
/*
|
|
* 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;
|
|
}
|
|
}
|