1 /*
2  * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*-
27  *      Reads GIF images from an InputStream and reports the
28  *      image data to an InputStreamImageSource object.
29  *
30  * The algorithm is copyright of CompuServe.
31  */
32 package sun.awt.image;
33 
34 import java.util.Hashtable;
35 import java.io.InputStream;
36 import java.io.IOException;
37 import java.awt.image.*;
38 
39 /**
40  * Gif Image converter
41  *
42  * @author Arthur van Hoff
43  * @author Jim Graham
44  */
45 public class GifImageDecoder extends ImageDecoder {
46     private static final boolean verbose = false;
47 
48     private static final int IMAGESEP           = 0x2c;
49     private static final int EXBLOCK            = 0x21;
50     private static final int EX_GRAPHICS_CONTROL= 0xf9;
51     private static final int EX_COMMENT         = 0xfe;
52     private static final int EX_APPLICATION     = 0xff;
53     private static final int TERMINATOR         = 0x3b;
54     private static final int TRANSPARENCYMASK   = 0x01;
55     private static final int INTERLACEMASK      = 0x40;
56     private static final int COLORMAPMASK       = 0x80;
57 
58     int num_global_colors;
59     byte[] global_colormap;
60     int trans_pixel = -1;
61     IndexColorModel global_model;
62 
63     Hashtable<String, Object> props = new Hashtable<>();
64 
65     byte[] saved_image;
66     IndexColorModel saved_model;
67 
68     int global_width;
69     int global_height;
70     int global_bgpixel;
71 
72     GifFrame curframe;
73 
GifImageDecoder(InputStreamImageSource src, InputStream is)74     public GifImageDecoder(InputStreamImageSource src, InputStream is) {
75         super(src, is);
76     }
77 
78     /**
79      * An error has occurred. Throw an exception.
80      */
error(String s1)81     private static void error(String s1) throws ImageFormatException {
82         throw new ImageFormatException(s1);
83     }
84 
85     /**
86      * Read a number of bytes into a buffer.
87      * @return number of bytes that were not read due to EOF or error
88      */
readBytes(byte[] buf, int off, int len)89     private int readBytes(byte[] buf, int off, int len) {
90         while (len > 0) {
91             try {
92                 int n = input.read(buf, off, len);
93                 if (n < 0) {
94                     break;
95                 }
96                 off += n;
97                 len -= n;
98             } catch (IOException e) {
99                 break;
100             }
101         }
102         return len;
103     }
104 
ExtractByte(byte[] buf, int off)105     private static final int ExtractByte(byte[] buf, int off) {
106         return (buf[off] & 0xFF);
107     }
108 
ExtractWord(byte[] buf, int off)109     private static final int ExtractWord(byte[] buf, int off) {
110         return (buf[off] & 0xFF) | ((buf[off + 1] & 0xFF) << 8);
111     }
112 
113     /**
114      * produce an image from the stream.
115      */
116     @SuppressWarnings({"fallthrough", "deprecation"})
produceImage()117     public void produceImage() throws IOException, ImageFormatException {
118         try {
119             readHeader();
120 
121             int totalframes = 0;
122             int frameno = 0;
123             int nloops = -1;
124             int disposal_method = 0;
125             int delay = -1;
126             boolean loopsRead = false;
127             boolean isAnimation = false;
128 
129             while (!aborted) {
130                 int code;
131 
132                 switch (code = input.read()) {
133                   case EXBLOCK:
134                     switch (code = input.read()) {
135                       case EX_GRAPHICS_CONTROL: {
136                         byte[] buf = new byte[6];
137                         if (readBytes(buf, 0, 6) != 0) {
138                             return;//error("corrupt GIF file");
139                         }
140                         if ((buf[0] != 4) || (buf[5] != 0)) {
141                             return;//error("corrupt GIF file (GCE size)");
142                         }
143                         // Get the index of the transparent color
144                         delay = ExtractWord(buf, 2) * 10;
145                         if (delay > 0 && !isAnimation) {
146                             isAnimation = true;
147                             ImageFetcher.startingAnimation();
148                         }
149                         disposal_method = (buf[1] >> 2) & 7;
150                         if ((buf[1] & TRANSPARENCYMASK) != 0) {
151                             trans_pixel = ExtractByte(buf, 4);
152                         } else {
153                             trans_pixel = -1;
154                         }
155                         break;
156                       }
157 
158                       case EX_COMMENT:
159                       case EX_APPLICATION:
160                       default:
161                         boolean loop_tag = false;
162                         String comment = "";
163                         while (true) {
164                             int n = input.read();
165                             if (n <= 0) {
166                                 break;
167                             }
168                             byte[] buf = new byte[n];
169                             if (readBytes(buf, 0, n) != 0) {
170                                 return;//error("corrupt GIF file");
171                             }
172                             if (code == EX_COMMENT) {
173                                 comment += new String(buf, 0);
174                             } else if (code == EX_APPLICATION) {
175                                 if (loop_tag) {
176                                     if (n == 3 && buf[0] == 1) {
177                                         if (loopsRead) {
178                                             ExtractWord(buf, 1);
179                                         }
180                                         else {
181                                             nloops = ExtractWord(buf, 1);
182                                             loopsRead = true;
183                                         }
184                                     } else {
185                                         loop_tag = false;
186                                     }
187                                 }
188                                 if ("NETSCAPE2.0".equals(new String(buf, 0))) {
189                                     loop_tag = true;
190                                 }
191                             }
192                         }
193                         if (code == EX_COMMENT) {
194                             props.put("comment", comment);
195                         }
196                         if (loop_tag && !isAnimation) {
197                             isAnimation = true;
198                             ImageFetcher.startingAnimation();
199                         }
200                         break;
201 
202                       case -1:
203                         return; //error("corrupt GIF file");
204                     }
205                     break;
206 
207                   case IMAGESEP:
208                     if (!isAnimation) {
209                         input.mark(0); // we don't need the mark buffer
210                     }
211                     try {
212                         if (!readImage(totalframes == 0,
213                                        disposal_method,
214                                        delay)) {
215                             return;
216                         }
217                     } catch (Exception e) {
218                         if (verbose) {
219                             e.printStackTrace();
220                         }
221                         return;
222                     }
223                     frameno++;
224                     totalframes++;
225                     break;
226 
227                   default:
228                   case -1:
229                     if (verbose) {
230                         if (code == -1) {
231                             System.err.println("Premature EOF in GIF file," +
232                                                " frame " + frameno);
233                         } else {
234                             System.err.println("corrupt GIF file (parse) ["
235                                                + code + "].");
236                         }
237                     }
238                     if (frameno == 0) {
239                         return;
240                     }
241                     // Fall through
242 
243                   case TERMINATOR:
244                     if (nloops == 0 || nloops-- >= 0) {
245                         try {
246                             if (curframe != null) {
247                                 curframe.dispose();
248                                 curframe = null;
249                             }
250                             input.reset();
251                             saved_image = null;
252                             saved_model = null;
253                             frameno = 0;
254                             break;
255                         } catch (IOException e) {
256                             return; // Unable to reset input buffer
257                         }
258                     }
259                     if (verbose && frameno != 1) {
260                         System.out.println("processing GIF terminator,"
261                                            + " frames: " + frameno
262                                            + " total: " + totalframes);
263                     }
264                     imageComplete(ImageConsumer.STATICIMAGEDONE, true);
265                     return;
266                 }
267             }
268         } finally {
269             close();
270         }
271     }
272 
273     /**
274      * Read Image header
275      */
readHeader()276     private void readHeader() throws IOException, ImageFormatException {
277         // Create a buffer
278         byte[] buf = new byte[13];
279 
280         // Read the header
281         if (readBytes(buf, 0, 13) != 0) {
282             throw new IOException();
283         }
284 
285         // Check header
286         if ((buf[0] != 'G') || (buf[1] != 'I') || (buf[2] != 'F')) {
287             error("not a GIF file.");
288         }
289 
290         // Global width&height
291         global_width = ExtractWord(buf, 6);
292         global_height = ExtractWord(buf, 8);
293 
294         // colormap info
295         int ch = ExtractByte(buf, 10);
296         if ((ch & COLORMAPMASK) == 0) {
297             // no global colormap so make up our own
298             // If there is a local colormap, it will override what we
299             // have here.  If there is not a local colormap, the rules
300             // for GIF89 say that we can use whatever colormap we want.
301             // This means that we should probably put in a full 256 colormap
302             // at some point.  REMIND!
303             num_global_colors = 2;
304             global_bgpixel = 0;
305             global_colormap = new byte[2*3];
306             global_colormap[0] = global_colormap[1] = global_colormap[2] = (byte)0;
307             global_colormap[3] = global_colormap[4] = global_colormap[5] = (byte)255;
308 
309         }
310         else {
311             num_global_colors = 1 << ((ch & 0x7) + 1);
312 
313             global_bgpixel = ExtractByte(buf, 11);
314 
315             if (buf[12] != 0) {
316                 props.put("aspectratio", ""+((ExtractByte(buf, 12) + 15) / 64.0));
317             }
318 
319             // Read colors
320             global_colormap = new byte[num_global_colors * 3];
321             if (readBytes(global_colormap, 0, num_global_colors * 3) != 0) {
322                 throw new IOException();
323             }
324         }
325         input.mark(Integer.MAX_VALUE); // set this mark in case this is an animated GIF
326     }
327 
328     /**
329      * The ImageConsumer hints flag for a non-interlaced GIF image.
330      */
331     private static final int normalflags =
332         ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
333         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
334 
335     /**
336      * The ImageConsumer hints flag for an interlaced GIF image.
337      */
338     private static final int interlaceflags =
339         ImageConsumer.RANDOMPIXELORDER | ImageConsumer.COMPLETESCANLINES |
340         ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME;
341 
342     private short[] prefix  = new short[4096];
343     private byte[]  suffix  = new byte[4096];
344     private byte[]  outCode = new byte[4097];
345 
initIDs()346     private static native void initIDs();
347 
348     static {
349         /* ensure that the necessary native libraries are loaded */
NativeLibLoader.loadLibraries()350         NativeLibLoader.loadLibraries();
initIDs()351         initIDs();
352     }
353 
parseImage(int x, int y, int width, int height, boolean interlace, int initCodeSize, byte[] block, byte[] rasline, IndexColorModel model)354     private native boolean parseImage(int x, int y, int width, int height,
355                                       boolean interlace, int initCodeSize,
356                                       byte[] block, byte[] rasline,
357                                       IndexColorModel model);
358 
sendPixels(int x, int y, int width, int height, byte[] rasline, ColorModel model)359     private int sendPixels(int x, int y, int width, int height,
360                            byte[] rasline, ColorModel model) {
361         int rasbeg, rasend, x2;
362         if (y < 0) {
363             height += y;
364             y = 0;
365         }
366         if (y + height > global_height) {
367             height = global_height - y;
368         }
369         if (height <= 0) {
370             return 1;
371         }
372         // rasline[0]     == pixel at coordinate (x,y)
373         // rasline[width] == pixel at coordinate (x+width, y)
374         if (x < 0) {
375             rasbeg = -x;
376             width += x;         // same as (width -= rasbeg)
377             x2 = 0;             // same as (x2     = x + rasbeg)
378         } else {
379             rasbeg = 0;
380             // width -= 0;      // same as (width -= rasbeg)
381             x2 = x;             // same as (x2     = x + rasbeg)
382         }
383         // rasline[rasbeg]          == pixel at coordinate (x2,y)
384         // rasline[width]           == pixel at coordinate (x+width, y)
385         // rasline[rasbeg + width]  == pixel at coordinate (x2+width, y)
386         if (x2 + width > global_width) {
387             width = global_width - x2;
388         }
389         if (width <= 0) {
390             return 1;
391         }
392         rasend = rasbeg + width;
393         // rasline[rasbeg] == pixel at coordinate (x2,y)
394         // rasline[rasend] == pixel at coordinate (x2+width, y)
395         int off = y * global_width + x2;
396         boolean save = (curframe.disposal_method == GifFrame.DISPOSAL_SAVE);
397         if (trans_pixel >= 0 && !curframe.initialframe) {
398             if (saved_image != null && model.equals(saved_model)) {
399                 for (int i = rasbeg; i < rasend; i++, off++) {
400                     byte pixel = rasline[i];
401                     if ((pixel & 0xff) == trans_pixel) {
402                         rasline[i] = saved_image[off];
403                     } else if (save) {
404                         saved_image[off] = pixel;
405                     }
406                 }
407             } else {
408                 // We have to do this the hard way - only transmit
409                 // the non-transparent sections of the line...
410                 // Fix for 6301050: the interlacing is ignored in this case
411                 // in order to avoid artefacts in case of animated images.
412                 int runstart = -1;
413                 int count = 1;
414                 for (int i = rasbeg; i < rasend; i++, off++) {
415                     byte pixel = rasline[i];
416                     if ((pixel & 0xff) == trans_pixel) {
417                         if (runstart >= 0) {
418                             count = setPixels(x + runstart, y,
419                                               i - runstart, 1,
420                                               model, rasline,
421                                               runstart, 0);
422                             if (count == 0) {
423                                 break;
424                             }
425                         }
426                         runstart = -1;
427                     } else {
428                         if (runstart < 0) {
429                             runstart = i;
430                         }
431                         if (save) {
432                             saved_image[off] = pixel;
433                         }
434                     }
435                 }
436                 if (runstart >= 0) {
437                     count = setPixels(x + runstart, y,
438                                       rasend - runstart, 1,
439                                       model, rasline,
440                                       runstart, 0);
441                 }
442                 return count;
443             }
444         } else if (save) {
445             System.arraycopy(rasline, rasbeg, saved_image, off, width);
446         }
447         int count = setPixels(x2, y, width, height, model,
448                               rasline, rasbeg, 0);
449         return count;
450     }
451 
452     /**
453      * Read Image data
454      */
readImage(boolean first, int disposal_method, int delay)455     private boolean readImage(boolean first, int disposal_method, int delay)
456         throws IOException
457     {
458         if (curframe != null && !curframe.dispose()) {
459             abort();
460             return false;
461         }
462 
463         long tm = 0;
464 
465         if (verbose) {
466             tm = System.currentTimeMillis();
467         }
468 
469         // Allocate the buffer
470         byte[] block = new byte[256 + 3];
471 
472         // Read the image descriptor
473         if (readBytes(block, 0, 10) != 0) {
474             throw new IOException();
475         }
476         int x = ExtractWord(block, 0);
477         int y = ExtractWord(block, 2);
478         int width = ExtractWord(block, 4);
479         int height = ExtractWord(block, 6);
480 
481         /*
482          * Majority of gif images have
483          * same logical screen and frame dimensions.
484          * Also, Photoshop and Mozilla seem to use the logical
485          * screen dimension (from the global stream header)
486          * if frame dimension is invalid.
487          *
488          * We use similar heuristic and trying to recover
489          * frame width from logical screen dimension and
490          * frame offset.
491          */
492         if (width == 0 && global_width != 0) {
493             width = global_width - x;
494         }
495         if (height == 0 && global_height != 0) {
496             height = global_height - y;
497         }
498 
499         boolean interlace = (block[8] & INTERLACEMASK) != 0;
500 
501         IndexColorModel model = global_model;
502 
503         if ((block[8] & COLORMAPMASK) != 0) {
504             // We read one extra byte above so now when we must
505             // transfer that byte as the first colormap byte
506             // and manually read the code size when we are done
507             int num_local_colors = 1 << ((block[8] & 0x7) + 1);
508 
509             // Read local colors
510             byte[] local_colormap = new byte[num_local_colors * 3];
511             local_colormap[0] = block[9];
512             if (readBytes(local_colormap, 1, num_local_colors * 3 - 1) != 0) {
513                 throw new IOException();
514             }
515 
516             // Now read the "real" code size byte which follows
517             // the local color table
518             if (readBytes(block, 9, 1) != 0) {
519                 throw new IOException();
520             }
521             if (trans_pixel >= num_local_colors) {
522                 // Fix for 4233748: extend colormap to contain transparent pixel
523                 num_local_colors = trans_pixel + 1;
524                 local_colormap = grow_colormap(local_colormap, num_local_colors);
525             }
526             model = new IndexColorModel(8, num_local_colors, local_colormap,
527                                         0, false, trans_pixel);
528         } else if (model == null
529                    || trans_pixel != model.getTransparentPixel()) {
530             if (trans_pixel >= num_global_colors) {
531                 // Fix for 4233748: extend colormap to contain transparent pixel
532                 num_global_colors = trans_pixel + 1;
533                 global_colormap = grow_colormap(global_colormap, num_global_colors);
534             }
535             model = new IndexColorModel(8, num_global_colors, global_colormap,
536                                         0, false, trans_pixel);
537             global_model = model;
538         }
539 
540         // Notify the consumers
541         if (first) {
542             if (global_width == 0) global_width = width;
543             if (global_height == 0) global_height = height;
544 
545             setDimensions(global_width, global_height);
546             setProperties(props);
547             setColorModel(model);
548             headerComplete();
549         }
550 
551         if (disposal_method == GifFrame.DISPOSAL_SAVE && saved_image == null) {
552             saved_image = new byte[global_width * global_height];
553             /*
554              * If height of current image is smaller than the global height,
555              * fill the gap with transparent pixels.
556              */
557             if ((height < global_height) && (model != null)) {
558                 byte tpix = (byte)model.getTransparentPixel();
559                 if (tpix >= 0) {
560                     byte[] trans_rasline = new byte[global_width];
561                     for (int i=0; i<global_width;i++) {
562                         trans_rasline[i] = tpix;
563                     }
564 
565                     setPixels(0, 0, global_width, y,
566                               model, trans_rasline, 0, 0);
567                     setPixels(0, y+height, global_width,
568                               global_height-height-y, model, trans_rasline,
569                               0, 0);
570                 }
571             }
572         }
573 
574         int hints = (interlace ? interlaceflags : normalflags);
575         setHints(hints);
576 
577         curframe = new GifFrame(this, disposal_method, delay,
578                                 (curframe == null), model,
579                                 x, y, width, height);
580 
581         // allocate the raster data
582         byte[] rasline = new byte[width];
583 
584         if (verbose) {
585             System.out.print("Reading a " + width + " by " + height + " " +
586                       (interlace ? "" : "non-") + "interlaced image...");
587         }
588         int initCodeSize = ExtractByte(block, 9);
589         if (initCodeSize >= 12) {
590             if (verbose) {
591                 System.out.println("Invalid initial code size: " +
592                                    initCodeSize);
593             }
594             return false;
595         }
596         boolean ret = parseImage(x, y, width, height,
597                                  interlace, initCodeSize,
598                                  block, rasline, model);
599 
600         if (!ret) {
601             abort();
602         }
603 
604         if (verbose) {
605             System.out.println("done in "
606                                + (System.currentTimeMillis() - tm)
607                                + "ms");
608         }
609 
610         return ret;
611     }
612 
grow_colormap(byte[] colormap, int newlen)613     public static byte[] grow_colormap(byte[] colormap, int newlen) {
614         byte[] newcm = new byte[newlen * 3];
615         System.arraycopy(colormap, 0, newcm, 0, colormap.length);
616         return newcm;
617     }
618 }
619 
620 class GifFrame {
621     private static final boolean verbose = false;
622 
623     static final int DISPOSAL_NONE      = 0x00;
624     static final int DISPOSAL_SAVE      = 0x01;
625     static final int DISPOSAL_BGCOLOR   = 0x02;
626     static final int DISPOSAL_PREVIOUS  = 0x03;
627 
628     GifImageDecoder decoder;
629 
630     int disposal_method;
631     int delay;
632 
633     IndexColorModel model;
634 
635     int x;
636     int y;
637     int width;
638     int height;
639 
640     boolean initialframe;
641 
GifFrame(GifImageDecoder id, int dm, int dl, boolean init, IndexColorModel cm, int x, int y, int w, int h)642     public GifFrame(GifImageDecoder id, int dm, int dl, boolean init,
643                     IndexColorModel cm, int x, int y, int w, int h) {
644         this.decoder = id;
645         this.disposal_method = dm;
646         this.delay = dl;
647         this.model = cm;
648         this.initialframe = init;
649         this.x = x;
650         this.y = y;
651         this.width = w;
652         this.height = h;
653     }
654 
setPixels(int x, int y, int w, int h, ColorModel cm, byte[] pix, int off, int scan)655     private void setPixels(int x, int y, int w, int h,
656                            ColorModel cm, byte[] pix, int off, int scan) {
657         decoder.setPixels(x, y, w, h, cm, pix, off, scan);
658     }
659 
dispose()660     public boolean dispose() {
661         if (decoder.imageComplete(ImageConsumer.SINGLEFRAMEDONE, false) == 0) {
662             return false;
663         } else {
664             if (delay > 0) {
665                 try {
666                     if (verbose) {
667                         System.out.println("sleeping: "+delay);
668                     }
669                     Thread.sleep(delay);
670                 } catch (InterruptedException e) {
671                     return false;
672                 }
673             } else {
674                 Thread.yield();
675             }
676 
677             if (verbose && disposal_method != 0) {
678                 System.out.println("disposal method: "+disposal_method);
679             }
680 
681             int global_width = decoder.global_width;
682             int global_height = decoder.global_height;
683 
684             if (x < 0) {
685                 width += x;
686                 x = 0;
687             }
688             if (x + width > global_width) {
689                 width = global_width - x;
690             }
691             if (width <= 0) {
692                 disposal_method = DISPOSAL_NONE;
693             } else {
694                 if (y < 0) {
695                     height += y;
696                     y = 0;
697                 }
698                 if (y + height > global_height) {
699                     height = global_height - y;
700                 }
701                 if (height <= 0) {
702                     disposal_method = DISPOSAL_NONE;
703                 }
704             }
705 
706             switch (disposal_method) {
707             case DISPOSAL_PREVIOUS:
708                 byte[] saved_image = decoder.saved_image;
709                 IndexColorModel saved_model = decoder.saved_model;
710                 if (saved_image != null) {
711                     setPixels(x, y, width, height,
712                               saved_model, saved_image,
713                               y * global_width + x, global_width);
714                 }
715                 break;
716             case DISPOSAL_BGCOLOR:
717                 byte tpix;
718                 if (model.getTransparentPixel() < 0) {
719                     tpix = 0;
720                 } else {
721                     tpix = (byte) model.getTransparentPixel();
722                 }
723                 byte[] rasline = new byte[width];
724                 if (tpix != 0) {
725                     for (int i = 0; i < width; i++) {
726                         rasline[i] = tpix;
727                     }
728                 }
729 
730                 // clear saved_image using transparent pixels
731                 // this will be used as the background in the next display
732                 if( decoder.saved_image != null ) {
733                     for( int i = 0; i < global_width * global_height; i ++ )
734                         decoder.saved_image[i] = tpix;
735                 }
736 
737                 setPixels(x, y, width, height, model, rasline, 0, 0);
738                 break;
739             case DISPOSAL_SAVE:
740                 decoder.saved_model = model;
741                 break;
742             }
743         }
744         return true;
745     }
746 }
747