KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jrobin > graph > GifEncoder


1 /* ============================================================
2  * JRobin : Pure java implementation of RRDTool's functionality
3  * ============================================================
4  *
5  * Project Info: http://www.jrobin.org
6  * Project Lead: Sasa Markovic (saxon@jrobin.org)
7  *
8  * Developers: Sasa Markovic (saxon@jrobin.org)
9  * Arne Vandamme (cobralord@jrobin.org)
10  *
11  * (C) Copyright 2003, by Sasa Markovic.
12  *
13  * This library is free software; you can redistribute it and/or modify it under the terms
14  * of the GNU Lesser General Public License as published by the Free Software Foundation;
15  * either version 2.1 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  * See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along with this
22  * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23  * Boston, MA 02111-1307, USA.
24  */

25
26 /*
27  * GifEncoder from J.M.G. Elliott
28  * http://jmge.net/java/gifenc/
29  */

30 package org.jrobin.graph;
31
32 import java.awt.*;
33 import java.awt.image.PixelGrabber JavaDoc;
34 import java.io.*;
35 import java.util.Vector JavaDoc;
36
37 class GifEncoder
38 {
39     private Dimension dispDim = new Dimension(0, 0);
40     private GifColorTable colorTable;
41     private int bgIndex = 0;
42     private int loopCount = 1;
43     private String JavaDoc theComments;
44     private Vector JavaDoc vFrames = new Vector JavaDoc();
45
46     GifEncoder() {
47         colorTable = new GifColorTable();
48     }
49
50     GifEncoder(Image static_image) throws IOException {
51         this();
52         addFrame(static_image);
53     }
54
55     GifEncoder(Color[] colors) {
56         colorTable = new GifColorTable(colors);
57     }
58
59     GifEncoder(Color[] colors, int width, int height, byte ci_pixels[])
60         throws IOException {
61         this(colors);
62         addFrame(width, height, ci_pixels);
63     }
64
65     int getFrameCount() {
66         return vFrames.size();
67     }
68
69     Gif89Frame getFrameAt(int index) {
70         return isOk(index) ? (Gif89Frame) vFrames.elementAt(index) : null;
71     }
72
73     void addFrame(Gif89Frame gf) throws IOException {
74         accommodateFrame(gf);
75         vFrames.addElement(gf);
76     }
77
78     void addFrame(Image image) throws IOException {
79         addFrame(new DirectGif89Frame(image));
80     }
81
82     void addFrame(int width, int height, byte ci_pixels[])
83         throws IOException {
84         addFrame(new IndexGif89Frame(width, height, ci_pixels));
85     }
86
87     void insertFrame(int index, Gif89Frame gf) throws IOException {
88         accommodateFrame(gf);
89         vFrames.insertElementAt(gf, index);
90     }
91
92     void setTransparentIndex(int index) {
93         colorTable.setTransparent(index);
94     }
95
96     void setLogicalDisplay(Dimension dim, int background) {
97         dispDim = new Dimension(dim);
98         bgIndex = background;
99     }
100
101     void setLoopCount(int count) {
102         loopCount = count;
103     }
104
105     void setComments(String JavaDoc comments) {
106         theComments = comments;
107     }
108
109     void setUniformDelay(int interval) {
110         for (int i = 0; i < vFrames.size(); ++i)
111             ((Gif89Frame) vFrames.elementAt(i)).setDelay(interval);
112     }
113
114     void encode(OutputStream out) throws IOException {
115         int nframes = getFrameCount();
116         boolean is_sequence = nframes > 1;
117         colorTable.closePixelProcessing();
118         Put.ascii("GIF89a", out);
119         writeLogicalScreenDescriptor(out);
120         colorTable.encode(out);
121         if (is_sequence && loopCount != 1)
122             writeNetscapeExtension(out);
123         if (theComments != null && theComments.length() > 0)
124             writeCommentExtension(out);
125         for (int i = 0; i < nframes; ++i)
126             ((Gif89Frame) vFrames.elementAt(i)).encode(
127                 out, is_sequence, colorTable.getDepth(), colorTable.getTransparent()
128             );
129         out.write((int) ';');
130         out.flush();
131     }
132
133     private void accommodateFrame(Gif89Frame gf) throws IOException {
134         dispDim.width = Math.max(dispDim.width, gf.getWidth());
135         dispDim.height = Math.max(dispDim.height, gf.getHeight());
136         colorTable.processPixels(gf);
137     }
138
139     private void writeLogicalScreenDescriptor(OutputStream os) throws IOException {
140         Put.leShort(dispDim.width, os);
141         Put.leShort(dispDim.height, os);
142         os.write(0xf0 | colorTable.getDepth() - 1);
143         os.write(bgIndex);
144         os.write(0);
145     }
146
147
148     private void writeNetscapeExtension(OutputStream os) throws IOException {
149         os.write((int) '!');
150         os.write(0xff);
151         os.write(11);
152         Put.ascii("NETSCAPE2.0", os);
153         os.write(3);
154         os.write(1);
155         Put.leShort(loopCount > 1 ? loopCount - 1 : 0, os);
156         os.write(0);
157     }
158
159
160     private void writeCommentExtension(OutputStream os) throws IOException {
161         os.write((int) '!');
162         os.write(0xfe);
163         int remainder = theComments.length() % 255;
164         int nsubblocks_full = theComments.length() / 255;
165         int nsubblocks = nsubblocks_full + (remainder > 0 ? 1 : 0);
166         int ibyte = 0;
167         for (int isb = 0; isb < nsubblocks; ++isb) {
168             int size = isb < nsubblocks_full ? 255 : remainder;
169             os.write(size);
170             Put.ascii(theComments.substring(ibyte, ibyte + size), os);
171             ibyte += size;
172         }
173         os.write(0);
174     }
175
176
177     private boolean isOk(int frame_index) {
178         return frame_index >= 0 && frame_index < vFrames.size();
179     }
180 }
181
182 class DirectGif89Frame extends Gif89Frame {
183     private int[] argbPixels;
184
185     DirectGif89Frame(Image img) throws IOException {
186         PixelGrabber JavaDoc pg = new PixelGrabber JavaDoc(img, 0, 0, -1, -1, true);
187         String JavaDoc errmsg = null;
188         try {
189             if (!pg.grabPixels())
190                 errmsg = "can't grab pixels from image";
191         } catch (InterruptedException JavaDoc e) {
192             errmsg = "interrupted grabbing pixels from image";
193         }
194         if (errmsg != null)
195             throw new IOException(errmsg + " (" + getClass().getName() + ")");
196         theWidth = pg.getWidth();
197         theHeight = pg.getHeight();
198         argbPixels = (int[]) pg.getPixels();
199         ciPixels = new byte[argbPixels.length];
200     }
201
202     DirectGif89Frame(int width, int height, int argb_pixels[]) {
203         theWidth = width;
204         theHeight = height;
205         argbPixels = new int[theWidth * theHeight];
206         System.arraycopy(argb_pixels, 0, argbPixels, 0, argbPixels.length);
207         ciPixels = new byte[argbPixels.length];
208     }
209
210     Object JavaDoc getPixelSource() {
211         return argbPixels;
212     }
213 }
214
215
216 class GifColorTable {
217     private int[] theColors = new int[256];
218     private int colorDepth;
219     private int transparentIndex = -1;
220     private int ciCount = 0;
221     private ReverseColorMap ciLookup;
222
223     GifColorTable() {
224         ciLookup = new ReverseColorMap();
225     }
226
227     GifColorTable(Color[] colors) {
228         int n2copy = Math.min(theColors.length, colors.length);
229         for (int i = 0; i < n2copy; ++i)
230             theColors[i] = colors[i].getRGB();
231     }
232
233     int getDepth() {
234         return colorDepth;
235     }
236
237     int getTransparent() {
238         return transparentIndex;
239     }
240
241     void setTransparent(int color_index) {
242         transparentIndex = color_index;
243     }
244
245     void processPixels(Gif89Frame gf) throws IOException {
246         if (gf instanceof DirectGif89Frame)
247             filterPixels((DirectGif89Frame) gf);
248         else
249             trackPixelUsage((IndexGif89Frame) gf);
250     }
251
252     void closePixelProcessing() {
253         colorDepth = computeColorDepth(ciCount);
254     }
255
256     void encode(OutputStream os) throws IOException {
257         int palette_size = 1 << colorDepth;
258         for (int i = 0; i < palette_size; ++i) {
259             os.write(theColors[i] >> 16 & 0xff);
260             os.write(theColors[i] >> 8 & 0xff);
261             os.write(theColors[i] & 0xff);
262         }
263     }
264
265     private void filterPixels(DirectGif89Frame dgf) throws IOException {
266         if (ciLookup == null)
267             throw new IOException("RGB frames require palette autodetection");
268         int[] argb_pixels = (int[]) dgf.getPixelSource();
269         byte[] ci_pixels = dgf.getPixelSink();
270         int npixels = argb_pixels.length;
271         for (int i = 0; i < npixels; ++i) {
272             int argb = argb_pixels[i];
273             if ((argb >>> 24) < 0x80)
274                 if (transparentIndex == -1)
275                     transparentIndex = ciCount;
276                 else if (argb != theColors[transparentIndex]) {
277                     ci_pixels[i] = (byte) transparentIndex;
278                     continue;
279                 }
280             int color_index = ciLookup.getPaletteIndex(argb & 0xffffff);
281             if (color_index == -1) {
282                 if (ciCount == 256)
283                     throw new IOException("can't encode as GIF (> 256 colors)");
284                 theColors[ciCount] = argb;
285                 ciLookup.put(argb & 0xffffff, ciCount);
286                 ci_pixels[i] = (byte) ciCount;
287                 ++ciCount;
288             } else
289                 ci_pixels[i] = (byte) color_index;
290         }
291     }
292
293     private void trackPixelUsage(IndexGif89Frame igf) {
294         byte[] ci_pixels = (byte[]) igf.getPixelSource();
295         int npixels = ci_pixels.length;
296         for (int i = 0; i < npixels; ++i)
297             if (ci_pixels[i] >= ciCount)
298                 ciCount = ci_pixels[i] + 1;
299     }
300
301     private int computeColorDepth(int colorcount) {
302         if (colorcount <= 2)
303             return 1;
304         if (colorcount <= 4)
305             return 2;
306         if (colorcount <= 16)
307             return 4;
308         return 8;
309     }
310 }
311
312 class ReverseColorMap {
313     private static class ColorRecord {
314         int rgb;
315         int ipalette;
316
317         ColorRecord(int rgb, int ipalette) {
318             this.rgb = rgb;
319             this.ipalette = ipalette;
320         }
321     }
322
323     private static final int HCAPACITY = 2053;
324     private ColorRecord[] hTable = new ColorRecord[HCAPACITY];
325
326     int getPaletteIndex(int rgb) {
327         ColorRecord rec;
328         for (int itable = rgb % hTable.length;
329              (rec = hTable[itable]) != null && rec.rgb != rgb;
330              itable = ++itable % hTable.length
331             )
332             ;
333         if (rec != null)
334             return rec.ipalette;
335         return -1;
336     }
337
338
339     void put(int rgb, int ipalette) {
340         int itable;
341         for (itable = rgb % hTable.length;
342              hTable[itable] != null;
343              itable = ++itable % hTable.length
344             )
345             ;
346         hTable[itable] = new ColorRecord(rgb, ipalette);
347     }
348 }
349
350 abstract class Gif89Frame {
351     static final int DM_UNDEFINED = 0;
352     static final int DM_LEAVE = 1;
353     static final int DM_BGCOLOR = 2;
354     static final int DM_REVERT = 3;
355     int theWidth = -1;
356     int theHeight = -1;
357     byte[] ciPixels;
358
359     private Point thePosition = new Point(0, 0);
360     private boolean isInterlaced;
361     private int csecsDelay;
362     private int disposalCode = DM_LEAVE;
363
364     void setPosition(Point p) {
365         thePosition = new Point(p);
366     }
367
368     void setInterlaced(boolean b) {
369         isInterlaced = b;
370     }
371
372     void setDelay(int interval) {
373         csecsDelay = interval;
374     }
375
376     void setDisposalMode(int code) {
377         disposalCode = code;
378     }
379
380     Gif89Frame() {
381     }
382
383     abstract Object JavaDoc getPixelSource();
384
385     int getWidth() {
386         return theWidth;
387     }
388
389     int getHeight() {
390         return theHeight;
391     }
392
393     byte[] getPixelSink() {
394         return ciPixels;
395     }
396
397     void encode(OutputStream os, boolean epluribus, int color_depth,
398                 int transparent_index) throws IOException {
399         writeGraphicControlExtension(os, epluribus, transparent_index);
400         writeImageDescriptor(os);
401         new GifPixelsEncoder(
402             theWidth, theHeight, ciPixels, isInterlaced, color_depth
403         ).encode(os);
404     }
405
406     private void writeGraphicControlExtension(OutputStream os, boolean epluribus,
407                                               int itransparent) throws IOException {
408         int transflag = itransparent == -1 ? 0 : 1;
409         if (transflag == 1 || epluribus) {
410             os.write((int) '!');
411             os.write(0xf9);
412             os.write(4);
413             os.write((disposalCode << 2) | transflag);
414             Put.leShort(csecsDelay, os);
415             os.write(itransparent);
416             os.write(0);
417         }
418     }
419
420     private void writeImageDescriptor(OutputStream os) throws IOException {
421         os.write((int) ',');
422         Put.leShort(thePosition.x, os);
423         Put.leShort(thePosition.y, os);
424         Put.leShort(theWidth, os);
425         Put.leShort(theHeight, os);
426         os.write(isInterlaced ? 0x40 : 0);
427     }
428 }
429
430 class GifPixelsEncoder {
431     private static final int EOF = -1;
432     private int imgW, imgH;
433     private byte[] pixAry;
434     private boolean wantInterlaced;
435     private int initCodeSize;
436     private int countDown;
437     private int xCur, yCur;
438     private int curPass;
439
440     GifPixelsEncoder(int width, int height, byte[] pixels, boolean interlaced,
441                      int color_depth) {
442         imgW = width;
443         imgH = height;
444         pixAry = pixels;
445         wantInterlaced = interlaced;
446         initCodeSize = Math.max(2, color_depth);
447     }
448
449     void encode(OutputStream os) throws IOException {
450         os.write(initCodeSize);
451
452         countDown = imgW * imgH;
453         xCur = yCur = curPass = 0;
454
455         compress(initCodeSize + 1, os);
456
457         os.write(0);
458     }
459
460     private void bumpPosition() {
461         ++xCur;
462         if (xCur == imgW) {
463             xCur = 0;
464             if (!wantInterlaced)
465                 ++yCur;
466             else
467                 switch (curPass) {
468                     case 0:
469                         yCur += 8;
470                         if (yCur >= imgH) {
471                             ++curPass;
472                             yCur = 4;
473                         }
474                         break;
475                     case 1:
476                         yCur += 8;
477                         if (yCur >= imgH) {
478                             ++curPass;
479                             yCur = 2;
480                         }
481                         break;
482                     case 2:
483                         yCur += 4;
484                         if (yCur >= imgH) {
485                             ++curPass;
486                             yCur = 1;
487                         }
488                         break;
489                     case 3:
490                         yCur += 2;
491                         break;
492                 }
493         }
494     }
495
496     private int nextPixel() {
497         if (countDown == 0)
498             return EOF;
499         --countDown;
500         byte pix = pixAry[yCur * imgW + xCur];
501         bumpPosition();
502         return pix & 0xff;
503     }
504
505     static final int BITS = 12;
506     static final int HSIZE = 5003;
507     int n_bits;
508     int maxbits = BITS;
509     int maxcode;
510     int maxmaxcode = 1 << BITS;
511
512     final int MAXCODE(int n_bits) {
513         return (1 << n_bits) - 1;
514     }
515
516     int[] htab = new int[HSIZE];
517     int[] codetab = new int[HSIZE];
518     int hsize = HSIZE;
519     int free_ent = 0;
520     boolean clear_flg = false;
521     int g_init_bits;
522     int ClearCode;
523     int EOFCode;
524
525     void compress(int init_bits, OutputStream outs) throws IOException {
526         int fcode;
527         int i /* = 0 */;
528         int c;
529         int ent;
530         int disp;
531         int hsize_reg;
532         int hshift;
533         g_init_bits = init_bits;
534         clear_flg = false;
535         n_bits = g_init_bits;
536         maxcode = MAXCODE(n_bits);
537         ClearCode = 1 << (init_bits - 1);
538         EOFCode = ClearCode + 1;
539         free_ent = ClearCode + 2;
540
541         char_init();
542         ent = nextPixel();
543         hshift = 0;
544         for (fcode = hsize; fcode < 65536; fcode *= 2)
545             ++hshift;
546         hshift = 8 - hshift;
547         hsize_reg = hsize;
548         cl_hash(hsize_reg);
549         output(ClearCode, outs);
550         outer_loop:
551         while ((c = nextPixel()) != EOF) {
552             fcode = (c << maxbits) + ent;
553             i = (c << hshift) ^ ent;
554             if (htab[i] == fcode) {
555                 ent = codetab[i];
556                 continue;
557             } else if (htab[i] >= 0) {
558                 disp = hsize_reg - i;
559                 if (i == 0)
560                     disp = 1;
561                 do {
562                     if ((i -= disp) < 0)
563                         i += hsize_reg;
564
565                     if (htab[i] == fcode) {
566                         ent = codetab[i];
567                         continue outer_loop;
568                     }
569                 } while (htab[i] >= 0);
570             }
571             output(ent, outs);
572             ent = c;
573             if (free_ent < maxmaxcode) {
574                 codetab[i] = free_ent++;
575                 htab[i] = fcode;
576             } else
577                 cl_block(outs);
578         }
579         output(ent, outs);
580         output(EOFCode, outs);
581     }
582
583     int cur_accum = 0;
584     int cur_bits = 0;
585     int masks[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000F,
586                    0x001F, 0x003F, 0x007F, 0x00FF,
587                    0x01FF, 0x03FF, 0x07FF, 0x0FFF,
588                    0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF};
589
590     void output(int code, OutputStream outs) throws IOException {
591         cur_accum &= masks[cur_bits];
592         if (cur_bits > 0)
593             cur_accum |= (code << cur_bits);
594         else
595             cur_accum = code;
596
597         cur_bits += n_bits;
598
599         while (cur_bits >= 8) {
600             char_out((byte) (cur_accum & 0xff), outs);
601             cur_accum >>= 8;
602             cur_bits -= 8;
603         }
604         if (free_ent > maxcode || clear_flg) {
605             if (clear_flg) {
606                 maxcode = MAXCODE(n_bits = g_init_bits);
607                 clear_flg = false;
608             } else {
609                 ++n_bits;
610                 if (n_bits == maxbits)
611                     maxcode = maxmaxcode;
612                 else
613                     maxcode = MAXCODE(n_bits);
614             }
615         }
616         if (code == EOFCode) {
617
618             while (cur_bits > 0) {
619                 char_out((byte) (cur_accum & 0xff), outs);
620                 cur_accum >>= 8;
621                 cur_bits -= 8;
622             }
623             flush_char(outs);
624         }
625     }
626
627
628     void cl_block(OutputStream outs) throws IOException {
629         cl_hash(hsize);
630         free_ent = ClearCode + 2;
631         clear_flg = true;
632
633         output(ClearCode, outs);
634     }
635
636
637     void cl_hash(int hsize) {
638         for (int i = 0; i < hsize; ++i)
639             htab[i] = -1;
640     }
641
642     int a_count;
643
644     void char_init() {
645         a_count = 0;
646     }
647
648     byte[] accum = new byte[256];
649
650     void char_out(byte c, OutputStream outs) throws IOException {
651         accum[a_count++] = c;
652         if (a_count >= 254)
653             flush_char(outs);
654     }
655
656     void flush_char(OutputStream outs) throws IOException {
657         if (a_count > 0) {
658             outs.write(a_count);
659             outs.write(accum, 0, a_count);
660             a_count = 0;
661         }
662     }
663 }
664
665 class IndexGif89Frame extends Gif89Frame {
666
667     IndexGif89Frame(int width, int height, byte ci_pixels[]) {
668         theWidth = width;
669         theHeight = height;
670         ciPixels = new byte[theWidth * theHeight];
671         System.arraycopy(ci_pixels, 0, ciPixels, 0, ciPixels.length);
672     }
673
674     Object JavaDoc getPixelSource() {
675         return ciPixels;
676     }
677 }
678
679
680 final class Put {
681     static void ascii(String JavaDoc s, OutputStream os) throws IOException {
682         byte[] bytes = new byte[s.length()];
683         for (int i = 0; i < bytes.length; ++i)
684             bytes[i] = (byte) s.charAt(i);
685         os.write(bytes);
686     }
687
688     static void leShort(int i16, OutputStream os) throws IOException {
689         os.write(i16 & 0xff);
690         os.write(i16 >> 8 & 0xff);
691     }
692 }
Popular Tags