Mercurial > hg > orthanc-webviewer
comparison WebApplication/jpeg-decoder.js @ 0:02f7a0400a91
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 25 Feb 2015 13:45:35 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:02f7a0400a91 |
---|---|
1 /** | |
2 * SOURCE: https://github.com/notmasteryet/jpgjs/blob/master/jpg.js | |
3 **/ | |
4 | |
5 | |
6 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / | |
7 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ | |
8 /* | |
9 Copyright 2011 notmasteryet | |
10 | |
11 Licensed under the Apache License, Version 2.0 (the "License"); | |
12 you may not use this file except in compliance with the License. | |
13 You may obtain a copy of the License at | |
14 | |
15 http://www.apache.org/licenses/LICENSE-2.0 | |
16 | |
17 Unless required by applicable law or agreed to in writing, software | |
18 distributed under the License is distributed on an "AS IS" BASIS, | |
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
20 See the License for the specific language governing permissions and | |
21 limitations under the License. | |
22 */ | |
23 | |
24 // - The JPEG specification can be found in the ITU CCITT Recommendation T.81 | |
25 // (www.w3.org/Graphics/JPEG/itu-t81.pdf) | |
26 // - The JFIF specification can be found in the JPEG File Interchange Format | |
27 // (www.w3.org/Graphics/JPEG/jfif3.pdf) | |
28 // - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters | |
29 // in PostScript Level 2, Technical Note #5116 | |
30 // (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) | |
31 | |
32 var JpegImage = (function jpegImage() { | |
33 "use strict"; | |
34 var dctZigZag = new Int32Array([ | |
35 0, | |
36 1, 8, | |
37 16, 9, 2, | |
38 3, 10, 17, 24, | |
39 32, 25, 18, 11, 4, | |
40 5, 12, 19, 26, 33, 40, | |
41 48, 41, 34, 27, 20, 13, 6, | |
42 7, 14, 21, 28, 35, 42, 49, 56, | |
43 57, 50, 43, 36, 29, 22, 15, | |
44 23, 30, 37, 44, 51, 58, | |
45 59, 52, 45, 38, 31, | |
46 39, 46, 53, 60, | |
47 61, 54, 47, | |
48 55, 62, | |
49 63 | |
50 ]); | |
51 | |
52 var dctCos1 = 4017 // cos(pi/16) | |
53 var dctSin1 = 799 // sin(pi/16) | |
54 var dctCos3 = 3406 // cos(3*pi/16) | |
55 var dctSin3 = 2276 // sin(3*pi/16) | |
56 var dctCos6 = 1567 // cos(6*pi/16) | |
57 var dctSin6 = 3784 // sin(6*pi/16) | |
58 var dctSqrt2 = 5793 // sqrt(2) | |
59 var dctSqrt1d2 = 2896 // sqrt(2) / 2 | |
60 | |
61 function constructor() { | |
62 } | |
63 | |
64 function buildHuffmanTable(codeLengths, values) { | |
65 var k = 0, code = [], i, j, length = 16; | |
66 while (length > 0 && !codeLengths[length - 1]) | |
67 length--; | |
68 code.push({children: [], index: 0}); | |
69 var p = code[0], q; | |
70 for (i = 0; i < length; i++) { | |
71 for (j = 0; j < codeLengths[i]; j++) { | |
72 p = code.pop(); | |
73 p.children[p.index] = values[k]; | |
74 while (p.index > 0) { | |
75 p = code.pop(); | |
76 } | |
77 p.index++; | |
78 code.push(p); | |
79 while (code.length <= i) { | |
80 code.push(q = {children: [], index: 0}); | |
81 p.children[p.index] = q.children; | |
82 p = q; | |
83 } | |
84 k++; | |
85 } | |
86 if (i + 1 < length) { | |
87 // p here points to last code | |
88 code.push(q = {children: [], index: 0}); | |
89 p.children[p.index] = q.children; | |
90 p = q; | |
91 } | |
92 } | |
93 return code[0].children; | |
94 } | |
95 | |
96 function getBlockBufferOffset(component, row, col) { | |
97 return 64 * ((component.blocksPerLine + 1) * row + col); | |
98 } | |
99 | |
100 function decodeScan(data, offset, | |
101 frame, components, resetInterval, | |
102 spectralStart, spectralEnd, | |
103 successivePrev, successive) { | |
104 var precision = frame.precision; | |
105 var samplesPerLine = frame.samplesPerLine; | |
106 var scanLines = frame.scanLines; | |
107 var mcusPerLine = frame.mcusPerLine; | |
108 var progressive = frame.progressive; | |
109 var maxH = frame.maxH, maxV = frame.maxV; | |
110 | |
111 var startOffset = offset, bitsData = 0, bitsCount = 0; | |
112 | |
113 function readBit() { | |
114 if (bitsCount > 0) { | |
115 bitsCount--; | |
116 return (bitsData >> bitsCount) & 1; | |
117 } | |
118 bitsData = data[offset++]; | |
119 if (bitsData == 0xFF) { | |
120 var nextByte = data[offset++]; | |
121 if (nextByte) { | |
122 throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16); | |
123 } | |
124 // unstuff 0 | |
125 } | |
126 bitsCount = 7; | |
127 return bitsData >>> 7; | |
128 } | |
129 | |
130 function decodeHuffman(tree) { | |
131 var node = tree; | |
132 var bit; | |
133 while ((bit = readBit()) !== null) { | |
134 node = node[bit]; | |
135 if (typeof node === 'number') | |
136 return node; | |
137 if (typeof node !== 'object') | |
138 throw "invalid huffman sequence"; | |
139 } | |
140 return null; | |
141 } | |
142 | |
143 function receive(length) { | |
144 var n = 0; | |
145 while (length > 0) { | |
146 var bit = readBit(); | |
147 if (bit === null) return; | |
148 n = (n << 1) | bit; | |
149 length--; | |
150 } | |
151 return n; | |
152 } | |
153 | |
154 function receiveAndExtend(length) { | |
155 var n = receive(length); | |
156 if (n >= 1 << (length - 1)) | |
157 return n; | |
158 return n + (-1 << length) + 1; | |
159 } | |
160 | |
161 function decodeBaseline(component, offset) { | |
162 var t = decodeHuffman(component.huffmanTableDC); | |
163 var diff = t === 0 ? 0 : receiveAndExtend(t); | |
164 component.blockData[offset] = (component.pred += diff); | |
165 var k = 1; | |
166 while (k < 64) { | |
167 var rs = decodeHuffman(component.huffmanTableAC); | |
168 var s = rs & 15, r = rs >> 4; | |
169 if (s === 0) { | |
170 if (r < 15) | |
171 break; | |
172 k += 16; | |
173 continue; | |
174 } | |
175 k += r; | |
176 var z = dctZigZag[k]; | |
177 component.blockData[offset + z] = receiveAndExtend(s); | |
178 k++; | |
179 } | |
180 } | |
181 | |
182 function decodeDCFirst(component, offset) { | |
183 var t = decodeHuffman(component.huffmanTableDC); | |
184 var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive); | |
185 component.blockData[offset] = (component.pred += diff); | |
186 } | |
187 | |
188 function decodeDCSuccessive(component, offset) { | |
189 component.blockData[offset] |= readBit() << successive; | |
190 } | |
191 | |
192 var eobrun = 0; | |
193 function decodeACFirst(component, offset) { | |
194 if (eobrun > 0) { | |
195 eobrun--; | |
196 return; | |
197 } | |
198 var k = spectralStart, e = spectralEnd; | |
199 while (k <= e) { | |
200 var rs = decodeHuffman(component.huffmanTableAC); | |
201 var s = rs & 15, r = rs >> 4; | |
202 if (s === 0) { | |
203 if (r < 15) { | |
204 eobrun = receive(r) + (1 << r) - 1; | |
205 break; | |
206 } | |
207 k += 16; | |
208 continue; | |
209 } | |
210 k += r; | |
211 var z = dctZigZag[k]; | |
212 component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive); | |
213 k++; | |
214 } | |
215 } | |
216 | |
217 var successiveACState = 0, successiveACNextValue; | |
218 function decodeACSuccessive(component, offset) { | |
219 var k = spectralStart, e = spectralEnd, r = 0; | |
220 while (k <= e) { | |
221 var z = dctZigZag[k]; | |
222 switch (successiveACState) { | |
223 case 0: // initial state | |
224 var rs = decodeHuffman(component.huffmanTableAC); | |
225 var s = rs & 15, r = rs >> 4; | |
226 if (s === 0) { | |
227 if (r < 15) { | |
228 eobrun = receive(r) + (1 << r); | |
229 successiveACState = 4; | |
230 } else { | |
231 r = 16; | |
232 successiveACState = 1; | |
233 } | |
234 } else { | |
235 if (s !== 1) | |
236 throw "invalid ACn encoding"; | |
237 successiveACNextValue = receiveAndExtend(s); | |
238 successiveACState = r ? 2 : 3; | |
239 } | |
240 continue; | |
241 case 1: // skipping r zero items | |
242 case 2: | |
243 if (component.blockData[offset + z]) { | |
244 component.blockData[offset + z] += (readBit() << successive); | |
245 } else { | |
246 r--; | |
247 if (r === 0) | |
248 successiveACState = successiveACState == 2 ? 3 : 0; | |
249 } | |
250 break; | |
251 case 3: // set value for a zero item | |
252 if (component.blockData[offset + z]) { | |
253 component.blockData[offset + z] += (readBit() << successive); | |
254 } else { | |
255 component.blockData[offset + z] = successiveACNextValue << successive; | |
256 successiveACState = 0; | |
257 } | |
258 break; | |
259 case 4: // eob | |
260 if (component.blockData[offset + z]) { | |
261 component.blockData[offset + z] += (readBit() << successive); | |
262 } | |
263 break; | |
264 } | |
265 k++; | |
266 } | |
267 if (successiveACState === 4) { | |
268 eobrun--; | |
269 if (eobrun === 0) | |
270 successiveACState = 0; | |
271 } | |
272 } | |
273 | |
274 function decodeMcu(component, decode, mcu, row, col) { | |
275 var mcuRow = (mcu / mcusPerLine) | 0; | |
276 var mcuCol = mcu % mcusPerLine; | |
277 var blockRow = mcuRow * component.v + row; | |
278 var blockCol = mcuCol * component.h + col; | |
279 var offset = getBlockBufferOffset(component, blockRow, blockCol); | |
280 decode(component, offset); | |
281 } | |
282 | |
283 function decodeBlock(component, decode, mcu) { | |
284 var blockRow = (mcu / component.blocksPerLine) | 0; | |
285 var blockCol = mcu % component.blocksPerLine; | |
286 var offset = getBlockBufferOffset(component, blockRow, blockCol); | |
287 decode(component, offset); | |
288 } | |
289 | |
290 var componentsLength = components.length; | |
291 var component, i, j, k, n; | |
292 var decodeFn; | |
293 if (progressive) { | |
294 if (spectralStart === 0) | |
295 decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; | |
296 else | |
297 decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; | |
298 } else { | |
299 decodeFn = decodeBaseline; | |
300 } | |
301 | |
302 var mcu = 0, marker; | |
303 var mcuExpected; | |
304 if (componentsLength == 1) { | |
305 mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; | |
306 } else { | |
307 mcuExpected = mcusPerLine * frame.mcusPerColumn; | |
308 } | |
309 if (!resetInterval) { | |
310 resetInterval = mcuExpected; | |
311 } | |
312 | |
313 var h, v; | |
314 while (mcu < mcuExpected) { | |
315 // reset interval stuff | |
316 for (i = 0; i < componentsLength; i++) { | |
317 components[i].pred = 0; | |
318 } | |
319 eobrun = 0; | |
320 | |
321 if (componentsLength == 1) { | |
322 component = components[0]; | |
323 for (n = 0; n < resetInterval; n++) { | |
324 decodeBlock(component, decodeFn, mcu); | |
325 mcu++; | |
326 } | |
327 } else { | |
328 for (n = 0; n < resetInterval; n++) { | |
329 for (i = 0; i < componentsLength; i++) { | |
330 component = components[i]; | |
331 h = component.h; | |
332 v = component.v; | |
333 for (j = 0; j < v; j++) { | |
334 for (k = 0; k < h; k++) { | |
335 decodeMcu(component, decodeFn, mcu, j, k); | |
336 } | |
337 } | |
338 } | |
339 mcu++; | |
340 } | |
341 } | |
342 | |
343 // find marker | |
344 bitsCount = 0; | |
345 marker = (data[offset] << 8) | data[offset + 1]; | |
346 if (marker <= 0xFF00) { | |
347 throw "marker was not found"; | |
348 } | |
349 | |
350 if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx | |
351 offset += 2; | |
352 } else { | |
353 break; | |
354 } | |
355 } | |
356 | |
357 return offset - startOffset; | |
358 } | |
359 | |
360 // A port of poppler's IDCT method which in turn is taken from: | |
361 // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, | |
362 // "Practical Fast 1-D DCT Algorithms with 11 Multiplications", | |
363 // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, | |
364 // 988-991. | |
365 function quantizeAndInverse(component, blockBufferOffset, p) { | |
366 var qt = component.quantizationTable; | |
367 var v0, v1, v2, v3, v4, v5, v6, v7, t; | |
368 var i; | |
369 | |
370 // dequant | |
371 for (i = 0; i < 64; i++) { | |
372 p[i] = component.blockData[blockBufferOffset + i] * qt[i]; | |
373 } | |
374 | |
375 // inverse DCT on rows | |
376 for (i = 0; i < 8; ++i) { | |
377 var row = 8 * i; | |
378 | |
379 // check for all-zero AC coefficients | |
380 if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 && | |
381 p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 && | |
382 p[7 + row] == 0) { | |
383 t = (dctSqrt2 * p[0 + row] + 512) >> 10; | |
384 p[0 + row] = t; | |
385 p[1 + row] = t; | |
386 p[2 + row] = t; | |
387 p[3 + row] = t; | |
388 p[4 + row] = t; | |
389 p[5 + row] = t; | |
390 p[6 + row] = t; | |
391 p[7 + row] = t; | |
392 continue; | |
393 } | |
394 | |
395 // stage 4 | |
396 v0 = (dctSqrt2 * p[0 + row] + 128) >> 8; | |
397 v1 = (dctSqrt2 * p[4 + row] + 128) >> 8; | |
398 v2 = p[2 + row]; | |
399 v3 = p[6 + row]; | |
400 v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8; | |
401 v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8; | |
402 v5 = p[3 + row] << 4; | |
403 v6 = p[5 + row] << 4; | |
404 | |
405 // stage 3 | |
406 t = (v0 - v1+ 1) >> 1; | |
407 v0 = (v0 + v1 + 1) >> 1; | |
408 v1 = t; | |
409 t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; | |
410 v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; | |
411 v3 = t; | |
412 t = (v4 - v6 + 1) >> 1; | |
413 v4 = (v4 + v6 + 1) >> 1; | |
414 v6 = t; | |
415 t = (v7 + v5 + 1) >> 1; | |
416 v5 = (v7 - v5 + 1) >> 1; | |
417 v7 = t; | |
418 | |
419 // stage 2 | |
420 t = (v0 - v3 + 1) >> 1; | |
421 v0 = (v0 + v3 + 1) >> 1; | |
422 v3 = t; | |
423 t = (v1 - v2 + 1) >> 1; | |
424 v1 = (v1 + v2 + 1) >> 1; | |
425 v2 = t; | |
426 t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; | |
427 v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; | |
428 v7 = t; | |
429 t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; | |
430 v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; | |
431 v6 = t; | |
432 | |
433 // stage 1 | |
434 p[0 + row] = v0 + v7; | |
435 p[7 + row] = v0 - v7; | |
436 p[1 + row] = v1 + v6; | |
437 p[6 + row] = v1 - v6; | |
438 p[2 + row] = v2 + v5; | |
439 p[5 + row] = v2 - v5; | |
440 p[3 + row] = v3 + v4; | |
441 p[4 + row] = v3 - v4; | |
442 } | |
443 | |
444 // inverse DCT on columns | |
445 for (i = 0; i < 8; ++i) { | |
446 var col = i; | |
447 | |
448 // check for all-zero AC coefficients | |
449 if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 && | |
450 p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 && | |
451 p[7*8 + col] == 0) { | |
452 t = (dctSqrt2 * p[i+0] + 8192) >> 14; | |
453 p[0*8 + col] = t; | |
454 p[1*8 + col] = t; | |
455 p[2*8 + col] = t; | |
456 p[3*8 + col] = t; | |
457 p[4*8 + col] = t; | |
458 p[5*8 + col] = t; | |
459 p[6*8 + col] = t; | |
460 p[7*8 + col] = t; | |
461 continue; | |
462 } | |
463 | |
464 // stage 4 | |
465 v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12; | |
466 v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12; | |
467 v2 = p[2*8 + col]; | |
468 v3 = p[6*8 + col]; | |
469 v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12; | |
470 v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12; | |
471 v5 = p[3*8 + col]; | |
472 v6 = p[5*8 + col]; | |
473 | |
474 // stage 3 | |
475 t = (v0 - v1 + 1) >> 1; | |
476 v0 = (v0 + v1 + 1) >> 1; | |
477 v1 = t; | |
478 t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; | |
479 v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; | |
480 v3 = t; | |
481 t = (v4 - v6 + 1) >> 1; | |
482 v4 = (v4 + v6 + 1) >> 1; | |
483 v6 = t; | |
484 t = (v7 + v5 + 1) >> 1; | |
485 v5 = (v7 - v5 + 1) >> 1; | |
486 v7 = t; | |
487 | |
488 // stage 2 | |
489 t = (v0 - v3 + 1) >> 1; | |
490 v0 = (v0 + v3 + 1) >> 1; | |
491 v3 = t; | |
492 t = (v1 - v2 + 1) >> 1; | |
493 v1 = (v1 + v2 + 1) >> 1; | |
494 v2 = t; | |
495 t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; | |
496 v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; | |
497 v7 = t; | |
498 t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; | |
499 v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; | |
500 v6 = t; | |
501 | |
502 // stage 1 | |
503 p[0*8 + col] = v0 + v7; | |
504 p[7*8 + col] = v0 - v7; | |
505 p[1*8 + col] = v1 + v6; | |
506 p[6*8 + col] = v1 - v6; | |
507 p[2*8 + col] = v2 + v5; | |
508 p[5*8 + col] = v2 - v5; | |
509 p[3*8 + col] = v3 + v4; | |
510 p[4*8 + col] = v3 - v4; | |
511 } | |
512 | |
513 // convert to 8-bit integers | |
514 for (i = 0; i < 64; ++i) { | |
515 var index = blockBufferOffset + i; | |
516 var q = p[i]; | |
517 q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4; | |
518 component.blockData[index] = q; | |
519 } | |
520 } | |
521 | |
522 function buildComponentData(frame, component) { | |
523 var lines = []; | |
524 var blocksPerLine = component.blocksPerLine; | |
525 var blocksPerColumn = component.blocksPerColumn; | |
526 var samplesPerLine = blocksPerLine << 3; | |
527 var computationBuffer = new Int32Array(64); | |
528 | |
529 var i, j, ll = 0; | |
530 for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { | |
531 for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { | |
532 var offset = getBlockBufferOffset(component, blockRow, blockCol) | |
533 quantizeAndInverse(component, offset, computationBuffer); | |
534 } | |
535 } | |
536 return component.blockData; | |
537 } | |
538 | |
539 function clampToUint8(a) { | |
540 return a <= 0 ? 0 : a >= 255 ? 255 : a | 0; | |
541 } | |
542 | |
543 constructor.prototype = { | |
544 load: function load(path) { | |
545 var handleData = (function(data) { | |
546 this.parse(data); | |
547 if (this.onload) | |
548 this.onload(); | |
549 }).bind(this); | |
550 | |
551 if (path.indexOf("data:") > -1) { | |
552 var offset = path.indexOf("base64,")+7; | |
553 var data = atob(path.substring(offset)); | |
554 var arr = new Uint8Array(data.length); | |
555 for (var i = data.length - 1; i >= 0; i--) { | |
556 arr[i] = data.charCodeAt(i); | |
557 } | |
558 handleData(data); | |
559 } else { | |
560 var xhr = new XMLHttpRequest(); | |
561 xhr.open("GET", path, true); | |
562 xhr.responseType = "arraybuffer"; | |
563 xhr.onload = (function() { | |
564 // TODO catch parse error | |
565 var data = new Uint8Array(xhr.response); | |
566 handleData(data); | |
567 }).bind(this); | |
568 xhr.send(null); | |
569 } | |
570 }, | |
571 | |
572 parse: function parse(data) { | |
573 | |
574 function readUint16() { | |
575 var value = (data[offset] << 8) | data[offset + 1]; | |
576 offset += 2; | |
577 return value; | |
578 } | |
579 | |
580 function readDataBlock() { | |
581 var length = readUint16(); | |
582 var array = data.subarray(offset, offset + length - 2); | |
583 offset += array.length; | |
584 return array; | |
585 } | |
586 | |
587 function prepareComponents(frame) { | |
588 var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); | |
589 var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); | |
590 for (var i = 0; i < frame.components.length; i++) { | |
591 component = frame.components[i]; | |
592 var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH); | |
593 var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV); | |
594 var blocksPerLineForMcu = mcusPerLine * component.h; | |
595 var blocksPerColumnForMcu = mcusPerColumn * component.v; | |
596 | |
597 var blocksBufferSize = 64 * blocksPerColumnForMcu | |
598 * (blocksPerLineForMcu + 1); | |
599 component.blockData = new Int16Array(blocksBufferSize); | |
600 component.blocksPerLine = blocksPerLine; | |
601 component.blocksPerColumn = blocksPerColumn; | |
602 } | |
603 frame.mcusPerLine = mcusPerLine; | |
604 frame.mcusPerColumn = mcusPerColumn; | |
605 } | |
606 | |
607 var offset = 0, length = data.length; | |
608 var jfif = null; | |
609 var adobe = null; | |
610 var pixels = null; | |
611 var frame, resetInterval; | |
612 var quantizationTables = []; | |
613 var huffmanTablesAC = [], huffmanTablesDC = []; | |
614 var fileMarker = readUint16(); | |
615 if (fileMarker != 0xFFD8) { // SOI (Start of Image) | |
616 throw "SOI not found"; | |
617 } | |
618 | |
619 fileMarker = readUint16(); | |
620 while (fileMarker != 0xFFD9) { // EOI (End of image) | |
621 var i, j, l; | |
622 switch(fileMarker) { | |
623 case 0xFFE0: // APP0 (Application Specific) | |
624 case 0xFFE1: // APP1 | |
625 case 0xFFE2: // APP2 | |
626 case 0xFFE3: // APP3 | |
627 case 0xFFE4: // APP4 | |
628 case 0xFFE5: // APP5 | |
629 case 0xFFE6: // APP6 | |
630 case 0xFFE7: // APP7 | |
631 case 0xFFE8: // APP8 | |
632 case 0xFFE9: // APP9 | |
633 case 0xFFEA: // APP10 | |
634 case 0xFFEB: // APP11 | |
635 case 0xFFEC: // APP12 | |
636 case 0xFFED: // APP13 | |
637 case 0xFFEE: // APP14 | |
638 case 0xFFEF: // APP15 | |
639 case 0xFFFE: // COM (Comment) | |
640 var appData = readDataBlock(); | |
641 | |
642 if (fileMarker === 0xFFE0) { | |
643 if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 && | |
644 appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00' | |
645 jfif = { | |
646 version: { major: appData[5], minor: appData[6] }, | |
647 densityUnits: appData[7], | |
648 xDensity: (appData[8] << 8) | appData[9], | |
649 yDensity: (appData[10] << 8) | appData[11], | |
650 thumbWidth: appData[12], | |
651 thumbHeight: appData[13], | |
652 thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13]) | |
653 }; | |
654 } | |
655 } | |
656 // TODO APP1 - Exif | |
657 if (fileMarker === 0xFFEE) { | |
658 if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F && | |
659 appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00' | |
660 adobe = { | |
661 version: appData[6], | |
662 flags0: (appData[7] << 8) | appData[8], | |
663 flags1: (appData[9] << 8) | appData[10], | |
664 transformCode: appData[11] | |
665 }; | |
666 } | |
667 } | |
668 break; | |
669 | |
670 case 0xFFDB: // DQT (Define Quantization Tables) | |
671 var quantizationTablesLength = readUint16(); | |
672 var quantizationTablesEnd = quantizationTablesLength + offset - 2; | |
673 while (offset < quantizationTablesEnd) { | |
674 var quantizationTableSpec = data[offset++]; | |
675 var tableData = new Int32Array(64); | |
676 if ((quantizationTableSpec >> 4) === 0) { // 8 bit values | |
677 for (j = 0; j < 64; j++) { | |
678 var z = dctZigZag[j]; | |
679 tableData[z] = data[offset++]; | |
680 } | |
681 } else if ((quantizationTableSpec >> 4) === 1) { //16 bit | |
682 for (j = 0; j < 64; j++) { | |
683 var z = dctZigZag[j]; | |
684 tableData[z] = readUint16(); | |
685 } | |
686 } else | |
687 throw "DQT: invalid table spec"; | |
688 quantizationTables[quantizationTableSpec & 15] = tableData; | |
689 } | |
690 break; | |
691 | |
692 case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT) | |
693 case 0xFFC1: // SOF1 (Start of Frame, Extended DCT) | |
694 case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT) | |
695 if (frame) { | |
696 throw "Only single frame JPEGs supported"; | |
697 } | |
698 readUint16(); // skip data length | |
699 frame = {}; | |
700 frame.extended = (fileMarker === 0xFFC1); | |
701 frame.progressive = (fileMarker === 0xFFC2); | |
702 frame.precision = data[offset++]; | |
703 frame.scanLines = readUint16(); | |
704 frame.samplesPerLine = readUint16(); | |
705 frame.components = []; | |
706 frame.componentIds = {}; | |
707 var componentsCount = data[offset++], componentId; | |
708 var maxH = 0, maxV = 0; | |
709 for (i = 0; i < componentsCount; i++) { | |
710 componentId = data[offset]; | |
711 var h = data[offset + 1] >> 4; | |
712 var v = data[offset + 1] & 15; | |
713 if (maxH < h) maxH = h; | |
714 if (maxV < v) maxV = v; | |
715 var qId = data[offset + 2]; | |
716 var l = frame.components.push({ | |
717 h: h, | |
718 v: v, | |
719 quantizationTable: quantizationTables[qId] | |
720 }); | |
721 frame.componentIds[componentId] = l - 1; | |
722 offset += 3; | |
723 } | |
724 frame.maxH = maxH; | |
725 frame.maxV = maxV; | |
726 prepareComponents(frame); | |
727 break; | |
728 | |
729 case 0xFFC4: // DHT (Define Huffman Tables) | |
730 var huffmanLength = readUint16(); | |
731 for (i = 2; i < huffmanLength;) { | |
732 var huffmanTableSpec = data[offset++]; | |
733 var codeLengths = new Uint8Array(16); | |
734 var codeLengthSum = 0; | |
735 for (j = 0; j < 16; j++, offset++) | |
736 codeLengthSum += (codeLengths[j] = data[offset]); | |
737 var huffmanValues = new Uint8Array(codeLengthSum); | |
738 for (j = 0; j < codeLengthSum; j++, offset++) | |
739 huffmanValues[j] = data[offset]; | |
740 i += 17 + codeLengthSum; | |
741 | |
742 ((huffmanTableSpec >> 4) === 0 ? | |
743 huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = | |
744 buildHuffmanTable(codeLengths, huffmanValues); | |
745 } | |
746 break; | |
747 | |
748 case 0xFFDD: // DRI (Define Restart Interval) | |
749 readUint16(); // skip data length | |
750 resetInterval = readUint16(); | |
751 break; | |
752 | |
753 case 0xFFDA: // SOS (Start of Scan) | |
754 var scanLength = readUint16(); | |
755 var selectorsCount = data[offset++]; | |
756 var components = [], component; | |
757 for (i = 0; i < selectorsCount; i++) { | |
758 var componentIndex = frame.componentIds[data[offset++]]; | |
759 component = frame.components[componentIndex]; | |
760 var tableSpec = data[offset++]; | |
761 component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; | |
762 component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; | |
763 components.push(component); | |
764 } | |
765 var spectralStart = data[offset++]; | |
766 var spectralEnd = data[offset++]; | |
767 var successiveApproximation = data[offset++]; | |
768 var processed = decodeScan(data, offset, | |
769 frame, components, resetInterval, | |
770 spectralStart, spectralEnd, | |
771 successiveApproximation >> 4, successiveApproximation & 15); | |
772 offset += processed; | |
773 break; | |
774 default: | |
775 if (data[offset - 3] == 0xFF && | |
776 data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) { | |
777 // could be incorrect encoding -- last 0xFF byte of the previous | |
778 // block was eaten by the encoder | |
779 offset -= 3; | |
780 break; | |
781 } | |
782 throw "unknown JPEG marker " + fileMarker.toString(16); | |
783 } | |
784 fileMarker = readUint16(); | |
785 } | |
786 | |
787 this.width = frame.samplesPerLine; | |
788 this.height = frame.scanLines; | |
789 this.jfif = jfif; | |
790 this.adobe = adobe; | |
791 this.components = []; | |
792 for (var i = 0; i < frame.components.length; i++) { | |
793 var component = frame.components[i]; | |
794 this.components.push({ | |
795 output: buildComponentData(frame, component), | |
796 scaleX: component.h / frame.maxH, | |
797 scaleY: component.v / frame.maxV, | |
798 blocksPerLine: component.blocksPerLine, | |
799 blocksPerColumn: component.blocksPerColumn | |
800 }); | |
801 } | |
802 }, | |
803 | |
804 getData: function getData(width, height) { | |
805 var scaleX = this.width / width, scaleY = this.height / height; | |
806 | |
807 var component, componentScaleX, componentScaleY; | |
808 var x, y, i; | |
809 var offset = 0; | |
810 var Y, Cb, Cr, K, C, M, Ye, R, G, B; | |
811 var colorTransform; | |
812 var numComponents = this.components.length; | |
813 var dataLength = width * height * numComponents; | |
814 var data = new Uint8Array(dataLength); | |
815 var componentLine; | |
816 | |
817 // lineData is reused for all components. Assume first component is | |
818 // the biggest | |
819 var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) * | |
820 this.components[0].blocksPerColumn * 8); | |
821 | |
822 // First construct image data ... | |
823 for (i = 0; i < numComponents; i++) { | |
824 component = this.components[i]; | |
825 var blocksPerLine = component.blocksPerLine; | |
826 var blocksPerColumn = component.blocksPerColumn; | |
827 var samplesPerLine = blocksPerLine << 3; | |
828 | |
829 var j, k, ll = 0; | |
830 var lineOffset = 0; | |
831 for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { | |
832 var scanLine = blockRow << 3; | |
833 for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { | |
834 var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol); | |
835 var offset = 0, sample = blockCol << 3; | |
836 for (j = 0; j < 8; j++) { | |
837 var lineOffset = (scanLine + j) * samplesPerLine; | |
838 for (k = 0; k < 8; k++) { | |
839 lineData[lineOffset + sample + k] = | |
840 component.output[bufferOffset + offset++]; | |
841 } | |
842 } | |
843 } | |
844 } | |
845 | |
846 componentScaleX = component.scaleX * scaleX; | |
847 componentScaleY = component.scaleY * scaleY; | |
848 offset = i; | |
849 | |
850 var cx, cy; | |
851 var index; | |
852 for (y = 0; y < height; y++) { | |
853 for (x = 0; x < width; x++) { | |
854 cy = 0 | (y * componentScaleY); | |
855 cx = 0 | (x * componentScaleX); | |
856 index = cy * samplesPerLine + cx; | |
857 data[offset] = lineData[index]; | |
858 offset += numComponents; | |
859 } | |
860 } | |
861 } | |
862 | |
863 // ... then transform colors, if necessary | |
864 switch (numComponents) { | |
865 case 1: case 2: break; | |
866 // no color conversion for one or two compoenents | |
867 | |
868 case 3: | |
869 // The default transform for three components is true | |
870 colorTransform = true; | |
871 // The adobe transform marker overrides any previous setting | |
872 if (this.adobe && this.adobe.transformCode) | |
873 colorTransform = true; | |
874 else if (typeof this.colorTransform !== 'undefined') | |
875 colorTransform = !!this.colorTransform; | |
876 | |
877 if (colorTransform) { | |
878 for (i = 0; i < dataLength; i += numComponents) { | |
879 Y = data[i ]; | |
880 Cb = data[i + 1]; | |
881 Cr = data[i + 2]; | |
882 | |
883 R = clampToUint8(Y - 179.456 + 1.402 * Cr); | |
884 G = clampToUint8(Y + 135.459 - 0.344 * Cb - 0.714 * Cr); | |
885 B = clampToUint8(Y - 226.816 + 1.772 * Cb); | |
886 | |
887 data[i ] = R; | |
888 data[i + 1] = G; | |
889 data[i + 2] = B; | |
890 } | |
891 } | |
892 break; | |
893 case 4: | |
894 if (!this.adobe) | |
895 throw 'Unsupported color mode (4 components)'; | |
896 // The default transform for four components is false | |
897 colorTransform = false; | |
898 // The adobe transform marker overrides any previous setting | |
899 if (this.adobe && this.adobe.transformCode) | |
900 colorTransform = true; | |
901 else if (typeof this.colorTransform !== 'undefined') | |
902 colorTransform = !!this.colorTransform; | |
903 | |
904 if (colorTransform) { | |
905 for (i = 0; i < dataLength; i += numComponents) { | |
906 Y = data[i]; | |
907 Cb = data[i + 1]; | |
908 Cr = data[i + 2]; | |
909 | |
910 C = clampToUint8(434.456 - Y - 1.402 * Cr); | |
911 M = clampToUint8(119.541 - Y + 0.344 * Cb + 0.714 * Cr); | |
912 Y = clampToUint8(481.816 - Y - 1.772 * Cb); | |
913 | |
914 data[i ] = C; | |
915 data[i + 1] = M; | |
916 data[i + 2] = Y; | |
917 // K is unchanged | |
918 } | |
919 } | |
920 break; | |
921 default: | |
922 throw 'Unsupported color mode'; | |
923 } | |
924 return data; | |
925 }, | |
926 copyToImageData: function copyToImageData(imageData) { | |
927 var width = imageData.width, height = imageData.height; | |
928 var imageDataBytes = width * height * 4; | |
929 var imageDataArray = imageData.data; | |
930 var data = this.getData(width, height); | |
931 var i = 0, j = 0, k0, k1; | |
932 var Y, K, C, M, R, G, B; | |
933 switch (this.components.length) { | |
934 case 1: | |
935 while (j < imageDataBytes) { | |
936 Y = data[i++]; | |
937 | |
938 imageDataArray[j++] = Y; | |
939 imageDataArray[j++] = Y; | |
940 imageDataArray[j++] = Y; | |
941 imageDataArray[j++] = 255; | |
942 } | |
943 break; | |
944 case 3: | |
945 while (j < imageDataBytes) { | |
946 R = data[i++]; | |
947 G = data[i++]; | |
948 B = data[i++]; | |
949 | |
950 imageDataArray[j++] = R; | |
951 imageDataArray[j++] = G; | |
952 imageDataArray[j++] = B; | |
953 imageDataArray[j++] = 255; | |
954 } | |
955 break; | |
956 case 4: | |
957 while (j < imageDataBytes) { | |
958 C = data[i++]; | |
959 M = data[i++]; | |
960 Y = data[i++]; | |
961 K = data[i++]; | |
962 | |
963 k0 = 255 - K; | |
964 k1 = k0 / 255; | |
965 | |
966 | |
967 R = clampToUint8(k0 - C * k1); | |
968 G = clampToUint8(k0 - M * k1); | |
969 B = clampToUint8(k0 - Y * k1); | |
970 | |
971 imageDataArray[j++] = R; | |
972 imageDataArray[j++] = G; | |
973 imageDataArray[j++] = B; | |
974 imageDataArray[j++] = 255; | |
975 } | |
976 break; | |
977 default: | |
978 throw 'Unsupported color mode'; | |
979 } | |
980 } | |
981 }; | |
982 | |
983 return constructor; | |
984 })(); |