diff --git a/core/decoders/jpeg.js b/core/decoders/jpeg.js index e1f2bdf8..157bf4f3 100644 --- a/core/decoders/jpeg.js +++ b/core/decoders/jpeg.js @@ -11,131 +11,136 @@ 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) { + let segment = this._readSegment(sock); + if (segment === null) { 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; + // 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.rQslice(0, length-2+extra); + 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), 4); + + return segment; } }