diff --git a/core/decoders/hextile.js b/core/decoders/hextile.js index 8dbe8092..ac21eff0 100644 --- a/core/decoders/hextile.js +++ b/core/decoders/hextile.js @@ -13,6 +13,7 @@ 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) { @@ -87,6 +88,11 @@ export default class HextileDecoder { 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 { @@ -99,7 +105,7 @@ export default class HextileDecoder { rQi += 4; } - display.startTile(tx, ty, tw, th, this._background); + this._startTile(tx, ty, tw, th, this._background); if (subencoding & 0x08) { // AnySubrects let subrects = rQ[rQi]; rQi++; @@ -122,10 +128,10 @@ export default class HextileDecoder { const sw = (wh >> 4) + 1; const sh = (wh & 0x0f) + 1; - display.subTile(sx, sy, sw, sh, color); + this._subTile(sx, sy, sw, sh, color); } } - display.finishTile(); + this._finishTile(display); } sock.rQi = rQi; this._lastsubencoding = subencoding; @@ -134,4 +140,52 @@ export default class HextileDecoder { 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); + } } diff --git a/core/decoders/raw.js b/core/decoders/raw.js index 4d84d7d1..d7a77ec9 100644 --- a/core/decoders/raw.js +++ b/core/decoders/raw.js @@ -27,23 +27,29 @@ export default class RawDecoder { const curY = y + (height - this._lines); const currHeight = Math.min(this._lines, Math.floor(sock.rQlen / bytesPerLine)); + const pixels = width * currHeight; + let data = sock.rQ; let index = sock.rQi; // Convert data if needed if (depth == 8) { - const pixels = width * currHeight; const newdata = new Uint8Array(pixels * 4); for (let i = 0; i < pixels; i++) { newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3; newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3; - newdata[i * 4 + 4] = 0; + newdata[i * 4 + 3] = 255; } data = newdata; index = 0; } + // Max sure the image is fully opaque + for (let i = 0; i < pixels; i++) { + data[i * 4 + 3] = 255; + } + display.blitImage(x, curY, width, currHeight, data, index); sock.rQskipBytes(currHeight * bytesPerLine); this._lines -= currHeight; diff --git a/core/decoders/tight.js b/core/decoders/tight.js index b207419e..8bc97d10 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -56,7 +56,7 @@ export default class TightDecoder { } else if (this._ctl === 0x0A) { ret = this._pngRect(x, y, width, height, sock, display, depth); - } else if ((this._ctl & 0x80) == 0) { + } else if ((this._ctl & 0x08) == 0) { ret = this._basicRect(this._ctl, x, y, width, height, sock, display, depth); } else { @@ -80,7 +80,7 @@ export default class TightDecoder { const rQ = sock.rQ; display.fillRect(x, y, width, height, - [rQ[rQi + 2], rQ[rQi + 1], rQ[rQi]], false); + [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false); sock.rQskipBytes(3); return true; @@ -165,7 +165,15 @@ export default class TightDecoder { this._zlibs[streamId].setInput(null); } - display.blitRgbImage(x, y, width, height, data, 0, false); + let rgbx = new Uint8Array(width * height * 4); + for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) { + rgbx[i] = data[j]; + rgbx[i + 1] = data[j + 1]; + rgbx[i + 2] = data[j + 2]; + rgbx[i + 3] = 255; // Alpha + } + + display.blitImage(x, y, width, height, rgbx, 0, false); return true; } @@ -237,7 +245,7 @@ export default class TightDecoder { for (let b = 7; b >= 0; b--) { dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; @@ -247,14 +255,14 @@ export default class TightDecoder { for (let b = 7; b >= 8 - width % 8; b--) { dp = (y * width + x * 8 + 7 - b) * 4; sp = (data[y * w + x] >> b & 1) * 3; - dest[dp] = palette[sp]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; } } - display.blitRgbxImage(x, y, width, height, dest, 0, false); + display.blitImage(x, y, width, height, dest, 0, false); } _paletteRect(x, y, width, height, data, palette, display) { @@ -263,13 +271,13 @@ export default class TightDecoder { const total = width * height * 4; for (let i = 0, j = 0; i < total; i += 4, j++) { const sp = data[j] * 3; - dest[i] = palette[sp]; + dest[i] = palette[sp]; dest[i + 1] = palette[sp + 1]; dest[i + 2] = palette[sp + 2]; dest[i + 3] = 255; } - display.blitRgbxImage(x, y, width, height, dest, 0, false); + display.blitImage(x, y, width, height, dest, 0, false); } _gradientFilter(streamId, x, y, width, height, sock, display, depth) { diff --git a/core/display.js b/core/display.js index cf1a51aa..8eaa8001 100644 --- a/core/display.js +++ b/core/display.js @@ -23,10 +23,6 @@ export default class Display { this._fbHeight = 0; this._prevDrawStyle = ""; - this._tile = null; - this._tile16x16 = null; - this._tileX = 0; - this._tileY = 0; Log.Debug(">> Display.constructor"); @@ -65,7 +61,6 @@ export default class Display { throw new Error("Canvas does not support createImageData"); } - this._tile16x16 = this._drawCtx.createImageData(16, 16); Log.Debug("<< Display.constructor"); // ===== PROPERTIES ===== @@ -378,57 +373,6 @@ export default class Display { }); } - // start updating a tile - startTile(x, y, width, height, color) { - this._tileX = x; - this._tileY = y; - if (width === 16 && height === 16) { - this._tile = this._tile16x16; - } else { - this._tile = this._drawCtx.createImageData(width, height); - } - - const red = color[2]; - const green = color[1]; - const blue = color[0]; - - const data = this._tile.data; - 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[2]; - const green = color[1]; - const blue = color[0]; - const xend = x + w; - const yend = y + h; - - const data = this._tile.data; - const width = this._tile.width; - 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() { - this._drawCtx.putImageData(this._tile, this._tileX, this._tileY); - this._damage(this._tileX, this._tileY, - this._tile.width, this._tile.height); - } - blitImage(x, y, width, height, arr, offset, fromQueue) { if (this._renderQ.length !== 0 && !fromQueue) { // NB(directxman12): it's technically more performant here to use preallocated arrays, @@ -445,47 +389,19 @@ export default class Display { 'height': height, }); } else { - this._bgrxImageData(x, y, width, height, arr, offset); - } - } - - blitRgbImage(x, y, width, height, arr, offset, fromQueue) { - if (this._renderQ.length !== 0 && !fromQueue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - const newArr = new Uint8Array(width * height * 3); - newArr.set(new Uint8Array(arr.buffer, 0, newArr.length)); - this._renderQPush({ - 'type': 'blitRgb', - 'data': newArr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else { - this._rgbImageData(x, y, width, height, arr, offset); - } - } - - blitRgbxImage(x, y, width, height, arr, offset, fromQueue) { - if (this._renderQ.length !== 0 && !fromQueue) { - // NB(directxman12): it's technically more performant here to use preallocated arrays, - // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue, - // this probably isn't getting called *nearly* as much - const newArr = new Uint8Array(width * height * 4); - newArr.set(new Uint8Array(arr.buffer, 0, newArr.length)); - this._renderQPush({ - 'type': 'blitRgbx', - 'data': newArr, - 'x': x, - 'y': y, - 'width': width, - 'height': height, - }); - } else { - this._rgbxImageData(x, y, width, height, arr, offset); + // NB(directxman12): arr must be an Type Array view + let data = new Uint8ClampedArray(arr.buffer, + arr.byteOffset + offset, + width * height * 4); + let img; + if (supportsImageMetadata) { + img = new ImageData(data, width, height); + } else { + img = this._drawCtx.createImageData(width, height); + img.data.set(data); + } + this._drawCtx.putImageData(img, x, y); + this._damage(x, y, width, height); } } @@ -537,52 +453,13 @@ export default class Display { } _setFillColor(color) { - const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')'; + const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')'; if (newStyle !== this._prevDrawStyle) { this._drawCtx.fillStyle = newStyle; this._prevDrawStyle = newStyle; } } - _rgbImageData(x, y, width, height, arr, offset) { - const img = this._drawCtx.createImageData(width, height); - const data = img.data; - for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) { - data[i] = arr[j]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j + 2]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - - _bgrxImageData(x, y, width, height, arr, offset) { - const img = this._drawCtx.createImageData(width, height); - const data = img.data; - for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) { - data[i] = arr[j + 2]; - data[i + 1] = arr[j + 1]; - data[i + 2] = arr[j]; - data[i + 3] = 255; // Alpha - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - - _rgbxImageData(x, y, width, height, arr, offset) { - // NB(directxman12): arr must be an Type Array view - let img; - if (supportsImageMetadata) { - img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height); - } else { - img = this._drawCtx.createImageData(width, height); - img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4)); - } - this._drawCtx.putImageData(img, x, y); - this._damage(x, y, img.width, img.height); - } - _renderQPush(action) { this._renderQ.push(action); if (this._renderQ.length === 1) { @@ -616,12 +493,6 @@ export default class Display { case 'blit': this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true); break; - case 'blitRgb': - this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; - case 'blitRgbx': - this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true); - break; case 'img': /* IE tends to set "complete" prematurely, so check dimensions */ if (a.img.complete && (a.img.width !== 0) && (a.img.height !== 0)) { diff --git a/core/rfb.js b/core/rfb.js index f35d503f..b1ed4756 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2882,9 +2882,9 @@ RFB.messages = { buff[offset + 12] = 0; // blue-max buff[offset + 13] = (1 << bits) - 1; // blue-max - buff[offset + 14] = bits * 2; // red-shift + buff[offset + 14] = bits * 0; // red-shift buff[offset + 15] = bits * 1; // green-shift - buff[offset + 16] = bits * 0; // blue-shift + buff[offset + 16] = bits * 2; // blue-shift buff[offset + 17] = 0; // padding buff[offset + 18] = 0; // padding diff --git a/docs/API-internal.md b/docs/API-internal.md index cb4cc396..2cac8ddf 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -78,12 +78,7 @@ None | fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle | copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area | imageRect | (x, y, width, height, mime, arr) | Draw a rectangle with an image -| startTile | (x, y, width, height, color) | Begin updating a tile -| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile -| finishTile | () | Draw the current tile to the display | blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display -| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display -| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display | drawImage | (img, x, y) | Draw image and track damage | autoscale | (containerWidth, containerHeight) | Scale the display diff --git a/tests/test.copyrect.js b/tests/test.copyrect.js new file mode 100644 index 00000000..e2277cb5 --- /dev/null +++ b/tests/test.copyrect.js @@ -0,0 +1,60 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import CopyRectDecoder from '../core/decoders/copyrect.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +describe('CopyRect Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new CopyRectDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + it('should handle the CopyRect encoding', function () { + // seed some initial data to copy + display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]); + display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]); + display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); + + testDecodeRect(decoder, 0, 2, 2, 2, + [0x00, 0x02, 0x00, 0x00], + display, 24); + testDecodeRect(decoder, 2, 2, 2, 2, + [0x00, 0x00, 0x00, 0x00], + display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); +}); diff --git a/tests/test.display.js b/tests/test.display.js index a853778a..bd5a55f9 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -128,7 +128,7 @@ describe('Display/Canvas Helper', function () { }); it('should keep the framebuffer data', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); + display.fillRect(0, 0, 4, 4, [0xff, 0, 0]); display.resize(2, 2); display.flip(); const expected = []; @@ -271,7 +271,7 @@ describe('Display/Canvas Helper', function () { }); it('should not draw directly on the target canvas', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); + display.fillRect(0, 0, 4, 4, [0xff, 0, 0]); display.flip(); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); const expected = []; @@ -285,15 +285,15 @@ describe('Display/Canvas Helper', function () { it('should support filling a rectangle with particular color via #fillRect', function () { display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); - display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); + display.fillRect(0, 0, 2, 2, [0, 0, 0xff]); + display.fillRect(2, 2, 2, 2, [0, 0, 0xff]); display.flip(); expect(display).to.have.displayed(checkedData); }); it('should support copying an portion of the canvas via #copyImage', function () { display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); + display.fillRect(0, 0, 2, 2, [0, 0, 0xff]); display.copyImage(0, 0, 2, 2, 2, 2); display.flip(); expect(display).to.have.displayed(checkedData); @@ -309,62 +309,8 @@ describe('Display/Canvas Helper', function () { display.flush(); }); - it('should support drawing tile data with a background color and sub tiles', function () { - display.startTile(0, 0, 4, 4, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(checkedData); - }); - - // We have a special cache for 16x16 tiles that we need to test - it('should support drawing a 16x16 tile', function () { - const largeCheckedData = new Uint8Array(16*16*4); - display.resize(16, 16); - - for (let y = 0;y < 16;y++) { - for (let x = 0;x < 16;x++) { - let pixel; - if ((x < 4) && (y < 4)) { - // NB: of course IE11 doesn't support #slice on ArrayBufferViews... - pixel = Array.prototype.slice.call(checkedData, (y*4+x)*4, (y*4+x+1)*4); - } else { - pixel = [0, 0xff, 0, 255]; - } - largeCheckedData.set(pixel, (y*16+x)*4); - } - } - - display.startTile(0, 0, 16, 16, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(largeCheckedData); - }); - - it('should support drawing BGRX blit images with true color via #blitImage', function () { - const data = []; - for (let i = 0; i < 16; i++) { - data[i * 4] = checkedData[i * 4 + 2]; - data[i * 4 + 1] = checkedData[i * 4 + 1]; - data[i * 4 + 2] = checkedData[i * 4]; - data[i * 4 + 3] = checkedData[i * 4 + 3]; - } - display.blitImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checkedData); - }); - - it('should support drawing RGB blit images with true color via #blitRgbImage', function () { - const data = []; - for (let i = 0; i < 16; i++) { - data[i * 3] = checkedData[i * 4]; - data[i * 3 + 1] = checkedData[i * 4 + 1]; - data[i * 3 + 2] = checkedData[i * 4 + 2]; - } - display.blitRgbImage(0, 0, 4, 4, data, 0); + it('should support blit images with true color via #blitImage', function () { + display.blitImage(0, 0, 4, 4, checkedData, 0); display.flip(); expect(display).to.have.displayed(checkedData); }); @@ -451,13 +397,6 @@ describe('Display/Canvas Helper', function () { expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); }); - it('should draw a blit RGB image on type "blitRgb"', function () { - display.blitRgbImage = sinon.spy(); - display._renderQPush({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); - expect(display.blitRgbImage).to.have.been.calledOnce; - expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); - }); - it('should copy a region on type "copy"', function () { display.copyImage = sinon.spy(); display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 }); diff --git a/tests/test.hextile.js b/tests/test.hextile.js new file mode 100644 index 00000000..052c82e1 --- /dev/null +++ b/tests/test.hextile.js @@ -0,0 +1,209 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import HextileDecoder from '../core/decoders/hextile.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +function push32(arr, num) { + arr.push((num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + num & 0xFF); +} + +describe('Hextile Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new HextileDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + it('should handle a tile with fg, bg specified, normal subrects', function () { + let data = []; + data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + data.push(0x00); // becomes 0000ff00 --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0x00); + data.push(2); // 2 subrects + data.push(0); // x: 0, y: 0 + data.push(1 | (1 << 4)); // width: 2, height: 2 + data.push(2 | (2 << 4)); // x: 2, y: 2 + data.push(1 | (1 << 4)); // width: 2, height: 2 + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle a raw tile', function () { + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + let data = []; + data.push(0x01); // raw + for (let i = 0; i < targetData.length; i += 4) { + data.push(targetData[i]); + data.push(targetData[i + 1]); + data.push(targetData[i + 2]); + // Last byte zero to test correct alpha handling + data.push(0); + } + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle a tile with only bg specified (solid bg)', function () { + let data = []; + data.push(0x02); + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let expected = []; + for (let i = 0; i < 16; i++) { + push32(expected, 0x00ff00ff); + } + + expect(display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should handle a tile with only bg specified and an empty frame afterwards', function () { + // set the width so we can have two tiles + display.resize(8, 4); + + let data = []; + + // send a bg frame + data.push(0x02); + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + + // send an empty frame + data.push(0x00); + + testDecodeRect(decoder, 0, 0, 32, 4, data, display, 24); + + let expected = []; + for (let i = 0; i < 16; i++) { + push32(expected, 0x00ff00ff); // rect 1: solid + } + for (let i = 0; i < 16; i++) { + push32(expected, 0x00ff00ff); // rect 2: same bkground color + } + + expect(display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should handle a tile with bg and coloured subrects', function () { + let data = []; + data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + data.push(2); // 2 subrects + data.push(0x00); // becomes 0000ff00 --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0x00); + data.push(0); // x: 0, y: 0 + data.push(1 | (1 << 4)); // width: 2, height: 2 + data.push(0x00); // becomes 0000ff00 --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0x00); + data.push(2 | (2 << 4)); // x: 2, y: 2 + data.push(1 | (1 << 4)); // width: 2, height: 2 + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should carry over fg and bg colors from the previous tile if not specified', function () { + display.resize(4, 17); + + let data = []; + data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects + push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + data.push(0x00); // becomes 0000ffff --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0xff); + data.push(8); // 8 subrects + for (let i = 0; i < 4; i++) { + data.push((0 << 4) | (i * 4)); // x: 0, y: i*4 + data.push(1 | (1 << 4)); // width: 2, height: 2 + data.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2 + data.push(1 | (1 << 4)); // width: 2, height: 2 + } + data.push(0x08); // anysubrects + data.push(1); // 1 subrect + data.push(0); // x: 0, y: 0 + data.push(1 | (1 << 4)); // width: 2, height: 2 + + testDecodeRect(decoder, 0, 0, 4, 17, data, display, 24); + + let targetData = [ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]; + + let expected = []; + for (let i = 0; i < 4; i++) { + expected = expected.concat(targetData); + } + expected = expected.concat(targetData.slice(0, 16)); + + expect(display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should fail on an invalid subencoding', function () { + let data = [45]; // an invalid subencoding + expect(() => testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24)).to.throw(); + }); +}); diff --git a/tests/test.raw.js b/tests/test.raw.js new file mode 100644 index 00000000..95a33af9 --- /dev/null +++ b/tests/test.raw.js @@ -0,0 +1,89 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import RawDecoder from '../core/decoders/raw.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +describe('Raw Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new RawDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + it('should handle the Raw encoding', function () { + testDecodeRect(decoder, 0, 0, 2, 2, + [0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0, + 0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0], + display, 24); + testDecodeRect(decoder, 2, 0, 2, 2, + [0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0, + 0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0], + display, 24); + testDecodeRect(decoder, 0, 2, 4, 1, + [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, + 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], + display, 24); + testDecodeRect(decoder, 0, 3, 4, 1, + [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, + 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], + display, 24); + + let targetData = new Uint8Array([ + 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, + 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle the Raw encoding in low colour mode', function () { + testDecodeRect(decoder, 0, 0, 2, 2, + [0x30, 0x30, 0x30, 0x30], + display, 8); + testDecodeRect(decoder, 2, 0, 2, 2, + [0x0c, 0x0c, 0x0c, 0x0c], + display, 8); + testDecodeRect(decoder, 0, 2, 4, 1, + [0x0c, 0x0c, 0x30, 0x30], + display, 8); + testDecodeRect(decoder, 0, 3, 4, 1, + [0x0c, 0x0c, 0x30, 0x30], + display, 8); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); +}); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index db9340ab..da3cc9f6 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1645,28 +1645,6 @@ describe('Remote Frame Buffer Protocol Client', function () { }); describe('Framebuffer Update Handling', function () { - const targetDataArr = [ - 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, - 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 - ]; - let targetData; - - const targetDataCheckArr = [ - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, - 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 - ]; - let targetDataCheck; - - before(function () { - // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray - targetData = new Uint8Array(targetDataArr); - targetDataCheck = new Uint8Array(targetDataCheckArr); - }); - function sendFbuMsg(rectInfo, rectData, client, rectCnt) { let data = []; @@ -1735,22 +1713,6 @@ describe('Remote Frame Buffer Protocol Client', function () { expect(client._fail).to.have.been.calledOnce; }); - it('should be able to pause and resume receiving rects if not enought data', function () { - // seed some initial data to copy - client._fbWidth = 4; - client._fbHeight = 4; - client._display.resize(4, 4); - client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0); - - const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, - { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; - // data says [{ old_x: 2, old_y: 0 }, { old_x: 0, old_y: 0 }] - const rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; - sendFbuMsg([info[0]], [rects[0]], client, 2); - sendFbuMsg([info[1]], [rects[1]], client, -1); - expect(client._display).to.have.displayed(targetDataCheck); - }); - describe('Message Encoding Handlers', function () { beforeEach(function () { // a really small frame @@ -1760,216 +1722,6 @@ describe('Remote Frame Buffer Protocol Client', function () { client._display.resize(4, 4); }); - it('should handle the RAW encoding', function () { - const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, - { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; - // data is in bgrx - const rects = [ - [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], - [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]]; - sendFbuMsg(info, rects, client); - expect(client._display).to.have.displayed(targetData); - }); - - it('should handle the RAW encoding in low colour mode', function () { - const info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, - { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, - { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; - const rects = [ - [0x03, 0x03, 0x03, 0x03], - [0x0c, 0x0c, 0x0c, 0x0c], - [0x0c, 0x0c, 0x03, 0x03], - [0x0c, 0x0c, 0x03, 0x03]]; - client._fbDepth = 8; - sendFbuMsg(info, rects, client); - expect(client._display).to.have.displayed(targetDataCheck); - }); - - it('should handle the COPYRECT encoding', function () { - // seed some initial data to copy - client._display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetDataCheckArr.slice(0, 32)), 0); - - const info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, - { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; - // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }] - const rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; - sendFbuMsg(info, rects, client); - expect(client._display).to.have.displayed(targetDataCheck); - }); - - // TODO(directxman12): for encodings with subrects, test resuming on partial send? - // TODO(directxman12): test rre_chunk_sz (related to above about subrects)? - - it('should handle the RRE encoding', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }]; - const rect = []; - push32(rect, 2); // 2 subrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - push16(rect, 0); // x: 0 - push16(rect, 0); // y: 0 - push16(rect, 2); // width: 2 - push16(rect, 2); // height: 2 - rect.push(0xff); // becomes ff0000ff --> #0000FF color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - push16(rect, 2); // x: 2 - push16(rect, 2); // y: 2 - push16(rect, 2); // width: 2 - push16(rect, 2); // height: 2 - sendFbuMsg(info, [rect], client); - expect(client._display).to.have.displayed(targetDataCheck); - }); - - describe('the HEXTILE encoding handler', function () { - it('should handle a tile with fg, bg specified, normal subrects', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(2); // 2 subrects - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push(2 | (2 << 4)); // x: 2, y: 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - sendFbuMsg(info, [rect], client); - expect(client._display).to.have.displayed(targetDataCheck); - }); - - it('should handle a raw tile', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x01); // raw - for (let i = 0; i < targetData.length; i += 4) { - rect.push(targetData[i + 2]); - rect.push(targetData[i + 1]); - rect.push(targetData[i]); - rect.push(targetData[i + 3]); - } - sendFbuMsg(info, [rect], client); - expect(client._display).to.have.displayed(targetData); - }); - - it('should handle a tile with only bg specified (solid bg)', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02); - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - sendFbuMsg(info, [rect], client); - - const expected = []; - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should handle a tile with only bg specified and an empty frame afterwards', function () { - // set the width so we can have two tiles - client._fbWidth = 8; - client._display.resize(8, 4); - - const info = [{ x: 0, y: 0, width: 32, height: 4, encoding: 0x05 }]; - - const rect = []; - - // send a bg frame - rect.push(0x02); - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - - // send an empty frame - rect.push(0x00); - - sendFbuMsg(info, [rect], client); - - const expected = []; - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 1: solid - for (let i = 0; i < 16; i++) { push32(expected, 0xff00ff); } // rect 2: same bkground color - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should handle a tile with bg and coloured subrects', function () { - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rect = []; - rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(2); // 2 subrects - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(2 | (2 << 4)); // x: 2, y: 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - sendFbuMsg(info, [rect], client); - expect(client._display).to.have.displayed(targetDataCheck); - }); - - it('should carry over fg and bg colors from the previous tile if not specified', function () { - client._fbWidth = 4; - client._fbHeight = 17; - client._display.resize(4, 17); - - const info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}]; - const rect = []; - rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects - push32(rect, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - rect.push(0xff); // becomes ff0000ff --> #0000FF fg color - rect.push(0x00); - rect.push(0x00); - rect.push(0xff); - rect.push(8); // 8 subrects - for (let i = 0; i < 4; i++) { - rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - } - rect.push(0x08); // anysubrects - rect.push(1); // 1 subrect - rect.push(0); // x: 0, y: 0 - rect.push(1 | (1 << 4)); // width: 2, height: 2 - sendFbuMsg(info, [rect], client); - - let expected = []; - for (let i = 0; i < 4; i++) { expected = expected.concat(targetDataCheckArr); } - expected = expected.concat(targetDataCheckArr.slice(0, 16)); - expect(client._display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should fail on an invalid subencoding', function () { - sinon.spy(client, "_fail"); - const info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; - const rects = [[45]]; // an invalid subencoding - sendFbuMsg(info, rects, client); - expect(client._fail).to.have.been.calledOnce; - }); - }); - - it.skip('should handle the TIGHT encoding', function () { - // TODO(directxman12): test this - }); - - it.skip('should handle the TIGHT_PNG encoding', function () { - // TODO(directxman12): test this - }); - it('should handle the DesktopSize pseduo-encoding', function () { sinon.spy(client._display, 'resize'); sendFbuMsg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); diff --git a/tests/test.rre.js b/tests/test.rre.js new file mode 100644 index 00000000..fff1c945 --- /dev/null +++ b/tests/test.rre.js @@ -0,0 +1,84 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import RREDecoder from '../core/decoders/rre.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +function push16(arr, num) { + arr.push((num >> 8) & 0xFF, + num & 0xFF); +} + +function push32(arr, num) { + arr.push((num >> 24) & 0xFF, + (num >> 16) & 0xFF, + (num >> 8) & 0xFF, + num & 0xFF); +} + +describe('RRE Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new RREDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + // TODO(directxman12): test rre_chunk_sz? + + it('should handle the RRE encoding', function () { + let data = []; + push32(data, 2); // 2 subrects + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + data.push(0x00); // becomes 0000ff00 --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0x00); + push16(data, 0); // x: 0 + push16(data, 0); // y: 0 + push16(data, 2); // width: 2 + push16(data, 2); // height: 2 + data.push(0x00); // becomes 0000ff00 --> #0000FF fg color + data.push(0x00); + data.push(0xff); + data.push(0x00); + push16(data, 2); // x: 2 + push16(data, 2); // y: 2 + push16(data, 2); // width: 2 + push16(data, 2); // height: 2 + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); +}); diff --git a/tests/test.tight.js b/tests/test.tight.js new file mode 100644 index 00000000..10ef971c --- /dev/null +++ b/tests/test.tight.js @@ -0,0 +1,328 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import TightDecoder from '../core/decoders/tight.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +describe('Tight Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new TightDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + it('should handle fill rects', function () { + testDecodeRect(decoder, 0, 0, 4, 4, + [0x80, 0xff, 0x88, 0x44], + display, 24); + + let targetData = new Uint8Array([ + 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, + 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, + 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, + 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, 0xff, 0x88, 0x44, 255, + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle uncompressed copy rects', function () { + let blueData = [ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff ]; + let greenData = [ 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00 ]; + + testDecodeRect(decoder, 0, 0, 2, 1, blueData, display, 24); + testDecodeRect(decoder, 0, 1, 2, 1, blueData, display, 24); + testDecodeRect(decoder, 2, 0, 2, 1, greenData, display, 24); + testDecodeRect(decoder, 2, 1, 2, 1, greenData, display, 24); + testDecodeRect(decoder, 0, 2, 2, 1, greenData, display, 24); + testDecodeRect(decoder, 0, 3, 2, 1, greenData, display, 24); + testDecodeRect(decoder, 2, 2, 2, 1, blueData, display, 24); + testDecodeRect(decoder, 2, 3, 2, 1, blueData, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle compressed copy rects', function () { + let data = [ + // Control byte + 0x00, + // Pixels (compressed) + 0x15, + 0x78, 0x9c, 0x63, 0x60, 0xf8, 0xcf, 0x00, 0x44, + 0x60, 0x82, 0x01, 0x99, 0x8d, 0x29, 0x02, 0xa6, + 0x00, 0x7e, 0xbf, 0x0f, 0xf1 ]; + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle uncompressed mono rects', function () { + let data = [ + // Control bytes + 0x40, 0x01, + // Palette + 0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, + // Pixels + 0x30, 0x30, 0xc0, 0xc0 ]; + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle compressed mono rects', function () { + display.resize(4, 12); + + let data = [ + // Control bytes + 0x40, 0x01, + // Palette + 0x01, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, + // Pixels (compressed) + 0x0e, + 0x78, 0x9c, 0x33, 0x30, 0x38, 0x70, 0xc0, 0x00, + 0x8a, 0x01, 0x21, 0x3c, 0x05, 0xa1 ]; + + testDecodeRect(decoder, 0, 0, 4, 12, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle uncompressed palette rects', function () { + let data1 = [ + // Control bytes + 0x40, 0x01, + // Palette + 0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + // Pixels + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01 ]; + let data2 = [ + // Control bytes + 0x40, 0x01, + // Palette + 0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + // Pixels + 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00 ]; + + testDecodeRect(decoder, 0, 0, 4, 2, data1, display, 24); + testDecodeRect(decoder, 0, 2, 4, 2, data2, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it('should handle compressed palette rects', function () { + let data = [ + // Control bytes + 0x40, 0x01, + // Palette + 0x02, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, + // Pixels (compressed) + 0x12, + 0x78, 0x9c, 0x63, 0x60, 0x60, 0x64, 0x64, 0x00, + 0x62, 0x08, 0xc9, 0xc0, 0x00, 0x00, 0x00, 0x54, + 0x00, 0x09 ]; + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]); + + expect(display).to.have.displayed(targetData); + }); + + it.skip('should handle uncompressed gradient rects', function () { + // Not implemented yet + }); + + it.skip('should handle compressed gradient rects', function () { + // Not implemented yet + }); + + it('should handle JPEG rects', function (done) { + let data = [ + // Control bytes + 0x90, 0xd6, 0x05, + // JPEG data + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, + 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, + 0x00, 0x48, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x13, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, + 0x50, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xdb, + 0x00, 0x43, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0xff, 0xc2, 0x00, 0x11, 0x08, + 0x00, 0x04, 0x00, 0x04, 0x03, 0x01, 0x11, 0x00, + 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, + 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xff, 0xc4, 0x00, 0x14, + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, + 0x00, 0x02, 0x10, 0x03, 0x10, 0x00, 0x00, 0x01, + 0x1e, 0x0a, 0xa7, 0x7f, 0xff, 0xc4, 0x00, 0x14, + 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, + 0x00, 0x01, 0x05, 0x02, 0x5d, 0x74, 0x41, 0x47, + 0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00, 0x01, 0x04, + 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x05, + 0x07, 0x08, 0x14, 0x16, 0x03, 0x15, 0x17, 0x25, + 0x26, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, 0x01, + 0x01, 0x3f, 0x01, 0xad, 0x35, 0xa6, 0x13, 0xb8, + 0x10, 0x98, 0x5d, 0x8a, 0xb1, 0x41, 0x7e, 0x43, + 0x99, 0x24, 0x3d, 0x8f, 0x70, 0x30, 0xd8, 0xcb, + 0x44, 0xbb, 0x7d, 0x48, 0xb5, 0xf8, 0x18, 0x7f, + 0xe7, 0xc1, 0x9f, 0x86, 0x45, 0x9b, 0xfa, 0xf1, + 0x61, 0x96, 0x46, 0xbf, 0x56, 0xc8, 0x8b, 0x2b, + 0x0b, 0x35, 0x6e, 0x4b, 0x8a, 0x95, 0x6a, 0xf9, + 0xff, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x11, 0x00, + 0x01, 0x04, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x02, 0x04, 0x05, 0x12, 0x13, 0x14, 0x01, 0x06, + 0x11, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08, 0x01, + 0x02, 0x01, 0x01, 0x3f, 0x01, 0x85, 0x85, 0x8c, + 0xec, 0x31, 0x8d, 0xa6, 0x26, 0x1b, 0x6e, 0x48, + 0xbc, 0xcd, 0xb0, 0xe3, 0x33, 0x86, 0xf9, 0x35, + 0xdc, 0x15, 0xa8, 0xbe, 0x4d, 0x4a, 0x10, 0x22, + 0x80, 0x00, 0x91, 0xe8, 0x24, 0xda, 0xb6, 0x57, + 0x95, 0xf2, 0xa5, 0x73, 0xff, 0xc4, 0x00, 0x1e, + 0x10, 0x00, 0x01, 0x04, 0x03, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x01, 0x02, 0x04, 0x12, 0x05, 0x11, + 0x13, 0x14, 0x22, 0x23, 0xff, 0xda, 0x00, 0x08, + 0x01, 0x01, 0x00, 0x06, 0x3f, 0x02, 0x91, 0x89, + 0xc4, 0xc8, 0xf1, 0x60, 0x45, 0xe5, 0xc0, 0x1c, + 0x80, 0x7a, 0x77, 0x00, 0xe4, 0x97, 0xeb, 0x24, + 0x66, 0x33, 0xac, 0x63, 0x11, 0xfe, 0xe4, 0x76, + 0xad, 0x56, 0xe9, 0xa8, 0x88, 0x9f, 0xff, 0xc4, + 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x08, + 0x01, 0x01, 0x00, 0x01, 0x3f, 0x21, 0x68, 0x3f, + 0x92, 0x17, 0x81, 0x1f, 0x7f, 0xff, 0xda, 0x00, + 0x0c, 0x03, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x10, 0x5f, 0xff, 0xc4, 0x00, 0x14, + 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x03, + 0x01, 0x01, 0x3f, 0x10, 0x03, 0xeb, 0x11, 0xe4, + 0xa7, 0xe3, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14, + 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x02, + 0x01, 0x01, 0x3f, 0x10, 0x6b, 0xd3, 0x02, 0xdc, + 0x9a, 0xf4, 0xff, 0x00, 0xff, 0xc4, 0x00, 0x14, + 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, + 0x00, 0x01, 0x3f, 0x10, 0x62, 0x7b, 0x3a, 0xd0, + 0x3f, 0xeb, 0xff, 0x00, 0xff, 0xd9, + ]; + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + // We got some rounding errors when we compressed things, + // hence not perfect 0xff/0x00 values + let targetData = new Uint8Array([ + 0xfe, 0x00, 0x00, 255, 0xfe, 0x00, 0x00, 255, 0x00, 0xff, 0x01, 255, 0x00, 0xff, 0x01, 255, + 0xfe, 0x00, 0x00, 255, 0xfd, 0x00, 0x00, 255, 0x00, 0xff, 0x01, 255, 0x01, 0xff, 0x02, 255, + 0x00, 0xff, 0x01, 255, 0x00, 0xff, 0x01, 255, 0xfe, 0x00, 0x00, 255, 0xfe, 0x00, 0x00, 255, + 0x00, 0xff, 0x01, 255, 0x01, 0xff, 0x00, 255, 0xfe, 0x00, 0x00, 255, 0xfd, 0x01, 0x00, 255 + ]); + + display.onflush = () => { + expect(display).to.have.displayed(targetData); + done(); + }; + display.flush(); + }); +}); diff --git a/tests/test.tightpng.js b/tests/test.tightpng.js new file mode 100644 index 00000000..857cd84c --- /dev/null +++ b/tests/test.tightpng.js @@ -0,0 +1,131 @@ +const expect = chai.expect; + +import Websock from '../core/websock.js'; +import Display from '../core/display.js'; + +import TightPngDecoder from '../core/decoders/tightpng.js'; + +import FakeWebSocket from './fake.websocket.js'; + +function testDecodeRect(decoder, x, y, width, height, data, display, depth) { + let sock; + + sock = new Websock; + sock.open("ws://example.com"); + + sock.on('message', () => { + decoder.decodeRect(x, y, width, height, sock, display, depth); + }); + + sock._websocket._receiveData(new Uint8Array(data)); + + display.flip(); +} + +describe('TightPng Decoder', function () { + let decoder; + let display; + + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + beforeEach(function () { + decoder = new TightPngDecoder(); + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); + + it('should handle the TightPng encoding', function (done) { + let data = [ + // Control bytes + 0xa0, 0xb4, 0x04, + // PNG data + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x26, 0x93, 0x09, + 0x29, 0x00, 0x00, 0x01, 0x84, 0x69, 0x43, 0x43, + 0x50, 0x49, 0x43, 0x43, 0x20, 0x70, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x00, 0x00, 0x28, 0x91, + 0x7d, 0x91, 0x3d, 0x48, 0xc3, 0x40, 0x18, 0x86, + 0xdf, 0xa6, 0x6a, 0x45, 0x2a, 0x0e, 0x76, 0x10, + 0x71, 0x08, 0x52, 0x9d, 0x2c, 0x88, 0x8a, 0x38, + 0x6a, 0x15, 0x8a, 0x50, 0x21, 0xd4, 0x0a, 0xad, + 0x3a, 0x98, 0x5c, 0xfa, 0x07, 0x4d, 0x1a, 0x92, + 0x14, 0x17, 0x47, 0xc1, 0xb5, 0xe0, 0xe0, 0xcf, + 0x62, 0xd5, 0xc1, 0xc5, 0x59, 0x57, 0x07, 0x57, + 0x41, 0x10, 0xfc, 0x01, 0x71, 0x72, 0x74, 0x52, + 0x74, 0x91, 0x12, 0xbf, 0x4b, 0x0a, 0x2d, 0x62, + 0xbc, 0xe3, 0xb8, 0x87, 0xf7, 0xbe, 0xf7, 0xe5, + 0xee, 0x3b, 0x40, 0xa8, 0x97, 0x99, 0x66, 0x75, + 0x8c, 0x03, 0x9a, 0x6e, 0x9b, 0xa9, 0x44, 0x5c, + 0xcc, 0x64, 0x57, 0xc5, 0xd0, 0x2b, 0xba, 0x68, + 0x86, 0x31, 0x8c, 0x2e, 0x99, 0x59, 0xc6, 0x9c, + 0x24, 0x25, 0xe1, 0x3b, 0xbe, 0xee, 0x11, 0xe0, + 0xfb, 0x5d, 0x8c, 0x67, 0xf9, 0xd7, 0xfd, 0x39, + 0x7a, 0xd5, 0x9c, 0xc5, 0x80, 0x80, 0x48, 0x3c, + 0xcb, 0x0c, 0xd3, 0x26, 0xde, 0x20, 0x9e, 0xde, + 0xb4, 0x0d, 0xce, 0xfb, 0xc4, 0x11, 0x56, 0x94, + 0x55, 0xe2, 0x73, 0xe2, 0x31, 0x93, 0x2e, 0x48, + 0xfc, 0xc8, 0x75, 0xc5, 0xe3, 0x37, 0xce, 0x05, + 0x97, 0x05, 0x9e, 0x19, 0x31, 0xd3, 0xa9, 0x79, + 0xe2, 0x08, 0xb1, 0x58, 0x68, 0x63, 0xa5, 0x8d, + 0x59, 0xd1, 0xd4, 0x88, 0xa7, 0x88, 0xa3, 0xaa, + 0xa6, 0x53, 0xbe, 0x90, 0xf1, 0x58, 0xe5, 0xbc, + 0xc5, 0x59, 0x2b, 0x57, 0x59, 0xf3, 0x9e, 0xfc, + 0x85, 0xe1, 0x9c, 0xbe, 0xb2, 0xcc, 0x75, 0x5a, + 0x43, 0x48, 0x60, 0x11, 0x4b, 0x90, 0x20, 0x42, + 0x41, 0x15, 0x25, 0x94, 0x61, 0x23, 0x46, 0xbb, + 0x4e, 0x8a, 0x85, 0x14, 0x9d, 0xc7, 0x7d, 0xfc, + 0x83, 0xae, 0x5f, 0x22, 0x97, 0x42, 0xae, 0x12, + 0x18, 0x39, 0x16, 0x50, 0x81, 0x06, 0xd9, 0xf5, + 0x83, 0xff, 0xc1, 0xef, 0xde, 0x5a, 0xf9, 0xc9, + 0x09, 0x2f, 0x29, 0x1c, 0x07, 0x3a, 0x5f, 0x1c, + 0xe7, 0x63, 0x04, 0x08, 0xed, 0x02, 0x8d, 0x9a, + 0xe3, 0x7c, 0x1f, 0x3b, 0x4e, 0xe3, 0x04, 0x08, + 0x3e, 0x03, 0x57, 0x7a, 0xcb, 0x5f, 0xa9, 0x03, + 0x33, 0x9f, 0xa4, 0xd7, 0x5a, 0x5a, 0xf4, 0x08, + 0xe8, 0xdb, 0x06, 0x2e, 0xae, 0x5b, 0x9a, 0xb2, + 0x07, 0x5c, 0xee, 0x00, 0x03, 0x4f, 0x86, 0x6c, + 0xca, 0xae, 0x14, 0xa4, 0x25, 0xe4, 0xf3, 0xc0, + 0xfb, 0x19, 0x7d, 0x53, 0x16, 0xe8, 0xbf, 0x05, + 0x7a, 0xd6, 0xbc, 0xbe, 0x35, 0xcf, 0x71, 0xfa, + 0x00, 0xa4, 0xa9, 0x57, 0xc9, 0x1b, 0xe0, 0xe0, + 0x10, 0x18, 0x2d, 0x50, 0xf6, 0xba, 0xcf, 0xbb, + 0xbb, 0xdb, 0xfb, 0xf6, 0x6f, 0x4d, 0xb3, 0x7f, + 0x3f, 0x0a, 0x27, 0x72, 0x7d, 0x49, 0x29, 0x8b, + 0xbb, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, + 0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e, + 0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00, + 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xe4, + 0x06, 0x06, 0x0c, 0x23, 0x1d, 0x3f, 0x9f, 0xbb, + 0x94, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, + 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x00, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, + 0x4d, 0x50, 0x57, 0x81, 0x0e, 0x17, 0x00, 0x00, + 0x00, 0x1e, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, + 0x65, 0xc9, 0xb1, 0x0d, 0x00, 0x00, 0x08, 0x03, + 0x20, 0xea, 0xff, 0x3f, 0xd7, 0xd5, 0x44, 0x56, + 0x52, 0x90, 0xc2, 0x38, 0xa2, 0xd0, 0xbc, 0x59, + 0x8a, 0x9f, 0x04, 0x05, 0x6b, 0x38, 0x7b, 0xb2, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, + 0xae, 0x42, 0x60, 0x82, + ]; + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let targetData = new Uint8Array([ + 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0xff, 0x00, 0x00, 255 + ]); + + display.onflush = () => { + expect(display).to.have.displayed(targetData); + done(); + }; + display.flush(); + }); +});