tight.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2019 The noVNC Authors
  4. * (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
  5. * Licensed under MPL 2.0 (see LICENSE.txt)
  6. *
  7. * See README.md for usage and integration instructions.
  8. *
  9. */
  10. import * as Log from '../util/logging.js';
  11. import Inflator from "../inflator.js";
  12. export default class TightDecoder {
  13. constructor() {
  14. this._ctl = null;
  15. this._filter = null;
  16. this._numColors = 0;
  17. this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
  18. this._len = 0;
  19. this._zlibs = [];
  20. for (let i = 0; i < 4; i++) {
  21. this._zlibs[i] = new Inflator();
  22. }
  23. }
  24. decodeRect(x, y, width, height, sock, display, depth) {
  25. if (this._ctl === null) {
  26. if (sock.rQwait("TIGHT compression-control", 1)) {
  27. return false;
  28. }
  29. this._ctl = sock.rQshift8();
  30. // Reset streams if the server requests it
  31. for (let i = 0; i < 4; i++) {
  32. if ((this._ctl >> i) & 1) {
  33. this._zlibs[i].reset();
  34. Log.Info("Reset zlib stream " + i);
  35. }
  36. }
  37. // Figure out filter
  38. this._ctl = this._ctl >> 4;
  39. }
  40. let ret;
  41. if (this._ctl === 0x08) {
  42. ret = this._fillRect(x, y, width, height,
  43. sock, display, depth);
  44. } else if (this._ctl === 0x09) {
  45. ret = this._jpegRect(x, y, width, height,
  46. sock, display, depth);
  47. } else if (this._ctl === 0x0A) {
  48. ret = this._pngRect(x, y, width, height,
  49. sock, display, depth);
  50. } else if ((this._ctl & 0x08) == 0) {
  51. ret = this._basicRect(this._ctl, x, y, width, height,
  52. sock, display, depth);
  53. } else {
  54. throw new Error("Illegal tight compression received (ctl: " +
  55. this._ctl + ")");
  56. }
  57. if (ret) {
  58. this._ctl = null;
  59. }
  60. return ret;
  61. }
  62. _fillRect(x, y, width, height, sock, display, depth) {
  63. if (sock.rQwait("TIGHT", 3)) {
  64. return false;
  65. }
  66. let pixel = sock.rQshiftBytes(3);
  67. display.fillRect(x, y, width, height, pixel, false);
  68. return true;
  69. }
  70. _jpegRect(x, y, width, height, sock, display, depth) {
  71. let data = this._readData(sock);
  72. if (data === null) {
  73. return false;
  74. }
  75. display.imageRect(x, y, width, height, "image/jpeg", data);
  76. return true;
  77. }
  78. _pngRect(x, y, width, height, sock, display, depth) {
  79. throw new Error("PNG received in standard Tight rect");
  80. }
  81. _basicRect(ctl, x, y, width, height, sock, display, depth) {
  82. if (this._filter === null) {
  83. if (ctl & 0x4) {
  84. if (sock.rQwait("TIGHT", 1)) {
  85. return false;
  86. }
  87. this._filter = sock.rQshift8();
  88. } else {
  89. // Implicit CopyFilter
  90. this._filter = 0;
  91. }
  92. }
  93. let streamId = ctl & 0x3;
  94. let ret;
  95. switch (this._filter) {
  96. case 0: // CopyFilter
  97. ret = this._copyFilter(streamId, x, y, width, height,
  98. sock, display, depth);
  99. break;
  100. case 1: // PaletteFilter
  101. ret = this._paletteFilter(streamId, x, y, width, height,
  102. sock, display, depth);
  103. break;
  104. case 2: // GradientFilter
  105. ret = this._gradientFilter(streamId, x, y, width, height,
  106. sock, display, depth);
  107. break;
  108. default:
  109. throw new Error("Illegal tight filter received (ctl: " +
  110. this._filter + ")");
  111. }
  112. if (ret) {
  113. this._filter = null;
  114. }
  115. return ret;
  116. }
  117. _copyFilter(streamId, x, y, width, height, sock, display, depth) {
  118. const uncompressedSize = width * height * 3;
  119. let data;
  120. if (uncompressedSize === 0) {
  121. return true;
  122. }
  123. if (uncompressedSize < 12) {
  124. if (sock.rQwait("TIGHT", uncompressedSize)) {
  125. return false;
  126. }
  127. data = sock.rQshiftBytes(uncompressedSize);
  128. } else {
  129. data = this._readData(sock);
  130. if (data === null) {
  131. return false;
  132. }
  133. this._zlibs[streamId].setInput(data);
  134. data = this._zlibs[streamId].inflate(uncompressedSize);
  135. this._zlibs[streamId].setInput(null);
  136. }
  137. let rgbx = new Uint8Array(width * height * 4);
  138. for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
  139. rgbx[i] = data[j];
  140. rgbx[i + 1] = data[j + 1];
  141. rgbx[i + 2] = data[j + 2];
  142. rgbx[i + 3] = 255; // Alpha
  143. }
  144. display.blitImage(x, y, width, height, rgbx, 0, false);
  145. return true;
  146. }
  147. _paletteFilter(streamId, x, y, width, height, sock, display, depth) {
  148. if (this._numColors === 0) {
  149. if (sock.rQwait("TIGHT palette", 1)) {
  150. return false;
  151. }
  152. const numColors = sock.rQpeek8() + 1;
  153. const paletteSize = numColors * 3;
  154. if (sock.rQwait("TIGHT palette", 1 + paletteSize)) {
  155. return false;
  156. }
  157. this._numColors = numColors;
  158. sock.rQskipBytes(1);
  159. sock.rQshiftTo(this._palette, paletteSize);
  160. }
  161. const bpp = (this._numColors <= 2) ? 1 : 8;
  162. const rowSize = Math.floor((width * bpp + 7) / 8);
  163. const uncompressedSize = rowSize * height;
  164. let data;
  165. if (uncompressedSize === 0) {
  166. return true;
  167. }
  168. if (uncompressedSize < 12) {
  169. if (sock.rQwait("TIGHT", uncompressedSize)) {
  170. return false;
  171. }
  172. data = sock.rQshiftBytes(uncompressedSize);
  173. } else {
  174. data = this._readData(sock);
  175. if (data === null) {
  176. return false;
  177. }
  178. this._zlibs[streamId].setInput(data);
  179. data = this._zlibs[streamId].inflate(uncompressedSize);
  180. this._zlibs[streamId].setInput(null);
  181. }
  182. // Convert indexed (palette based) image data to RGB
  183. if (this._numColors == 2) {
  184. this._monoRect(x, y, width, height, data, this._palette, display);
  185. } else {
  186. this._paletteRect(x, y, width, height, data, this._palette, display);
  187. }
  188. this._numColors = 0;
  189. return true;
  190. }
  191. _monoRect(x, y, width, height, data, palette, display) {
  192. // Convert indexed (palette based) image data to RGB
  193. // TODO: reduce number of calculations inside loop
  194. const dest = this._getScratchBuffer(width * height * 4);
  195. const w = Math.floor((width + 7) / 8);
  196. const w1 = Math.floor(width / 8);
  197. for (let y = 0; y < height; y++) {
  198. let dp, sp, x;
  199. for (x = 0; x < w1; x++) {
  200. for (let b = 7; b >= 0; b--) {
  201. dp = (y * width + x * 8 + 7 - b) * 4;
  202. sp = (data[y * w + x] >> b & 1) * 3;
  203. dest[dp] = palette[sp];
  204. dest[dp + 1] = palette[sp + 1];
  205. dest[dp + 2] = palette[sp + 2];
  206. dest[dp + 3] = 255;
  207. }
  208. }
  209. for (let b = 7; b >= 8 - width % 8; b--) {
  210. dp = (y * width + x * 8 + 7 - b) * 4;
  211. sp = (data[y * w + x] >> b & 1) * 3;
  212. dest[dp] = palette[sp];
  213. dest[dp + 1] = palette[sp + 1];
  214. dest[dp + 2] = palette[sp + 2];
  215. dest[dp + 3] = 255;
  216. }
  217. }
  218. display.blitImage(x, y, width, height, dest, 0, false);
  219. }
  220. _paletteRect(x, y, width, height, data, palette, display) {
  221. // Convert indexed (palette based) image data to RGB
  222. const dest = this._getScratchBuffer(width * height * 4);
  223. const total = width * height * 4;
  224. for (let i = 0, j = 0; i < total; i += 4, j++) {
  225. const sp = data[j] * 3;
  226. dest[i] = palette[sp];
  227. dest[i + 1] = palette[sp + 1];
  228. dest[i + 2] = palette[sp + 2];
  229. dest[i + 3] = 255;
  230. }
  231. display.blitImage(x, y, width, height, dest, 0, false);
  232. }
  233. _gradientFilter(streamId, x, y, width, height, sock, display, depth) {
  234. // assume the TPIXEL is 3 bytes long
  235. const uncompressedSize = width * height * 3;
  236. let data;
  237. if (uncompressedSize === 0) {
  238. return true;
  239. }
  240. if (uncompressedSize < 12) {
  241. if (sock.rQwait("TIGHT", uncompressedSize)) {
  242. return false;
  243. }
  244. data = sock.rQshiftBytes(uncompressedSize);
  245. } else {
  246. data = this._readData(sock);
  247. if (data === null) {
  248. return false;
  249. }
  250. this._zlibs[streamId].setInput(data);
  251. data = this._zlibs[streamId].inflate(uncompressedSize);
  252. this._zlibs[streamId].setInput(null);
  253. }
  254. let rgbx = new Uint8Array(4 * width * height);
  255. let rgbxIndex = 0, dataIndex = 0;
  256. let left = new Uint8Array(3);
  257. for (let x = 0; x < width; x++) {
  258. for (let c = 0; c < 3; c++) {
  259. const prediction = left[c];
  260. const value = data[dataIndex++] + prediction;
  261. rgbx[rgbxIndex++] = value;
  262. left[c] = value;
  263. }
  264. rgbx[rgbxIndex++] = 255;
  265. }
  266. let upperIndex = 0;
  267. let upper = new Uint8Array(3),
  268. upperleft = new Uint8Array(3);
  269. for (let y = 1; y < height; y++) {
  270. left.fill(0);
  271. upperleft.fill(0);
  272. for (let x = 0; x < width; x++) {
  273. for (let c = 0; c < 3; c++) {
  274. upper[c] = rgbx[upperIndex++];
  275. let prediction = left[c] + upper[c] - upperleft[c];
  276. if (prediction < 0) {
  277. prediction = 0;
  278. } else if (prediction > 255) {
  279. prediction = 255;
  280. }
  281. const value = data[dataIndex++] + prediction;
  282. rgbx[rgbxIndex++] = value;
  283. upperleft[c] = upper[c];
  284. left[c] = value;
  285. }
  286. rgbx[rgbxIndex++] = 255;
  287. upperIndex++;
  288. }
  289. }
  290. display.blitImage(x, y, width, height, rgbx, 0, false);
  291. return true;
  292. }
  293. _readData(sock) {
  294. if (this._len === 0) {
  295. if (sock.rQwait("TIGHT", 3)) {
  296. return null;
  297. }
  298. let byte;
  299. byte = sock.rQshift8();
  300. this._len = byte & 0x7f;
  301. if (byte & 0x80) {
  302. byte = sock.rQshift8();
  303. this._len |= (byte & 0x7f) << 7;
  304. if (byte & 0x80) {
  305. byte = sock.rQshift8();
  306. this._len |= byte << 14;
  307. }
  308. }
  309. }
  310. if (sock.rQwait("TIGHT", this._len)) {
  311. return null;
  312. }
  313. let data = sock.rQshiftBytes(this._len, false);
  314. this._len = 0;
  315. return data;
  316. }
  317. _getScratchBuffer(size) {
  318. if (!this._scratchBuffer || (this._scratchBuffer.length < size)) {
  319. this._scratchBuffer = new Uint8Array(size);
  320. }
  321. return this._scratchBuffer;
  322. }
  323. }