main
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}