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