From 224f95f9974940c4a09f294a989c3d2f32182ebe Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 12 Oct 2018 17:07:16 +0200 Subject: [PATCH 1/7] Move tile handling to Hextile decoder It is only used there so no need for it to be in the general Display class. --- core/decoders/hextile.js | 55 ++++++++++++++++++++++++++++++++++++--- core/display.js | 56 ---------------------------------------- docs/API-internal.md | 3 --- tests/test.display.js | 35 ------------------------- 4 files changed, 52 insertions(+), 97 deletions(-) diff --git a/core/decoders/hextile.js b/core/decoders/hextile.js index 8dbe8092..f12e7f6a 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) { @@ -99,7 +100,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 +123,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 +135,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[2]; + const green = color[1]; + const blue = color[0]; + + const data = this._tileBuffer; + for (let i = 0; i < width * height * 4; i += 4) { + data[i] = blue; + data[i + 1] = green; + data[i + 2] = red; + 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._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] = blue; + data[p + 1] = green; + data[p + 2] = red; + 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/display.js b/core/display.js index 7b7f5363..378c6f1f 100644 --- a/core/display.js +++ b/core/display.js @@ -22,10 +22,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"); @@ -64,7 +60,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 ===== @@ -377,57 +372,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, diff --git a/docs/API-internal.md b/docs/API-internal.md index f1519422..1b5dfa90 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -104,9 +104,6 @@ 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 diff --git a/tests/test.display.js b/tests/test.display.js index a853778a..3492f1ec 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -309,41 +309,6 @@ 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++) { From 111225fa41f6828ea9e841f1f0822840c08c37cb Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 6 Jun 2020 14:24:44 +0200 Subject: [PATCH 2/7] Split decoder tests to separate files --- tests/test.copyrect.js | 58 ++++++++++ tests/test.hextile.js | 208 ++++++++++++++++++++++++++++++++++ tests/test.raw.js | 89 +++++++++++++++ tests/test.rfb.js | 248 ----------------------------------------- tests/test.rre.js | 84 ++++++++++++++ tests/test.tight.js | 41 +++++++ tests/test.tightpng.js | 41 +++++++ 7 files changed, 521 insertions(+), 248 deletions(-) create mode 100644 tests/test.copyrect.js create mode 100644 tests/test.hextile.js create mode 100644 tests/test.raw.js create mode 100644 tests/test.rre.js create mode 100644 tests/test.tight.js create mode 100644 tests/test.tightpng.js diff --git a/tests/test.copyrect.js b/tests/test.copyrect.js new file mode 100644 index 00000000..5a2b73b6 --- /dev/null +++ b/tests/test.copyrect.js @@ -0,0 +1,58 @@ +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 () { + 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 + ]); + + // seed some initial data to copy + display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetData.slice(0, 32)), 0); + + testDecodeRect(decoder, 0, 2, 2, 2, + [0x00, 0x02, 0x00, 0x00], + display, 24); + testDecodeRect(decoder, 2, 2, 2, 2, + [0x00, 0x00, 0x00, 0x00], + display, 24); + + expect(display).to.have.displayed(targetData); + }); +}); diff --git a/tests/test.hextile.js b/tests/test.hextile.js new file mode 100644 index 00000000..406b0628 --- /dev/null +++ b/tests/test.hextile.js @@ -0,0 +1,208 @@ +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, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + data.push(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0x00); + data.push(0x00); + data.push(0xff); + 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 + 2]); + data.push(targetData[i + 1]); + data.push(targetData[i]); + data.push(targetData[i + 3]); + } + + 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, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + + testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); + + let expected = []; + for (let i = 0; i < 16; i++) { + push32(expected, 0xff00ff); + } + + 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, 0xff00ff); // becomes 00ff00ff --> #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, 0xff00ff); // rect 1: solid + } + for (let i = 0; i < 16; i++) { + push32(expected, 0xff00ff); // 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, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + data.push(2); // 2 subrects + data.push(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0x00); + data.push(0x00); + data.push(0xff); + data.push(0); // x: 0, y: 0 + data.push(1 | (1 << 4)); // width: 2, height: 2 + data.push(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0x00); + data.push(0x00); + data.push(0xff); + 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(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0x00); + data.push(0x00); + 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..b42e4f39 --- /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, + [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, + 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], + display, 24); + testDecodeRect(decoder, 2, 0, 2, 2, + [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, + 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], + display, 24); + testDecodeRect(decoder, 0, 2, 4, 1, + [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, + 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], + display, 24); + testDecodeRect(decoder, 0, 3, 4, 1, + [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, + 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 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, + [0x03, 0x03, 0x03, 0x03], + display, 8); + testDecodeRect(decoder, 2, 0, 2, 2, + [0x0c, 0x0c, 0x0c, 0x0c], + display, 8); + testDecodeRect(decoder, 0, 2, 4, 1, + [0x0c, 0x0c, 0x03, 0x03], + display, 8); + testDecodeRect(decoder, 0, 3, 4, 1, + [0x0c, 0x0c, 0x03, 0x03], + 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 d5fa4733..95abdbf3 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -1643,28 +1643,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 = []; @@ -1733,22 +1711,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 @@ -1758,216 +1720,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..a24bec43 --- /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, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + data.push(0xff); // becomes ff0000ff --> #0000FF color + data.push(0x00); + data.push(0x00); + data.push(0xff); + push16(data, 0); // x: 0 + push16(data, 0); // y: 0 + push16(data, 2); // width: 2 + push16(data, 2); // height: 2 + data.push(0xff); // becomes ff0000ff --> #0000FF color + data.push(0x00); + data.push(0x00); + data.push(0xff); + 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..c6823dd5 --- /dev/null +++ b/tests/test.tight.js @@ -0,0 +1,41 @@ +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.skip('should handle the Tight encoding', function () { + // TODO(directxman12): test this + }); +}); diff --git a/tests/test.tightpng.js b/tests/test.tightpng.js new file mode 100644 index 00000000..46dbf5a4 --- /dev/null +++ b/tests/test.tightpng.js @@ -0,0 +1,41 @@ +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.skip('should handle the TightPng encoding', function () { + // TODO(directxman12): test this + }); +}); From 15cfa1356322216e32f149b309fc7277c6079389 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 6 Jun 2020 19:57:02 +0200 Subject: [PATCH 3/7] Add tests for the Tight decoders --- tests/test.tight.js | 291 ++++++++++++++++++++++++++++++++++++++++- tests/test.tightpng.js | 94 ++++++++++++- 2 files changed, 381 insertions(+), 4 deletions(-) diff --git a/tests/test.tight.js b/tests/test.tight.js index c6823dd5..10ef971c 100644 --- a/tests/test.tight.js +++ b/tests/test.tight.js @@ -35,7 +35,294 @@ describe('Tight Decoder', function () { display.resize(4, 4); }); - it.skip('should handle the Tight encoding', function () { - // TODO(directxman12): test this + 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 index 46dbf5a4..857cd84c 100644 --- a/tests/test.tightpng.js +++ b/tests/test.tightpng.js @@ -35,7 +35,97 @@ describe('TightPng Decoder', function () { display.resize(4, 4); }); - it.skip('should handle the TightPng encoding', function () { - // TODO(directxman12): test this + 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(); }); }); From 18a68dfac19f48fae0608154d2a930c08ae596a2 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 8 Jun 2020 07:40:56 +0200 Subject: [PATCH 4/7] Test correct handling of alpha The forth byte of a pixel is undefined in most encodings, so make sure the decoders don't leak that through as an alpha channel. --- tests/test.hextile.js | 29 +++++++++++++++-------------- tests/test.rre.js | 10 +++++----- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/test.hextile.js b/tests/test.hextile.js index 406b0628..7c97527b 100644 --- a/tests/test.hextile.js +++ b/tests/test.hextile.js @@ -45,11 +45,11 @@ describe('Hextile Decoder', function () { 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, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - data.push(0xff); // becomes ff0000ff --> #0000FF fg color + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + data.push(0xff); // becomes ff000000 --> #0000FF fg color + data.push(0x00); data.push(0x00); data.push(0x00); - data.push(0xff); data.push(2); // 2 subrects data.push(0); // x: 0, y: 0 data.push(1 | (1 << 4)); // width: 2, height: 2 @@ -82,7 +82,8 @@ describe('Hextile Decoder', function () { data.push(targetData[i + 2]); data.push(targetData[i + 1]); data.push(targetData[i]); - data.push(targetData[i + 3]); + // Last byte zero to test correct alpha handling + data.push(0); } testDecodeRect(decoder, 0, 0, 4, 4, data, display, 24); @@ -93,13 +94,13 @@ describe('Hextile Decoder', function () { it('should handle a tile with only bg specified (solid bg)', function () { let data = []; data.push(0x02); - push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + 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, 0xff00ff); + push32(expected, 0x00ff00ff); } expect(display).to.have.displayed(new Uint8Array(expected)); @@ -113,7 +114,7 @@ describe('Hextile Decoder', function () { // send a bg frame data.push(0x02); - push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color // send an empty frame data.push(0x00); @@ -122,10 +123,10 @@ describe('Hextile Decoder', function () { let expected = []; for (let i = 0; i < 16; i++) { - push32(expected, 0xff00ff); // rect 1: solid + push32(expected, 0x00ff00ff); // rect 1: solid } for (let i = 0; i < 16; i++) { - push32(expected, 0xff00ff); // rect 2: same bkground color + push32(expected, 0x00ff00ff); // rect 2: same bkground color } expect(display).to.have.displayed(new Uint8Array(expected)); @@ -134,18 +135,18 @@ describe('Hextile Decoder', function () { it('should handle a tile with bg and coloured subrects', function () { let data = []; data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects - push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color data.push(2); // 2 subrects - data.push(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0xff); // becomes ff000000 --> #0000FF fg color + data.push(0x00); data.push(0x00); data.push(0x00); - data.push(0xff); data.push(0); // x: 0, y: 0 data.push(1 | (1 << 4)); // width: 2, height: 2 - data.push(0xff); // becomes ff0000ff --> #0000FF fg color + data.push(0xff); // becomes ff000000 --> #0000FF fg color + data.push(0x00); data.push(0x00); data.push(0x00); - data.push(0xff); data.push(2 | (2 << 4)); // x: 2, y: 2 data.push(1 | (1 << 4)); // width: 2, height: 2 diff --git a/tests/test.rre.js b/tests/test.rre.js index a24bec43..78b5eaae 100644 --- a/tests/test.rre.js +++ b/tests/test.rre.js @@ -52,19 +52,19 @@ describe('RRE Decoder', function () { it('should handle the RRE encoding', function () { let data = []; push32(data, 2); // 2 subrects - push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - data.push(0xff); // becomes ff0000ff --> #0000FF color + push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color + data.push(0xff); // becomes ff000000 --> #0000FF color + data.push(0x00); data.push(0x00); data.push(0x00); - data.push(0xff); push16(data, 0); // x: 0 push16(data, 0); // y: 0 push16(data, 2); // width: 2 push16(data, 2); // height: 2 - data.push(0xff); // becomes ff0000ff --> #0000FF color + data.push(0xff); // becomes ff000000 --> #0000FF color + data.push(0x00); data.push(0x00); data.push(0x00); - data.push(0xff); push16(data, 2); // x: 2 push16(data, 2); // y: 2 push16(data, 2); // width: 2 From 34f52a8f41ec677463dc331ae31b89db32883817 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 7 Jun 2020 13:36:15 +0200 Subject: [PATCH 5/7] Fix bad BasicCompression check in Tight decoder --- core/decoders/tight.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/decoders/tight.js b/core/decoders/tight.js index b207419e..81335ab6 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 { From f5b5767c980da9fe5e2cb388eaa55c4368abd780 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 7 Jun 2020 13:44:20 +0200 Subject: [PATCH 6/7] Standardise on a single blit function Keep everything simpler by always blitting in the same pixel format. It's up to the decoders to convert if they need to. --- core/decoders/tight.js | 26 +++++++++------ core/display.js | 73 ------------------------------------------ core/util/browser.js | 9 ------ docs/API-internal.md | 2 -- tests/test.copyrect.js | 18 ++++++----- tests/test.display.js | 19 ----------- 6 files changed, 27 insertions(+), 120 deletions(-) diff --git a/core/decoders/tight.js b/core/decoders/tight.js index 81335ab6..e61f1329 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -165,7 +165,15 @@ export default class TightDecoder { this._zlibs[streamId].setInput(null); } - display.blitRgbImage(x, y, width, height, data, 0, false); + let bgrx = new Uint8Array(width * height * 4); + for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) { + bgrx[i] = data[j + 2]; + bgrx[i + 1] = data[j + 1]; + bgrx[i + 2] = data[j]; + bgrx[i + 3] = 255; // Alpha + } + + display.blitImage(x, y, width, height, bgrx, 0, false); return true; } @@ -237,9 +245,9 @@ 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 + 2]; dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; + dest[dp + 2] = palette[sp]; 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 + 2]; dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp + 2]; + dest[dp + 2] = palette[sp]; 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 + 2]; dest[i + 1] = palette[sp + 1]; - dest[i + 2] = palette[sp + 2]; + dest[i + 2] = palette[sp]; 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 378c6f1f..f06aa37c 100644 --- a/core/display.js +++ b/core/display.js @@ -8,7 +8,6 @@ import * as Log from './util/logging.js'; import Base64 from "./base64.js"; -import { supportsImageMetadata } from './util/browser.js'; export default class Display { constructor(target) { @@ -392,46 +391,6 @@ export default class Display { } } - 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); - } - } - drawImage(img, x, y) { this._drawCtx.drawImage(img, x, y); this._damage(x, y, img.width, img.height); @@ -487,19 +446,6 @@ export default class Display { } } - _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; @@ -513,19 +459,6 @@ export default class Display { 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) { @@ -559,12 +492,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/util/browser.js b/core/util/browser.js index 15548014..a18d5a46 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -45,15 +45,6 @@ try { export const supportsCursorURIs = _supportsCursorURIs; -let _supportsImageMetadata = false; -try { - new ImageData(new Uint8ClampedArray(4), 1, 1); - _supportsImageMetadata = true; -} catch (ex) { - // ignore failure -} -export const supportsImageMetadata = _supportsImageMetadata; - let _hasScrollbarGutter = true; try { // Create invisible container diff --git a/docs/API-internal.md b/docs/API-internal.md index 1b5dfa90..4eec6e33 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -105,8 +105,6 @@ None | 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 | 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 index 5a2b73b6..fcf42195 100644 --- a/tests/test.copyrect.js +++ b/tests/test.copyrect.js @@ -36,15 +36,10 @@ describe('CopyRect Decoder', function () { }); it('should handle the CopyRect encoding', 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 - ]); - // seed some initial data to copy - display.blitRgbxImage(0, 0, 4, 2, new Uint8Array(targetData.slice(0, 32)), 0); + display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]); + display.fillRect(0, 0, 2, 2, [ 0xff, 0x00, 0x00 ]); + display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); testDecodeRect(decoder, 0, 2, 2, 2, [0x00, 0x02, 0x00, 0x00], @@ -53,6 +48,13 @@ describe('CopyRect Decoder', function () { [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 3492f1ec..88c76074 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -322,18 +322,6 @@ describe('Display/Canvas Helper', function () { 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); - display.flip(); - expect(display).to.have.displayed(checkedData); - }); - it('should support drawing an image object via #drawImage', function () { const img = makeImageCanvas(checkedData); display.drawImage(img, 0, 0); @@ -416,13 +404,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 }); From 6a19390baafedbc2729597fe244010a2aaadc068 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 7 Jun 2020 14:22:07 +0200 Subject: [PATCH 7/7] Switch to RGBx pixel format This is what the browser wants so it avoids having to spend time converting everything. Unfortunately it usually means the server instead needs to convert it for us, but we assume it has more power than we do. --- core/decoders/hextile.js | 21 +++++++++++++-------- core/decoders/raw.js | 10 ++++++++-- core/decoders/tight.js | 26 +++++++++++++------------- core/display.js | 30 +++++++++++++++--------------- core/rfb.js | 4 ++-- core/util/browser.js | 9 +++++++++ tests/test.copyrect.js | 2 +- tests/test.display.js | 21 +++++++-------------- tests/test.hextile.js | 20 ++++++++++---------- tests/test.raw.js | 22 +++++++++++----------- tests/test.rre.js | 8 ++++---- 11 files changed, 93 insertions(+), 80 deletions(-) diff --git a/core/decoders/hextile.js b/core/decoders/hextile.js index f12e7f6a..ac21eff0 100644 --- a/core/decoders/hextile.js +++ b/core/decoders/hextile.js @@ -88,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 { @@ -143,24 +148,24 @@ export default class HextileDecoder { this._tileW = width; this._tileH = height; - const red = color[2]; + const red = color[0]; const green = color[1]; - const blue = color[0]; + const blue = color[2]; const data = this._tileBuffer; for (let i = 0; i < width * height * 4; i += 4) { - data[i] = blue; + data[i] = red; data[i + 1] = green; - data[i + 2] = red; + 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 red = color[0]; const green = color[1]; - const blue = color[0]; + const blue = color[2]; const xend = x + w; const yend = y + h; @@ -169,9 +174,9 @@ export default class HextileDecoder { for (let j = y; j < yend; j++) { for (let i = x; i < xend; i++) { const p = (i + (j * width)) * 4; - data[p] = blue; + data[p] = red; data[p + 1] = green; - data[p + 2] = red; + data[p + 2] = blue; data[p + 3] = 255; } } 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 e61f1329..8bc97d10 100644 --- a/core/decoders/tight.js +++ b/core/decoders/tight.js @@ -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,15 +165,15 @@ export default class TightDecoder { this._zlibs[streamId].setInput(null); } - let bgrx = new Uint8Array(width * height * 4); + let rgbx = new Uint8Array(width * height * 4); for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) { - bgrx[i] = data[j + 2]; - bgrx[i + 1] = data[j + 1]; - bgrx[i + 2] = data[j]; - bgrx[i + 3] = 255; // Alpha + 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, bgrx, 0, false); + display.blitImage(x, y, width, height, rgbx, 0, false); return true; } @@ -245,9 +245,9 @@ 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 + 2]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp]; + dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; } } @@ -255,9 +255,9 @@ 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 + 2]; + dest[dp] = palette[sp]; dest[dp + 1] = palette[sp + 1]; - dest[dp + 2] = palette[sp]; + dest[dp + 2] = palette[sp + 2]; dest[dp + 3] = 255; } } @@ -271,9 +271,9 @@ 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 + 2]; + dest[i] = palette[sp]; dest[i + 1] = palette[sp + 1]; - dest[i + 2] = palette[sp]; + dest[i + 2] = palette[sp + 2]; dest[i + 3] = 255; } diff --git a/core/display.js b/core/display.js index f06aa37c..494b7a37 100644 --- a/core/display.js +++ b/core/display.js @@ -8,6 +8,7 @@ import * as Log from './util/logging.js'; import Base64 from "./base64.js"; +import { supportsImageMetadata } from './util/browser.js'; export default class Display { constructor(target) { @@ -387,7 +388,19 @@ export default class Display { 'height': height, }); } else { - this._bgrxImageData(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); } } @@ -439,26 +452,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; } } - _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); - } - _renderQPush(action) { this._renderQ.push(action); if (this._renderQ.length === 1) { diff --git a/core/rfb.js b/core/rfb.js index ca50779e..40611966 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2584,9 +2584,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/core/util/browser.js b/core/util/browser.js index a18d5a46..15548014 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -45,6 +45,15 @@ try { export const supportsCursorURIs = _supportsCursorURIs; +let _supportsImageMetadata = false; +try { + new ImageData(new Uint8ClampedArray(4), 1, 1); + _supportsImageMetadata = true; +} catch (ex) { + // ignore failure +} +export const supportsImageMetadata = _supportsImageMetadata; + let _hasScrollbarGutter = true; try { // Create invisible container diff --git a/tests/test.copyrect.js b/tests/test.copyrect.js index fcf42195..e2277cb5 100644 --- a/tests/test.copyrect.js +++ b/tests/test.copyrect.js @@ -38,7 +38,7 @@ describe('CopyRect Decoder', function () { 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, [ 0xff, 0x00, 0x00 ]); + display.fillRect(0, 0, 2, 2, [ 0x00, 0x00, 0xff ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]); testDecodeRect(decoder, 0, 2, 2, 2, diff --git a/tests/test.display.js b/tests/test.display.js index 88c76074..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,15 +309,8 @@ describe('Display/Canvas Helper', function () { display.flush(); }); - 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); + 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); }); diff --git a/tests/test.hextile.js b/tests/test.hextile.js index 7c97527b..052c82e1 100644 --- a/tests/test.hextile.js +++ b/tests/test.hextile.js @@ -46,9 +46,9 @@ describe('Hextile Decoder', function () { let data = []; data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color - data.push(0xff); // becomes ff000000 --> #0000FF fg color - data.push(0x00); + 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 @@ -79,9 +79,9 @@ describe('Hextile Decoder', function () { let data = []; data.push(0x01); // raw for (let i = 0; i < targetData.length; i += 4) { - data.push(targetData[i + 2]); - data.push(targetData[i + 1]); 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); } @@ -137,15 +137,15 @@ describe('Hextile Decoder', function () { data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color data.push(2); // 2 subrects - data.push(0xff); // becomes ff000000 --> #0000FF fg color - data.push(0x00); + 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(0xff); // becomes ff000000 --> #0000FF fg color - data.push(0x00); + 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 @@ -168,10 +168,10 @@ describe('Hextile Decoder', function () { let data = []; data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color - data.push(0xff); // becomes ff0000ff --> #0000FF fg color - data.push(0x00); + 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 diff --git a/tests/test.raw.js b/tests/test.raw.js index b42e4f39..95a33af9 100644 --- a/tests/test.raw.js +++ b/tests/test.raw.js @@ -37,20 +37,20 @@ describe('Raw Decoder', function () { it('should handle the Raw encoding', function () { testDecodeRect(decoder, 0, 0, 2, 2, - [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, - 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], + [0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0, + 0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0], display, 24); testDecodeRect(decoder, 2, 0, 2, 2, - [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, - 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], + [0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0, + 0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0], display, 24); testDecodeRect(decoder, 0, 2, 4, 1, - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, - 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], + [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, + 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], display, 24); testDecodeRect(decoder, 0, 3, 4, 1, - [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, - 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], + [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0, + 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0], display, 24); let targetData = new Uint8Array([ @@ -65,16 +65,16 @@ describe('Raw Decoder', function () { it('should handle the Raw encoding in low colour mode', function () { testDecodeRect(decoder, 0, 0, 2, 2, - [0x03, 0x03, 0x03, 0x03], + [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, 0x03, 0x03], + [0x0c, 0x0c, 0x30, 0x30], display, 8); testDecodeRect(decoder, 0, 3, 4, 1, - [0x0c, 0x0c, 0x03, 0x03], + [0x0c, 0x0c, 0x30, 0x30], display, 8); let targetData = new Uint8Array([ diff --git a/tests/test.rre.js b/tests/test.rre.js index 78b5eaae..fff1c945 100644 --- a/tests/test.rre.js +++ b/tests/test.rre.js @@ -53,17 +53,17 @@ describe('RRE Decoder', function () { let data = []; push32(data, 2); // 2 subrects push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color - data.push(0xff); // becomes ff000000 --> #0000FF color - data.push(0x00); + 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(0xff); // becomes ff000000 --> #0000FF color - data.push(0x00); + 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