tight.js 9.4 KB

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