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