hextile.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /*
  2. * noVNC: HTML5 VNC client
  3. * Copyright (C) 2019 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. import * as Log from '../util/logging.js';
  10. export default class HextileDecoder {
  11. constructor() {
  12. this._tiles = 0;
  13. this._lastsubencoding = 0;
  14. this._tileBuffer = new Uint8Array(16 * 16 * 4);
  15. }
  16. decodeRect(x, y, width, height, sock, display, depth) {
  17. if (this._tiles === 0) {
  18. this._tilesX = Math.ceil(width / 16);
  19. this._tilesY = Math.ceil(height / 16);
  20. this._totalTiles = this._tilesX * this._tilesY;
  21. this._tiles = this._totalTiles;
  22. }
  23. while (this._tiles > 0) {
  24. let bytes = 1;
  25. if (sock.rQwait("HEXTILE", bytes)) {
  26. return false;
  27. }
  28. let subencoding = sock.rQpeek8();
  29. if (subencoding > 30) { // Raw
  30. throw new Error("Illegal hextile subencoding (subencoding: " +
  31. subencoding + ")");
  32. }
  33. const currTile = this._totalTiles - this._tiles;
  34. const tileX = currTile % this._tilesX;
  35. const tileY = Math.floor(currTile / this._tilesX);
  36. const tx = x + tileX * 16;
  37. const ty = y + tileY * 16;
  38. const tw = Math.min(16, (x + width) - tx);
  39. const th = Math.min(16, (y + height) - ty);
  40. // Figure out how much we are expecting
  41. if (subencoding & 0x01) { // Raw
  42. bytes += tw * th * 4;
  43. } else {
  44. if (subencoding & 0x02) { // Background
  45. bytes += 4;
  46. }
  47. if (subencoding & 0x04) { // Foreground
  48. bytes += 4;
  49. }
  50. if (subencoding & 0x08) { // AnySubrects
  51. bytes++; // Since we aren't shifting it off
  52. if (sock.rQwait("HEXTILE", bytes)) {
  53. return false;
  54. }
  55. let subrects = sock.rQpeekBytes(bytes).at(-1);
  56. if (subencoding & 0x10) { // SubrectsColoured
  57. bytes += subrects * (4 + 2);
  58. } else {
  59. bytes += subrects * 2;
  60. }
  61. }
  62. }
  63. if (sock.rQwait("HEXTILE", bytes)) {
  64. return false;
  65. }
  66. // We know the encoding and have a whole tile
  67. sock.rQshift8();
  68. if (subencoding === 0) {
  69. if (this._lastsubencoding & 0x01) {
  70. // Weird: ignore blanks are RAW
  71. Log.Debug(" Ignoring blank after RAW");
  72. } else {
  73. display.fillRect(tx, ty, tw, th, this._background);
  74. }
  75. } else if (subencoding & 0x01) { // Raw
  76. let pixels = tw * th;
  77. let data = sock.rQshiftBytes(pixels * 4, false);
  78. // Max sure the image is fully opaque
  79. for (let i = 0;i < pixels;i++) {
  80. data[i * 4 + 3] = 255;
  81. }
  82. display.blitImage(tx, ty, tw, th, data, 0);
  83. } else {
  84. if (subencoding & 0x02) { // Background
  85. this._background = new Uint8Array(sock.rQshiftBytes(4));
  86. }
  87. if (subencoding & 0x04) { // Foreground
  88. this._foreground = new Uint8Array(sock.rQshiftBytes(4));
  89. }
  90. this._startTile(tx, ty, tw, th, this._background);
  91. if (subencoding & 0x08) { // AnySubrects
  92. let subrects = sock.rQshift8();
  93. for (let s = 0; s < subrects; s++) {
  94. let color;
  95. if (subencoding & 0x10) { // SubrectsColoured
  96. color = sock.rQshiftBytes(4);
  97. } else {
  98. color = this._foreground;
  99. }
  100. const xy = sock.rQshift8();
  101. const sx = (xy >> 4);
  102. const sy = (xy & 0x0f);
  103. const wh = sock.rQshift8();
  104. const sw = (wh >> 4) + 1;
  105. const sh = (wh & 0x0f) + 1;
  106. this._subTile(sx, sy, sw, sh, color);
  107. }
  108. }
  109. this._finishTile(display);
  110. }
  111. this._lastsubencoding = subencoding;
  112. this._tiles--;
  113. }
  114. return true;
  115. }
  116. // start updating a tile
  117. _startTile(x, y, width, height, color) {
  118. this._tileX = x;
  119. this._tileY = y;
  120. this._tileW = width;
  121. this._tileH = height;
  122. const red = color[0];
  123. const green = color[1];
  124. const blue = color[2];
  125. const data = this._tileBuffer;
  126. for (let i = 0; i < width * height * 4; i += 4) {
  127. data[i] = red;
  128. data[i + 1] = green;
  129. data[i + 2] = blue;
  130. data[i + 3] = 255;
  131. }
  132. }
  133. // update sub-rectangle of the current tile
  134. _subTile(x, y, w, h, color) {
  135. const red = color[0];
  136. const green = color[1];
  137. const blue = color[2];
  138. const xend = x + w;
  139. const yend = y + h;
  140. const data = this._tileBuffer;
  141. const width = this._tileW;
  142. for (let j = y; j < yend; j++) {
  143. for (let i = x; i < xend; i++) {
  144. const p = (i + (j * width)) * 4;
  145. data[p] = red;
  146. data[p + 1] = green;
  147. data[p + 2] = blue;
  148. data[p + 3] = 255;
  149. }
  150. }
  151. }
  152. // draw the current tile to the screen
  153. _finishTile(display) {
  154. display.blitImage(this._tileX, this._tileY,
  155. this._tileW, this._tileH,
  156. this._tileBuffer, 0);
  157. }
  158. }