001: // GifEncoder - write out an image as a GIF
002: //
003: // Transparency handling and variable bit size courtesy of Jack Palevich.
004: //
005: // Copyright (C)1996,1998 by Jef Poskanzer <jef@acme.com>. All rights reserved.
006: //
007: // Redistribution and use in source and binary forms, with or without
008: // modification, are permitted provided that the following conditions
009: // are met:
010: // 1. Redistributions of source code must retain the above copyright
011: // notice, this list of conditions and the following disclaimer.
012: // 2. Redistributions in binary form must reproduce the above copyright
013: // notice, this list of conditions and the following disclaimer in the
014: // documentation and/or other materials provided with the distribution.
015: //
016: // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
017: // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
018: // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
019: // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
020: // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
021: // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
022: // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
023: // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
024: // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
025: // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
026: // SUCH DAMAGE.
027: //
028: // Visit the ACME Labs Java page for up-to-date versions of this and other
029: // fine Java utilities: http://www.acme.com/java/
030:
031: package Acme.JPM.Encoders;
032:
033: import java.awt.*;
034: import java.awt.image.ImageProducer;
035: import java.io.IOException;
036: import java.io.OutputStream;
037: import java.util.Enumeration;
038:
039: /// Write out an image as a GIF.
040: // <P>
041: // <A HREF="/resources/classes/Acme/JPM/Encoders/GifEncoder.java">Fetch the software.</A><BR>
042: // <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
043: // <P>
044: // @see ToGif
045:
046: public class GifEncoder extends ImageEncoder {
047:
048: private boolean interlace = false;
049:
050: /// Constructor from Image.
051: // @param img The image to encode.
052: // @param out The stream to write the GIF to.
053: public GifEncoder(Image img, OutputStream out) throws IOException {
054: super (img, out);
055: }
056:
057: /// Constructor from Image with interlace setting.
058: // @param img The image to encode.
059: // @param out The stream to write the GIF to.
060: // @param interlace Whether to interlace.
061: public GifEncoder(Image img, OutputStream out, boolean interlace)
062: throws IOException {
063: super (img, out);
064: this .interlace = interlace;
065: }
066:
067: /// Constructor from ImageProducer.
068: // @param prod The ImageProducer to encode.
069: // @param out The stream to write the GIF to.
070: public GifEncoder(ImageProducer prod, OutputStream out)
071: throws IOException {
072: super (prod, out);
073: }
074:
075: /// Constructor from ImageProducer with interlace setting.
076: // @param prod The ImageProducer to encode.
077: // @param out The stream to write the GIF to.
078: public GifEncoder(ImageProducer prod, OutputStream out,
079: boolean interlace) throws IOException {
080: super (prod, out);
081: this .interlace = interlace;
082: }
083:
084: int width, height;
085: int[][] rgbPixels;
086:
087: void encodeStart(int width, int height) throws IOException {
088: this .width = width;
089: this .height = height;
090: rgbPixels = new int[height][width];
091: }
092:
093: void encodePixels(int x, int y, int w, int h, int[] rgbPixels,
094: int off, int scansize) throws IOException {
095: // Save the pixels.
096: for (int row = 0; row < h; ++row)
097: System.arraycopy(rgbPixels, row * scansize + off,
098: this .rgbPixels[y + row], x, w);
099:
100: }
101:
102: Acme.IntHashtable colorHash;
103:
104: void encodeDone() throws IOException {
105: int transparentIndex = -1;
106: int transparentRgb = -1;
107: // Put all the pixels into a hash table.
108: colorHash = new Acme.IntHashtable();
109: int index = 0;
110: for (int row = 0; row < height; ++row) {
111: int rowOffset = row * width;
112: for (int col = 0; col < width; ++col) {
113: int rgb = rgbPixels[row][col];
114: boolean isTransparent = ((rgb >>> 24) < 0x80);
115: if (isTransparent) {
116: if (transparentIndex < 0) {
117: // First transparent color; remember it.
118: transparentIndex = index;
119: transparentRgb = rgb;
120: } else if (rgb != transparentRgb) {
121: // A second transparent color; replace it with
122: // the first one.
123: rgbPixels[row][col] = rgb = transparentRgb;
124: }
125: }
126: GifEncoderHashitem item = (GifEncoderHashitem) colorHash
127: .get(rgb);
128: if (item == null) {
129: if (index >= 256)
130: throw new IOException(
131: "too many colors for a GIF");
132: item = new GifEncoderHashitem(rgb, 1, index,
133: isTransparent);
134: ++index;
135: colorHash.put(rgb, item);
136: } else
137: ++item.count;
138: }
139: }
140:
141: // Figure out how many bits to use.
142: int logColors;
143: if (index <= 2)
144: logColors = 1;
145: else if (index <= 4)
146: logColors = 2;
147: else if (index <= 16)
148: logColors = 4;
149: else
150: logColors = 8;
151:
152: // Turn colors into colormap entries.
153: int mapSize = 1 << logColors;
154: byte[] reds = new byte[mapSize];
155: byte[] grns = new byte[mapSize];
156: byte[] blus = new byte[mapSize];
157: for (Enumeration e = colorHash.elements(); e.hasMoreElements();) {
158: GifEncoderHashitem item = (GifEncoderHashitem) e
159: .nextElement();
160: reds[item.index] = (byte) ((item.rgb >> 16) & 0xff);
161: grns[item.index] = (byte) ((item.rgb >> 8) & 0xff);
162: blus[item.index] = (byte) (item.rgb & 0xff);
163: }
164:
165: GIFEncode(out, width, height, interlace, (byte) 0,
166: transparentIndex, logColors, reds, grns, blus);
167: }
168:
169: byte GetPixel(int x, int y) throws IOException {
170: GifEncoderHashitem item = (GifEncoderHashitem) colorHash
171: .get(rgbPixels[y][x]);
172: if (item == null)
173: throw new IOException("color not found");
174: return (byte) item.index;
175: }
176:
177: static void writeString(OutputStream out, String str)
178: throws IOException {
179: byte[] buf = str.getBytes();
180: out.write(buf);
181: }
182:
183: // Adapted from ppmtogif, which is based on GIFENCOD by David
184: // Rowley <mgardi@watdscu.waterloo.edu>. Lempel-Zim compression
185: // based on "compress".
186:
187: int Width, Height;
188: boolean Interlace;
189: int curx, cury;
190: int CountDown;
191: int Pass = 0;
192:
193: void GIFEncode(OutputStream outs, int Width, int Height,
194: boolean Interlace, byte Background, int Transparent,
195: int BitsPerPixel, byte[] Red, byte[] Green, byte[] Blue)
196: throws IOException {
197: byte B;
198: int LeftOfs, TopOfs;
199: int ColorMapSize;
200: int InitCodeSize;
201: int i;
202:
203: this .Width = Width;
204: this .Height = Height;
205: this .Interlace = Interlace;
206: ColorMapSize = 1 << BitsPerPixel;
207: LeftOfs = TopOfs = 0;
208:
209: // Calculate number of bits we are expecting
210: CountDown = Width * Height;
211:
212: // Indicate which pass we are on (if interlace)
213: Pass = 0;
214:
215: // The initial code size
216: if (BitsPerPixel <= 1)
217: InitCodeSize = 2;
218: else
219: InitCodeSize = BitsPerPixel;
220:
221: // Set up the current x and y position
222: curx = 0;
223: cury = 0;
224:
225: // Write the Magic header
226: writeString(outs, "GIF89a");
227:
228: // Write out the screen width and height
229: Putword(Width, outs);
230: Putword(Height, outs);
231:
232: // Indicate that there is a global colour map
233: B = (byte) 0x80; // Yes, there is a color map
234: // OR in the resolution
235: B |= (byte) ((8 - 1) << 4);
236: // Not sorted
237: // OR in the Bits per Pixel
238: B |= (byte) ((BitsPerPixel - 1));
239:
240: // Write it out
241: Putbyte(B, outs);
242:
243: // Write out the Background colour
244: Putbyte(Background, outs);
245:
246: // Pixel aspect ratio - 1:1.
247: //Putbyte( (byte) 49, outs );
248: // Java's GIF reader currently has a bug, if the aspect ratio byte is
249: // not zero it throws an ImageFormatException. It doesn't know that
250: // 49 means a 1:1 aspect ratio. Well, whatever, zero works with all
251: // the other decoders I've tried so it probably doesn't hurt.
252: Putbyte((byte) 0, outs);
253:
254: // Write out the Global Colour Map
255: for (i = 0; i < ColorMapSize; ++i) {
256: Putbyte(Red[i], outs);
257: Putbyte(Green[i], outs);
258: Putbyte(Blue[i], outs);
259: }
260:
261: // Write out extension for transparent colour index, if necessary.
262: if (Transparent != -1) {
263: Putbyte((byte) '!', outs);
264: Putbyte((byte) 0xf9, outs);
265: Putbyte((byte) 4, outs);
266: Putbyte((byte) 1, outs);
267: Putbyte((byte) 0, outs);
268: Putbyte((byte) 0, outs);
269: Putbyte((byte) Transparent, outs);
270: Putbyte((byte) 0, outs);
271: }
272:
273: // Write an Image separator
274: Putbyte((byte) ',', outs);
275:
276: // Write the Image header
277: Putword(LeftOfs, outs);
278: Putword(TopOfs, outs);
279: Putword(Width, outs);
280: Putword(Height, outs);
281:
282: // Write out whether or not the image is interlaced
283: if (Interlace)
284: Putbyte((byte) 0x40, outs);
285: else
286: Putbyte((byte) 0x00, outs);
287:
288: // Write out the initial code size
289: Putbyte((byte) InitCodeSize, outs);
290:
291: // Go and actually compress the data
292: compress(InitCodeSize + 1, outs);
293:
294: // Write out a Zero-length packet (to end the series)
295: Putbyte((byte) 0, outs);
296:
297: // Write the GIF file terminator
298: Putbyte((byte) ';', outs);
299: }
300:
301: // Bump the 'curx' and 'cury' to point to the next pixel
302: void BumpPixel() {
303: // Bump the current X position
304: ++curx;
305:
306: // If we are at the end of a scan line, set curx back to the beginning
307: // If we are interlaced, bump the cury to the appropriate spot,
308: // otherwise, just increment it.
309: if (curx == Width) {
310: curx = 0;
311:
312: if (!Interlace)
313: ++cury;
314: else {
315: switch (Pass) {
316: case 0:
317: cury += 8;
318: if (cury >= Height) {
319: ++Pass;
320: cury = 4;
321: }
322: break;
323:
324: case 1:
325: cury += 8;
326: if (cury >= Height) {
327: ++Pass;
328: cury = 2;
329: }
330: break;
331:
332: case 2:
333: cury += 4;
334: if (cury >= Height) {
335: ++Pass;
336: cury = 1;
337: }
338: break;
339:
340: case 3:
341: cury += 2;
342: break;
343: }
344: }
345: }
346: }
347:
348: static final int EOF = -1;
349:
350: // Return the next pixel from the image
351: int GIFNextPixel() throws IOException {
352: byte r;
353:
354: if (CountDown == 0)
355: return EOF;
356:
357: --CountDown;
358:
359: r = GetPixel(curx, cury);
360:
361: BumpPixel();
362:
363: return r & 0xff;
364: }
365:
366: // Write out a word to the GIF file
367: void Putword(int w, OutputStream outs) throws IOException {
368: Putbyte((byte) (w & 0xff), outs);
369: Putbyte((byte) ((w >> 8) & 0xff), outs);
370: }
371:
372: // Write out a byte to the GIF file
373: void Putbyte(byte b, OutputStream outs) throws IOException {
374: outs.write(b);
375: }
376:
377: // GIFCOMPR.C - GIF Image compression routines
378: //
379: // Lempel-Ziv compression based on 'compress'. GIF modifications by
380: // David Rowley (mgardi@watdcsu.waterloo.edu)
381:
382: // General DEFINEs
383:
384: static final int BITS = 12;
385:
386: static final int HSIZE = 5003; // 80% occupancy
387:
388: // GIF Image compression - modified 'compress'
389: //
390: // Based on: compress.c - File compression ala IEEE Computer, June 1984.
391: //
392: // By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
393: // Jim McKie (decvax!mcvax!jim)
394: // Steve Davies (decvax!vax135!petsd!peora!srd)
395: // Ken Turkowski (decvax!decwrl!turtlevax!ken)
396: // James A. Woods (decvax!ihnp4!ames!jaw)
397: // Joe Orost (decvax!vax135!petsd!joe)
398:
399: int n_bits; // number of bits/code
400: int maxbits = BITS; // user settable max # bits/code
401: int maxcode; // maximum code, given n_bits
402: int maxmaxcode = 1 << BITS; // should NEVER generate this code
403:
404: final int MAXCODE(int n_bits) {
405: return (1 << n_bits) - 1;
406: }
407:
408: int[] htab = new int[HSIZE];
409: int[] codetab = new int[HSIZE];
410:
411: int hsize = HSIZE; // for dynamic table sizing
412:
413: int free_ent = 0; // first unused entry
414:
415: // block compression parameters -- after all codes are used up,
416: // and compression rate changes, start over.
417: boolean clear_flg = false;
418:
419: // Algorithm: use open addressing double hashing (no chaining) on the
420: // prefix code / next character combination. We do a variant of Knuth's
421: // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
422: // secondary probe. Here, the modular division first probe is gives way
423: // to a faster exclusive-or manipulation. Also do block compression with
424: // an adaptive reset, whereby the code table is cleared when the compression
425: // ratio decreases, but after the table fills. The variable-length output
426: // codes are re-sized at this point, and a special CLEAR code is generated
427: // for the decompressor. Late addition: construct the table according to
428: // file size for noticeable speed improvement on small files. Please direct
429: // questions about this implementation to ames!jaw.
430:
431: int g_init_bits;
432:
433: int ClearCode;
434: int EOFCode;
435:
436: void compress(int init_bits, OutputStream outs) throws IOException {
437: int fcode;
438: int i /* = 0 */;
439: int c;
440: int ent;
441: int disp;
442: int hsize_reg;
443: int hshift;
444:
445: // Set up the globals: g_init_bits - initial number of bits
446: g_init_bits = init_bits;
447:
448: // Set up the necessary values
449: clear_flg = false;
450: n_bits = g_init_bits;
451: maxcode = MAXCODE(n_bits);
452:
453: ClearCode = 1 << (init_bits - 1);
454: EOFCode = ClearCode + 1;
455: free_ent = ClearCode + 2;
456:
457: char_init();
458:
459: ent = GIFNextPixel();
460:
461: hshift = 0;
462: for (fcode = hsize; fcode < 65536; fcode *= 2)
463: ++hshift;
464: hshift = 8 - hshift; // set hash code range bound
465:
466: hsize_reg = hsize;
467: cl_hash(hsize_reg); // clear hash table
468:
469: output(ClearCode, outs);
470:
471: outer_loop: while ((c = GIFNextPixel()) != EOF) {
472: fcode = (c << maxbits) + ent;
473: i = (c << hshift) ^ ent; // xor hashing
474:
475: if (htab[i] == fcode) {
476: ent = codetab[i];
477: continue;
478: } else if (htab[i] >= 0) // non-empty slot
479: {
480: disp = hsize_reg - i; // secondary hash (after G. Knott)
481: if (i == 0)
482: disp = 1;
483: do {
484: if ((i -= disp) < 0)
485: i += hsize_reg;
486:
487: if (htab[i] == fcode) {
488: ent = codetab[i];
489: continue outer_loop;
490: }
491: } while (htab[i] >= 0);
492: }
493: output(ent, outs);
494: ent = c;
495: if (free_ent < maxmaxcode) {
496: codetab[i] = free_ent++; // code -> hashtable
497: htab[i] = fcode;
498: } else
499: cl_block(outs);
500: }
501: // Put out the final code.
502: output(ent, outs);
503: output(EOFCode, outs);
504: }
505:
506: // output
507: //
508: // Output the given code.
509: // Inputs:
510: // code: A n_bits-bit integer. If == -1, then EOF. This assumes
511: // that n_bits =< wordsize - 1.
512: // Outputs:
513: // Outputs code to the file.
514: // Assumptions:
515: // Chars are 8 bits long.
516: // Algorithm:
517: // Maintain a BITS character long buffer (so that 8 codes will
518: // fit in it exactly). Use the VAX insv instruction to insert each
519: // code in turn. When the buffer fills up empty it and start over.
520:
521: int cur_accum = 0;
522: int cur_bits = 0;
523:
524: int masks[] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F,
525: 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF,
526: 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
527:
528: void output(int code, OutputStream outs) throws IOException {
529: cur_accum &= masks[cur_bits];
530:
531: if (cur_bits > 0)
532: cur_accum |= (code << cur_bits);
533: else
534: cur_accum = code;
535:
536: cur_bits += n_bits;
537:
538: while (cur_bits >= 8) {
539: char_out((byte) (cur_accum & 0xff), outs);
540: cur_accum >>= 8;
541: cur_bits -= 8;
542: }
543:
544: // If the next entry is going to be too big for the code size,
545: // then increase it, if possible.
546: if (free_ent > maxcode || clear_flg) {
547: if (clear_flg) {
548: maxcode = MAXCODE(n_bits = g_init_bits);
549: clear_flg = false;
550: } else {
551: ++n_bits;
552: if (n_bits == maxbits)
553: maxcode = maxmaxcode;
554: else
555: maxcode = MAXCODE(n_bits);
556: }
557: }
558:
559: if (code == EOFCode) {
560: // At EOF, write the rest of the buffer.
561: while (cur_bits > 0) {
562: char_out((byte) (cur_accum & 0xff), outs);
563: cur_accum >>= 8;
564: cur_bits -= 8;
565: }
566:
567: flush_char(outs);
568: }
569: }
570:
571: // Clear out the hash table
572:
573: // table clear for block compress
574: void cl_block(OutputStream outs) throws IOException {
575: cl_hash(hsize);
576: free_ent = ClearCode + 2;
577: clear_flg = true;
578:
579: output(ClearCode, outs);
580: }
581:
582: // reset code table
583: void cl_hash(int hsize) {
584: for (int i = 0; i < hsize; ++i)
585: htab[i] = -1;
586: }
587:
588: // GIF Specific routines
589:
590: // Number of characters so far in this 'packet'
591: int a_count;
592:
593: // Set up the 'byte output' routine
594: void char_init() {
595: a_count = 0;
596: }
597:
598: // Define the storage for the packet accumulator
599: byte[] accum = new byte[256];
600:
601: // Add a character to the end of the current packet, and if it is 254
602: // characters, flush the packet to disk.
603: void char_out(byte c, OutputStream outs) throws IOException {
604: accum[a_count++] = c;
605: if (a_count >= 254)
606: flush_char(outs);
607: }
608:
609: // Flush the packet to disk, and reset the accumulator
610: void flush_char(OutputStream outs) throws IOException {
611: if (a_count > 0) {
612: outs.write(a_count);
613: outs.write(accum, 0, a_count);
614: a_count = 0;
615: }
616: }
617:
618: }
619:
620: class GifEncoderHashitem {
621:
622: public int rgb;
623: public int count;
624: public int index;
625: public boolean isTransparent;
626:
627: public GifEncoderHashitem(int rgb, int count, int index,
628: boolean isTransparent) {
629: this.rgb = rgb;
630: this.count = count;
631: this.index = index;
632: this.isTransparent = isTransparent;
633: }
634:
635: }
|