main
Raw Download raw file
  1/*
  2 * noVNC: HTML5 VNC client
  3 * Copyright (C) 2019 The noVNC authors
  4 * Licensed under MPL 2.0 (see LICENSE.txt)
  5 *
  6 * See README.md for usage and integration instructions.
  7 *
  8 */
  9
 10export default class JPEGDecoder {
 11    constructor() {
 12        // RealVNC will reuse the quantization tables
 13        // and Huffman tables, so we need to cache them.
 14        this._cachedQuantTables = [];
 15        this._cachedHuffmanTables = [];
 16
 17        this._segments = [];
 18    }
 19
 20    decodeRect(x, y, width, height, sock, display, depth) {
 21        // A rect of JPEG encodings is simply a JPEG file
 22        while (true) {
 23            let segment = this._readSegment(sock);
 24            if (segment === null) {
 25                return false;
 26            }
 27            this._segments.push(segment);
 28            // End of image?
 29            if (segment[1] === 0xD9) {
 30                break;
 31            }
 32        }
 33
 34        let huffmanTables = [];
 35        let quantTables = [];
 36        for (let segment of this._segments) {
 37            let type = segment[1];
 38            if (type === 0xC4) {
 39                // Huffman tables
 40                huffmanTables.push(segment);
 41            } else if (type === 0xDB) {
 42                // Quantization tables
 43                quantTables.push(segment);
 44            }
 45        }
 46
 47        const sofIndex = this._segments.findIndex(
 48            x => x[1] == 0xC0 || x[1] == 0xC2
 49        );
 50        if (sofIndex == -1) {
 51            throw new Error("Illegal JPEG image without SOF");
 52        }
 53
 54        if (quantTables.length === 0) {
 55            this._segments.splice(sofIndex+1, 0,
 56                                  ...this._cachedQuantTables);
 57        }
 58        if (huffmanTables.length === 0) {
 59            this._segments.splice(sofIndex+1, 0,
 60                                  ...this._cachedHuffmanTables);
 61        }
 62
 63        let length = 0;
 64        for (let segment of this._segments) {
 65            length += segment.length;
 66        }
 67
 68        let data = new Uint8Array(length);
 69        length = 0;
 70        for (let segment of this._segments) {
 71            data.set(segment, length);
 72            length += segment.length;
 73        }
 74
 75        display.imageRect(x, y, width, height, "image/jpeg", data);
 76
 77        if (huffmanTables.length !== 0) {
 78            this._cachedHuffmanTables = huffmanTables;
 79        }
 80        if (quantTables.length !== 0) {
 81            this._cachedQuantTables = quantTables;
 82        }
 83
 84        this._segments = [];
 85
 86        return true;
 87    }
 88
 89    _readSegment(sock) {
 90        if (sock.rQwait("JPEG", 2)) {
 91            return null;
 92        }
 93
 94        let marker = sock.rQshift8();
 95        if (marker != 0xFF) {
 96            throw new Error("Illegal JPEG marker received (byte: " +
 97                               marker + ")");
 98        }
 99        let type = sock.rQshift8();
100        if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
101            // No length after marker
102            return new Uint8Array([marker, type]);
103        }
104
105        if (sock.rQwait("JPEG", 2, 2)) {
106            return null;
107        }
108
109        let length = sock.rQshift16();
110        if (length < 2) {
111            throw new Error("Illegal JPEG length received (length: " +
112                               length + ")");
113        }
114
115        if (sock.rQwait("JPEG", length-2, 4)) {
116            return null;
117        }
118
119        let extra = 0;
120        if (type === 0xDA) {
121            // start of scan
122            if (sock.rQwait("JPEG", length-2 + 2, 4)) {
123                return null;
124            }
125
126            let len = sock.rQlen();
127            let data = sock.rQpeekBytes(len, false);
128
129            while (true) {
130                let idx = data.indexOf(0xFF, length-2+extra);
131                if (idx === -1) {
132                    sock.rQwait("JPEG", Infinity, 4);
133                    return null;
134                }
135
136                if (idx === len-1) {
137                    sock.rQwait("JPEG", Infinity, 4);
138                    return null;
139                }
140
141                if (data.at(idx+1) === 0x00 ||
142                    (data.at(idx+1) >= 0xD0 && data.at(idx+1) <= 0xD7)) {
143                    extra = idx+2 - (length-2);
144                    continue;
145                }
146
147                extra = idx - (length-2);
148                break;
149            }
150        }
151
152        let segment = new Uint8Array(2 + length + extra);
153        segment[0] = marker;
154        segment[1] = type;
155        segment[2] = length >> 8;
156        segment[3] = length;
157        segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
158
159        return segment;
160    }
161}