noVNC/core/decoders/zrle.js

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