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