1 /*
2 * Copyright (c) 1995, 2016, 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
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 */
116 @SuppressWarnings({"fallthrough", "deprecation"})
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))) {
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
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)
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.
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 }
|
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
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 */
116 @SuppressWarnings({"fallthrough", "deprecation"})
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))) {
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
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)
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.
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 }
|