1 7 8 package com.sun.imageio.plugins.gif; 9 10 import java.awt.Dimension ; 11 import java.awt.Rectangle ; 12 import java.awt.image.ColorModel ; 13 import java.awt.image.ComponentSampleModel ; 14 import java.awt.image.DataBufferByte ; 15 import java.awt.image.IndexColorModel ; 16 import java.awt.image.Raster ; 17 import java.awt.image.RenderedImage ; 18 import java.awt.image.SampleModel ; 19 import java.awt.image.WritableRaster ; 20 import java.io.IOException ; 21 import java.nio.ByteOrder ; 22 import java.util.Arrays ; 23 import java.util.Iterator ; 24 import java.util.Locale ; 25 import javax.imageio.IIOException ; 26 import javax.imageio.IIOImage ; 27 import javax.imageio.ImageTypeSpecifier ; 28 import javax.imageio.ImageWriteParam ; 29 import javax.imageio.ImageWriter ; 30 import javax.imageio.spi.ImageWriterSpi ; 31 import javax.imageio.metadata.IIOInvalidTreeException ; 32 import javax.imageio.metadata.IIOMetadata ; 33 import javax.imageio.metadata.IIOMetadataFormatImpl ; 34 import javax.imageio.metadata.IIOMetadataNode ; 35 import javax.imageio.stream.ImageOutputStream ; 36 import org.w3c.dom.Node ; 37 import org.w3c.dom.NodeList ; 38 import com.sun.imageio.plugins.common.LZWCompressor; 39 import com.sun.imageio.plugins.common.PaletteBuilder; 40 41 public class GIFImageWriter extends ImageWriter { 42 private static final boolean DEBUG = false; 44 static final String STANDARD_METADATA_NAME = 45 IIOMetadataFormatImpl.standardMetadataFormatName; 46 47 static final String STREAM_METADATA_NAME = 48 GIFWritableStreamMetadata.NATIVE_FORMAT_NAME; 49 50 static final String IMAGE_METADATA_NAME = 51 GIFWritableImageMetadata.NATIVE_FORMAT_NAME; 52 53 56 private ImageOutputStream stream = null; 57 58 61 private boolean isWritingSequence = false; 62 63 66 private boolean wroteSequenceHeader = false; 67 68 71 private GIFWritableStreamMetadata theStreamMetadata = null; 72 73 76 private int imageIndex = 0; 77 78 82 private static int getNumBits(int value) throws IOException { 83 int numBits; 84 switch(value) { 85 case 2: 86 numBits = 1; 87 break; 88 case 4: 89 numBits = 2; 90 break; 91 case 8: 92 numBits = 3; 93 break; 94 case 16: 95 numBits = 4; 96 break; 97 case 32: 98 numBits = 5; 99 break; 100 case 64: 101 numBits = 6; 102 break; 103 case 128: 104 numBits = 7; 105 break; 106 case 256: 107 numBits = 8; 108 break; 109 default: 110 throw new IOException ("Bad palette length: "+value+"!"); 111 } 112 113 return numBits; 114 } 115 116 120 private static void computeRegions(Rectangle sourceBounds, 121 Dimension destSize, 122 ImageWriteParam p) { 123 ImageWriteParam param; 124 int periodX = 1; 125 int periodY = 1; 126 if (p != null) { 127 int[] sourceBands = p.getSourceBands(); 128 if (sourceBands != null && 129 (sourceBands.length != 1 || 130 sourceBands[0] != 0)) { 131 throw new IllegalArgumentException ("Cannot sub-band image!"); 132 } 133 134 Rectangle sourceRegion = p.getSourceRegion(); 136 if (sourceRegion != null) { 137 sourceRegion = sourceRegion.intersection(sourceBounds); 139 sourceBounds.setBounds(sourceRegion); 140 } 141 142 int gridX = p.getSubsamplingXOffset(); 144 int gridY = p.getSubsamplingYOffset(); 145 sourceBounds.x += gridX; 146 sourceBounds.y += gridY; 147 sourceBounds.width -= gridX; 148 sourceBounds.height -= gridY; 149 150 periodX = p.getSourceXSubsampling(); 152 periodY = p.getSourceYSubsampling(); 153 } 154 155 destSize.setSize((sourceBounds.width + periodX - 1)/periodX, 157 (sourceBounds.height + periodY - 1)/periodY); 158 if (destSize.width <= 0 || destSize.height <= 0) { 159 throw new IllegalArgumentException ("Empty source region!"); 160 } 161 } 162 163 166 private static byte[] createColorTable(ColorModel colorModel, 167 SampleModel sampleModel) 168 { 169 byte[] colorTable; 170 if (colorModel instanceof IndexColorModel ) { 171 IndexColorModel icm = (IndexColorModel )colorModel; 172 int mapSize = icm.getMapSize(); 173 174 179 int ctSize = getGifPaletteSize(mapSize); 180 181 byte[] reds = new byte[ctSize]; 182 byte[] greens = new byte[ctSize]; 183 byte[] blues = new byte[ctSize]; 184 icm.getReds(reds); 185 icm.getGreens(greens); 186 icm.getBlues(blues); 187 188 192 for (int i = mapSize; i < ctSize; i++) { 193 reds[i] = reds[0]; 194 greens[i] = greens[0]; 195 blues[i] = blues[0]; 196 } 197 198 colorTable = new byte[3*ctSize]; 199 int idx = 0; 200 for (int i = 0; i < ctSize; i++) { 201 colorTable[idx++] = reds[i]; 202 colorTable[idx++] = greens[i]; 203 colorTable[idx++] = blues[i]; 204 } 205 } else if (sampleModel.getNumBands() == 1) { 206 int numBits = sampleModel.getSampleSize()[0]; 208 if (numBits > 8) { 209 numBits = 8; 210 } 211 int colorTableLength = 3*(1 << numBits); 212 colorTable = new byte[colorTableLength]; 213 for (int i = 0; i < colorTableLength; i++) { 214 colorTable[i] = (byte)(i/3); 215 } 216 } else { 217 colorTable = null; 220 } 221 222 return colorTable; 223 } 224 225 229 private static int getGifPaletteSize(int x) { 230 if (x <= 2) { 231 return 2; 232 } 233 x = x - 1; 234 x = x | (x >> 1); 235 x = x | (x >> 2); 236 x = x | (x >> 4); 237 x = x | (x >> 8); 238 x = x | (x >> 16); 239 return x + 1; 240 } 241 242 243 244 public GIFImageWriter(GIFImageWriterSpi originatingProvider) { 245 super(originatingProvider); 246 if (DEBUG) { 247 System.err.println("GIF Writer is created"); 248 } 249 } 250 251 public boolean canWriteSequence() { 252 return true; 253 } 254 255 260 private void convertMetadata(String metadataFormatName, 261 IIOMetadata inData, 262 IIOMetadata outData) { 263 String formatName = null; 264 265 String nativeFormatName = inData.getNativeMetadataFormatName(); 266 if (nativeFormatName != null && 267 nativeFormatName.equals(metadataFormatName)) { 268 formatName = metadataFormatName; 269 } else { 270 String [] extraFormatNames = inData.getExtraMetadataFormatNames(); 271 272 if (extraFormatNames != null) { 273 for (int i = 0; i < extraFormatNames.length; i++) { 274 if (extraFormatNames[i].equals(metadataFormatName)) { 275 formatName = metadataFormatName; 276 break; 277 } 278 } 279 } 280 } 281 282 if (formatName == null && 283 inData.isStandardMetadataFormatSupported()) { 284 formatName = STANDARD_METADATA_NAME; 285 } 286 287 if (formatName != null) { 288 try { 289 Node root = inData.getAsTree(formatName); 290 outData.mergeTree(formatName, root); 291 } catch(IIOInvalidTreeException e) { 292 } 294 } 295 } 296 297 301 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 302 ImageWriteParam param) { 303 if (inData == null) { 304 throw new IllegalArgumentException ("inData == null!"); 305 } 306 307 IIOMetadata sm = getDefaultStreamMetadata(param); 308 309 convertMetadata(STREAM_METADATA_NAME, inData, sm); 310 311 return sm; 312 } 313 314 318 public IIOMetadata convertImageMetadata(IIOMetadata inData, 319 ImageTypeSpecifier imageType, 320 ImageWriteParam param) { 321 if (inData == null) { 322 throw new IllegalArgumentException ("inData == null!"); 323 } 324 if (imageType == null) { 325 throw new IllegalArgumentException ("imageType == null!"); 326 } 327 328 GIFWritableImageMetadata im = 329 (GIFWritableImageMetadata)getDefaultImageMetadata(imageType, 330 param); 331 332 334 boolean isProgressive = im.interlaceFlag; 335 336 convertMetadata(IMAGE_METADATA_NAME, inData, im); 337 338 340 if (param != null && param.canWriteProgressive() && 341 param.getProgressiveMode() != param.MODE_COPY_FROM_METADATA) { 342 im.interlaceFlag = isProgressive; 343 } 344 345 return im; 346 } 347 348 public void endWriteSequence() throws IOException { 349 if (stream == null) { 350 throw new IllegalStateException ("output == null!"); 351 } 352 if (!isWritingSequence) { 353 throw new IllegalStateException ("prepareWriteSequence() was not invoked!"); 354 } 355 writeTrailer(); 356 resetLocal(); 357 } 358 359 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, 360 ImageWriteParam param) { 361 GIFWritableImageMetadata imageMetadata = 362 new GIFWritableImageMetadata(); 363 364 366 SampleModel sampleModel = imageType.getSampleModel(); 367 368 Rectangle sourceBounds = new Rectangle (sampleModel.getWidth(), 369 sampleModel.getHeight()); 370 Dimension destSize = new Dimension (); 371 computeRegions(sourceBounds, destSize, param); 372 373 imageMetadata.imageWidth = destSize.width; 374 imageMetadata.imageHeight = destSize.height; 375 376 378 if (param != null && param.canWriteProgressive() && 379 param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) { 380 imageMetadata.interlaceFlag = false; 381 } else { 382 imageMetadata.interlaceFlag = true; 383 } 384 385 387 ColorModel colorModel = imageType.getColorModel(); 388 389 imageMetadata.localColorTable = 390 createColorTable(colorModel, sampleModel); 391 392 394 if (colorModel instanceof IndexColorModel ) { 395 int transparentIndex = 396 ((IndexColorModel )colorModel).getTransparentPixel(); 397 if (transparentIndex != -1) { 398 imageMetadata.transparentColorFlag = true; 399 imageMetadata.transparentColorIndex = transparentIndex; 400 } 401 } 402 403 return imageMetadata; 404 } 405 406 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 407 GIFWritableStreamMetadata streamMetadata = 408 new GIFWritableStreamMetadata(); 409 streamMetadata.version = "89a"; 410 return streamMetadata; 411 } 412 413 public ImageWriteParam getDefaultWriteParam() { 414 return new GIFImageWriteParam(getLocale()); 415 } 416 417 public void prepareWriteSequence(IIOMetadata streamMetadata) 418 throws IOException { 419 420 if (stream == null) { 421 throw new IllegalStateException ("Output is not set."); 422 } 423 424 resetLocal(); 425 426 if (streamMetadata == null) { 428 this.theStreamMetadata = 429 (GIFWritableStreamMetadata)getDefaultStreamMetadata(null); 430 } else { 431 this.theStreamMetadata = new GIFWritableStreamMetadata(); 432 convertMetadata(STREAM_METADATA_NAME, streamMetadata, 433 theStreamMetadata); 434 } 435 436 this.isWritingSequence = true; 437 } 438 439 public void reset() { 440 super.reset(); 441 resetLocal(); 442 } 443 444 447 private void resetLocal() { 448 this.isWritingSequence = false; 449 this.wroteSequenceHeader = false; 450 this.theStreamMetadata = null; 451 this.imageIndex = 0; 452 } 453 454 public void setOutput(Object output) { 455 super.setOutput(output); 456 if (output != null) { 457 if (!(output instanceof ImageOutputStream )) { 458 throw new 459 IllegalArgumentException ("output is not an ImageOutputStream"); 460 } 461 this.stream = (ImageOutputStream )output; 462 this.stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 463 } else { 464 this.stream = null; 465 } 466 } 467 468 public void write(IIOMetadata sm, 469 IIOImage iioimage, 470 ImageWriteParam p) throws IOException { 471 if (stream == null) { 472 throw new IllegalStateException ("output == null!"); 473 } 474 if (iioimage == null) { 475 throw new IllegalArgumentException ("iioimage == null!"); 476 } 477 if (iioimage.hasRaster()) { 478 throw new UnsupportedOperationException ("canWriteRasters() == false!"); 479 } 480 481 resetLocal(); 482 483 GIFWritableStreamMetadata streamMetadata; 484 if (sm == null) { 485 streamMetadata = 486 (GIFWritableStreamMetadata)getDefaultStreamMetadata(p); 487 } else { 488 streamMetadata = 489 (GIFWritableStreamMetadata)convertStreamMetadata(sm, p); 490 } 491 492 write(true, true, streamMetadata, iioimage, p); 493 } 494 495 public void writeToSequence(IIOImage image, ImageWriteParam param) 496 throws IOException { 497 if (stream == null) { 498 throw new IllegalStateException ("output == null!"); 499 } 500 if (image == null) { 501 throw new IllegalArgumentException ("image == null!"); 502 } 503 if (image.hasRaster()) { 504 throw new UnsupportedOperationException ("canWriteRasters() == false!"); 505 } 506 if (!isWritingSequence) { 507 throw new IllegalStateException ("prepareWriteSequence() was not invoked!"); 508 } 509 510 write(!wroteSequenceHeader, false, theStreamMetadata, 511 image, param); 512 513 if (!wroteSequenceHeader) { 514 wroteSequenceHeader = true; 515 } 516 517 this.imageIndex++; 518 } 519 520 521 private boolean needToCreateIndex(RenderedImage image) { 522 523 SampleModel sampleModel = image.getSampleModel(); 524 ColorModel colorModel = image.getColorModel(); 525 526 return sampleModel.getNumBands() != 1 || 527 sampleModel.getSampleSize()[0] > 8 || 528 colorModel.getComponentSize()[0] > 8; 529 } 530 531 553 private void write(boolean writeHeader, 554 boolean writeTrailer, 555 IIOMetadata sm, 556 IIOImage iioimage, 557 ImageWriteParam p) throws IOException { 558 clearAbortRequest(); 559 560 RenderedImage image = iioimage.getRenderedImage(); 561 562 if (needToCreateIndex(image)) { 564 image = PaletteBuilder.createIndexedImage(image); 565 iioimage.setRenderedImage(image); 566 } 567 568 ColorModel colorModel = image.getColorModel(); 569 SampleModel sampleModel = image.getSampleModel(); 570 571 Rectangle sourceBounds = new Rectangle (image.getMinX(), 573 image.getMinY(), 574 image.getWidth(), 575 image.getHeight()); 576 Dimension destSize = new Dimension (); 577 computeRegions(sourceBounds, destSize, p); 578 579 GIFWritableImageMetadata imageMetadata = null; 581 if (iioimage.getMetadata() != null) { 582 imageMetadata = new GIFWritableImageMetadata(); 583 convertMetadata(IMAGE_METADATA_NAME, iioimage.getMetadata(), 584 imageMetadata); 585 if (imageMetadata.localColorTable == null) { 593 imageMetadata.localColorTable = 594 createColorTable(colorModel, sampleModel); 595 596 if (colorModel instanceof IndexColorModel ) { 599 IndexColorModel icm = 600 (IndexColorModel )colorModel; 601 int index = icm.getTransparentPixel(); 602 imageMetadata.transparentColorFlag = (index != -1); 603 if (imageMetadata.transparentColorFlag) { 604 imageMetadata.transparentColorIndex = index; 605 } 606 612 } 613 } 614 } 615 616 byte[] globalColorTable = null; 618 619 if (writeHeader) { 622 if (sm == null) { 623 throw new IllegalArgumentException ("Cannot write null header!"); 624 } 625 626 GIFWritableStreamMetadata streamMetadata = 627 (GIFWritableStreamMetadata)sm; 628 629 if (streamMetadata.version == null) { 631 streamMetadata.version = "89a"; 632 } 633 634 if (streamMetadata.logicalScreenWidth == 636 GIFMetadata.UNDEFINED_INTEGER_VALUE) 637 { 638 streamMetadata.logicalScreenWidth = destSize.width; 639 } 640 641 if (streamMetadata.logicalScreenHeight == 642 GIFMetadata.UNDEFINED_INTEGER_VALUE) 643 { 644 streamMetadata.logicalScreenHeight = destSize.height; 645 } 646 647 if (streamMetadata.colorResolution == 648 GIFMetadata.UNDEFINED_INTEGER_VALUE) 649 { 650 streamMetadata.colorResolution = colorModel != null ? 651 colorModel.getComponentSize()[0] : 652 sampleModel.getSampleSize()[0]; 653 } 654 655 if (streamMetadata.globalColorTable == null) { 658 if (isWritingSequence && imageMetadata != null && 659 imageMetadata.localColorTable != null) { 660 streamMetadata.globalColorTable = 663 imageMetadata.localColorTable; 664 } else if (imageMetadata == null || 665 imageMetadata.localColorTable == null) { 666 streamMetadata.globalColorTable = 668 createColorTable(colorModel, sampleModel); 669 } 670 } 671 672 globalColorTable = streamMetadata.globalColorTable; 682 683 int bitsPerPixel; 685 if (globalColorTable != null) { 686 bitsPerPixel = getNumBits(globalColorTable.length/3); 687 } else if (imageMetadata != null && 688 imageMetadata.localColorTable != null) { 689 bitsPerPixel = 690 getNumBits(imageMetadata.localColorTable.length/3); 691 } else { 692 bitsPerPixel = sampleModel.getSampleSize(0); 693 } 694 writeHeader(streamMetadata, bitsPerPixel); 695 } else if (isWritingSequence) { 696 globalColorTable = theStreamMetadata.globalColorTable; 697 } else { 698 throw new IllegalArgumentException ("Must write header for single image!"); 699 } 700 701 writeImage(iioimage.getRenderedImage(), imageMetadata, p, 703 globalColorTable, sourceBounds, destSize); 704 705 if (writeTrailer) { 707 writeTrailer(); 708 } 709 } 710 711 720 private void writeImage(RenderedImage image, 721 GIFWritableImageMetadata imageMetadata, 722 ImageWriteParam param, byte[] globalColorTable, 723 Rectangle sourceBounds, Dimension destSize) 724 throws IOException { 725 ColorModel colorModel = image.getColorModel(); 726 SampleModel sampleModel = image.getSampleModel(); 727 728 boolean writeGraphicsControlExtension; 729 if (imageMetadata == null) { 730 imageMetadata = (GIFWritableImageMetadata)getDefaultImageMetadata( 732 new ImageTypeSpecifier (image), param); 733 734 writeGraphicsControlExtension = imageMetadata.transparentColorFlag; 737 } else { 738 NodeList list = null; 740 try { 741 IIOMetadataNode root = (IIOMetadataNode ) 742 imageMetadata.getAsTree(IMAGE_METADATA_NAME); 743 list = root.getElementsByTagName("GraphicControlExtension"); 744 } catch(IllegalArgumentException iae) { 745 } 747 748 writeGraphicsControlExtension = 750 list != null && list.getLength() > 0; 751 752 if (param != null && param.canWriteProgressive()) { 755 if (param.getProgressiveMode() == 756 ImageWriteParam.MODE_DISABLED) { 757 imageMetadata.interlaceFlag = false; 758 } else if (param.getProgressiveMode() == 759 ImageWriteParam.MODE_DEFAULT) { 760 imageMetadata.interlaceFlag = true; 761 } 762 } 763 } 764 765 if (Arrays.equals(globalColorTable, imageMetadata.localColorTable)) { 767 imageMetadata.localColorTable = null; 768 } 769 770 imageMetadata.imageWidth = destSize.width; 772 imageMetadata.imageHeight = destSize.height; 773 774 if (writeGraphicsControlExtension) { 776 writeGraphicControlExtension(imageMetadata); 777 } 778 779 writePlainTextExtension(imageMetadata); 781 writeApplicationExtension(imageMetadata); 782 writeCommentExtension(imageMetadata); 783 784 int bitsPerPixel = 786 getNumBits(imageMetadata.localColorTable == null ? 787 (globalColorTable == null ? 788 sampleModel.getSampleSize(0) : 789 globalColorTable.length/3) : 790 imageMetadata.localColorTable.length/3); 791 writeImageDescriptor(imageMetadata, bitsPerPixel); 792 793 writeRasterData(image, sourceBounds, destSize, 795 param, imageMetadata.interlaceFlag); 796 } 797 798 private void writeRows(RenderedImage image, LZWCompressor compressor, 799 int sx, int sdx, int sy, int sdy, int sw, 800 int dy, int ddy, int dw, int dh, 801 int numRowsWritten, int progressReportRowPeriod) 802 throws IOException { 803 if (DEBUG) System.out.println("Writing unoptimized"); 804 805 int[] sbuf = new int[sw]; 806 byte[] dbuf = new byte[dw]; 807 808 Raster raster = 809 image.getNumXTiles() == 1 && image.getNumYTiles() == 1 ? 810 image.getTile(0, 0) : image.getData(); 811 for (int y = dy; y < dh; y += ddy) { 812 if (numRowsWritten % progressReportRowPeriod == 0) { 813 if (abortRequested()) { 814 processWriteAborted(); 815 return; 816 } 817 processImageProgress((numRowsWritten*100.0F)/dh); 818 } 819 820 raster.getSamples(sx, sy, sw, 1, 0, sbuf); 821 for (int i = 0, j = 0; i < dw; i++, j += sdx) { 822 dbuf[i] = (byte)sbuf[j]; 823 } 824 compressor.compress(dbuf, 0, dw); 825 numRowsWritten++; 826 sy += sdy; 827 } 828 } 829 830 private void writeRowsOpt(byte[] data, int offset, int lineStride, 831 LZWCompressor compressor, 832 int dy, int ddy, int dw, int dh, 833 int numRowsWritten, int progressReportRowPeriod) 834 throws IOException { 835 if (DEBUG) System.out.println("Writing optimized"); 836 837 offset += dy*lineStride; 838 lineStride *= ddy; 839 for (int y = dy; y < dh; y += ddy) { 840 if (numRowsWritten % progressReportRowPeriod == 0) { 841 if (abortRequested()) { 842 processWriteAborted(); 843 return; 844 } 845 processImageProgress((numRowsWritten*100.0F)/dh); 846 } 847 848 compressor.compress(data, offset, dw); 849 numRowsWritten++; 850 offset += lineStride; 851 } 852 } 853 854 private void writeRasterData(RenderedImage image, 855 Rectangle sourceBounds, 856 Dimension destSize, 857 ImageWriteParam param, 858 boolean interlaceFlag) throws IOException { 859 860 int sourceXOffset = sourceBounds.x; 861 int sourceYOffset = sourceBounds.y; 862 int sourceWidth = sourceBounds.width; 863 int sourceHeight = sourceBounds.height; 864 865 int destWidth = destSize.width; 866 int destHeight = destSize.height; 867 868 int periodX; 869 int periodY; 870 if (param == null) { 871 periodX = 1; 872 periodY = 1; 873 } else { 874 periodX = param.getSourceXSubsampling(); 875 periodY = param.getSourceYSubsampling(); 876 } 877 878 SampleModel sampleModel = image.getSampleModel(); 879 int bitsPerPixel = sampleModel.getSampleSize()[0]; 880 881 int initCodeSize = bitsPerPixel; 882 if (initCodeSize == 1) { 883 initCodeSize++; 884 } 885 stream.write(initCodeSize); 886 887 LZWCompressor compressor = 888 new LZWCompressor(stream, initCodeSize, false); 889 890 boolean isOptimizedCase = 891 periodX == 1 && periodY == 1 && 892 sampleModel instanceof ComponentSampleModel && 893 image.getNumXTiles() == 1 && image.getNumYTiles() == 1 && 894 image.getTile(0, 0).getDataBuffer() instanceof DataBufferByte ; 895 896 int numRowsWritten = 0; 897 898 int progressReportRowPeriod = Math.max(destHeight/20, 1); 899 900 processImageStarted(imageIndex); 901 902 if (interlaceFlag) { 903 if (DEBUG) System.out.println("Writing interlaced"); 904 905 if (isOptimizedCase) { 906 Raster tile = image.getTile(0, 0); 907 byte[] data = ((DataBufferByte )tile.getDataBuffer()).getData(); 908 ComponentSampleModel csm = 909 (ComponentSampleModel )tile.getSampleModel(); 910 int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0); 911 int lineStride = csm.getScanlineStride(); 912 913 writeRowsOpt(data, offset, lineStride, compressor, 914 0, 8, destWidth, destHeight, 915 numRowsWritten, progressReportRowPeriod); 916 917 if (abortRequested()) { 918 return; 919 } 920 921 numRowsWritten += destHeight/8; 922 923 writeRowsOpt(data, offset, lineStride, compressor, 924 4, 8, destWidth, destHeight, 925 numRowsWritten, progressReportRowPeriod); 926 927 if (abortRequested()) { 928 return; 929 } 930 931 numRowsWritten += (destHeight - 4)/8; 932 933 writeRowsOpt(data, offset, lineStride, compressor, 934 2, 4, destWidth, destHeight, 935 numRowsWritten, progressReportRowPeriod); 936 937 if (abortRequested()) { 938 return; 939 } 940 941 numRowsWritten += (destHeight - 2)/4; 942 943 writeRowsOpt(data, offset, lineStride, compressor, 944 1, 2, destWidth, destHeight, 945 numRowsWritten, progressReportRowPeriod); 946 } else { 947 writeRows(image, compressor, 948 sourceXOffset, periodX, 949 sourceYOffset, 8*periodY, 950 sourceWidth, 951 0, 8, destWidth, destHeight, 952 numRowsWritten, progressReportRowPeriod); 953 954 if (abortRequested()) { 955 return; 956 } 957 958 numRowsWritten += destHeight/8; 959 960 writeRows(image, compressor, sourceXOffset, periodX, 961 sourceYOffset + 4*periodY, 8*periodY, 962 sourceWidth, 963 4, 8, destWidth, destHeight, 964 numRowsWritten, progressReportRowPeriod); 965 966 if (abortRequested()) { 967 return; 968 } 969 970 numRowsWritten += (destHeight - 4)/8; 971 972 writeRows(image, compressor, sourceXOffset, periodX, 973 sourceYOffset + 2*periodY, 4*periodY, 974 sourceWidth, 975 2, 4, destWidth, destHeight, 976 numRowsWritten, progressReportRowPeriod); 977 978 if (abortRequested()) { 979 return; 980 } 981 982 numRowsWritten += (destHeight - 2)/4; 983 984 writeRows(image, compressor, sourceXOffset, periodX, 985 sourceYOffset + periodY, 2*periodY, 986 sourceWidth, 987 1, 2, destWidth, destHeight, 988 numRowsWritten, progressReportRowPeriod); 989 } 990 } else { 991 if (DEBUG) System.out.println("Writing non-interlaced"); 992 993 if (isOptimizedCase) { 994 Raster tile = image.getTile(0, 0); 995 byte[] data = ((DataBufferByte )tile.getDataBuffer()).getData(); 996 ComponentSampleModel csm = 997 (ComponentSampleModel )tile.getSampleModel(); 998 int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0); 999 int lineStride = csm.getScanlineStride(); 1000 1001 writeRowsOpt(data, offset, lineStride, compressor, 1002 0, 1, destWidth, destHeight, 1003 numRowsWritten, progressReportRowPeriod); 1004 } else { 1005 writeRows(image, compressor, 1006 sourceXOffset, periodX, 1007 sourceYOffset, periodY, 1008 sourceWidth, 1009 0, 1, destWidth, destHeight, 1010 numRowsWritten, progressReportRowPeriod); 1011 } 1012 } 1013 1014 if (abortRequested()) { 1015 return; 1016 } 1017 1018 processImageProgress(100.0F); 1019 1020 compressor.flush(); 1021 1022 stream.write(0x00); 1023 1024 processImageComplete(); 1025 } 1026 1027 private void writeHeader(String version, 1028 int logicalScreenWidth, 1029 int logicalScreenHeight, 1030 int colorResolution, 1031 int pixelAspectRatio, 1032 int backgroundColorIndex, 1033 boolean sortFlag, 1034 int bitsPerPixel, 1035 byte[] globalColorTable) throws IOException { 1036 try { 1037 stream.writeBytes("GIF"+version); 1039 1040 stream.writeShort((short)logicalScreenWidth); 1043 1044 stream.writeShort((short)logicalScreenHeight); 1046 1047 int packedFields = globalColorTable != null ? 0x80 : 0x00; 1050 packedFields |= ((colorResolution - 1) & 0x7) << 4; 1051 if (sortFlag) { 1052 packedFields |= 0x8; 1053 } 1054 packedFields |= (bitsPerPixel - 1); 1055 stream.write(packedFields); 1056 1057 stream.write(backgroundColorIndex); 1059 1060 stream.write(pixelAspectRatio); 1062 1063 if (globalColorTable != null) { 1065 stream.write(globalColorTable); 1066 } 1067 } catch (IOException e) { 1068 throw new IIOException ("I/O error writing header!", e); 1069 } 1070 } 1071 1072 private void writeHeader(IIOMetadata streamMetadata, int bitsPerPixel) 1073 throws IOException { 1074 1075 GIFWritableStreamMetadata sm; 1076 if (streamMetadata instanceof GIFWritableStreamMetadata) { 1077 sm = (GIFWritableStreamMetadata)streamMetadata; 1078 } else { 1079 sm = new GIFWritableStreamMetadata(); 1080 Node root = 1081 streamMetadata.getAsTree(STREAM_METADATA_NAME); 1082 sm.setFromTree(STREAM_METADATA_NAME, root); 1083 } 1084 1085 writeHeader(sm.version, 1086 sm.logicalScreenWidth, 1087 sm.logicalScreenHeight, 1088 sm.colorResolution, 1089 sm.pixelAspectRatio, 1090 sm.backgroundColorIndex, 1091 sm.sortFlag, 1092 bitsPerPixel, 1093 sm.globalColorTable); 1094 } 1095 1096 private void writeGraphicControlExtension(int disposalMethod, 1097 boolean userInputFlag, 1098 boolean transparentColorFlag, 1099 int delayTime, 1100 int transparentColorIndex) 1101 throws IOException { 1102 try { 1103 stream.write(0x21); 1104 stream.write(0xf9); 1105 1106 stream.write(4); 1107 1108 int packedFields = (disposalMethod & 0x3) << 2; 1109 if (userInputFlag) { 1110 packedFields |= 0x2; 1111 } 1112 if (transparentColorFlag) { 1113 packedFields |= 0x1; 1114 } 1115 stream.write(packedFields); 1116 1117 stream.writeShort((short)delayTime); 1118 1119 stream.write(transparentColorIndex); 1120 stream.write(0x00); 1121 } catch (IOException e) { 1122 throw new IIOException ("I/O error writing Graphic Control Extension!", e); 1123 } 1124 } 1125 1126 private void writeGraphicControlExtension(GIFWritableImageMetadata im) 1127 throws IOException { 1128 writeGraphicControlExtension(im.disposalMethod, 1129 im.userInputFlag, 1130 im.transparentColorFlag, 1131 im.delayTime, 1132 im.transparentColorIndex); 1133 } 1134 1135 private void writeBlocks(byte[] data) throws IOException { 1136 if (data != null && data.length > 0) { 1137 int offset = 0; 1138 while (offset < data.length) { 1139 int len = Math.min(data.length - offset, 255); 1140 stream.write(len); 1141 stream.write(data, offset, len); 1142 offset += len; 1143 } 1144 } 1145 } 1146 1147 private void writePlainTextExtension(GIFWritableImageMetadata im) 1148 throws IOException { 1149 if (im.hasPlainTextExtension) { 1150 try { 1151 stream.write(0x21); 1152 stream.write(0x1); 1153 1154 stream.write(12); 1155 1156 stream.writeShort(im.textGridLeft); 1157 stream.writeShort(im.textGridTop); 1158 stream.writeShort(im.textGridWidth); 1159 stream.writeShort(im.textGridHeight); 1160 stream.write(im.characterCellWidth); 1161 stream.write(im.characterCellHeight); 1162 stream.write(im.textForegroundColor); 1163 stream.write(im.textBackgroundColor); 1164 1165 writeBlocks(im.text); 1166 1167 stream.write(0x00); 1168 } catch (IOException e) { 1169 throw new IIOException ("I/O error writing Plain Text Extension!", e); 1170 } 1171 } 1172 } 1173 1174 private void writeApplicationExtension(GIFWritableImageMetadata im) 1175 throws IOException { 1176 if (im.applicationIDs != null) { 1177 Iterator iterIDs = im.applicationIDs.iterator(); 1178 Iterator iterCodes = im.authenticationCodes.iterator(); 1179 Iterator iterData = im.applicationData.iterator(); 1180 1181 while (iterIDs.hasNext()) { 1182 try { 1183 stream.write(0x21); 1184 stream.write(0xff); 1185 1186 stream.write(11); 1187 stream.write((byte[])iterIDs.next(), 0, 8); 1188 stream.write((byte[])iterCodes.next(), 0, 3); 1189 1190 writeBlocks((byte[])iterData.next()); 1191 1192 stream.write(0x00); 1193 } catch (IOException e) { 1194 throw new IIOException ("I/O error writing Application Extension!", e); 1195 } 1196 } 1197 } 1198 } 1199 1200 private void writeCommentExtension(GIFWritableImageMetadata im) 1201 throws IOException { 1202 if (im.comments != null) { 1203 try { 1204 Iterator iter = im.comments.iterator(); 1205 while (iter.hasNext()) { 1206 stream.write(0x21); 1207 stream.write(0xfe); 1208 writeBlocks((byte[])iter.next()); 1209 stream.write(0x00); 1210 } 1211 } catch (IOException e) { 1212 throw new IIOException ("I/O error writing Comment Extension!", e); 1213 } 1214 } 1215 } 1216 1217 private void writeImageDescriptor(int imageLeftPosition, 1218 int imageTopPosition, 1219 int imageWidth, 1220 int imageHeight, 1221 boolean interlaceFlag, 1222 boolean sortFlag, 1223 int bitsPerPixel, 1224 byte[] localColorTable) 1225 throws IOException { 1226 1227 try { 1228 stream.write(0x2c); 1229 1230 stream.writeShort((short)imageLeftPosition); 1231 stream.writeShort((short)imageTopPosition); 1232 stream.writeShort((short)imageWidth); 1233 stream.writeShort((short)imageHeight); 1234 1235 int packedFields = localColorTable != null ? 0x80 : 0x00; 1236 if (interlaceFlag) { 1237 packedFields |= 0x40; 1238 } 1239 if (sortFlag) { 1240 packedFields |= 0x8; 1241 } 1242 packedFields |= (bitsPerPixel - 1); 1243 stream.write(packedFields); 1244 1245 if (localColorTable != null) { 1246 stream.write(localColorTable); 1247 } 1248 } catch (IOException e) { 1249 throw new IIOException ("I/O error writing Image Descriptor!", e); 1250 } 1251 } 1252 1253 private void writeImageDescriptor(GIFWritableImageMetadata imageMetadata, 1254 int bitsPerPixel) 1255 throws IOException { 1256 1257 writeImageDescriptor(imageMetadata.imageLeftPosition, 1258 imageMetadata.imageTopPosition, 1259 imageMetadata.imageWidth, 1260 imageMetadata.imageHeight, 1261 imageMetadata.interlaceFlag, 1262 imageMetadata.sortFlag, 1263 bitsPerPixel, 1264 imageMetadata.localColorTable); 1265 } 1266 1267 private void writeTrailer() throws IOException { 1268 stream.write(0x3b); 1269 } 1270} 1271 1272class GIFImageWriteParam extends ImageWriteParam { 1273 GIFImageWriteParam(Locale locale) { 1274 super(locale); 1275 this.canWriteCompressed = true; 1276 this.canWriteProgressive = true; 1277 this.compressionTypes = new String [] {"LZW", "lzw"}; 1278 this.compressionType = compressionTypes[0]; 1279 } 1280 1281 public void setCompressionMode(int mode) { 1282 if (mode == MODE_DISABLED) { 1283 throw new UnsupportedOperationException ("MODE_DISABLED is not supported."); 1284 } 1285 super.setCompressionMode(mode); 1286 } 1287} 1288 | Popular Tags |