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);
}
} 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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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);
});

View File

@ -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

View File

@ -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([

View File

@ -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