| /* |
| * noVNC: HTML5 VNC client |
| * Copyright (C) 2019 The noVNC Authors |
| * Licensed under MPL 2.0 (see LICENSE.txt) |
| * |
| * See README.md for usage and integration instructions. |
| * |
| */ |
| |
| import * as Log from '../util/logging.js'; |
| |
| export default class HextileDecoder { |
| constructor() { |
| this._tiles = 0; |
| this._lastsubencoding = 0; |
| this._tileBuffer = new Uint8Array(16 * 16 * 4); |
| } |
| |
| decodeRect(x, y, width, height, sock, display, depth) { |
| if (this._tiles === 0) { |
| this._tilesX = Math.ceil(width / 16); |
| this._tilesY = Math.ceil(height / 16); |
| this._totalTiles = this._tilesX * this._tilesY; |
| this._tiles = this._totalTiles; |
| } |
| |
| while (this._tiles > 0) { |
| let bytes = 1; |
| |
| if (sock.rQwait("HEXTILE", bytes)) { |
| return false; |
| } |
| |
| let rQ = sock.rQ; |
| let rQi = sock.rQi; |
| |
| let subencoding = rQ[rQi]; // Peek |
| if (subencoding > 30) { // Raw |
| throw new Error("Illegal hextile subencoding (subencoding: " + |
| subencoding + ")"); |
| } |
| |
| const currTile = this._totalTiles - this._tiles; |
| const tileX = currTile % this._tilesX; |
| const tileY = Math.floor(currTile / this._tilesX); |
| const tx = x + tileX * 16; |
| const ty = y + tileY * 16; |
| const tw = Math.min(16, (x + width) - tx); |
| const th = Math.min(16, (y + height) - ty); |
| |
| // Figure out how much we are expecting |
| if (subencoding & 0x01) { // Raw |
| bytes += tw * th * 4; |
| } else { |
| if (subencoding & 0x02) { // Background |
| bytes += 4; |
| } |
| if (subencoding & 0x04) { // Foreground |
| bytes += 4; |
| } |
| if (subencoding & 0x08) { // AnySubrects |
| bytes++; // Since we aren't shifting it off |
| |
| if (sock.rQwait("HEXTILE", bytes)) { |
| return false; |
| } |
| |
| let subrects = rQ[rQi + bytes - 1]; // Peek |
| if (subencoding & 0x10) { // SubrectsColoured |
| bytes += subrects * (4 + 2); |
| } else { |
| bytes += subrects * 2; |
| } |
| } |
| } |
| |
| if (sock.rQwait("HEXTILE", bytes)) { |
| return false; |
| } |
| |
| // We know the encoding and have a whole tile |
| rQi++; |
| if (subencoding === 0) { |
| if (this._lastsubencoding & 0x01) { |
| // Weird: ignore blanks are RAW |
| Log.Debug(" Ignoring blank after RAW"); |
| } else { |
| display.fillRect(tx, ty, tw, th, this._background); |
| } |
| } else if (subencoding & 0x01) { // Raw |
| let pixels = tw * th; |
| // Max sure the image is fully opaque |
| for (let i = 0;i < pixels;i++) { |
| rQ[rQi + i * 4 + 3] = 255; |
| } |
| display.blitImage(tx, ty, tw, th, rQ, rQi); |
| rQi += bytes - 1; |
| } else { |
| if (subencoding & 0x02) { // Background |
| this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; |
| rQi += 4; |
| } |
| if (subencoding & 0x04) { // Foreground |
| this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; |
| rQi += 4; |
| } |
| |
| this._startTile(tx, ty, tw, th, this._background); |
| if (subencoding & 0x08) { // AnySubrects |
| let subrects = rQ[rQi]; |
| rQi++; |
| |
| for (let s = 0; s < subrects; s++) { |
| let color; |
| if (subencoding & 0x10) { // SubrectsColoured |
| color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]]; |
| rQi += 4; |
| } else { |
| color = this._foreground; |
| } |
| const xy = rQ[rQi]; |
| rQi++; |
| const sx = (xy >> 4); |
| const sy = (xy & 0x0f); |
| |
| const wh = rQ[rQi]; |
| rQi++; |
| const sw = (wh >> 4) + 1; |
| const sh = (wh & 0x0f) + 1; |
| |
| this._subTile(sx, sy, sw, sh, color); |
| } |
| } |
| this._finishTile(display); |
| } |
| sock.rQi = rQi; |
| this._lastsubencoding = subencoding; |
| this._tiles--; |
| } |
| |
| return true; |
| } |
| |
| // start updating a tile |
| _startTile(x, y, width, height, color) { |
| this._tileX = x; |
| this._tileY = y; |
| this._tileW = width; |
| this._tileH = height; |
| |
| const red = color[0]; |
| const green = color[1]; |
| const blue = color[2]; |
| |
| const data = this._tileBuffer; |
| for (let i = 0; i < width * height * 4; i += 4) { |
| data[i] = red; |
| data[i + 1] = green; |
| data[i + 2] = blue; |
| data[i + 3] = 255; |
| } |
| } |
| |
| // update sub-rectangle of the current tile |
| _subTile(x, y, w, h, color) { |
| const red = color[0]; |
| const green = color[1]; |
| const blue = color[2]; |
| const xend = x + w; |
| const yend = y + h; |
| |
| const data = this._tileBuffer; |
| const width = this._tileW; |
| for (let j = y; j < yend; j++) { |
| for (let i = x; i < xend; i++) { |
| const p = (i + (j * width)) * 4; |
| data[p] = red; |
| data[p + 1] = green; |
| data[p + 2] = blue; |
| data[p + 3] = 255; |
| } |
| } |
| } |
| |
| // draw the current tile to the screen |
| _finishTile(display) { |
| display.blitImage(this._tileX, this._tileY, |
| this._tileW, this._tileH, |
| this._tileBuffer, 0); |
| } |
| } |