correct the pixel shifts — the stream was cyan, not grey

SetPixelFormat asked for 32bpp big-endian with shifts 16/8/0, which is the
pixel 0x00RRGGBB and puts X,R,G,B on the wire. The Raw path copies four bytes
straight into ImageData and forces alpha opaque, so every channel landed one
place along and blue was dropped: window grey rendered cyan, the red LED
digits rendered green, blue "1" cells and the title bar rendered black.

Shifts are now 24/16/8, making the pixel 0xRRGGBB00 and the wire R,G,B,X —
which is what the file already claimed, in two comments, since it was written.

Verified against the live x11vnc socket rather than by reading: probing the
same framebuffer under both formats returns `00 c0 c0 c0` for window grey with
the old shifts and `c0 c0 c0 00` with the new ones, and `00 ff 00 00` vs
`ff 00 00 00` for the digits. x11vnc honours a shift of 24 without complaint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
2026-08-23 21:03:44 +00:00
co-authored by Claude Opus 5
parent b8a4f56c7c
commit 76b6a9c567
+13 -7
View File
@@ -16,9 +16,15 @@
* ── The pixel format ──
*
* The client DICTATES the format via SetPixelFormat rather than accepting the server's, so decoding is
* a memcpy into ImageData instead of a per-pixel shift-and-mask. We ask for 32bpp true colour with
* big-endian byte order, which puts the bytes on the wire as R,G,B,X — exactly the order a canvas
* ImageData buffer wants, so a Raw rectangle copies straight in.
* a memcpy into ImageData instead of a per-pixel shift-and-mask. We ask for 32bpp true colour,
* big-endian, with red/green/blue shifts of 24/16/8. That makes the pixel value 0xRRGGBB00, which
* big-endian puts on the wire as R,G,B,X — exactly the order a canvas ImageData buffer wants, so a Raw
* rectangle copies straight in.
*
* The shifts are the load-bearing part, and 16/8/0 is the tempting wrong answer: it is the familiar
* 0x00RRGGBB, and big-endian then sends X,R,G,B. Copied into ImageData that moves every channel one
* place along and drops blue — grey renders cyan, red renders green, blue renders black. The first
* alpha shipped that way, with this comment already claiming otherwise.
*/
export type RfbEvents = {
@@ -190,7 +196,7 @@ export class RfbClient {
return true;
}
/** 32bpp true colour, big-endian, shifts 16/8/0 — i.e. R,G,B,X on the wire, which is ImageData order. */
/** 32bpp true colour, big-endian, shifts 24/16/8 — i.e. R,G,B,X on the wire, which is ImageData order. */
private setPixelFormat(): void {
const message = new Uint8Array(20);
const view = new DataView(message.buffer);
@@ -202,9 +208,9 @@ export class RfbClient {
view.setUint16(8, 255); // red max
view.setUint16(10, 255); // green max
view.setUint16(12, 255); // blue max
message[14] = 16; // red shift
message[15] = 8; // green shift
message[16] = 0; // blue shift
message[14] = 24; // red shift
message[15] = 16; // green shift
message[16] = 8; // blue shift
this.send(message);
}