1 /** 2 New GIF decoder, to replace gifdec.d. 3 */ 4 module gamut.codecs.gif; 5 6 version(decodeGIF): 7 8 9 10 import gamut.io; 11 import gamut.image; 12 import gamut.types; 13 import core.stdc.string: memcmp, memset, memcpy; 14 import core.stdc.stdlib: malloc, free; 15 16 nothrow @nogc: 17 18 //debug = gifLoading; 19 20 debug(gifLoading) import core.stdc.stdio; 21 22 /// Reference: GIF89a Specification. 23 struct GIFDecoder 24 { 25 nothrow @nogc: 26 27 // MAYDO: it's not sure if disposal 3 method should be better supported. 28 // it seems that different open source GIF decoders do it differently, 29 // and it's quite uncommon too. So, we have no support (it will decode incorrectly). 30 31 // Parse the GIF minimally to have the layers count. 32 void open(IOStream *io, IOHandle handle, bool* err) 33 { 34 this.io = io; 35 this.handle = handle; 36 parseHeader(err); if (*err) return; 37 } 38 39 // <available after open> 40 int width() 41 { 42 return logicalScreenWidth; 43 } 44 int height() 45 { 46 return logicalScreenHeight; 47 } 48 int numLayers() 49 { 50 return layers; 51 } 52 float FPS() 53 { 54 return imageFPS; 55 } 56 float getPixelAspectRatio() 57 { 58 if (pixelAspectRatio == 0) 59 { 60 return GAMUT_UNKNOWN_ASPECT_RATIO; 61 } 62 else 63 { 64 return (pixelAspectRatio + 15.0f) / 64; 65 } 66 } 67 // </available after open> 68 69 void parseHeader(bool* err) 70 { 71 debug(gifLoading) printf("HEADER\n"); 72 char[6] magic; 73 if (1 != io.read(magic.ptr, 6, 1, handle)) 74 { 75 *err = true; 76 return; 77 } 78 79 if (memcmp(magic.ptr, "GIF87a".ptr, 6) == 0) 80 { 81 isGIF89 = false; 82 } 83 else if (memcmp(magic.ptr, "GIF89a".ptr, 6) == 0) 84 { 85 isGIF89 = true; 86 } 87 else 88 { 89 *err = true; 90 return; 91 } 92 93 logicalScreenWidth = read_ushort(err); if (*err) return; 94 logicalScreenHeight = read_ushort(err); if (*err) return; 95 ubyte imageFlags = read_ubyte(err); if (*err) return; 96 backgroundColorIndex = read_ubyte(err); if (*err) return; 97 pixelAspectRatio = read_ubyte(err); if (*err) return; 98 99 transparent = -1; 100 101 102 // Allocate all memory needed for decoder 103 int pcount = logicalScreenWidth * logicalScreenHeight; 104 105 if (globalAlloc is null) 106 { 107 // Always need 34kb of memory. 108 size_t gctSizeMax = 256*4; 109 size_t lctSizeMax = 256*4; 110 size_t outSize = pcount*4; 111 size_t bgSize = pcount*4; 112 size_t historySize = pcount; 113 size_t codesSize = 8192 * LZWCode.sizeof; 114 globalAlloc = cast(ubyte*) malloc(gctSizeMax + lctSizeMax + outSize 115 + bgSize + historySize + codesSize); 116 ubyte* mem = globalAlloc; 117 gct = mem; mem += gctSizeMax; 118 lct = mem; mem += lctSizeMax; 119 out_ = mem; mem += outSize; 120 background = mem; mem += bgSize; 121 history = mem; mem += historySize; 122 codes = cast(LZWCode*)mem; mem += codesSize; 123 } 124 125 if (imageFlags & 0x80) 126 { 127 // "Size of Global Color Table - If the Global Color Table Flag is 128 // set to 1, the value in this field is used to calculate the number 129 // of bytes contained in the Global Color Table." 130 gctSize = 1 << ((imageFlags & 0x07) + 1); 131 132 // Parse GCT 133 parseColortable(gct, gctSize, -1, err); if (*err) return; 134 } 135 else 136 { 137 gct = null; 138 gctSize = 0; 139 } 140 141 currentPalette = gct; 142 currentPaletteSize = gctSize; 143 144 // Number of bits to represent components. 145 // "For example, if the value in this field is 3, then the palette of 146 // the original image had 4 bits per primary color available to create 147 // the image. This value should be set to indicate the richness of 148 // the original palette, even if not every color from the whole 149 // palette is available on the source machine." 150 colorResolution = ((imageFlags >> 4) & 7) + 1; 151 152 // Count frames in image. 153 154 auto offset = saveFileOffset(err); if (*err) return; 155 156 layers = 0; 157 int frameDurationMs = 100; 158 double sumDurationsMs = 0.0; 159 firstFrame = true; 160 while(true) 161 { 162 bool gotOneFrame; 163 decodeNextFrame(&gotOneFrame, null, &frameDurationMs, err); 164 if (*err) return; 165 if (!gotOneFrame) 166 break; 167 layers++; 168 sumDurationsMs += frameDurationMs; 169 170 // note: by breaking here, we can limit number of decoded frames. 171 //break; 172 } 173 // Note: last GCE encountered gives the "FPS", we assume GIF frame have same delay, 174 // which is incorrect. 175 if (sumDurationsMs == 0) 176 { 177 imageFPS = 10; 178 } 179 else 180 { 181 imageFPS = layers * 1000.0f / sumDurationsMs; 182 } 183 184 restoreFileOffset(offset, err); if (*err) return; 185 186 firstFrame = true; 187 *err = false; 188 return; 189 } 190 191 ~this() 192 { 193 free(globalAlloc); 194 globalAlloc = null; 195 } 196 197 /// Decode next GIF frame in a rgba8 buffer. 198 void decodeNextFrame(bool* gotOneFrame, // Filled with true if got one frame. 199 Image* outFrame, // can be null if no pixel decoding needed, pixels if got one frame 200 int* frameDurationMs, // written with that frame duration. 201 bool* err) 202 { 203 *gotOneFrame = false; 204 *err = false; 205 bool needDecode = outFrame !is null; 206 int res = parseFrame(err, needDecode); 207 if (*err) return; 208 209 if (res == 0) 210 { 211 // end of stream 212 return; 213 } 214 else if (res == 1) 215 { 216 if (outFrame) 217 { 218 assert(outFrame.width == logicalScreenWidth); 219 assert(outFrame.height == logicalScreenHeight); 220 221 int scanlineBytes = 4 * outFrame.width; 222 for (int y = 0; y < outFrame.height; ++y) 223 { 224 int pixelIndex = y * logicalScreenWidth; 225 memcpy( outFrame.scanptr(y), &out_[pixelIndex*4], scanlineBytes ); 226 } 227 } 228 229 *gotOneFrame = true; 230 *frameDurationMs = gce.frameDurationMs; 231 } 232 else 233 assert(false); 234 } 235 236 private: 237 IOStream *io; 238 IOHandle handle; 239 bool isGIF89; 240 int logicalScreenWidth; 241 int logicalScreenHeight; 242 int layers; 243 float imageFPS; 244 ubyte backgroundColorIndex; 245 ubyte pixelAspectRatio; 246 247 // like in stb_image, palette are a RGBA quadruplet 248 ubyte* gct, lct, currentPalette; 249 int gctSize, lctSize, currentPaletteSize; 250 251 // Latest frame information 252 int frameX, frameY, frameW, frameH; 253 254 // Next pixel to write to. 255 int currentX, currentY; 256 257 // Pass of interlaced scan (0 to 3). 258 int interlacedPass; 259 260 // If the frame is interlaced in first place. 261 bool frameInterlaced; 262 263 // Current transparent color index. This is used to modify global color palette in place. 264 int transparent; 265 266 ubyte* globalAlloc; 267 ubyte* background; 268 ubyte* history; 269 ubyte* out_; 270 bool firstFrame; 271 272 static struct GCE 273 { 274 nothrow @nogc: 275 ubyte disposalMethod; 276 bool transparencyFlag; 277 ushort delayTime = 0; 278 ubyte transparentColorIndex; 279 280 int frameDurationMs() 281 { 282 if (delayTime == 0 || delayTime == 1) 283 return 100; // 100 ms duration 284 else 285 return delayTime * 10; 286 } 287 } 288 289 GCE gce; 290 291 int colorResolution; 292 293 align(1) static struct LZWCode 294 { 295 align(1): 296 short prefix; 297 ubyte first; 298 ubyte suffix; 299 } 300 static assert(LZWCode.sizeof == 4); 301 LZWCode* codes; 302 303 304 ushort read_ushort(bool* err) 305 { 306 return io.read_ushort_LE(handle, err); 307 } 308 309 ubyte read_ubyte(bool* err) 310 { 311 return io.read_ubyte(handle, err); 312 } 313 314 c_long saveFileOffset(bool* err) 315 { 316 c_long res = io.tell(handle); 317 if (res == -1) 318 *err = true; 319 else 320 *err = false; 321 return res; 322 } 323 324 void restoreFileOffset(c_long absOffset, bool* err) 325 { 326 if (io.seekAbsolute(handle, absOffset)) 327 *err = false; 328 else 329 *err = true; 330 } 331 332 enum ubyte PART_LWZ_IMAGE = ','; // 0x2C 333 enum ubyte PART_END_OF_STREAM = ';'; // 0x3B 334 enum ubyte PART_EXTENSION = '!'; // 0x21 335 336 enum ubyte LABEL_TEXT_ENTRY_DEFINITION = 0x01; 337 enum ubyte LABEL_GRAPHICS_CONTROL = 0xF9; 338 enum ubyte LABEL_COMMENT = 0xFE; 339 enum ubyte LABEL_APPLICATION_INFO = 0xFF; 340 341 // Parse until can return one frame. 342 // Return 0 for end of stream. 343 // Return 1 for one frame. 344 // *err is true if input error, in which case abandon decoding. Return -1 in that case. 345 // outImage can be null if no pixel decoding needed. 346 int parseFrame(bool* err, bool needDecode) 347 { 348 if (firstFrame) 349 { 350 int pcount = logicalScreenWidth * logicalScreenHeight; 351 352 // image is treated as "transparent" at the start - ie, nothing overwrites the current 353 // background; 354 // background colour is only used for pixels that are not rendered first frame, after 355 // that "background" color refers to the color that was there the previous frame. 356 memset(out_, 0, 4 * pcount); 357 memset(background, 0, 4 * pcount); // state of the background (starts transparent) 358 memset(history, 0, pcount); // pixels that were affected previous frame 359 360 firstFrame = false; 361 } 362 else if (needDecode) 363 { 364 // Dispose previous frame 365 // second frame - how do we dispose of the previous one? 366 367 int dispose = gce.disposalMethod; 368 int pcount = logicalScreenWidth * logicalScreenHeight; 369 370 ubyte* two_back = null; 371 372 if ((dispose == 3) && (two_back == null)) 373 { 374 dispose = 2; // if I don't have an image to revert back to, default to the old background 375 } 376 377 if (dispose == 3) // use previous graphic 378 { 379 for (int pi = 0; pi < pcount; ++pi) 380 { 381 if (history[pi]) 382 { 383 memcpy(&out_[pi * 4], &two_back[pi * 4], 4 ); 384 } 385 } 386 } 387 else if (dispose == 2) 388 { 389 // restore what was changed last frame to background before that frame; 390 for (int pi = 0; pi < pcount; ++pi) 391 { 392 if (history[pi]) 393 { 394 memcpy(&out_[pi * 4], &background[pi * 4], 4 ); 395 } 396 } 397 } 398 else 399 { 400 // This is a non-disposal case either way, so just 401 // leave the pixels as is, and they will become the new background 402 // 1: do not dispose 403 // 0: not specified. 404 } 405 406 // background is what out is after the undoing of the previou frame; 407 assert(background !is null); 408 assert(out_ !is null); 409 memcpy(background, out_, 4 * pcount); 410 } 411 412 while(true) 413 { 414 ubyte sep = read_ubyte(err); if (*err) return -1; 415 if (sep == PART_LWZ_IMAGE) 416 { 417 parseLWZImage(err, needDecode); if (*err) return -1; 418 return 1; 419 } 420 else if (sep == PART_END_OF_STREAM) 421 { 422 return 0; // EOF 423 } 424 else if (sep == PART_EXTENSION) 425 { 426 ubyte label = read_ubyte(err); if (*err) return -1; 427 428 switch (label) 429 { 430 case LABEL_TEXT_ENTRY_DEFINITION: 431 parseTextEntryExt(err); 432 if (*err) return -1; 433 break; 434 435 case LABEL_GRAPHICS_CONTROL: 436 parseGraphicsControlExt(err); 437 if (*err) return -1; 438 break; 439 440 case LABEL_COMMENT: 441 parseCommentExt(err); 442 if (*err) return -1; 443 break; 444 445 case LABEL_APPLICATION_INFO: 446 parseApplicationInfoExt(err); 447 if (*err) return -1; 448 break; 449 450 default: 451 *err = true; 452 return -1; 453 // unknown extension, error 454 } 455 } 456 else 457 { 458 *err = true; 459 return -1; // unknown syntax 460 } 461 } 462 } 463 464 bool skip_bytes(int numBytes) 465 { 466 return io.skipBytes(handle, numBytes); 467 } 468 469 void skipSubblocks(bool* err) 470 { 471 ubyte size; 472 do 473 { 474 size = read_ubyte(err); if (*err) return; 475 if (!skip_bytes(size)) 476 { 477 *err = true; 478 return; 479 } 480 } while (size); 481 *err = false; 482 } 483 484 void parseGraphicsControlExt(bool* err) 485 { 486 debug(gifLoading) printf("GCE\n"); 487 488 ubyte size = read_ubyte(err); if (*err) return; 489 if (size != 4) 490 { 491 *err = true; 492 return; 493 } 494 495 ubyte rdit = read_ubyte(err); if (*err) return; 496 497 gce.disposalMethod = (rdit >> 2) & 3; 498 gce.transparencyFlag = rdit & 1; 499 gce.delayTime = read_ushort(err); if (*err) return; 500 gce.transparentColorIndex = read_ubyte(err); if (*err) return; 501 502 // unset old transparent 503 if (transparent >= 0 && gct) 504 { 505 gct[4 * transparent + 3] = 255; 506 } 507 508 if (gce.transparencyFlag) 509 { 510 transparent = gce.transparentColorIndex; 511 if (transparent >= 0 && gct) 512 { 513 gct[4 * transparent + 3] = 0; 514 } 515 } 516 else 517 { 518 transparent = -1; 519 } 520 521 debug(gifLoading) printf(" * delayTime = %d ms\n", gce.frameDurationMs()); 522 debug(gifLoading) printf(" * disposal method = %d\n", gce.disposalMethod); 523 524 ubyte zero = read_ubyte(err); if (*err) return; 525 if (zero != 0) 526 { 527 *err = true; 528 return; 529 } 530 531 *err = false; 532 } 533 534 void parseCommentExt(bool* err) 535 { 536 debug(gifLoading) printf("COMMENT\n"); 537 538 skipSubblocks(err); // ignore all 539 } 540 541 void parseTextEntryExt(bool* err) 542 { 543 debug(gifLoading) printf("TEXT ENTRY\n"); 544 // Discard plain text metadata. 545 if (!skip_bytes(13)) /* block size = 12 */ 546 { 547 *err = true; 548 return; 549 } 550 skipSubblocks(err); 551 } 552 553 void parseLWZImage(bool* err, bool needDecode) 554 { 555 debug(gifLoading) printf("IMAGE\n"); 556 557 frameX = read_ushort(err); if (*err) return; 558 frameY = read_ushort(err); if (*err) return; 559 frameW = read_ushort(err); if (*err) return; 560 frameH = read_ushort(err); if (*err) return; 561 ubyte frameFlags = read_ubyte(err); if (*err) return; 562 563 // Reference: https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art011 564 // "If the interlace flag is set, then the lines of the image will be included out of 565 // order: first, every 8th line. Second, every 8th line, starting from the fourth. 566 // Third, every 4th line, starting from the second, and finally every other line, 567 // completing the image. 568 frameInterlaced = (frameFlags & 0x40) != 0; 569 570 /* Local Color Table? */ 571 if (frameFlags & 0x80) 572 { 573 // has local table 574 lctSize = 1 << ((frameFlags & 0x07) + 1); 575 576 // Parse LCT 577 int transpIndex = gce.transparencyFlag ? transparent : -1; 578 if (needDecode) 579 { 580 parseColortable(lct, lctSize, transpIndex, err); if (*err) return; 581 } 582 else 583 { 584 if (!io.skipBytes(handle, lctSize*3)) 585 { 586 *err = true; 587 return; 588 } 589 } 590 591 currentPalette = lct; 592 currentPaletteSize = lctSize; 593 } 594 else 595 { 596 currentPalette = gct; 597 currentPaletteSize = gctSize; 598 } 599 600 debug(gifLoading) printf(" * frameX = %d\n", frameX); 601 debug(gifLoading) printf(" * frameY = %d\n", frameY); 602 debug(gifLoading) printf(" * frameW = %d\n", frameW); 603 debug(gifLoading) printf(" * frameH = %d\n", frameH); 604 debug(gifLoading) printf(" * interlaced = %d\n", frameInterlaced); 605 debug(gifLoading) printf(" * local table = %d\n", (frameFlags & 0x80) ); 606 607 interlacedPass = 0; 608 currentX = frameX; 609 currentY = frameY; 610 611 parseImageData(err, needDecode); 612 } 613 614 void parseApplicationInfoExt(bool* err) 615 { 616 debug(gifLoading) printf("APPLICATION INFO\n"); 617 ubyte blockSize = read_ubyte(err); if (*err) return; 618 619 // skip 620 if (!skip_bytes(blockSize)) 621 { 622 *err = true; 623 return; 624 } 625 skipSubblocks(err); 626 } 627 628 void parseImageData(bool* err, bool needDecode) 629 { 630 *err = false; 631 632 LZWCode *p; 633 ubyte lzw_cs = read_ubyte(err); if (*err) return; 634 635 if (lzw_cs > 12) 636 { 637 *err = true; 638 return; 639 } 640 641 int clear = 1 << lzw_cs; 642 uint first = 1; 643 int codesize = lzw_cs + 1; 644 int codemask = (1 << codesize) - 1; 645 int bits = 0; 646 int valid_bits = 0; 647 int init_code; 648 649 for (init_code = 0; init_code < clear; init_code++) 650 { 651 codes[init_code].prefix = -1; 652 codes[init_code].first = cast(ubyte) init_code; 653 codes[init_code].suffix = cast(ubyte) init_code; 654 } 655 656 // support no starting clear code 657 int avail = clear+2; 658 int oldcode = -1; 659 660 int len = 0; 661 662 void* output = null; 663 664 while(true) 665 { 666 if (valid_bits < codesize) 667 { 668 if (len == 0) 669 { 670 len = read_ubyte(err); if (*err) return; 671 if (len == 0) 672 return /* output */; 673 } 674 --len; 675 int newbits = read_ubyte(err); if (*err) return; 676 bits |= (newbits << valid_bits); 677 valid_bits += 8; 678 } 679 else 680 { 681 int code = bits & codemask; 682 bits >>= codesize; 683 valid_bits -= codesize; 684 // @OPTIMIZE: is there some way we can accelerate the non-clear path? 685 if (code == clear) 686 { 687 // clear code 688 codesize = lzw_cs + 1; 689 codemask = (1 << codesize) - 1; 690 avail = clear + 2; 691 oldcode = -1; 692 first = 0; 693 } 694 else if (code == clear + 1) 695 { 696 // end of stream code 697 if (!io.skipBytes(handle, len)) 698 { 699 *err = true; 700 return; 701 } 702 703 len = read_ubyte(err); if (*err) return; 704 while (len > 0) 705 { 706 if (!io.skipBytes(handle, len)) 707 { 708 *err = true; 709 return; 710 } 711 len = read_ubyte(err); if (*err) return; 712 } 713 return /* output */; 714 } 715 else if (code <= avail) 716 { 717 if (first) 718 { 719 // no clear code, corrupt GIF 720 *err = true; 721 return; 722 } 723 724 if (oldcode >= 0) 725 { 726 p = &codes[avail++]; 727 if (avail > 8192) 728 { 729 // too many codes, corrupt GIF 730 *err = true; 731 return; 732 } 733 p.prefix = cast(short) oldcode; 734 p.first = codes[oldcode].first; 735 p.suffix = (code == avail) ? p.first : codes[code].first; 736 } 737 else if (code == avail) 738 { 739 // illegal code in raster, corrupt GIF 740 *err = true; 741 return; 742 } 743 744 //printf("code %d\n", cast(short)code); 745 if (needDecode) 746 stbi__out_gif_code(cast(ushort) code); 747 748 if ((avail & codemask) == 0 && avail <= 0x0FFF) 749 { 750 codesize++; 751 codemask = (1 << codesize) - 1; 752 } 753 754 oldcode = code; 755 } 756 else 757 { 758 // illegal code in raster, corrupt GIF 759 *err = true; 760 return; 761 } 762 } 763 } 764 } 765 766 // "The rows of an Interlaced images are arranged in the following order: 767 // 768 // Group 1 : Every 8th. row, starting with row 0. (Pass 1) 769 // Group 2 : Every 8th. row, starting with row 4. (Pass 2) 770 // Group 3 : Every 4th. row, starting with row 2. (Pass 3) 771 // Group 4 : Every 2nd. row, starting with row 1. (Pass 4)" 772 static immutable interlacedPassYStep = [8, 8, 4, 2]; 773 static immutable interlacedPassYStart = [0, 4, 2, 1]; 774 775 void stbi__out_gif_code(ushort code) 776 { 777 // Recurse to decode the prefixes, since the linked-list is backwards, 778 // and working backwards through an interleaved image would be nasty 779 if (codes[code].prefix >= 0) 780 stbi__out_gif_code(codes[code].prefix); 781 782 if (currentY >= logicalScreenHeight) 783 return; 784 785 int pIndex = (currentY * logicalScreenWidth + currentX); 786 787 ubyte* pixelptr = &out_[pIndex * 4 ]; 788 789 history[pIndex] = 1; 790 791 ubyte* c = ¤tPalette[codes[code].suffix * 4]; 792 793 if (c[3] > 128) 794 { 795 pixelptr[0] = c[0]; 796 pixelptr[1] = c[1]; 797 pixelptr[2] = c[2]; 798 pixelptr[3] = 255; 799 } 800 801 // Manages location of next pixel 802 803 currentX += 1; 804 if (currentX >= frameX + frameW) 805 { 806 currentX = frameX; 807 if (frameInterlaced) 808 { 809 currentY += interlacedPassYStep[interlacedPass]; 810 811 if (currentY >= frameY + frameH) 812 { 813 if (interlacedPass < 3) 814 { 815 interlacedPass += 1; 816 currentY = frameY + interlacedPassYStart[interlacedPass]; 817 } 818 } 819 } 820 else 821 { 822 currentY += 1; 823 } 824 } 825 } 826 827 void parseColortable(ubyte* palette, int num_entries, int transp, bool* err) 828 { 829 *err = false; 830 for (int i = 0; i < num_entries; ++i) 831 { 832 if (1 != io.read(&palette[4*i], 3, 1, handle)) 833 { 834 *err = true; 835 return; 836 } 837 palette[4*i+3] = (transp == i ? 0 : 255); 838 } 839 } 840 }