Merge branch 'add-jpeg-encoding' of https://github.com/pdlan/noVNC
This commit is contained in:
141
core/decoders/jpeg.js
Normal file
141
core/decoders/jpeg.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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._quantTables = [];
|
||||
this._huffmanTables = [];
|
||||
this._cachedQuantTables = [];
|
||||
this._cachedHuffmanTables = [];
|
||||
|
||||
this._jpegLength = 0;
|
||||
this._segments = [];
|
||||
}
|
||||
|
||||
decodeRect(x, y, width, height, sock, display, depth) {
|
||||
// A rect of JPEG encodings is simply a JPEG file
|
||||
if (!this._parseJPEG(sock.rQslice(0))) {
|
||||
return false;
|
||||
}
|
||||
const data = sock.rQshiftBytes(this._jpegLength);
|
||||
if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
|
||||
// If there are quantization tables and Huffman tables in the JPEG
|
||||
// image, we can directly render it.
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
return true;
|
||||
} else {
|
||||
// Otherwise we need to insert cached tables.
|
||||
const sofIndex = this._segments.findIndex(
|
||||
x => x[1] == 0xC0 || x[1] == 0xC2
|
||||
);
|
||||
if (sofIndex == -1) {
|
||||
throw new Error("Illegal JPEG image without SOF");
|
||||
}
|
||||
let segments = this._segments.slice(0, sofIndex);
|
||||
segments = segments.concat(this._quantTables.length ?
|
||||
this._quantTables :
|
||||
this._cachedQuantTables);
|
||||
segments.push(this._segments[sofIndex]);
|
||||
segments = segments.concat(this._huffmanTables.length ?
|
||||
this._huffmanTables :
|
||||
this._cachedHuffmanTables,
|
||||
this._segments.slice(sofIndex + 1));
|
||||
let length = 0;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
length += segments[i].length;
|
||||
}
|
||||
const data = new Uint8Array(length);
|
||||
length = 0;
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
data.set(segments[i], length);
|
||||
length += segments[i].length;
|
||||
}
|
||||
display.imageRect(x, y, width, height, "image/jpeg", data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_parseJPEG(buffer) {
|
||||
if (this._quantTables.length != 0) {
|
||||
this._cachedQuantTables = this._quantTables;
|
||||
}
|
||||
if (this._huffmanTables.length != 0) {
|
||||
this._cachedHuffmanTables = this._huffmanTables;
|
||||
}
|
||||
this._quantTables = [];
|
||||
this._huffmanTables = [];
|
||||
this._segments = [];
|
||||
let i = 0;
|
||||
let bufferLength = buffer.length;
|
||||
while (true) {
|
||||
let j = i;
|
||||
if (j + 2 > bufferLength) {
|
||||
return false;
|
||||
}
|
||||
if (buffer[j] != 0xFF) {
|
||||
throw new Error("Illegal JPEG marker received (byte: " +
|
||||
buffer[j] + ")");
|
||||
}
|
||||
const type = buffer[j+1];
|
||||
j += 2;
|
||||
if (type == 0xD9) {
|
||||
this._jpegLength = j;
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
return true;
|
||||
} else if (type == 0xDA) {
|
||||
// start of scan
|
||||
let hasFoundEndOfScan = false;
|
||||
for (let k = j + 3; k + 1 < bufferLength; k++) {
|
||||
if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
|
||||
!(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
|
||||
j = k;
|
||||
hasFoundEndOfScan = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasFoundEndOfScan) {
|
||||
return false;
|
||||
}
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
i = j;
|
||||
continue;
|
||||
} else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
|
||||
// No length after marker
|
||||
this._segments.push(buffer.slice(i, j));
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
if (j + 2 > bufferLength) {
|
||||
return false;
|
||||
}
|
||||
const length = (buffer[j] << 8) + buffer[j+1] - 2;
|
||||
if (length < 0) {
|
||||
throw new Error("Illegal JPEG length received (length: " +
|
||||
length + ")");
|
||||
}
|
||||
j += 2;
|
||||
if (j + length > bufferLength) {
|
||||
return false;
|
||||
}
|
||||
j += length;
|
||||
const segment = buffer.slice(i, j);
|
||||
if (type == 0xC4) {
|
||||
// Huffman tables
|
||||
this._huffmanTables.push(segment);
|
||||
} else if (type == 0xDB) {
|
||||
// Quantization tables
|
||||
this._quantTables.push(segment);
|
||||
}
|
||||
this._segments.push(segment);
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const encodings = {
|
||||
encodingTight: 7,
|
||||
encodingZRLE: 16,
|
||||
encodingTightPNG: -260,
|
||||
encodingJPEG: 21,
|
||||
|
||||
pseudoEncodingQualityLevel9: -23,
|
||||
pseudoEncodingQualityLevel0: -32,
|
||||
@@ -41,6 +42,7 @@ export function encodingName(num) {
|
||||
case encodings.encodingTight: return "Tight";
|
||||
case encodings.encodingZRLE: return "ZRLE";
|
||||
case encodings.encodingTightPNG: return "TightPNG";
|
||||
case encodings.encodingJPEG: return "JPEG";
|
||||
default: return "[unknown encoding " + num + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import HextileDecoder from "./decoders/hextile.js";
|
||||
import TightDecoder from "./decoders/tight.js";
|
||||
import TightPNGDecoder from "./decoders/tightpng.js";
|
||||
import ZRLEDecoder from "./decoders/zrle.js";
|
||||
import JPEGDecoder from "./decoders/jpeg.js";
|
||||
|
||||
// How many seconds to wait for a disconnect to finish
|
||||
const DISCONNECT_TIMEOUT = 3;
|
||||
@@ -220,6 +221,7 @@ export default class RFB extends EventTargetMixin {
|
||||
this._decoders[encodings.encodingTight] = new TightDecoder();
|
||||
this._decoders[encodings.encodingTightPNG] = new TightPNGDecoder();
|
||||
this._decoders[encodings.encodingZRLE] = new ZRLEDecoder();
|
||||
this._decoders[encodings.encodingJPEG] = new JPEGDecoder();
|
||||
|
||||
// NB: nothing that needs explicit teardown should be done
|
||||
// before this point, since this can throw an exception
|
||||
@@ -1775,6 +1777,7 @@ export default class RFB extends EventTargetMixin {
|
||||
encs.push(encodings.encodingTight);
|
||||
encs.push(encodings.encodingTightPNG);
|
||||
encs.push(encodings.encodingZRLE);
|
||||
encs.push(encodings.encodingJPEG);
|
||||
encs.push(encodings.encodingHextile);
|
||||
encs.push(encodings.encodingRRE);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user