1 /* 2 Copyright (c) 2015 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 // dimage is actually stripped out part of dlib - just to support reading PNG and JPEG 30 module dimage.jpeg; 31 32 version = USE_DIMAGE; 33 version(USE_DIMAGE): 34 35 36 import std.stdio; 37 import std.algorithm; 38 import std.string; 39 import std.traits; 40 41 import dimage.huffman; 42 import dimage.stream; 43 import dimage.compound; 44 import dimage.array; 45 //import dimage.color; 46 import dimage.image; 47 import dimage.bitio; 48 import dimage.memory; 49 import dimage.idct; 50 //import dlib.core.memory; 51 //import dlib.core.stream; 52 //import dlib.core.compound; 53 //import dlib.container.array; 54 //import dlib.filesystem.local; 55 //import dlib.image.color; 56 //import dlib.image.image; 57 //import dlib.image.io.idct; 58 59 //import dlib.core.bitio; 60 //import dlib.coding.huffman; 61 62 /* 63 * Simple JPEG decoder 64 * 65 * Limitations: 66 * - Doesn't support progressive JPEG 67 * - Doesn't perform chroma interpolation 68 * - Doesn't read EXIF metadata 69 */ 70 71 // Uncomment this to see debug messages 72 //version = JPEGDebug; 73 74 T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) 75 if (is(T == ubyte)) 76 { 77 ubyte b; 78 istrm.readBytes(&b, 1); 79 return b; 80 } 81 82 T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) 83 if (is(T == ushort)) 84 { 85 union U16 86 { 87 ubyte[2] asBytes; 88 ushort asUshort; 89 } 90 U16 u16; 91 istrm.readBytes(u16.asBytes.ptr, 2); 92 version(LittleEndian) 93 { 94 if (endian == Endian.Big) 95 return u16.asUshort.swapEndian16; 96 else 97 return u16.asUshort; 98 } 99 else 100 { 101 if (endian == Endian.Little) 102 return u16.asUshort.swapEndian16; 103 else 104 return u16.asUshort; 105 } 106 } 107 108 char[size] readChars(size_t size) (InputStream istrm) 109 { 110 char[size] chars; 111 istrm.readBytes(chars.ptr, size); 112 return chars; 113 } 114 115 /* 116 * JPEG-related Huffman coding 117 */ 118 119 struct HuffmanCode 120 { 121 ushort bits; 122 ushort length; 123 124 auto bitString() 125 { 126 return .bitString(bits, length); 127 } 128 } 129 130 struct HuffmanTableEntry 131 { 132 HuffmanCode code; 133 ubyte value; 134 } 135 136 DynamicArray!char bitString(T)(T n, uint len = 1) if (isIntegral!T) 137 { 138 DynamicArray!char arr; 139 140 const int size = T.sizeof * 8; 141 142 bool s = 0; 143 for (int a = 0; a < size; a++) 144 { 145 bool bit = n >> (size - 1); 146 if (bit) 147 s = 1; 148 if (s) 149 { 150 arr.append(bit + '0'); 151 } 152 n <<= 1; 153 } 154 155 while (arr.length < len) 156 arr.appendLeft('0'); 157 158 return arr; 159 } 160 161 HuffmanTreeNode* emptyNode() 162 { 163 return New!HuffmanTreeNode(null, null, cast(ubyte)0, 0, false); 164 } 165 166 HuffmanTreeNode* treeFromTable(DynamicArray!(HuffmanTableEntry) table) 167 { 168 HuffmanTreeNode* root = emptyNode(); 169 170 foreach(i, v; table.data) 171 treeAddCode(root, v.code, v.value); 172 173 return root; 174 } 175 176 void treeAddCode(HuffmanTreeNode* root, HuffmanCode code, ubyte value) 177 { 178 HuffmanTreeNode* node = root; 179 auto bs = code.bitString; 180 foreach(bit; bs.data) 181 { 182 if (bit == '0') 183 { 184 if (node.left is null) 185 { 186 node.left = emptyNode(); 187 node.left.parent = node; 188 } 189 190 node = node.left; 191 } 192 else if (bit == '1') 193 { 194 if (node.right is null) 195 { 196 node.right = emptyNode(); 197 node.right.parent = node; 198 } 199 200 node = node.right; 201 } 202 } 203 assert (node !is null); 204 node.ch = value; 205 bs.free(); 206 } 207 208 /* 209 * JPEG-related data types 210 */ 211 212 enum JPEGMarkerType 213 { 214 Unknown, 215 SOI, 216 SOF0, 217 SOF1, 218 SOF2, 219 DHT, 220 DQT, 221 DRI, 222 SOS, 223 RSTn, 224 APP0, 225 APPn, 226 COM, 227 EOI 228 } 229 230 struct JPEGImage 231 { 232 struct JFIF 233 { 234 ubyte versionMajor; 235 ubyte versionMinor; 236 ubyte units; 237 ushort xDensity; 238 ushort yDensity; 239 ubyte thumbnailWidth; 240 ubyte thumbnailHeight; 241 ubyte[] thumbnail; 242 243 void free() 244 { 245 if (thumbnail.length) 246 Delete(thumbnail); 247 } 248 } 249 250 struct DQT 251 { 252 ubyte precision; 253 ubyte tableId; 254 ubyte[] table; 255 256 void free() 257 { 258 if (table.length) 259 Delete(table); 260 } 261 } 262 263 struct SOF0Component 264 { 265 ubyte hSubsampling; 266 ubyte vSubsampling; 267 ubyte dqtTableId; 268 } 269 270 struct SOF0 271 { 272 ubyte precision; 273 ushort height; 274 ushort width; 275 ubyte componentsNum; 276 SOF0Component[] components; 277 278 void free() 279 { 280 if (components.length) 281 Delete(components); 282 } 283 } 284 285 struct DHT 286 { 287 ubyte clas; 288 ubyte tableId; 289 DynamicArray!HuffmanTableEntry huffmanTable; 290 HuffmanTreeNode* huffmanTree; 291 292 void free() 293 { 294 huffmanTree.free(); 295 Delete(huffmanTree); 296 huffmanTable.free(); 297 } 298 } 299 300 struct SOSComponent 301 { 302 ubyte tableIdDC; 303 ubyte tableIdAC; 304 } 305 306 struct SOS 307 { 308 ubyte componentsNum; 309 SOSComponent[] components; 310 ubyte spectralSelectionStart; 311 ubyte spectralSelectionEnd; 312 ubyte successiveApproximationBitHigh; 313 ubyte successiveApproximationBitLow; 314 315 void free() 316 { 317 if (components.length) 318 Delete(components); 319 } 320 } 321 322 JFIF jfif; 323 DQT[] dqt; 324 SOF0 sof0; 325 DHT[] dht; 326 SOS sos; 327 328 DQT* addDQT() 329 { 330 if (dqt.length > 0) 331 reallocateArray(dqt, dqt.length+1); 332 else 333 dqt = New!(DQT[])(1); 334 return &dqt[$-1]; 335 } 336 337 DHT* addDHT() 338 { 339 if (dht.length > 0) 340 reallocateArray(dht, dht.length+1); 341 else 342 dht = New!(DHT[])(1); 343 return &dht[$-1]; 344 } 345 346 void free() 347 { 348 jfif.free(); 349 foreach(ref t; dqt) t.free(); 350 Delete(dqt); 351 sof0.free(); 352 foreach(ref t; dht) t.free(); 353 Delete(dht); 354 sos.free(); 355 } 356 357 DQT* getQuantizationTable(ubyte id) 358 { 359 foreach(ref t; dqt) 360 if (t.tableId == id) 361 return &t; 362 return null; 363 } 364 365 DHT* getHuffmanTable(ubyte clas, ubyte id) 366 { 367 foreach(ref t; dht) 368 if (t.clas == clas && 369 t.tableId == id) 370 return &t; 371 return null; 372 } 373 } 374 375 /* 376 * Load JPEG from file using local FileSystem. 377 * Causes GC allocation 378 */ 379 //SuperImage loadJPEG(string filename) 380 //{ 381 // InputStream input = openForInput(filename); 382 // auto img = loadJPEG(input); 383 // input.close(); 384 // return img; 385 //} 386 387 /* 388 * Load JPEG from stream using default image factory. 389 * Causes GC allocation 390 */ 391 SuperImage loadJPEG(InputStream istrm) 392 { 393 Compound!(SuperImage, string) res = 394 loadJPEG(istrm, defaultImageFactory); 395 if (res[0] is null) 396 throw new Exception(res[1]); 397 else 398 return res[0]; 399 } 400 401 /* 402 * Load JPEG from stream using specified image factory. 403 * GC-free 404 */ 405 Compound!(SuperImage, string) loadJPEG( 406 InputStream istrm, 407 SuperImageFactory imgFac) 408 { 409 JPEGImage jpg; 410 SuperImage img = null; 411 412 while (istrm.readable) 413 { 414 JPEGMarkerType mt; 415 auto res = readMarker(&jpg, istrm, &mt); 416 if (res[0]) 417 { 418 // TODO: add progressive JPEG support 419 if (mt == JPEGMarkerType.SOF2) 420 { 421 jpg.free(); 422 return compound(img, "loadJPEG error: progressive JPEG is not supported"); 423 } 424 else if (mt == JPEGMarkerType.SOS) 425 break; 426 } 427 else 428 { 429 jpg.free(); 430 return compound(img, res[1]); 431 } 432 } 433 auto res = decodeScanData(&jpg, istrm, imgFac); 434 jpg.free(); 435 return res; 436 } 437 438 /* 439 * Decode marker from JPEG stream 440 */ 441 Compound!(bool, string) readMarker( 442 JPEGImage* jpg, 443 InputStream istrm, 444 JPEGMarkerType* mt) 445 { 446 ushort magic = istrm.readNumeric!ushort(Endian.Big); 447 448 switch (magic) 449 { 450 case 0xFFD8: 451 *mt = JPEGMarkerType.SOI; 452 version(JPEGDebug) writeln("SOI"); 453 break; 454 455 case 0xFFE0: 456 *mt = JPEGMarkerType.APP0; 457 return readJFIF(jpg, istrm); 458 459 case 0xFFE1: 460 *mt = JPEGMarkerType.APPn; 461 return readAPPn(jpg, istrm, 1); 462 463 case 0xFFE2: 464 *mt = JPEGMarkerType.APPn; 465 return readAPPn(jpg, istrm, 2); 466 467 case 0xFFE3: 468 *mt = JPEGMarkerType.APPn; 469 return readAPPn(jpg, istrm, 3); 470 471 case 0xFFE4: 472 *mt = JPEGMarkerType.APPn; 473 return readAPPn(jpg, istrm, 4); 474 475 case 0xFFE5: 476 *mt = JPEGMarkerType.APPn; 477 return readAPPn(jpg, istrm, 5); 478 479 case 0xFFE6: 480 *mt = JPEGMarkerType.APPn; 481 return readAPPn(jpg, istrm, 6); 482 483 case 0xFFE7: 484 *mt = JPEGMarkerType.APPn; 485 return readAPPn(jpg, istrm, 7); 486 487 case 0xFFE8: 488 *mt = JPEGMarkerType.APPn; 489 return readAPPn(jpg, istrm, 8); 490 491 case 0xFFE9: 492 *mt = JPEGMarkerType.APPn; 493 return readAPPn(jpg, istrm, 9); 494 495 case 0xFFEA: 496 *mt = JPEGMarkerType.APPn; 497 return readAPPn(jpg, istrm, 10); 498 499 case 0xFFEB: 500 *mt = JPEGMarkerType.APPn; 501 return readAPPn(jpg, istrm, 11); 502 503 case 0xFFEC: 504 *mt = JPEGMarkerType.APPn; 505 return readAPPn(jpg, istrm, 12); 506 507 case 0xFFED: 508 *mt = JPEGMarkerType.APPn; 509 return readAPPn(jpg, istrm, 13); 510 511 case 0xFFEE: 512 *mt = JPEGMarkerType.APPn; 513 return readAPPn(jpg, istrm, 14); 514 515 case 0xFFEF: 516 *mt = JPEGMarkerType.APPn; 517 return readAPPn(jpg, istrm, 15); 518 519 case 0xFFDB: 520 *mt = JPEGMarkerType.DQT; 521 return readDQT(jpg, istrm); 522 523 case 0xFFC0: 524 *mt = JPEGMarkerType.SOF0; 525 return readSOF0(jpg, istrm); 526 527 case 0xFFC2: 528 *mt = JPEGMarkerType.SOF2; 529 break; 530 531 case 0xFFC4: 532 *mt = JPEGMarkerType.DHT; 533 return readDHT(jpg, istrm); 534 535 case 0xFFDA: 536 *mt = JPEGMarkerType.SOS; 537 return readSOS(jpg, istrm); 538 539 case 0xFFFE: 540 *mt = JPEGMarkerType.COM; 541 return readCOM(jpg, istrm); 542 543 default: 544 *mt = JPEGMarkerType.Unknown; 545 break; 546 } 547 548 return compound(true, ""); 549 } 550 551 Compound!(bool, string) readJFIF(JPEGImage* jpg, InputStream istrm) 552 { 553 ushort jfif_length = istrm.readNumeric!ushort(Endian.Big); 554 555 char[5] jfif_id = istrm.readChars!5; 556 if (jfif_id != "JFIF\0") 557 return compound(false, "loadJPEG error: illegal JFIF header"); 558 559 jpg.jfif.versionMajor = istrm.readNumeric!ubyte; 560 jpg.jfif.versionMinor = istrm.readNumeric!ubyte; 561 jpg.jfif.units = istrm.readNumeric!ubyte; 562 jpg.jfif.xDensity = istrm.readNumeric!ushort(Endian.Big); 563 jpg.jfif.yDensity = istrm.readNumeric!ushort(Endian.Big); 564 jpg.jfif.thumbnailWidth = istrm.readNumeric!ubyte; 565 jpg.jfif.thumbnailHeight = istrm.readNumeric!ubyte; 566 567 uint jfif_thumb_length = jpg.jfif.thumbnailWidth * jpg.jfif.thumbnailHeight * 3; 568 if (jfif_thumb_length > 0) 569 { 570 jpg.jfif.thumbnail = New!(ubyte[])(jfif_thumb_length); 571 istrm.readBytes(jpg.jfif.thumbnail.ptr, jfif_thumb_length); 572 } 573 574 version(JPEGDebug) 575 { 576 writefln("APP0/JFIF length: %s", jfif_length); 577 writefln("APP0/JFIF identifier: %s", jfif_id); 578 writefln("APP0/JFIF version major: %s", jpg.jfif.versionMajor); 579 writefln("APP0/JFIF version minor: %s", jpg.jfif.versionMinor); 580 writefln("APP0/JFIF units: %s", jpg.jfif.units); 581 writefln("APP0/JFIF xdensity: %s", jpg.jfif.xDensity); 582 writefln("APP0/JFIF ydensity: %s", jpg.jfif.yDensity); 583 writefln("APP0/JFIF xthumbnail: %s", jpg.jfif.thumbnailWidth); 584 writefln("APP0/JFIF ythumbnail: %s", jpg.jfif.thumbnailHeight); 585 } 586 587 return compound(true, ""); 588 } 589 590 /* 591 * APP1 - EXIF, XMP, ExtendedXMP, QVCI, FLIR 592 * APP2 - ICC, FPXR, MPF, PreviewImage 593 * APP3 - Kodak Meta, Stim, PreviewImage 594 * APP4 - Scalado, FPXR, PreviewImage 595 * APP5 - RMETA, PreviewImage 596 * APP6 - EPPIM, NITF, HP TDHD 597 * APP7 - Pentax, Qualcomm 598 * APP8 - SPIFF 599 * APP9 - MediaJukebox 600 * APP10 - PhotoStudio comment 601 * APP11 - JPEG-HDR 602 * APP12 - PictureInfo, Ducky 603 * APP13 - Photoshop, Adobe CM 604 * APP14 - Adobe 605 * APP15 - GraphicConverter 606 */ 607 Compound!(bool, string) readAPPn(JPEGImage* jpg, InputStream istrm, uint n) 608 { 609 ushort app_length = istrm.readNumeric!ushort(Endian.Big); 610 ubyte[] app = New!(ubyte[])(app_length-2); 611 istrm.readBytes(app.ptr, app_length-2); 612 613 // TODO: interpret APP data (EXIF etc.) and save it somewhere. 614 // Maybe add a generic ImageInfo object for this? 615 Delete(app); 616 617 version(JPEGDebug) 618 { 619 writefln("APP%s length: %s", n, app_length); 620 } 621 622 return compound(true, ""); 623 } 624 625 Compound!(bool, string) readCOM(JPEGImage* jpg, InputStream istrm) 626 { 627 ushort com_length = istrm.readNumeric!ushort(Endian.Big); 628 ubyte[] com = New!(ubyte[])(com_length-2); 629 istrm.readBytes(com.ptr, com_length-2); 630 631 version(JPEGDebug) 632 { 633 writefln("COM string: \"%s\"", cast(string)com); 634 writefln("COM length: %s", com_length); 635 } 636 637 // TODO: save COM data somewhere. 638 // Maybe add a generic ImageInfo object for this? 639 Delete(com); 640 641 return compound(true, ""); 642 } 643 644 Compound!(bool, string) readDQT(JPEGImage* jpg, InputStream istrm) 645 { 646 ushort dqt_length = istrm.readNumeric!ushort(Endian.Big); 647 version(JPEGDebug) 648 { 649 writefln("DQT length: %s", dqt_length); 650 } 651 652 dqt_length -= 2; 653 654 while(dqt_length) 655 { 656 JPEGImage.DQT* dqt = jpg.addDQT(); 657 658 ubyte bite = istrm.readNumeric!ubyte; 659 dqt.precision = bite.hiNibble; 660 dqt.tableId = bite.loNibble; 661 662 dqt_length--; 663 664 if (dqt.precision == 0) 665 { 666 dqt.table = New!(ubyte[])(64); 667 dqt_length -= 64; 668 } 669 else if (dqt.precision == 1) 670 { 671 dqt.table = New!(ubyte[])(128); 672 dqt_length -= 128; 673 } 674 675 istrm.readBytes(dqt.table.ptr, dqt.table.length); 676 677 version(JPEGDebug) 678 { 679 writefln("DQT precision: %s", dqt.precision); 680 writefln("DQT table id: %s", dqt.tableId); 681 writefln("DQT table: %s", dqt.table); 682 } 683 } 684 685 return compound(true, ""); 686 } 687 688 Compound!(bool, string) readSOF0(JPEGImage* jpg, InputStream istrm) 689 { 690 ushort sof0_length = istrm.readNumeric!ushort(Endian.Big); 691 jpg.sof0.precision = istrm.readNumeric!ubyte; 692 jpg.sof0.height = istrm.readNumeric!ushort(Endian.Big); 693 jpg.sof0.width = istrm.readNumeric!ushort(Endian.Big); 694 jpg.sof0.componentsNum = istrm.readNumeric!ubyte; 695 696 version(JPEGDebug) 697 { 698 writefln("SOF0 length: %s", sof0_length); 699 writefln("SOF0 precision: %s", jpg.sof0.precision); 700 writefln("SOF0 height: %s", jpg.sof0.height); 701 writefln("SOF0 width: %s", jpg.sof0.width); 702 writefln("SOF0 components: %s", jpg.sof0.componentsNum); 703 } 704 705 jpg.sof0.components = New!(JPEGImage.SOF0Component[])(jpg.sof0.componentsNum); 706 707 foreach(ref c; jpg.sof0.components) 708 { 709 ubyte c_id = istrm.readNumeric!ubyte; 710 ubyte bite = istrm.readNumeric!ubyte; 711 c.hSubsampling = bite.hiNibble; 712 c.vSubsampling = bite.loNibble; 713 c.dqtTableId = istrm.readNumeric!ubyte; 714 version(JPEGDebug) 715 { 716 writefln("SOF0 component id: %s", c_id); 717 writefln("SOF0 component %s hsubsampling: %s", c_id, c.hSubsampling); 718 writefln("SOF0 component %s vsubsampling: %s", c_id, c.vSubsampling); 719 writefln("SOF0 component %s table id: %s", c_id, c.dqtTableId); 720 } 721 } 722 723 return compound(true, ""); 724 } 725 726 Compound!(bool, string) readDHT(JPEGImage* jpg, InputStream istrm) 727 { 728 ushort dht_length = istrm.readNumeric!ushort(Endian.Big); 729 version(JPEGDebug) 730 { 731 writefln("DHT length: %s", dht_length); 732 } 733 734 dht_length -= 2; 735 736 while(dht_length > 0) 737 { 738 JPEGImage.DHT* dht = jpg.addDHT(); 739 740 ubyte bite = istrm.readNumeric!ubyte; 741 dht_length--; 742 dht.clas = bite.hiNibble; 743 dht.tableId = bite.loNibble; 744 745 ubyte[16] dht_code_lengths; 746 istrm.readBytes(dht_code_lengths.ptr, 16); 747 dht_length -= 16; 748 749 version(JPEGDebug) 750 { 751 writefln("DHT class: %s (%s)", 752 dht.clas, 753 dht.clas? "AC":"DC"); 754 writefln("DHT tableId: %s", dht.tableId); 755 writefln("DHT Huffman code lengths: %s", dht_code_lengths); 756 } 757 758 // Read Huffman table 759 int totalCodes = reduce!("a + b")(0, dht_code_lengths); 760 int storedCodes = 0; 761 ubyte treeLevel = 0; 762 ushort bits = 0; 763 764 while (storedCodes != totalCodes) 765 { 766 while (treeLevel < 15 && 767 dht_code_lengths[treeLevel] == 0) 768 { 769 treeLevel++; 770 bits *= 2; 771 } 772 773 if (treeLevel < 16) 774 { 775 uint bitsNum = treeLevel + 1; 776 HuffmanCode code = HuffmanCode(bits, cast(ushort)bitsNum); 777 778 auto entry = HuffmanTableEntry(code, istrm.readNumeric!ubyte); 779 dht.huffmanTable.append(entry); 780 781 dht_length--; 782 783 storedCodes++; 784 bits++; 785 dht_code_lengths[treeLevel]--; 786 } 787 } 788 789 dht.huffmanTree = treeFromTable(dht.huffmanTable); 790 } 791 792 return compound(true, ""); 793 } 794 795 Compound!(bool, string) readSOS(JPEGImage* jpg, InputStream istrm) 796 { 797 ushort sos_length = istrm.readNumeric!ushort(Endian.Big); 798 jpg.sos.componentsNum = istrm.readNumeric!ubyte; 799 800 version(JPEGDebug) 801 { 802 writefln("SOS length: %s", sos_length); 803 writefln("SOS components: %s", jpg.sos.componentsNum); 804 } 805 806 jpg.sos.components = New!(JPEGImage.SOSComponent[])(jpg.sos.componentsNum); 807 808 foreach(ref c; jpg.sos.components) 809 { 810 ubyte c_id = istrm.readNumeric!ubyte; 811 ubyte bite = istrm.readNumeric!ubyte; 812 c.tableIdDC = bite.hiNibble; 813 c.tableIdAC = bite.loNibble; 814 version(JPEGDebug) 815 { 816 writefln("SOS component id: %s", c_id); 817 writefln("SOS component %s DC table id: %s", c_id, c.tableIdDC); 818 writefln("SOS component %s AC table id: %s", c_id, c.tableIdAC); 819 } 820 } 821 822 jpg.sos.spectralSelectionStart = istrm.readNumeric!ubyte; 823 jpg.sos.spectralSelectionEnd = istrm.readNumeric!ubyte; 824 ubyte bite = istrm.readNumeric!ubyte; 825 jpg.sos.successiveApproximationBitHigh = bite.hiNibble; 826 jpg.sos.successiveApproximationBitLow = bite.loNibble; 827 828 version(JPEGDebug) 829 { 830 writefln("SOS spectral selection start: %s", jpg.sos.spectralSelectionStart); 831 writefln("SOS spectral selection end: %s", jpg.sos.spectralSelectionEnd); 832 writefln("SOS successive approximation bit: %s", jpg.sos.successiveApproximationBitHigh); 833 writefln("SOS successive approximation bit low: %s", jpg.sos.successiveApproximationBitLow); 834 } 835 836 return compound(true, ""); 837 } 838 839 struct ScanBitStream 840 { 841 InputStream istrm; 842 843 bool endMarkerFound = false; 844 uint bytesRead = 0; 845 ubyte prevByte = 0x00; 846 ubyte curByte = 0x00; 847 848 ubyte readNextByte() 849 { 850 ubyte b = istrm.readNumeric!ubyte; 851 bytesRead++; 852 endMarkerFound = (prevByte == 0xFF && b == 0xD9); 853 assert(!endMarkerFound); 854 if (!endMarkerFound) 855 { 856 prevByte = b; 857 curByte = b; 858 return b; 859 } 860 else 861 { 862 curByte = 0; 863 } 864 return curByte; 865 } 866 867 bool readable() 868 { 869 return !istrm.readable || endMarkerFound; 870 } 871 872 uint bitPos = 0; 873 874 // Huffman decode a byte 875 Compound!(bool, string) decodeByte(HuffmanTreeNode* node, ubyte* result) 876 { 877 while(!node.isLeaf) 878 { 879 ubyte b = curByte; 880 881 bool bit = getBit(b, 7-bitPos); 882 bitPos++; 883 if (bitPos == 8) 884 { 885 bitPos = 0; 886 readNextByte(); 887 888 if (b == 0xFF) 889 { 890 b = curByte; 891 if (b == 0x00) 892 { 893 readNextByte(); 894 } 895 } 896 } 897 898 if (bit) 899 node = node.right; 900 else 901 node = node.left; 902 903 if (node is null) 904 return compound(false, "loadJPEG error: no Huffman code found"); 905 } 906 907 *result = node.ch; 908 return compound(true, ""); 909 } 910 911 // Read len bits from stream to buffer 912 uint readBits(ubyte len) 913 { 914 uint buffer = 0; 915 uint i = 0; 916 uint by = 0; 917 uint bi = 0; 918 919 while (i < len) 920 { 921 ubyte b = curByte; 922 923 bool bit = getBit(b, 7-bitPos); 924 buffer = setBit(buffer, (by * 8 + bi), bit); 925 926 bi++; 927 if (bi == 8) 928 { 929 bi = 0; 930 by++; 931 } 932 933 i++; 934 935 bitPos++; 936 if (bitPos == 8) 937 { 938 bitPos = 0; 939 readNextByte(); 940 941 if (b == 0xFF) 942 { 943 b = curByte; 944 if (b == 0x00) 945 readNextByte(); 946 } 947 } 948 } 949 950 return buffer; 951 } 952 } 953 954 /* 955 * Decodes compressed data and creates RGB image from it 956 */ 957 Compound!(SuperImage, string) decodeScanData( 958 JPEGImage* jpg, 959 InputStream istrm, 960 SuperImageFactory imgFac) 961 { 962 SuperImage img = imgFac.createImage(jpg.sof0.width, jpg.sof0.height, 3, 8); 963 964 MCU mcu; 965 foreach(ci, ref c; jpg.sof0.components) 966 { 967 if (ci == 0) 968 mcu.createYBlocks(c.hSubsampling, c.vSubsampling); 969 else if (ci == 1) 970 mcu.createCbBlocks(c.hSubsampling, c.vSubsampling); 971 else if (ci == 2) 972 mcu.createCrBlocks(c.hSubsampling, c.vSubsampling); 973 } 974 975 Compound!(SuperImage, string) error(string errorMsg) 976 { 977 mcu.free(); 978 if (img) 979 { 980 img.free(); 981 img = null; 982 } 983 return compound(img, errorMsg); 984 } 985 986 // Decode DCT coefficient from bit buffer 987 int decodeCoef(uint buffer, ubyte numBits) 988 { 989 bool positive = getBit(buffer, 0); 990 991 int value = 0; 992 foreach(j; 0..numBits) 993 { 994 bool bit = getBit(buffer, numBits-1-j); 995 value = setBit(value, j, bit); 996 } 997 998 if (positive) 999 return value; 1000 else 1001 return value - 2^^numBits + 1; 1002 } 1003 1004 static const ubyte[64] dezigzag = 1005 [ 1006 0, 1, 8, 16, 9, 2, 3, 10, 1007 17, 24, 32, 25, 18, 11, 4, 5, 1008 12, 19, 26, 33, 40, 48, 41, 34, 1009 27, 20, 13, 6, 7, 14, 21, 28, 1010 35, 42, 49, 56, 57, 50, 43, 36, 1011 29, 22, 15, 23, 30, 37, 44, 51, 1012 58, 59, 52, 45, 38, 31, 39, 46, 1013 53, 60, 61, 54, 47, 55, 62, 63 1014 ]; 1015 1016 if (jpg.sos.componentsNum != 3) 1017 { 1018 return error(format( 1019 "loadJPEG error: unsupported number of components: %s", 1020 jpg.sos.componentsNum)); 1021 } 1022 1023 // Store previous DC coefficients 1024 int[3] dcCoefPrev; 1025 1026 if (jpg.dqt.length == 0) 1027 return error("loadJPEG error: no DQTs found"); 1028 1029 ScanBitStream sbs; 1030 sbs.endMarkerFound = false; 1031 sbs.bytesRead = 0; 1032 sbs.prevByte = 0x00; 1033 sbs.curByte = 0x00; 1034 sbs.istrm = istrm; 1035 sbs.readNextByte(); 1036 1037 uint numMCUsH = jpg.sof0.width / mcu.width + ((jpg.sof0.width % mcu.width) > 0); 1038 uint numMCUsV = jpg.sof0.height / mcu.height + ((jpg.sof0.height % mcu.height) > 0); 1039 1040 // Read MCUs 1041 foreach(mcuY; 0..numMCUsV) 1042 foreach(mcuX; 0..numMCUsH) 1043 { 1044 // Read MCU for each channel 1045 foreach(ci, ref c; jpg.sos.components) 1046 { 1047 auto tableDC = jpg.getHuffmanTable(0, c.tableIdDC); 1048 auto tableAC = jpg.getHuffmanTable(1, c.tableIdAC); 1049 1050 if (tableDC is null) 1051 return error("loadJPEG error: illegal DC table index in MCU component"); 1052 if (tableAC is null) 1053 return error("loadJPEG error: illegal AC table index in MCU component"); 1054 1055 auto component = jpg.sof0.components[ci]; 1056 auto hblocks = component.hSubsampling; 1057 auto vblocks = component.vSubsampling; 1058 auto dqtTableId = component.dqtTableId; 1059 if (dqtTableId >= jpg.dqt.length) 1060 return error("loadJPEG error: illegal DQT table index in MCU component"); 1061 1062 // Read 8x8 blocks 1063 foreach(by; 0..vblocks) 1064 foreach(bx; 0..hblocks) 1065 { 1066 int[8*8] block; 1067 1068 // Read DC coefficient 1069 ubyte dcDiffLen; 1070 auto res = sbs.decodeByte(tableDC.huffmanTree, &dcDiffLen); 1071 if (!res[0]) return error(res[1]); 1072 1073 if (dcDiffLen > 0) 1074 { 1075 uint dcBuffer = sbs.readBits(dcDiffLen); 1076 dcCoefPrev[ci] += decodeCoef(dcBuffer, dcDiffLen); 1077 } 1078 1079 block[0] = dcCoefPrev[ci]; 1080 1081 // Read AC coefficients 1082 { 1083 uint i = 1; 1084 bool eob = false; 1085 while (!eob && i < 64) 1086 { 1087 ubyte code; 1088 res = sbs.decodeByte(tableAC.huffmanTree, &code); 1089 if (!res[0]) return error(res[1]); 1090 1091 if (code == 0x00) // EOB, all next values are zero 1092 eob = true; 1093 else if (code == 0xF0) // ZRL, next 16 values are zero 1094 { 1095 foreach(j; 0..16) 1096 if (i < 64) 1097 { 1098 block[i] = 0; 1099 i++; 1100 } 1101 } 1102 else 1103 { 1104 ubyte hi = hiNibble(code); 1105 ubyte lo = loNibble(code); 1106 1107 uint zeroes = hi; 1108 foreach(j; 0..zeroes) 1109 if (i < 64) 1110 { 1111 block[i] = 0; 1112 i++; 1113 } 1114 1115 int acCoef = 0; 1116 if (lo > 0) 1117 { 1118 uint acBuffer = sbs.readBits(lo); 1119 acCoef = decodeCoef(acBuffer, lo); 1120 } 1121 1122 if (i < 64) 1123 block[i] = acCoef; 1124 1125 i++; 1126 } 1127 } 1128 } 1129 1130 // Multiply block by quantization matrix 1131 foreach(i, ref v; block) 1132 v *= jpg.dqt[dqtTableId].table[i]; 1133 1134 // Convert matrix from zig-zag order to normal order 1135 int[8*8] dctMatrix; 1136 1137 foreach(i, v; block) 1138 dctMatrix[dezigzag[i]] = v; 1139 1140 idct64(dctMatrix.ptr); 1141 1142 // Copy the matrix into corresponding channel 1143 int* outMatrixPtr; 1144 if (ci == 0) 1145 outMatrixPtr = mcu.yBlocks[by * hblocks + bx].ptr; 1146 else if (ci == 1) 1147 outMatrixPtr = mcu.cbBlocks[by * hblocks + bx].ptr; 1148 else if (ci == 2) 1149 outMatrixPtr = mcu.crBlocks[by * hblocks + bx].ptr; 1150 else 1151 return error("loadJPEG error: illegal component index"); 1152 1153 for(uint i = 0; i < 64; i++) 1154 outMatrixPtr[i] = dctMatrix[i]; 1155 } 1156 } 1157 1158 // Convert MCU from YCbCr to RGB 1159 foreach(y; 0..mcu.height) // Pixel coordinates in MCU 1160 foreach(x; 0..mcu.width) 1161 { 1162 //Color4f col = mcu.getPixel(x, y); 1163 uint col = mcu.getPixel(x, y); 1164 1165 // Pixel coordinates in image 1166 uint ix = mcuX * mcu.width + x; 1167 uint iy = mcuY * mcu.height + y; 1168 1169 if (ix < img.width && iy < img.height) 1170 img[ix, iy] = col; 1171 } 1172 } 1173 1174 version(JPEGDebug) 1175 { 1176 writefln("Bytes read: %s", sbs.bytesRead); 1177 } 1178 1179 mcu.free(); 1180 1181 return compound(img, ""); 1182 } 1183 1184 /* 1185 * MCU struct keeps a storage for one Minimal Code Unit 1186 * and provides a generalized interface for decoding 1187 * images with different subsampling modes. 1188 * Decoder should read 8x8 blocks one by one for each channel 1189 * and fill corresponding arrays in MCU. 1190 */ 1191 struct MCU 1192 { 1193 uint width; 1194 uint height; 1195 1196 alias int[8*8] Block; 1197 Block[] yBlocks; 1198 Block[] cbBlocks; 1199 Block[] crBlocks; 1200 1201 uint ySamplesH, ySamplesV; 1202 uint cbSamplesH, cbSamplesV; 1203 uint crSamplesH, crSamplesV; 1204 1205 uint yWidth, yHeight; 1206 uint cbWidth, cbHeight; 1207 uint crWidth, crHeight; 1208 1209 void createYBlocks(uint hsubsampling, uint vsubsampling) 1210 { 1211 yBlocks = New!(Block[])(hsubsampling * vsubsampling); 1212 1213 width = hsubsampling * 8; 1214 height = vsubsampling * 8; 1215 1216 ySamplesH = hsubsampling; 1217 ySamplesV = vsubsampling; 1218 1219 yWidth = width / ySamplesH; 1220 yHeight = height / ySamplesV; 1221 } 1222 1223 void createCbBlocks(uint hsubsampling, uint vsubsampling) 1224 { 1225 cbBlocks = New!(Block[])(hsubsampling * vsubsampling); 1226 1227 cbSamplesH = hsubsampling; 1228 cbSamplesV = vsubsampling; 1229 1230 cbWidth = width / cbSamplesH; 1231 cbHeight = height / cbSamplesV; 1232 } 1233 1234 void createCrBlocks(uint hsubsampling, uint vsubsampling) 1235 { 1236 crBlocks = New!(Block[])(hsubsampling * vsubsampling); 1237 1238 crSamplesH = hsubsampling; 1239 crSamplesV = vsubsampling; 1240 1241 crWidth = width / crSamplesH; 1242 crHeight = height / crSamplesV; 1243 } 1244 1245 void free() 1246 { 1247 if (yBlocks.length) Delete(yBlocks); 1248 if (cbBlocks.length) Delete(cbBlocks); 1249 if (crBlocks.length) Delete(crBlocks); 1250 } 1251 1252 uint getPixel(uint x, uint y) // coordinates relative to upper-left MCU corner 1253 { 1254 // Y block coordinates 1255 uint ybx = x / yWidth; 1256 uint yby = y / yHeight; 1257 uint ybi = yby * ySamplesH + ybx; 1258 1259 // Pixel coordinates in Y block 1260 uint ybpx = x - ybx * yWidth; 1261 uint ybpy = y - yby * yHeight; 1262 1263 // Cb block coordinates 1264 uint cbx = x / cbWidth; 1265 uint cby = y / cbHeight; 1266 uint cbi = cby * cbSamplesH + cbx; 1267 1268 // Pixel coordinates in Cb block 1269 uint cbpx = (x - cbx * cbWidth) / ySamplesH; 1270 uint cbpy = (y - cby * cbHeight) / ySamplesV; 1271 1272 // Cr block coordinates 1273 uint crx = x / crWidth; 1274 uint cry = y / crHeight; 1275 uint cri = cry * crSamplesH + crx; 1276 1277 // Pixel coordinates in Cr block 1278 uint crpx = (x - crx * crWidth) / ySamplesH; 1279 uint crpy = (y - cry * crHeight) / ySamplesV; 1280 1281 // Get color components 1282 float Y = cast(float)yBlocks [ybi][ybpy * 8 + ybpx] + 128.0f; 1283 float Cb = cast(float)cbBlocks[cbi][cbpy * 8 + cbpx]; 1284 float Cr = cast(float)crBlocks[cri][crpy * 8 + crpx]; 1285 1286 // Convert from YCbCr to RGB 1287 //Color4f col; 1288 uint col_r = clamp(Y + 1.402f * Cr); 1289 uint col_g = clamp(Y - 0.34414f * Cb - 0.71414f * Cr); 1290 uint col_b = clamp(Y + 1.772f * Cb); 1291 //col = col / 255.0f; 1292 //col.a = 1.0f; 1293 1294 return 0xFF000000 | (col_r << 16) | (col_g << 8) | (col_b) ; 1295 } 1296 } 1297 1298 uint clamp(float v) { 1299 import std.conv; 1300 if (v < 0) 1301 return 0; 1302 uint res = to!uint(v); 1303 if (v > 255) 1304 return 255; 1305 return res; 1306 }