The OpenD Programming Language

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 = &currentPalette[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 }