mirror of
https://github.com/novnc/noVNC.git
synced 2026-06-03 02:49:40 +00:00
We don't know how long the caller will hang on to this data, so we need to be safe by default and assume it will kept indefinitely. That means we can't return a reference to the internal buffer, as that will get overwritten with future messages. We want to avoid unnecessary copying in performance critical code, though. So allow code to explicitly ask for a shared buffer, assuming they know the data needs to be consumed immediately.
147 lines
4.1 KiB
JavaScript
147 lines
4.1 KiB
JavaScript
/*
|
|
* noVNC: HTML5 VNC client
|
|
* Copyright (C) 2019 The noVNC Authors
|
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
|
*
|
|
* See README.md for usage and integration instructions.
|
|
*
|
|
*/
|
|
|
|
export default class JPEGDecoder {
|
|
constructor() {
|
|
// RealVNC will reuse the quantization tables
|
|
// and Huffman tables, so we need to cache them.
|
|
this._cachedQuantTables = [];
|
|
this._cachedHuffmanTables = [];
|
|
|
|
this._segments = [];
|
|
}
|
|
|
|
decodeRect(x, y, width, height, sock, display, depth) {
|
|
// A rect of JPEG encodings is simply a JPEG file
|
|
while (true) {
|
|
let segment = this._readSegment(sock);
|
|
if (segment === null) {
|
|
return false;
|
|
}
|
|
this._segments.push(segment);
|
|
// End of image?
|
|
if (segment[1] === 0xD9) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let huffmanTables = [];
|
|
let quantTables = [];
|
|
for (let segment of this._segments) {
|
|
let type = segment[1];
|
|
if (type === 0xC4) {
|
|
// Huffman tables
|
|
huffmanTables.push(segment);
|
|
} else if (type === 0xDB) {
|
|
// Quantization tables
|
|
quantTables.push(segment);
|
|
}
|
|
}
|
|
|
|
const sofIndex = this._segments.findIndex(
|
|
x => x[1] == 0xC0 || x[1] == 0xC2
|
|
);
|
|
if (sofIndex == -1) {
|
|
throw new Error("Illegal JPEG image without SOF");
|
|
}
|
|
|
|
if (quantTables.length === 0) {
|
|
this._segments.splice(sofIndex+1, 0,
|
|
...this._cachedQuantTables);
|
|
}
|
|
if (huffmanTables.length === 0) {
|
|
this._segments.splice(sofIndex+1, 0,
|
|
...this._cachedHuffmanTables);
|
|
}
|
|
|
|
let length = 0;
|
|
for (let segment of this._segments) {
|
|
length += segment.length;
|
|
}
|
|
|
|
let data = new Uint8Array(length);
|
|
length = 0;
|
|
for (let segment of this._segments) {
|
|
data.set(segment, length);
|
|
length += segment.length;
|
|
}
|
|
|
|
display.imageRect(x, y, width, height, "image/jpeg", data);
|
|
|
|
if (huffmanTables.length !== 0) {
|
|
this._cachedHuffmanTables = huffmanTables;
|
|
}
|
|
if (quantTables.length !== 0) {
|
|
this._cachedQuantTables = quantTables;
|
|
}
|
|
|
|
this._segments = [];
|
|
|
|
return true;
|
|
}
|
|
|
|
_readSegment(sock) {
|
|
if (sock.rQwait("JPEG", 2)) {
|
|
return null;
|
|
}
|
|
|
|
let marker = sock.rQshift8();
|
|
if (marker != 0xFF) {
|
|
throw new Error("Illegal JPEG marker received (byte: " +
|
|
marker + ")");
|
|
}
|
|
let type = sock.rQshift8();
|
|
if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
|
|
// No length after marker
|
|
return new Uint8Array([marker, type]);
|
|
}
|
|
|
|
if (sock.rQwait("JPEG", 2, 2)) {
|
|
return null;
|
|
}
|
|
|
|
let length = sock.rQshift16();
|
|
if (length < 2) {
|
|
throw new Error("Illegal JPEG length received (length: " +
|
|
length + ")");
|
|
}
|
|
|
|
if (sock.rQwait("JPEG", length-2, 4)) {
|
|
return null;
|
|
}
|
|
|
|
let extra = 0;
|
|
if (type === 0xDA) {
|
|
// start of scan
|
|
extra += 2;
|
|
while (true) {
|
|
if (sock.rQwait("JPEG", length-2+extra, 4)) {
|
|
return null;
|
|
}
|
|
let data = sock.rQpeekBytes(length-2+extra, false);
|
|
if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
|
|
!(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
|
|
extra -= 2;
|
|
break;
|
|
}
|
|
extra++;
|
|
}
|
|
}
|
|
|
|
let segment = new Uint8Array(2 + length + extra);
|
|
segment[0] = marker;
|
|
segment[1] = type;
|
|
segment[2] = length >> 8;
|
|
segment[3] = length;
|
|
segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
|
|
|
|
return segment;
|
|
}
|
|
}
|