blob: fa4612f1848a4eff350673b7c32d61f4b9c18760 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
Jayathirth D V7395b4d2016-11-15 12:52:24 +05302 * Copyright (c) 1995, 2016, Oracle and/or its affiliates. All rights reserved.
J. Duke319a3b92007-12-01 00:00:00 +00003 * 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
Kelly O'Hairfe008ae2010-05-25 15:58:33 -07007 * published by the Free Software Foundation. Oracle designates this
J. Duke319a3b92007-12-01 00:00:00 +00008 * particular file as subject to the "Classpath" exception as provided
Kelly O'Hairfe008ae2010-05-25 15:58:33 -07009 * by Oracle in the LICENSE file that accompanied this code.
J. Duke319a3b92007-12-01 00:00:00 +000010 *
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 *
Kelly O'Hairfe008ae2010-05-25 15:58:33 -070021 * 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.
J. Duke319a3b92007-12-01 00:00:00 +000024 */
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 */
32package sun.awt.image;
33
J. Duke319a3b92007-12-01 00:00:00 +000034import java.util.Hashtable;
35import java.io.InputStream;
36import java.io.IOException;
37import java.awt.image.*;
38
39/**
40 * Gif Image converter
41 *
42 * @author Arthur van Hoff
43 * @author Jim Graham
44 */
45public 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
Henry Jencb91afd2014-04-28 19:05:49 -070063 Hashtable<String, Object> props = new Hashtable<>();
J. Duke319a3b92007-12-01 00:00:00 +000064
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
74 public GifImageDecoder(InputStreamImageSource src, InputStream is) {
75 super(src, is);
76 }
77
78 /**
79 * An error has occurred. Throw an exception.
80 */
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 */
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
105 private static final int ExtractByte(byte buf[], int off) {
106 return (buf[off] & 0xFF);
107 }
108
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 */
Joe Darcy6e57ed82014-12-15 17:20:17 -0800116 @SuppressWarnings({"fallthrough", "deprecation"})
J. Duke319a3b92007-12-01 00:00:00 +0000117 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 }
Joe Darcy745db9c2014-04-17 09:45:16 -0700241 // Fall through
J. Duke319a3b92007-12-01 00:00:00 +0000242
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 */
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
346 private static native void initIDs();
347
348 static {
349 /* ensure that the necessary native libraries are loaded */
350 NativeLibLoader.loadLibraries();
351 initIDs();
352 }
353
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
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 */
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 }
Andrew Brygind033b162009-03-05 19:36:51 +0300588 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 }
J. Duke319a3b92007-12-01 00:00:00 +0000596 boolean ret = parseImage(x, y, width, height,
Andrew Brygind033b162009-03-05 19:36:51 +0300597 interlace, initCodeSize,
J. Duke319a3b92007-12-01 00:00:00 +0000598 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
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
620class GifFrame {
621 private static final boolean verbose = false;
J. Duke319a3b92007-12-01 00:00:00 +0000622
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
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
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
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) {
J. Duke319a3b92007-12-01 00:00:00 +0000719 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}