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