1 25 26 30 package org.jrobin.graph; 31 32 import java.awt.*; 33 import java.awt.image.PixelGrabber ; 34 import java.io.*; 35 import java.util.Vector ; 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 theComments; 44 private Vector vFrames = new Vector (); 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 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 pg = new PixelGrabber (img, 0, 0, -1, -1, true); 187 String errmsg = null; 188 try { 189 if (!pg.grabPixels()) 190 errmsg = "can't grab pixels from image"; 191 } catch (InterruptedException 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 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 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 ; 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 getPixelSource() { 675 return ciPixels; 676 } 677 } 678 679 680 final class Put { 681 static void ascii(String 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 |