001: /*
002: * $Id: GIFHelper.java,v 1.2 2002/02/15 23:44:28 skavish Exp $
003: *
004: * ===========================================================================
005: *
006: * The JGenerator Software License, Version 1.0
007: *
008: * Copyright (c) 2000 Dmitry Skavish (skavish@usa.net). All rights reserved.
009: *
010: * Redistribution and use in source and binary forms, with or without
011: * modification, are permitted provided that the following conditions are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by Dmitry Skavish
024: * (skavish@usa.net, http://www.flashgap.com/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The name "The JGenerator" must not be used to endorse or promote
029: * products derived from this software without prior written permission.
030: * For written permission, please contact skavish@usa.net.
031: *
032: * 5. Products derived from this software may not be called "The JGenerator"
033: * nor may "The JGenerator" appear in their names without prior written
034: * permission of Dmitry Skavish.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL DMITRY SKAVISH OR THE OTHER
040: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: *
049: */
050:
051: package org.openlaszlo.iv.flash.util;
052:
053: import java.io.*;
054: import java.util.zip.Deflater;
055: import org.openlaszlo.iv.flash.parser.DataMarker;
056:
057: /**
058: * GIF Reader utility class
059: * Parse GIF code from an inputStream without using java.awt
060: * (to avoid the need of a display on a server)
061: *
062: * Use a modified version of the GIFDecoder part the PJA Toolkit
063: * Part of the implementation has been commented because we didn't need it
064: * Basically everything that was dealing with GIF comments output
065: * and the creation of an RGB image
066: *
067: * With kind permission of Emmanuel PUYBARET from eteks
068: * Lots of contributions here, but the code is reliable and approved
069: *
070: * Following is the initial copyright notice (which for this project is Apache style)
071: *
072: * ****************************************************************************
073: *
074: * Copyright (c) 2000 Emmanuel PUYBARET / eTeks <info@eteks.com>. All Rights Reserved.
075: *
076: * Raster over-run errors fixed by Alan Dix (alan@hcibook.com www.hiraeth.com/alan)
077: *
078: * Visit eTeks web site for up-to-date versions of the original file and other
079: * Java tools and tutorials : http://www.eteks.com/
080: *
081: * ****************************************************************************
082: *
083: * This file is based and translated from the following C source file :
084: *
085: * xvgif.c - GIF loading code for 'xv'. Based strongly on...
086: *
087: * gif2ras.c - Converts from a Compuserve GIF (tm) image to a Sun raster image.
088: *
089: * Copyright (c) 1988, 1989 by Patrick J. Naughton
090: *
091: * Author: Patrick J. Naughton
092: * naughton@wind.sun.com
093: *
094: * Permission to use, copy, modify, and distribute this software and its
095: * documentation for any purpose and without fee is hereby granted,
096: * provided that the above copyright notice appear in all copies and that
097: * both that copyright notice and this permission notice appear in
098: * supporting documentation.
099: *
100: * This file is provided AS IS with no warranties of any kind. The author
101: * shall have no liability with respect to the infringement of copyrights,
102: * trade secrets or any patents by this file or any part thereof. In no
103: * event will the author be liable for any lost revenue or profits or
104: * other special, indirect and consequential damages.
105: *
106: * ****************************************************************************
107: *
108: */
109:
110: public class GIFHelper {
111:
112: private byte bytePixels[]; // image data in index model
113: private int width; // image width
114: private int height; // image height
115: private int bitMask; // size of the colormap - 1;
116: private int colorMapSize; // actual size of the colormap
117:
118: private InputStream input; // the GIF ByteArrayInputStream
119: private byte data[]; // the compressed (zlib) byte array
120:
121: /******************************************************************************
122: * The following methods are the getters needed for JGen to process
123: * a new LBitmap object to insert into the Script
124: * Added to the original GIFReader especially for JGen
125: ******************************************************************************/
126:
127: public int getColorTableSize() {
128: return bitMask;
129: }
130:
131: public int getWidth() {
132: return width;
133: }
134:
135: public int getHeight() {
136: return height;
137: }
138:
139: public DataMarker getZlibData() throws IOException {
140: int deflatedSize = deflate(); // compress the data
141: return new DataMarker(data, 0, deflatedSize);
142: }
143:
144: public boolean isAlpha() {
145: return (transparentIndex > -1);
146: }
147:
148: /**
149: * Launch the loadGIF routine
150: *
151: * @param buffer
152: * @exception IOException
153: */
154: public void doRead(byte[] buffer) throws IOException {
155: doRead(new ByteArrayInputStream(buffer, 0, buffer.length));
156: }
157:
158: public void doRead(FlashBuffer fob) throws IOException {
159: doRead(fob.getInputStream());
160: }
161:
162: public void doRead(InputStream input) throws IOException {
163: this .input = input;
164: try {
165: loadGIF(input);
166: } finally {
167: try {
168: input.close();
169: } catch (IOException ioe) {
170: }
171: }
172: }
173:
174: /**
175: * Performs the ZLib compression.<br>
176: *
177: * <p>Everything from here is the private implementation of the reader
178: * But we don't want to disturb other classes with that stuff, do we ?
179: *
180: * @return
181: * @exception IOException
182: */
183: private int deflate() throws IOException {
184: int retour = 0;
185: int plus = 0;
186: if (transparentIndex > -1)
187: plus++;
188: int maxpixels = bytePixels.length;
189: int falsewidth = width;
190: int added = 0;
191: if (width % 4 > 0) {
192: while (falsewidth % 4 > 0) {
193: falsewidth++;
194: added++;
195: }
196: maxpixels = falsewidth * height;
197: }
198: byte[] map = new byte[colorMapSize * (3 + plus) + maxpixels];
199: for (int i = 0; i < colorMapSize; i++) {
200: if (transparentIndex > -1) {
201: if (transparentIndex == i) {
202: map[i * (3 + plus)] = 0;
203: map[(i * (3 + plus)) + 1] = 0;
204: map[(i * (3 + plus)) + 2] = 0;
205: map[(i * (3 + plus)) + 3] = 0;
206: } else {
207: map[i * (3 + plus)] = r[i];
208: map[(i * (3 + plus)) + 1] = g[i];
209: map[(i * (3 + plus)) + 2] = b[i];
210: map[(i * (3 + plus)) + 3] = (byte) 255;
211: }
212: } else {
213: map[i * (3 + plus)] = r[i];
214: map[(i * (3 + plus)) + 1] = g[i];
215: map[(i * (3 + plus)) + 2] = b[i];
216: }
217: }
218: if (width % 4 > 0) {
219: byte[] tempPixels = new byte[maxpixels];
220: int idTemp = 0;
221: int idByte = 0;
222: for (int h = 0; h < height; h++) {
223: for (int w = 0; w < width; w++)
224: tempPixels[idTemp++] = bytePixels[idByte++];
225: for (int i = 0; i < added; i++)
226: tempPixels[idTemp++] = 0x00;
227: }
228: bytePixels = tempPixels;
229: }
230: System.arraycopy(bytePixels, 0, map,
231: (colorMapSize * (3 + plus)), bytePixels.length);
232: Deflater def = new Deflater();
233: def.setInput(map);
234: data = new byte[(colorMapSize * (3 + plus)) + bytePixels.length];
235: def.finish();
236: retour = def.deflate(data);
237: return retour;
238: }
239:
240: /******************************************************************************
241: * This is where the original GIFReader from PJA starts
242: * A few things had been commented out because it wasn't needed
243: ******************************************************************************/
244:
245: // Where the xvgif.c translation starts
246: private int bitOffset = 0; // Bit Offset of next code
247: private int XC = 0; // Output X coords of current pixel
248: private int YC = 0; // Output Y coords of curent pixel
249: private int pass = 0; // Used by output routine if interlaced pixels
250: private int ptr = 0; // Just an index
251: private int oldYC = -1; // Here to remember YC
252:
253: private byte r[] = new byte[256]; // red colormap value
254: private byte g[] = new byte[256]; // green colormap value
255: private byte b[] = new byte[256]; // blue colormap, value
256: private int transparentIndex = -1; // index of the transparent color in the index array
257:
258: // private int intPixels []; // image data in RGB model
259: // we don't really need this one, since we only compute the bytePixels Array
260:
261: // private String fullInfo; // Format: field in info box
262: // private String shortInfo; // short format info
263: // private String comment; // comment text
264:
265: private final static String id87 = "GIF87a";
266: private final static String id89 = "GIF89a";
267:
268: private final static short EGA_PALETTE[][] = { { 0, 0, 0 },
269: { 0, 0, 128 }, { 0, 128, 0 }, { 0, 128, 128 },
270: { 128, 0, 0 }, { 128, 0, 128 }, { 128, 128, 0 },
271: { 200, 200, 200 }, { 100, 100, 100 }, { 100, 100, 255 },
272: { 100, 255, 100 }, { 100, 255, 255 }, { 255, 100, 100 },
273: { 255, 100, 255 }, { 255, 255, 100 }, { 255, 255, 255 } };
274:
275: private final static byte EXTENSION = 0x21;
276: private final static byte IMAGESEP = 0x2c;
277: private final static byte TRAILER = 0x3b;
278: private final static byte INTERLACEMASK = 0x40;
279: private final static byte COLORMAPMASK = (byte) 0x80;
280:
281: private void loadGIF(InputStream input) throws IOException {
282: if (!(input instanceof BufferedInputStream))
283: input = new BufferedInputStream(input);
284:
285: // Use a DataInputStream to have EOFException if file is corrupted
286: DataInputStream dataInput = new DataInputStream(input);
287:
288: // initialize variables
289: bitOffset = XC = YC = pass = 0;
290: boolean gotimage = false;
291: boolean gif89 = false;
292:
293: byte[] idBytes = new byte[6];
294: for (int i = 0; i < 6; i++)
295: idBytes[i] = dataInput.readByte();
296:
297: String id = new String(idBytes);
298: if (id.equals(id87))
299: gif89 = false;
300: else if (id.equals(id89))
301: gif89 = true;
302: else
303: warning(input, "not a GIF file");
304:
305: // Get variables from the GIF screen descriptor
306: byte aByte = dataInput.readByte();
307: int screenWidth = ((int) aByte & 0xFF) + 0x100
308: * (dataInput.readByte() & 0xFF); // screen dimensions... not used.
309: aByte = dataInput.readByte();
310: int screenHeight = ((int) aByte & 0xFF) + 0x100
311: * (dataInput.readByte() & 0xFF);
312:
313: aByte = dataInput.readByte();
314: boolean hasColormap = (aByte & COLORMAPMASK) != 0;
315:
316: // Bits per pixel, read from GIF header
317: int bitsPerPixel = (aByte & 7) + 1;
318: // Number of colors
319: colorMapSize = 1 << bitsPerPixel;
320: // AND mask for data size
321: bitMask = colorMapSize - 1;
322:
323: int background = dataInput.readByte() & 0xFF; // background color... not used.
324:
325: int aspect = dataInput.readByte() & 0xFF;
326: if (aspect != 0)
327: if (!gif89)
328: warning(input, "corrupt GIF file (screen descriptor)");
329:
330: // Read in global colormap.
331: if (hasColormap)
332: for (int i = 0; i < colorMapSize; i++) {
333: r[i] = dataInput.readByte();
334: g[i] = dataInput.readByte();
335: b[i] = dataInput.readByte();
336: }
337: else {
338: colorMapSize = 256;
339: bitMask = 255;
340: // no colormap in GIF file
341: // put std EGA palette (repeated 16 times) into colormap, for lack of
342: // anything better to do
343: for (int i = 0; i < 256; i++) {
344: r[i] = (byte) EGA_PALETTE[i & 15][0];
345: g[i] = (byte) EGA_PALETTE[i & 15][1];
346: b[i] = (byte) EGA_PALETTE[i & 15][2];
347: }
348: }
349:
350: for (int block = 0; (block = dataInput.readByte() & 0xFF) != TRAILER;)
351: if (block == EXTENSION) {
352: // possible things at this point are:
353: // an application extension block
354: // a comment extension block
355: // an (optional) graphic control extension block
356: // followed by either an image
357: // or a plaintext extension
358:
359: // parse extension blocks
360: int fn, blocksize, aspnum, aspden;
361:
362: // read extension block
363: fn = dataInput.readByte() & 0xFF;
364:
365: if (fn == 'R') {
366: // GIF87 aspect extension
367: int blockSize;
368:
369: blocksize = dataInput.readByte() & 0xFF;
370: if (blocksize == 2) {
371: aspnum = dataInput.readByte();
372: aspden = dataInput.readByte();
373: if (aspden <= 0 || aspnum <= 0) {
374: aspnum = aspden = 1;
375: }
376: } else
377: dataInput.skipBytes(blocksize);
378:
379: while ((blockSize = dataInput.readByte() & 0xFF) > 0)
380: // eat any following data subblocks
381: dataInput.skipBytes(blockSize);
382: } else if (fn == 0xFE) {
383: // Comment Extension
384: // We don't really need them, so let's eat that too :
385: int blockSize;
386: while ((blockSize = dataInput.readByte() & 0xFF) > 0)
387: dataInput.skipBytes(blockSize);
388:
389: /* This part was in the original GIFReader but we don't use the GIF comments
390: // to read the comments :
391: for (int blockSize = 0; (blockSize = dataInput.readByte () & 0xFF) != 0; )
392: {
393: byte commentBytes [] = new byte [blockSize];
394: for (int i = 0; i < blockSize; i++)
395: commentBytes [i] = dataInput.readByte ();
396:
397: if (comment != null)
398: comment += "\n" + new String (commentBytes);
399: else
400: comment = new String (commentBytes);
401: }
402: */
403: } else if (fn == 0x01) {
404: // PlainText Extension
405: int blockSize = dataInput.readByte() & 0xFF;
406: int tgLeft = dataInput.readByte() & 0xFF;
407: tgLeft += (dataInput.readByte() & 0xFF) << 8;
408: int tgTop = dataInput.readByte() & 0xFF;
409: tgTop += (dataInput.readByte() & 0xFF) << 8;
410: int tgWidth = dataInput.readByte() & 0xFF;
411: tgWidth += (dataInput.readByte() & 0xFF) << 8;
412: int tgHeight = dataInput.readByte() & 0xFF;
413: tgHeight += (dataInput.readByte() & 0xFF) << 8;
414: int cWidth = dataInput.readByte() & 0xFF;
415: int cHeight = dataInput.readByte() & 0xFF;
416: int fg = dataInput.readByte() & 0xFF;
417: int bg = dataInput.readByte() & 0xFF;
418:
419: dataInput.skipBytes(blockSize - 12); // read rest of first subblock
420:
421: // read (and ignore) data sub-blocks
422: while ((blockSize = dataInput.readByte() & 0xFF) != 0)
423: dataInput.skipBytes(blockSize);
424: } else if (fn == 0xF9) {
425: // Graphic Control Extension
426: for (int blockSize = 0; (blockSize = dataInput
427: .readByte() & 0xFF) != 0;)
428: // Added transparent GIF management here
429: if (blockSize == 4) {
430: int ext1 = (dataInput.readByte() & 0xFF);
431: int ext2 = (dataInput.readByte() & 0xFF);
432: int ext3 = (dataInput.readByte() & 0xFF);
433: int ext4 = (dataInput.readByte() & 0xFF);
434:
435: // v2.1.1 Changed condition for transparent GIFs
436: if ((ext1 & 0x01) != 0)
437: transparentIndex = ext4;
438: } else
439: dataInput.skipBytes(blockSize);
440: } else if (fn == 0xFF) {
441: // Application Extension
442: // read (and ignore) data sub-blocks
443: for (int blockSize = 0; (blockSize = dataInput
444: .readByte() & 0xFF) != 0;)
445: dataInput.skipBytes(blockSize);
446: } else {
447: // unknown extension
448: // read (and ignore) data sub-blocks
449: for (int blockSize = 0; (blockSize = dataInput
450: .readByte() & 0xFF) != 0;)
451: dataInput.skipBytes(blockSize);
452: }
453: } else if (block == IMAGESEP) {
454: if (gotimage) {
455: // just skip over remaining images
456: dataInput.skipBytes(8); // left position
457: // top position
458: // width
459: // height
460: int misc = dataInput.readByte() & 0xFF; // misc. bits
461:
462: if ((misc & 0x80) != 0)
463: // image has local colormap. skip it
464: for (int i = 0; i < 1 << ((misc & 7) + 1); i++)
465: dataInput.skipBytes(3);
466:
467: dataInput.skipBytes(1); // minimum code size
468:
469: // skip image data sub-blocks
470: for (int blockSize = 0; (blockSize = dataInput
471: .readByte() & 0xFF) != 0;)
472: dataInput.skipBytes(blockSize);
473: } else {
474: readImage(dataInput, bitsPerPixel, bitMask,
475: hasColormap, gif89);
476: gotimage = true;
477: }
478: } else {
479: // unknown block type
480: // don't mention bad block if file was trunc'd, as it's all bogus
481: String str = "Unknown block type (0x"
482: + Integer.toString(block, 16) + ")";
483: warning(input, str);
484: break;
485: }
486:
487: if (!gotimage)
488: warning(input, "no image data found in GIF file");
489: }
490:
491: private void readImage(DataInputStream dataInput, int bitsPerPixel,
492: int bitMask, boolean hasColormap, boolean gif89)
493: throws IOException {
494: int npixels = 0;
495: int maxpixels = 0;
496:
497: // read in values from the image descriptor
498: byte aByte = dataInput.readByte();
499: int leftOffset = (aByte & 0xFF) + 0x100
500: * (dataInput.readByte() & 0xFF);
501: aByte = dataInput.readByte();
502: int topOffset = (aByte & 0xFF) + 0x100
503: * (dataInput.readByte() & 0xFF);
504: aByte = dataInput.readByte();
505: width = (aByte & 0xFF) + 0x100 * (dataInput.readByte() & 0xFF);
506: aByte = dataInput.readByte();
507: height = (aByte & 0xFF) + 0x100 * (dataInput.readByte() & 0xFF);
508:
509: int misc = dataInput.readByte(); // miscellaneous bits (interlace, local cmap)
510: boolean interlace = (misc & INTERLACEMASK) != 0;
511:
512: if ((misc & 0x80) != 0)
513: for (int i = 0; i < 1 << ((misc & 7) + 1); i++) {
514: r[i] = dataInput.readByte();
515: g[i] = dataInput.readByte();
516: b[i] = dataInput.readByte();
517: }
518:
519: if (!hasColormap && (misc & 0x80) == 0) {
520: // no global or local colormap
521: }
522:
523: // Start reading the raster data. First we get the intial code size
524: // and compute decompressor constant values, based on this code size.
525:
526: // Code size, read from GIF header
527: int codeSize = dataInput.readByte() & 0xFF;
528:
529: int clearCode = (1 << codeSize); // GIF clear code
530: int EOFCode = clearCode + 1; // GIF end-of-information code
531: int firstFree = clearCode + 2; // First free code, generated per GIF spec
532: int freeCode = firstFree; // Decompressor,next free slot in hash table
533:
534: // The GIF spec has it that the code size is the code size used to
535: // compute the above values is the code size given in the file, but the
536: // code size used in compression/decompression is the code size given in
537: // the file plus one. (thus the ++).
538: codeSize++;
539: int initCodeSize = codeSize; // Starting code size, used during Clear
540: int maxCode = (1 << codeSize); // limiting value for current code size
541: int readMask = maxCode - 1; // Code AND mask for current code size
542:
543: // UNBLOCK:
544: // Read the raster data. Here we just transpose it from the GIF array
545: // to the raster array, turning it from a series of blocks into one long
546: // data stream, which makes life much easier for readCode ().
547: ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
548: byte[] raster = null;
549: for (int blockSize = 0; (blockSize = dataInput.readByte() & 0xFF) != 0;) {
550: while (blockSize-- > 0) {
551: bos.write(dataInput.readByte());
552: }
553: }
554: raster = bos.toByteArray();
555:
556: // Allocate the 'pixels'
557: maxpixels = width * height;
558: bytePixels = new byte[maxpixels];
559: int picptr = 0;
560:
561: // The hash table used by the decompressor
562: int prefix[] = new int[4096];
563: int suffix[] = new int[4096];
564: // An output array used by the decompressor
565: int outCode[] = new int[4097];
566: int outCount = 0; // Decompressor output 'stack count'
567:
568: int currentCode; // Decompressor variables
569: int oldCode = 0;
570: int inCode;
571: int finChar = 0;
572: // Decompress the file, continuing until you see the GIF EOF code.
573: // One obvious enhancement is to add checking for corrupt files here.
574: int code = readCode(dataInput, raster, codeSize, readMask);
575: while (code != EOFCode) {
576: // Clear code sets everything back to its initial value, then reads the
577: // immediately subsequent code as uncompressed data.
578: if (code == clearCode) {
579: codeSize = initCodeSize;
580: maxCode = (1 << codeSize);
581: readMask = maxCode - 1;
582: freeCode = firstFree;
583: code = readCode(dataInput, raster, codeSize, readMask);
584: currentCode = oldCode = code;
585: finChar = currentCode & bitMask;
586: if (!interlace)
587: bytePixels[picptr++] = (byte) finChar;
588: else
589: doInterlace(finChar);
590: npixels++;
591: } else {
592: // If not a clear code, must be data: save same as currentCode and inCode
593:
594: // if we're at maxcode and didn't get a clear, stop loading
595: if (freeCode >= 4096)
596: break;
597:
598: currentCode = inCode = code;
599:
600: // If greater or equal to freeCode, not in the hash table yet;
601: // repeat the last character decoded
602: if (currentCode >= freeCode) {
603: currentCode = oldCode;
604: if (outCount > 4096)
605: break;
606: outCode[outCount++] = finChar;
607: }
608:
609: // Unless this code is raw data, pursue the chain pointed to by currentCode
610: // through the hash table to its end; each code in the chain puts its
611: // associated output code on the output queue.
612: while (currentCode > bitMask) {
613: if (outCount > 4096)
614: break; // corrupt file
615: outCode[outCount++] = suffix[currentCode];
616: currentCode = prefix[currentCode];
617: }
618:
619: if (outCount > 4096)
620: break;
621:
622: // The last code in the chain is treated as raw data.
623: finChar = currentCode & bitMask;
624: outCode[outCount++] = finChar;
625:
626: // Now we put the data out to the Output routine.
627: // It's been stacked LIFO, so deal with it that way...
628:
629: // safety thing: prevent exceeding range of 'bytePixels'
630: if (npixels + outCount > maxpixels)
631: outCount = maxpixels - npixels;
632:
633: npixels += outCount;
634: if (!interlace)
635: for (int i = outCount - 1; i >= 0; i--)
636: bytePixels[picptr++] = (byte) outCode[i];
637: else
638: for (int i = outCount - 1; i >= 0; i--)
639: doInterlace(outCode[i]);
640: outCount = 0;
641:
642: // Build the hash table on-the-fly. No table is stored in the file.
643: prefix[freeCode] = oldCode;
644: suffix[freeCode] = finChar;
645: oldCode = inCode;
646:
647: // Point to the next slot in the table. If we exceed the current
648: // maxCode value, increment the code size unless it's already 12. If it
649: // is, do nothing: the next code decompressed better be CLEAR
650:
651: freeCode++;
652: if (freeCode >= maxCode) {
653: if (codeSize < 12) {
654: codeSize++;
655: maxCode *= 2;
656: readMask = (1 << codeSize) - 1;
657: }
658: }
659: }
660:
661: code = readCode(dataInput, raster, codeSize, readMask);
662: if (npixels >= maxpixels)
663: break;
664: }
665:
666: if (npixels != maxpixels) {
667: if (!interlace) // clear.EOBuffer
668: for (int i = 0; i < maxpixels - npixels; i++)
669: bytePixels[npixels + i] = 0;
670: }
671:
672: /* This was part of the original GIFReader, used to make an RGB Image
673: // But we don't need the following since we only compute the bytePixels Array
674: // fill in the GifImage structure
675:
676: intPixels = new int [bytePixels.length];
677: for (int i = 0; i < bytePixels.length; i++)
678: intPixels [i] = transparentIndex > 0
679: && ((bytePixels [i] & 0xFF) == transparentIndex)
680: ? 0
681: : 0xFF000000
682: | ((r [bytePixels [i]] & 0xFF) << 16)
683: | ((g [bytePixels [i]] & 0xFF) << 8)
684: | (b [bytePixels [i]] & 0xFF);
685: */
686:
687: // We don't need any info or comments, really, so let's get rid of this
688: /*
689: fullInfo = "GIF" + ((gif89) ? "89" : "87")
690: + ", " + bitsPerPixel + " bit" + ((bitsPerPixel == 1) ? "" : "s") + "per pixel, "
691: + (interlace ? "" : "non-") + "interlaced.";
692:
693: shortInfo = width + "x" + height + " GIF" + ((gif89) ? "89" : "87");
694: */
695:
696: // comment gets handled in main LoadGIF() block-reader
697: }
698:
699: /**
700: * Fetch the next code from the raster data stream. The codes can be
701: * any length from 3 to 12 bits, packed into 8-bit bytes, so we have to
702: * maintain our location in the raster array as a BIT Offset. We compute
703: * the byte Offset into the raster array by dividing this by 8, pick up
704: * three bytes, compute the bit Offset into our 24-bit chunk, shift to
705: * bring the desired code to the bottom, then mask it off and return it.
706: */
707: private int readCode(DataInputStream input, byte raster[],
708: int codeSize, int readMask) throws IOException {
709: int byteOffset = bitOffset / 8;
710: int inWordOffset = bitOffset % 8;
711: // Alan Dix modification to fix raster over-run errors
712: // int rawCode = (raster [byteOffset] & 0xFF)
713: // + ((raster [byteOffset + 1] & 0xFF) << 8);
714: int rawCode = (raster[byteOffset] & 0xFF);
715: if (byteOffset + 1 < raster.length)
716: rawCode += ((raster[byteOffset + 1] & 0xFF) << 8);
717: else if (codeSize + inWordOffset > 8)
718: warning(input, "short raster ? raster.length = "
719: + raster.length + ", codeSize = " + codeSize
720: + ", readMask = " + readMask);
721: // end of modification
722:
723: if (codeSize >= 8 && byteOffset + 2 < raster.length)
724: rawCode += (raster[byteOffset + 2] & 0xFF) << 16;
725: rawCode >>= (bitOffset % 8);
726: bitOffset += codeSize;
727: return rawCode & readMask;
728: }
729:
730: private void doInterlace(int index) {
731: if (oldYC != YC) {
732: ptr = YC * width;
733: oldYC = YC;
734: }
735:
736: if (YC < height)
737: bytePixels[ptr++] = (byte) index;
738:
739: // Update the X-coordinate, and if it overflows, update the Y-coordinate
740: if (++XC == width) {
741: // deal with the interlace as described in the GIF
742: // spec. Put the decoded scan line out to the screen if we haven't gone
743: // past the bottom of it
744: XC = 0;
745:
746: switch (pass) {
747: case 0:
748: YC += 8;
749: if (YC >= height) {
750: pass++;
751: YC = 4;
752: }
753: break;
754:
755: case 1:
756: YC += 8;
757: if (YC >= height) {
758: pass++;
759: YC = 2;
760: }
761: break;
762:
763: case 2:
764: YC += 4;
765: if (YC >= height) {
766: pass++;
767: YC = 1;
768: }
769: break;
770:
771: case 3:
772: YC += 2;
773: break;
774:
775: default:
776: break;
777: }
778: }
779: }
780:
781: private void warning(InputStream input, String st)
782: throws IOException {
783: throw new IOException("Warning ! " + input + " : " + st);
784: }
785: }
|