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.
This commit is contained in:
Pierre Ossman 2020-06-07 14:22:07 +02:00
parent f5b5767c98
commit 6a19390baa
11 changed files with 93 additions and 80 deletions

View File

@ -88,6 +88,11 @@ export default class HextileDecoder {
display.fillRect(tx, ty, tw, th, this._background); display.fillRect(tx, ty, tw, th, this._background);
} }
} else if (subencoding & 0x01) { // Raw } 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); display.blitImage(tx, ty, tw, th, rQ, rQi);
rQi += bytes - 1; rQi += bytes - 1;
} else { } else {
@ -143,24 +148,24 @@ export default class HextileDecoder {
this._tileW = width; this._tileW = width;
this._tileH = height; this._tileH = height;
const red = color[2]; const red = color[0];
const green = color[1]; const green = color[1];
const blue = color[0]; const blue = color[2];
const data = this._tileBuffer; const data = this._tileBuffer;
for (let i = 0; i < width * height * 4; i += 4) { for (let i = 0; i < width * height * 4; i += 4) {
data[i] = blue; data[i] = red;
data[i + 1] = green; data[i + 1] = green;
data[i + 2] = red; data[i + 2] = blue;
data[i + 3] = 255; data[i + 3] = 255;
} }
} }
// update sub-rectangle of the current tile // update sub-rectangle of the current tile
_subTile(x, y, w, h, color) { _subTile(x, y, w, h, color) {
const red = color[2]; const red = color[0];
const green = color[1]; const green = color[1];
const blue = color[0]; const blue = color[2];
const xend = x + w; const xend = x + w;
const yend = y + h; const yend = y + h;
@ -169,9 +174,9 @@ export default class HextileDecoder {
for (let j = y; j < yend; j++) { for (let j = y; j < yend; j++) {
for (let i = x; i < xend; i++) { for (let i = x; i < xend; i++) {
const p = (i + (j * width)) * 4; const p = (i + (j * width)) * 4;
data[p] = blue; data[p] = red;
data[p + 1] = green; data[p + 1] = green;
data[p + 2] = red; data[p + 2] = blue;
data[p + 3] = 255; data[p + 3] = 255;
} }
} }

View File

@ -27,23 +27,29 @@ export default class RawDecoder {
const curY = y + (height - this._lines); const curY = y + (height - this._lines);
const currHeight = Math.min(this._lines, const currHeight = Math.min(this._lines,
Math.floor(sock.rQlen / bytesPerLine)); Math.floor(sock.rQlen / bytesPerLine));
const pixels = width * currHeight;
let data = sock.rQ; let data = sock.rQ;
let index = sock.rQi; let index = sock.rQi;
// Convert data if needed // Convert data if needed
if (depth == 8) { if (depth == 8) {
const pixels = width * currHeight;
const newdata = new Uint8Array(pixels * 4); const newdata = new Uint8Array(pixels * 4);
for (let i = 0; i < pixels; i++) { for (let i = 0; i < pixels; i++) {
newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3; 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 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
newdata[i * 4 + 2] = ((data[index + i] >> 4) & 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; data = newdata;
index = 0; 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); display.blitImage(x, curY, width, currHeight, data, index);
sock.rQskipBytes(currHeight * bytesPerLine); sock.rQskipBytes(currHeight * bytesPerLine);
this._lines -= currHeight; this._lines -= currHeight;

View File

@ -80,7 +80,7 @@ export default class TightDecoder {
const rQ = sock.rQ; const rQ = sock.rQ;
display.fillRect(x, y, width, height, 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); sock.rQskipBytes(3);
return true; return true;
@ -165,15 +165,15 @@ export default class TightDecoder {
this._zlibs[streamId].setInput(null); 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) { for (let i = 0, j = 0; i < width * height * 4; i += 4, j += 3) {
bgrx[i] = data[j + 2]; rgbx[i] = data[j];
bgrx[i + 1] = data[j + 1]; rgbx[i + 1] = data[j + 1];
bgrx[i + 2] = data[j]; rgbx[i + 2] = data[j + 2];
bgrx[i + 3] = 255; // Alpha 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; return true;
} }
@ -245,9 +245,9 @@ export default class TightDecoder {
for (let b = 7; b >= 0; b--) { for (let b = 7; b >= 0; b--) {
dp = (y * width + x * 8 + 7 - b) * 4; dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3; 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 + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp]; dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255; dest[dp + 3] = 255;
} }
} }
@ -255,9 +255,9 @@ export default class TightDecoder {
for (let b = 7; b >= 8 - width % 8; b--) { for (let b = 7; b >= 8 - width % 8; b--) {
dp = (y * width + x * 8 + 7 - b) * 4; dp = (y * width + x * 8 + 7 - b) * 4;
sp = (data[y * w + x] >> b & 1) * 3; 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 + 1] = palette[sp + 1];
dest[dp + 2] = palette[sp]; dest[dp + 2] = palette[sp + 2];
dest[dp + 3] = 255; dest[dp + 3] = 255;
} }
} }
@ -271,9 +271,9 @@ export default class TightDecoder {
const total = width * height * 4; const total = width * height * 4;
for (let i = 0, j = 0; i < total; i += 4, j++) { for (let i = 0, j = 0; i < total; i += 4, j++) {
const sp = data[j] * 3; const sp = data[j] * 3;
dest[i] = palette[sp + 2]; dest[i] = palette[sp];
dest[i + 1] = palette[sp + 1]; dest[i + 1] = palette[sp + 1];
dest[i + 2] = palette[sp]; dest[i + 2] = palette[sp + 2];
dest[i + 3] = 255; dest[i + 3] = 255;
} }

View File

@ -8,6 +8,7 @@
import * as Log from './util/logging.js'; import * as Log from './util/logging.js';
import Base64 from "./base64.js"; import Base64 from "./base64.js";
import { supportsImageMetadata } from './util/browser.js';
export default class Display { export default class Display {
constructor(target) { constructor(target) {
@ -387,7 +388,19 @@ export default class Display {
'height': height, 'height': height,
}); });
} else { } 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) { _setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')'; const newStyle = 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
if (newStyle !== this._prevDrawStyle) { if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle; this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = 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) { _renderQPush(action) {
this._renderQ.push(action); this._renderQ.push(action);
if (this._renderQ.length === 1) { if (this._renderQ.length === 1) {

View File

@ -2584,9 +2584,9 @@ RFB.messages = {
buff[offset + 12] = 0; // blue-max buff[offset + 12] = 0; // blue-max
buff[offset + 13] = (1 << bits) - 1; // 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 + 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 + 17] = 0; // padding
buff[offset + 18] = 0; // padding buff[offset + 18] = 0; // padding

View File

@ -45,6 +45,15 @@ try {
export const supportsCursorURIs = _supportsCursorURIs; 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; let _hasScrollbarGutter = true;
try { try {
// Create invisible container // Create invisible container

View File

@ -38,7 +38,7 @@ describe('CopyRect Decoder', function () {
it('should handle the CopyRect encoding', function () { it('should handle the CopyRect encoding', function () {
// seed some initial data to copy // seed some initial data to copy
display.fillRect(0, 0, 4, 4, [ 0x11, 0x22, 0x33 ]); 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 ]); display.fillRect(2, 0, 2, 2, [ 0x00, 0xff, 0x00 ]);
testDecodeRect(decoder, 0, 2, 2, 2, testDecodeRect(decoder, 0, 2, 2, 2,

View File

@ -128,7 +128,7 @@ describe('Display/Canvas Helper', function () {
}); });
it('should keep the framebuffer data', 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.resize(2, 2);
display.flip(); display.flip();
const expected = []; const expected = [];
@ -271,7 +271,7 @@ describe('Display/Canvas Helper', function () {
}); });
it('should not draw directly on the target canvas', 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.flip();
display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]);
const expected = []; const expected = [];
@ -285,15 +285,15 @@ describe('Display/Canvas Helper', function () {
it('should support filling a rectangle with particular color via #fillRect', 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, 4, 4, [0, 0xff, 0]);
display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); display.fillRect(0, 0, 2, 2, [0, 0, 0xff]);
display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); display.fillRect(2, 2, 2, 2, [0, 0, 0xff]);
display.flip(); display.flip();
expect(display).to.have.displayed(checkedData); expect(display).to.have.displayed(checkedData);
}); });
it('should support copying an portion of the canvas via #copyImage', function () { 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, 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.copyImage(0, 0, 2, 2, 2, 2);
display.flip(); display.flip();
expect(display).to.have.displayed(checkedData); expect(display).to.have.displayed(checkedData);
@ -309,15 +309,8 @@ describe('Display/Canvas Helper', function () {
display.flush(); display.flush();
}); });
it('should support drawing BGRX blit images with true color via #blitImage', function () { it('should support blit images with true color via #blitImage', function () {
const data = []; display.blitImage(0, 0, 4, 4, checkedData, 0);
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(); display.flip();
expect(display).to.have.displayed(checkedData); expect(display).to.have.displayed(checkedData);
}); });

View File

@ -46,9 +46,9 @@ describe('Hextile Decoder', function () {
let data = []; let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0xff); // becomes ff000000 --> #0000FF fg color data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff);
data.push(0x00); data.push(0x00);
data.push(2); // 2 subrects data.push(2); // 2 subrects
data.push(0); // x: 0, y: 0 data.push(0); // x: 0, y: 0
@ -79,9 +79,9 @@ describe('Hextile Decoder', function () {
let data = []; let data = [];
data.push(0x01); // raw data.push(0x01); // raw
for (let i = 0; i < targetData.length; i += 4) { 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]);
data.push(targetData[i + 1]);
data.push(targetData[i + 2]);
// Last byte zero to test correct alpha handling // Last byte zero to test correct alpha handling
data.push(0); data.push(0);
} }
@ -137,15 +137,15 @@ describe('Hextile Decoder', function () {
data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects data.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(2); // 2 subrects data.push(2); // 2 subrects
data.push(0xff); // becomes ff000000 --> #0000FF fg color data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff);
data.push(0x00); data.push(0x00);
data.push(0); // x: 0, y: 0 data.push(0); // x: 0, y: 0
data.push(1 | (1 << 4)); // width: 2, height: 2 data.push(1 | (1 << 4)); // width: 2, height: 2
data.push(0xff); // becomes ff000000 --> #0000FF fg color data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff);
data.push(0x00); data.push(0x00);
data.push(2 | (2 << 4)); // x: 2, y: 2 data.push(2 | (2 << 4)); // x: 2, y: 2
data.push(1 | (1 << 4)); // width: 2, height: 2 data.push(1 | (1 << 4)); // width: 2, height: 2
@ -168,10 +168,10 @@ describe('Hextile Decoder', function () {
let data = []; let data = [];
data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects data.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects
push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color push32(data, 0xff00ff); // becomes 00ff00ff --> #00FF00 bg color
data.push(0xff); // becomes ff0000ff --> #0000FF fg color data.push(0x00); // becomes 0000ffff --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff); data.push(0xff);
data.push(0xff);
data.push(8); // 8 subrects data.push(8); // 8 subrects
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
data.push((0 << 4) | (i * 4)); // x: 0, y: i*4 data.push((0 << 4) | (i * 4)); // x: 0, y: i*4

View File

@ -37,20 +37,20 @@ describe('Raw Decoder', function () {
it('should handle the Raw encoding', function () { it('should handle the Raw encoding', function () {
testDecodeRect(decoder, 0, 0, 2, 2, testDecodeRect(decoder, 0, 0, 2, 2,
[0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, [0xff, 0x00, 0x00, 0, 0x00, 0xff, 0x00, 0,
0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], 0x00, 0xff, 0x00, 0, 0xff, 0x00, 0x00, 0],
display, 24); display, 24);
testDecodeRect(decoder, 2, 0, 2, 2, testDecodeRect(decoder, 2, 0, 2, 2,
[0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, [0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0,
0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], 0x00, 0x00, 0xff, 0, 0x00, 0x00, 0xff, 0],
display, 24); display, 24);
testDecodeRect(decoder, 0, 2, 4, 1, testDecodeRect(decoder, 0, 2, 4, 1,
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24); display, 24);
testDecodeRect(decoder, 0, 3, 4, 1, testDecodeRect(decoder, 0, 3, 4, 1,
[0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, [0xee, 0x00, 0xff, 0, 0x00, 0xee, 0xff, 0,
0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], 0xaa, 0xee, 0xff, 0, 0xab, 0xee, 0xff, 0],
display, 24); display, 24);
let targetData = new Uint8Array([ let targetData = new Uint8Array([
@ -65,16 +65,16 @@ describe('Raw Decoder', function () {
it('should handle the Raw encoding in low colour mode', function () { it('should handle the Raw encoding in low colour mode', function () {
testDecodeRect(decoder, 0, 0, 2, 2, testDecodeRect(decoder, 0, 0, 2, 2,
[0x03, 0x03, 0x03, 0x03], [0x30, 0x30, 0x30, 0x30],
display, 8); display, 8);
testDecodeRect(decoder, 2, 0, 2, 2, testDecodeRect(decoder, 2, 0, 2, 2,
[0x0c, 0x0c, 0x0c, 0x0c], [0x0c, 0x0c, 0x0c, 0x0c],
display, 8); display, 8);
testDecodeRect(decoder, 0, 2, 4, 1, testDecodeRect(decoder, 0, 2, 4, 1,
[0x0c, 0x0c, 0x03, 0x03], [0x0c, 0x0c, 0x30, 0x30],
display, 8); display, 8);
testDecodeRect(decoder, 0, 3, 4, 1, testDecodeRect(decoder, 0, 3, 4, 1,
[0x0c, 0x0c, 0x03, 0x03], [0x0c, 0x0c, 0x30, 0x30],
display, 8); display, 8);
let targetData = new Uint8Array([ let targetData = new Uint8Array([

View File

@ -53,17 +53,17 @@ describe('RRE Decoder', function () {
let data = []; let data = [];
push32(data, 2); // 2 subrects push32(data, 2); // 2 subrects
push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color push32(data, 0x00ff0000); // becomes 00ff0000 --> #00FF00 bg color
data.push(0xff); // becomes ff000000 --> #0000FF color data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff);
data.push(0x00); data.push(0x00);
push16(data, 0); // x: 0 push16(data, 0); // x: 0
push16(data, 0); // y: 0 push16(data, 0); // y: 0
push16(data, 2); // width: 2 push16(data, 2); // width: 2
push16(data, 2); // height: 2 push16(data, 2); // height: 2
data.push(0xff); // becomes ff000000 --> #0000FF color data.push(0x00); // becomes 0000ff00 --> #0000FF fg color
data.push(0x00);
data.push(0x00); data.push(0x00);
data.push(0xff);
data.push(0x00); data.push(0x00);
push16(data, 2); // x: 2 push16(data, 2); // x: 2
push16(data, 2); // y: 2 push16(data, 2); // y: 2