main
Raw Download raw file
  1/*
  2 * noVNC: HTML5 VNC client
  3 * Copyright (C) 2021 The noVNC authors
  4 * Licensed under MPL 2.0 (see LICENSE.txt)
  5 *
  6 * See README.md for usage and integration instructions.
  7 *
  8 */
  9
 10import Inflate from "../inflator.js";
 11
 12const ZRLE_TILE_WIDTH = 64;
 13const ZRLE_TILE_HEIGHT = 64;
 14
 15export default class ZRLEDecoder {
 16    constructor() {
 17        this._length = 0;
 18        this._inflator = new Inflate();
 19
 20        this._pixelBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
 21        this._tileBuffer = new Uint8Array(ZRLE_TILE_WIDTH * ZRLE_TILE_HEIGHT * 4);
 22    }
 23
 24    decodeRect(x, y, width, height, sock, display, depth) {
 25        if (this._length === 0) {
 26            if (sock.rQwait("ZLib data length", 4)) {
 27                return false;
 28            }
 29            this._length = sock.rQshift32();
 30        }
 31        if (sock.rQwait("Zlib data", this._length)) {
 32            return false;
 33        }
 34
 35        const data = sock.rQshiftBytes(this._length, false);
 36
 37        this._inflator.setInput(data);
 38
 39        for (let ty = y; ty < y + height; ty += ZRLE_TILE_HEIGHT) {
 40            let th = Math.min(ZRLE_TILE_HEIGHT, y + height - ty);
 41
 42            for (let tx = x; tx < x + width; tx += ZRLE_TILE_WIDTH) {
 43                let tw = Math.min(ZRLE_TILE_WIDTH, x + width - tx);
 44
 45                const tileSize = tw * th;
 46                const subencoding = this._inflator.inflate(1)[0];
 47                if (subencoding === 0) {
 48                    // raw data
 49                    const data = this._readPixels(tileSize);
 50                    display.blitImage(tx, ty, tw, th, data, 0, false);
 51                } else if (subencoding === 1) {
 52                    // solid
 53                    const background = this._readPixels(1);
 54                    display.fillRect(tx, ty, tw, th, [background[0], background[1], background[2]]);
 55                } else if (subencoding >= 2 && subencoding <= 16) {
 56                    const data = this._decodePaletteTile(subencoding, tileSize, tw, th);
 57                    display.blitImage(tx, ty, tw, th, data, 0, false);
 58                } else if (subencoding === 128) {
 59                    const data = this._decodeRLETile(tileSize);
 60                    display.blitImage(tx, ty, tw, th, data, 0, false);
 61                } else if (subencoding >= 130 && subencoding <= 255) {
 62                    const data = this._decodeRLEPaletteTile(subencoding - 128, tileSize);
 63                    display.blitImage(tx, ty, tw, th, data, 0, false);
 64                } else {
 65                    throw new Error('Unknown subencoding: ' + subencoding);
 66                }
 67            }
 68        }
 69        this._length = 0;
 70        return true;
 71    }
 72
 73    _getBitsPerPixelInPalette(paletteSize) {
 74        if (paletteSize <= 2) {
 75            return 1;
 76        } else if (paletteSize <= 4) {
 77            return 2;
 78        } else if (paletteSize <= 16) {
 79            return 4;
 80        }
 81    }
 82
 83    _readPixels(pixels) {
 84        let data = this._pixelBuffer;
 85        const buffer = this._inflator.inflate(3*pixels);
 86        for (let i = 0, j = 0; i < pixels*4; i += 4, j += 3) {
 87            data[i]     = buffer[j];
 88            data[i + 1] = buffer[j + 1];
 89            data[i + 2] = buffer[j + 2];
 90            data[i + 3] = 255;  // Add the Alpha
 91        }
 92        return data;
 93    }
 94
 95    _decodePaletteTile(paletteSize, tileSize, tilew, tileh) {
 96        const data = this._tileBuffer;
 97        const palette = this._readPixels(paletteSize);
 98        const bitsPerPixel = this._getBitsPerPixelInPalette(paletteSize);
 99        const mask = (1 << bitsPerPixel) - 1;
100
101        let offset = 0;
102        let encoded = this._inflator.inflate(1)[0];
103
104        for (let y=0; y<tileh; y++) {
105            let shift = 8-bitsPerPixel;
106            for (let x=0; x<tilew; x++) {
107                if (shift<0) {
108                    shift=8-bitsPerPixel;
109                    encoded = this._inflator.inflate(1)[0];
110                }
111                let indexInPalette = (encoded>>shift) & mask;
112
113                data[offset] = palette[indexInPalette * 4];
114                data[offset + 1] = palette[indexInPalette * 4 + 1];
115                data[offset + 2] = palette[indexInPalette * 4 + 2];
116                data[offset + 3] = palette[indexInPalette * 4 + 3];
117                offset += 4;
118                shift-=bitsPerPixel;
119            }
120            if (shift<8-bitsPerPixel && y<tileh-1) {
121                encoded =  this._inflator.inflate(1)[0];
122            }
123        }
124        return data;
125    }
126
127    _decodeRLETile(tileSize) {
128        const data = this._tileBuffer;
129        let i = 0;
130        while (i < tileSize) {
131            const pixel = this._readPixels(1);
132            const length = this._readRLELength();
133            for (let j = 0; j < length; j++) {
134                data[i * 4] = pixel[0];
135                data[i * 4 + 1] = pixel[1];
136                data[i * 4 + 2] = pixel[2];
137                data[i * 4 + 3] = pixel[3];
138                i++;
139            }
140        }
141        return data;
142    }
143
144    _decodeRLEPaletteTile(paletteSize, tileSize) {
145        const data = this._tileBuffer;
146
147        // palette
148        const palette = this._readPixels(paletteSize);
149
150        let offset = 0;
151        while (offset < tileSize) {
152            let indexInPalette = this._inflator.inflate(1)[0];
153            let length = 1;
154            if (indexInPalette >= 128) {
155                indexInPalette -= 128;
156                length = this._readRLELength();
157            }
158            if (indexInPalette > paletteSize) {
159                throw new Error('Too big index in palette: ' + indexInPalette + ', palette size: ' + paletteSize);
160            }
161            if (offset + length > tileSize) {
162                throw new Error('Too big rle length in palette mode: ' + length + ', allowed length is: ' + (tileSize - offset));
163            }
164
165            for (let j = 0; j < length; j++) {
166                data[offset * 4] = palette[indexInPalette * 4];
167                data[offset * 4 + 1] = palette[indexInPalette * 4 + 1];
168                data[offset * 4 + 2] = palette[indexInPalette * 4 + 2];
169                data[offset * 4 + 3] = palette[indexInPalette * 4 + 3];
170                offset++;
171            }
172        }
173        return data;
174    }
175
176    _readRLELength() {
177        let length = 0;
178        let current = 0;
179        do {
180            current = this._inflator.inflate(1)[0];
181            length += current;
182        } while (current === 255);
183        return length + 1;
184    }
185}