KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > imageio > plugins > gif > GIFImageWriter


1 /*
2  * @(#)GIFImageWriter.java 1.13 05/11/17
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.imageio.plugins.gif;
9
10 import java.awt.Dimension JavaDoc;
11 import java.awt.Rectangle JavaDoc;
12 import java.awt.image.ColorModel JavaDoc;
13 import java.awt.image.ComponentSampleModel JavaDoc;
14 import java.awt.image.DataBufferByte JavaDoc;
15 import java.awt.image.IndexColorModel JavaDoc;
16 import java.awt.image.Raster JavaDoc;
17 import java.awt.image.RenderedImage JavaDoc;
18 import java.awt.image.SampleModel JavaDoc;
19 import java.awt.image.WritableRaster JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.nio.ByteOrder JavaDoc;
22 import java.util.Arrays JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.Locale JavaDoc;
25 import javax.imageio.IIOException JavaDoc;
26 import javax.imageio.IIOImage JavaDoc;
27 import javax.imageio.ImageTypeSpecifier JavaDoc;
28 import javax.imageio.ImageWriteParam JavaDoc;
29 import javax.imageio.ImageWriter JavaDoc;
30 import javax.imageio.spi.ImageWriterSpi JavaDoc;
31 import javax.imageio.metadata.IIOInvalidTreeException JavaDoc;
32 import javax.imageio.metadata.IIOMetadata JavaDoc;
33 import javax.imageio.metadata.IIOMetadataFormatImpl JavaDoc;
34 import javax.imageio.metadata.IIOMetadataNode JavaDoc;
35 import javax.imageio.stream.ImageOutputStream JavaDoc;
36 import org.w3c.dom.Node JavaDoc;
37 import org.w3c.dom.NodeList JavaDoc;
38 import com.sun.imageio.plugins.common.LZWCompressor;
39 import com.sun.imageio.plugins.common.PaletteBuilder;
40
41 public class GIFImageWriter extends ImageWriter JavaDoc {
42     private static final boolean DEBUG = false; // XXX false for release!
43

44     static final String JavaDoc STANDARD_METADATA_NAME =
45     IIOMetadataFormatImpl.standardMetadataFormatName;
46
47     static final String JavaDoc STREAM_METADATA_NAME =
48     GIFWritableStreamMetadata.NATIVE_FORMAT_NAME;
49     
50     static final String JavaDoc IMAGE_METADATA_NAME =
51     GIFWritableImageMetadata.NATIVE_FORMAT_NAME;
52     
53     /**
54      * The <code>output</code> case to an <code>ImageOutputStream</code>.
55      */

56     private ImageOutputStream JavaDoc stream = null;
57
58     /**
59      * Whether a sequence is being written.
60      */

61     private boolean isWritingSequence = false;
62
63     /**
64      * Whether the header has been written.
65      */

66     private boolean wroteSequenceHeader = false;
67
68     /**
69      * The stream metadata of a sequence.
70      */

71     private GIFWritableStreamMetadata theStreamMetadata = null;
72
73     /**
74      * The index of the image being written.
75      */

76     private int imageIndex = 0;
77
78     /**
79      * The number of bits represented by the value which should be a
80      * legal length for a color table.
81      */

82     private static int getNumBits(int value) throws IOException JavaDoc {
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 JavaDoc("Bad palette length: "+value+"!");
111         }
112         
113         return numBits;
114     }
115     
116     /**
117      * Compute the source region and destination dimensions taking any
118      * parameter settings into account.
119      */

120     private static void computeRegions(Rectangle JavaDoc sourceBounds,
121                                        Dimension JavaDoc destSize,
122                                        ImageWriteParam JavaDoc p) {
123         ImageWriteParam JavaDoc 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 JavaDoc("Cannot sub-band image!");
132             }
133             
134             // Get source region and subsampling factors
135
Rectangle JavaDoc sourceRegion = p.getSourceRegion();
136             if (sourceRegion != null) {
137                 // Clip to actual image bounds
138
sourceRegion = sourceRegion.intersection(sourceBounds);
139                 sourceBounds.setBounds(sourceRegion);
140             }
141             
142             // Adjust for subsampling offsets
143
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             // Get subsampling factors
151
periodX = p.getSourceXSubsampling();
152             periodY = p.getSourceYSubsampling();
153         }
154         
155         // Compute output dimensions
156
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 JavaDoc("Empty source region!");
160         }
161     }
162
163     /**
164      * Create a color table from the image ColorModel and SampleModel.
165      */

166     private static byte[] createColorTable(ColorModel JavaDoc colorModel,
167                                            SampleModel JavaDoc sampleModel)
168     {
169         byte[] colorTable;
170         if (colorModel instanceof IndexColorModel JavaDoc) {
171             IndexColorModel JavaDoc icm = (IndexColorModel JavaDoc)colorModel;
172             int mapSize = icm.getMapSize();
173
174             /**
175              * The GIF image format assumes that size of image palette
176              * is power of two. We will use closest larger power of two
177              * as size of color table.
178              */

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             /**
189              * fill tail of color component arrays by replica of first color
190              * in order to avoid appearance of extra colors in the color table
191              */

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             // create gray-scaled color table for single-banded images
207
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             // We do not have enough information here
218
// to create well-fit color table for RGB image.
219
colorTable = null;
220         }
221         
222         return colorTable;
223     }
224     
225     /**
226      * According do GIF specification size of clor table (palette here)
227      * must be in range from 2 to 256 and must be power of 2.
228      */

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     /**
256      * Merges <code>inData</code> into <code>outData</code>. The supplied
257      * metadata format name is attempted first and failing that the standard
258      * metadata format name is attempted.
259      */

260     private void convertMetadata(String JavaDoc metadataFormatName,
261                                  IIOMetadata JavaDoc inData,
262                                  IIOMetadata JavaDoc outData) {
263         String JavaDoc formatName = null;
264         
265         String JavaDoc nativeFormatName = inData.getNativeMetadataFormatName();
266         if (nativeFormatName != null &&
267             nativeFormatName.equals(metadataFormatName)) {
268             formatName = metadataFormatName;
269         } else {
270             String JavaDoc[] 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 JavaDoc root = inData.getAsTree(formatName);
290                 outData.mergeTree(formatName, root);
291             } catch(IIOInvalidTreeException JavaDoc e) {
292                 // ignore
293
}
294         }
295     }
296     
297     /**
298      * Creates a default stream metadata object and merges in the
299      * supplied metadata.
300      */

301     public IIOMetadata JavaDoc convertStreamMetadata(IIOMetadata JavaDoc inData,
302                                              ImageWriteParam JavaDoc param) {
303         if (inData == null) {
304             throw new IllegalArgumentException JavaDoc("inData == null!");
305         }
306         
307         IIOMetadata JavaDoc sm = getDefaultStreamMetadata(param);
308         
309         convertMetadata(STREAM_METADATA_NAME, inData, sm);
310         
311         return sm;
312     }
313     
314     /**
315      * Creates a default image metadata object and merges in the
316      * supplied metadata.
317      */

318     public IIOMetadata JavaDoc convertImageMetadata(IIOMetadata JavaDoc inData,
319                                             ImageTypeSpecifier JavaDoc imageType,
320                                             ImageWriteParam JavaDoc param) {
321         if (inData == null) {
322             throw new IllegalArgumentException JavaDoc("inData == null!");
323         }
324         if (imageType == null) {
325             throw new IllegalArgumentException JavaDoc("imageType == null!");
326         }
327         
328         GIFWritableImageMetadata im =
329             (GIFWritableImageMetadata)getDefaultImageMetadata(imageType,
330                                                               param);
331         
332         // Save interlace flag state.
333

334         boolean isProgressive = im.interlaceFlag;
335         
336         convertMetadata(IMAGE_METADATA_NAME, inData, im);
337         
338         // Undo change to interlace flag if not MODE_COPY_FROM_METADATA.
339

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 JavaDoc {
349         if (stream == null) {
350             throw new IllegalStateException JavaDoc("output == null!");
351         }
352         if (!isWritingSequence) {
353             throw new IllegalStateException JavaDoc("prepareWriteSequence() was not invoked!");
354         }
355         writeTrailer();
356         resetLocal();
357     }
358     
359     public IIOMetadata JavaDoc getDefaultImageMetadata(ImageTypeSpecifier JavaDoc imageType,
360                                                ImageWriteParam JavaDoc param) {
361         GIFWritableImageMetadata imageMetadata =
362             new GIFWritableImageMetadata();
363         
364         // Image dimensions
365

366         SampleModel JavaDoc sampleModel = imageType.getSampleModel();
367         
368         Rectangle JavaDoc sourceBounds = new Rectangle JavaDoc(sampleModel.getWidth(),
369                                                sampleModel.getHeight());
370         Dimension JavaDoc destSize = new Dimension JavaDoc();
371         computeRegions(sourceBounds, destSize, param);
372         
373         imageMetadata.imageWidth = destSize.width;
374         imageMetadata.imageHeight = destSize.height;
375         
376         // Interlacing
377

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         // Local color table
386

387         ColorModel JavaDoc colorModel = imageType.getColorModel();
388         
389         imageMetadata.localColorTable =
390             createColorTable(colorModel, sampleModel);
391         
392         // Transparency
393

394         if (colorModel instanceof IndexColorModel JavaDoc) {
395             int transparentIndex =
396                 ((IndexColorModel JavaDoc)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 JavaDoc getDefaultStreamMetadata(ImageWriteParam JavaDoc param) {
407         GIFWritableStreamMetadata streamMetadata =
408             new GIFWritableStreamMetadata();
409         streamMetadata.version = "89a";
410         return streamMetadata;
411     }
412     
413     public ImageWriteParam JavaDoc getDefaultWriteParam() {
414         return new GIFImageWriteParam(getLocale());
415     }
416     
417     public void prepareWriteSequence(IIOMetadata JavaDoc streamMetadata)
418       throws IOException JavaDoc {
419
420         if (stream == null) {
421             throw new IllegalStateException JavaDoc("Output is not set.");
422         }
423         
424         resetLocal();
425
426         // Save the possibly converted stream metadata as an instance variable.
427
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     /**
445      * Resets locally defined instance variables.
446      */

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 JavaDoc output) {
455         super.setOutput(output);
456         if (output != null) {
457             if (!(output instanceof ImageOutputStream JavaDoc)) {
458                 throw new
459                     IllegalArgumentException JavaDoc("output is not an ImageOutputStream");
460             }
461             this.stream = (ImageOutputStream JavaDoc)output;
462             this.stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
463         } else {
464             this.stream = null;
465         }
466     }
467
468     public void write(IIOMetadata JavaDoc sm,
469                       IIOImage JavaDoc iioimage,
470                       ImageWriteParam JavaDoc p) throws IOException JavaDoc {
471         if (stream == null) {
472             throw new IllegalStateException JavaDoc("output == null!");
473         }
474         if (iioimage == null) {
475             throw new IllegalArgumentException JavaDoc("iioimage == null!");
476         }
477         if (iioimage.hasRaster()) {
478             throw new UnsupportedOperationException JavaDoc("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 JavaDoc image, ImageWriteParam JavaDoc param)
496       throws IOException JavaDoc {
497         if (stream == null) {
498             throw new IllegalStateException JavaDoc("output == null!");
499         }
500         if (image == null) {
501             throw new IllegalArgumentException JavaDoc("image == null!");
502         }
503         if (image.hasRaster()) {
504             throw new UnsupportedOperationException JavaDoc("canWriteRasters() == false!");
505         }
506         if (!isWritingSequence) {
507             throw new IllegalStateException JavaDoc("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 JavaDoc image) {
522         
523         SampleModel JavaDoc sampleModel = image.getSampleModel();
524         ColorModel JavaDoc colorModel = image.getColorModel();
525         
526         return sampleModel.getNumBands() != 1 ||
527             sampleModel.getSampleSize()[0] > 8 ||
528             colorModel.getComponentSize()[0] > 8;
529     }
530     
531     /**
532      * Writes any extension blocks, the Image Descriptor, the image data,
533      * and optionally the header (Signature and Logical Screen Descriptor)
534      * and trailer (Block Terminator).
535      *
536      * @param writeHeader Whether to write the header.
537      * @param writeTrailer Whether to write the trailer.
538      * @param sm The stream metadata or <code>null</code> if
539      * <code>writeHeader</code> is <code>false</code>.
540      * @param iioimage The image and image metadata.
541      * @param p The write parameters.
542      *
543      * @throws IllegalArgumentException if the number of bands is not 1.
544      * @throws IllegalArgumentException if the number of bits per sample is
545      * greater than 8.
546      * @throws IllegalArgumentException if the color component size is
547      * greater than 8.
548      * @throws IllegalArgumentException if <code>writeHeader</code> is
549      * <code>true</code> and <code>sm</code> is <code>null</code>.
550      * @throws IllegalArgumentException if <code>writeHeader</code> is
551      * <code>false</code> and a sequence is not being written.
552      */

553     private void write(boolean writeHeader,
554                        boolean writeTrailer,
555                        IIOMetadata JavaDoc sm,
556                        IIOImage JavaDoc iioimage,
557                        ImageWriteParam JavaDoc p) throws IOException JavaDoc {
558         clearAbortRequest();
559
560         RenderedImage JavaDoc image = iioimage.getRenderedImage();
561         
562         // Check for ability to encode image.
563
if (needToCreateIndex(image)) {
564             image = PaletteBuilder.createIndexedImage(image);
565             iioimage.setRenderedImage(image);
566         }
567
568         ColorModel JavaDoc colorModel = image.getColorModel();
569         SampleModel JavaDoc sampleModel = image.getSampleModel();
570         
571         // Determine source region and destination dimensions.
572
Rectangle JavaDoc sourceBounds = new Rectangle JavaDoc(image.getMinX(),
573                                                image.getMinY(),
574                                                image.getWidth(),
575                                                image.getHeight());
576         Dimension JavaDoc destSize = new Dimension JavaDoc();
577         computeRegions(sourceBounds, destSize, p);
578         
579         // Convert any provided image metadata.
580
GIFWritableImageMetadata imageMetadata = null;
581         if (iioimage.getMetadata() != null) {
582             imageMetadata = new GIFWritableImageMetadata();
583             convertMetadata(IMAGE_METADATA_NAME, iioimage.getMetadata(),
584                             imageMetadata);
585             // Converted rgb image can use palette different from global.
586
// In order to avoid color artefacts we want to be sure we use
587
// appropriate palette. For this we initialize local color table
588
// from current color and sample models.
589
// At this point we can guarantee that local color table can be
590
// build because image was already converted to indexed or
591
// gray-scale representations
592
if (imageMetadata.localColorTable == null) {
593                 imageMetadata.localColorTable =
594                     createColorTable(colorModel, sampleModel);
595                 
596                 // in case of indexed image we should take care of
597
// transparent pixels
598
if (colorModel instanceof IndexColorModel JavaDoc) {
599                     IndexColorModel JavaDoc icm =
600                         (IndexColorModel JavaDoc)colorModel;
601                     int index = icm.getTransparentPixel();
602                     imageMetadata.transparentColorFlag = (index != -1);
603                     if (imageMetadata.transparentColorFlag) {
604                         imageMetadata.transparentColorIndex = index;
605                     }
606                     /* NB: transparentColorFlag might have not beed reset for
607                        greyscale images but explicitly reseting it here
608                        is potentially not right thing to do until we have way
609                        to find whether current value was explicitly set by
610                        the user.
611                     */

612                 }
613             }
614         }
615         
616         // Global color table values.
617
byte[] globalColorTable = null;
618         
619         // Write the header (Signature+Logical Screen Descriptor+
620
// Global Color Table).
621
if (writeHeader) {
622             if (sm == null) {
623                 throw new IllegalArgumentException JavaDoc("Cannot write null header!");
624             }
625             
626             GIFWritableStreamMetadata streamMetadata =
627                 (GIFWritableStreamMetadata)sm;
628             
629             // Set the version if not set.
630
if (streamMetadata.version == null) {
631                 streamMetadata.version = "89a";
632             }
633             
634             // Set the Logical Screen Desriptor if not set.
635
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             // Set the Global Color Table if not set, i.e., if not
656
// provided in the stream metadata.
657
if (streamMetadata.globalColorTable == null) {
658                 if (isWritingSequence && imageMetadata != null &&
659                     imageMetadata.localColorTable != null) {
660                     // Writing a sequence and a local color table was
661
// provided in the metadata of the first image: use it.
662
streamMetadata.globalColorTable =
663                         imageMetadata.localColorTable;
664                 } else if (imageMetadata == null ||
665                            imageMetadata.localColorTable == null) {
666                     // Create a color table.
667
streamMetadata.globalColorTable =
668                         createColorTable(colorModel, sampleModel);
669                 }
670             }
671
672             // Set the Global Color Table. At this point it should be
673
// A) the global color table provided in stream metadata, if any;
674
// B) the local color table of the image metadata, if any, if
675
// writing a sequence;
676
// C) a table created on the basis of the first image ColorModel
677
// and SampleModel if no local color table is available; or
678
// D) null if none of the foregoing conditions obtain (which
679
// should only be if a sequence is not being written and
680
// a local color table is provided in image metadata).
681
globalColorTable = streamMetadata.globalColorTable;
682             
683             // Write the header.
684
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 JavaDoc("Must write header for single image!");
699         }
700         
701         // Write extension blocks, Image Descriptor, and image data.
702
writeImage(iioimage.getRenderedImage(), imageMetadata, p,
703                    globalColorTable, sourceBounds, destSize);
704         
705         // Write the trailer.
706
if (writeTrailer) {
707             writeTrailer();
708         }
709     }
710     
711     /**
712      * Writes any extension blocks, the Image Descriptor, and the image data
713      *
714      * @param iioimage The image and image metadata.
715      * @param param The write parameters.
716      * @param globalColorTable The Global Color Table.
717      * @param sourceBounds The source region.
718      * @param destSize The destination dimensions.
719      */

720     private void writeImage(RenderedImage JavaDoc image,
721                             GIFWritableImageMetadata imageMetadata,
722                             ImageWriteParam JavaDoc param, byte[] globalColorTable,
723                             Rectangle JavaDoc sourceBounds, Dimension JavaDoc destSize)
724       throws IOException JavaDoc {
725         ColorModel JavaDoc colorModel = image.getColorModel();
726         SampleModel JavaDoc sampleModel = image.getSampleModel();
727         
728         boolean writeGraphicsControlExtension;
729         if (imageMetadata == null) {
730             // Create default metadata.
731
imageMetadata = (GIFWritableImageMetadata)getDefaultImageMetadata(
732                 new ImageTypeSpecifier JavaDoc(image), param);
733             
734             // Set GraphicControlExtension flag only if there is
735
// transparency.
736
writeGraphicsControlExtension = imageMetadata.transparentColorFlag;
737         } else {
738             // Check for GraphicControlExtension element.
739
NodeList JavaDoc list = null;
740             try {
741                 IIOMetadataNode JavaDoc root = (IIOMetadataNode JavaDoc)
742                     imageMetadata.getAsTree(IMAGE_METADATA_NAME);
743                 list = root.getElementsByTagName("GraphicControlExtension");
744             } catch(IllegalArgumentException JavaDoc iae) {
745                 // Should never happen.
746
}
747             
748             // Set GraphicControlExtension flag if element present.
749
writeGraphicsControlExtension =
750                 list != null && list.getLength() > 0;
751             
752             // If progressive mode is not MODE_COPY_FROM_METADATA, ensure
753
// the interlacing is set per the ImageWriteParam mode setting.
754
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         // Unset local color table if equal to global color table.
766
if (Arrays.equals(globalColorTable, imageMetadata.localColorTable)) {
767             imageMetadata.localColorTable = null;
768         }
769       
770         // Override dimensions
771
imageMetadata.imageWidth = destSize.width;
772         imageMetadata.imageHeight = destSize.height;
773         
774         // Write Graphics Control Extension.
775
if (writeGraphicsControlExtension) {
776             writeGraphicControlExtension(imageMetadata);
777         }
778         
779         // Write extension blocks.
780
writePlainTextExtension(imageMetadata);
781         writeApplicationExtension(imageMetadata);
782         writeCommentExtension(imageMetadata);
783         
784         // Write Image Descriptor
785
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         // Write image data
794
writeRasterData(image, sourceBounds, destSize,
795                         param, imageMetadata.interlaceFlag);
796     }
797     
798     private void writeRows(RenderedImage JavaDoc 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 JavaDoc {
803         if (DEBUG) System.out.println("Writing unoptimized");
804         
805         int[] sbuf = new int[sw];
806         byte[] dbuf = new byte[dw];
807         
808         Raster JavaDoc 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 JavaDoc {
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 JavaDoc image,
855                                  Rectangle JavaDoc sourceBounds,
856                                  Dimension JavaDoc destSize,
857                                  ImageWriteParam JavaDoc param,
858                                  boolean interlaceFlag) throws IOException JavaDoc {
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 JavaDoc 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 JavaDoc &&
893             image.getNumXTiles() == 1 && image.getNumYTiles() == 1 &&
894             image.getTile(0, 0).getDataBuffer() instanceof DataBufferByte JavaDoc;
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 JavaDoc tile = image.getTile(0, 0);
907                 byte[] data = ((DataBufferByte JavaDoc)tile.getDataBuffer()).getData();
908                 ComponentSampleModel JavaDoc csm =
909                     (ComponentSampleModel JavaDoc)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 JavaDoc tile = image.getTile(0, 0);
995                 byte[] data = ((DataBufferByte JavaDoc)tile.getDataBuffer()).getData();
996                 ComponentSampleModel JavaDoc csm =
997                     (ComponentSampleModel JavaDoc)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 JavaDoc 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 JavaDoc {
1036        try {
1037            // Signature
1038
stream.writeBytes("GIF"+version);
1039            
1040            // Screen Descriptor
1041
// Width
1042
stream.writeShort((short)logicalScreenWidth);
1043            
1044            // Height
1045
stream.writeShort((short)logicalScreenHeight);
1046            
1047            // Global Color Table
1048
// Packed fields
1049
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            // Background color index
1058
stream.write(backgroundColorIndex);
1059
1060            // Pixel aspect ratio
1061
stream.write(pixelAspectRatio);
1062            
1063            // Global Color Table
1064
if (globalColorTable != null) {
1065                stream.write(globalColorTable);
1066            }
1067        } catch (IOException JavaDoc e) {
1068            throw new IIOException JavaDoc("I/O error writing header!", e);
1069        }
1070    }
1071    
1072    private void writeHeader(IIOMetadata JavaDoc streamMetadata, int bitsPerPixel)
1073      throws IOException JavaDoc {
1074
1075        GIFWritableStreamMetadata sm;
1076        if (streamMetadata instanceof GIFWritableStreamMetadata) {
1077            sm = (GIFWritableStreamMetadata)streamMetadata;
1078        } else {
1079            sm = new GIFWritableStreamMetadata();
1080            Node JavaDoc 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 JavaDoc {
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 JavaDoc e) {
1122            throw new IIOException JavaDoc("I/O error writing Graphic Control Extension!", e);
1123        }
1124    }
1125    
1126    private void writeGraphicControlExtension(GIFWritableImageMetadata im)
1127      throws IOException JavaDoc {
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 JavaDoc {
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 JavaDoc {
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 JavaDoc e) {
1169                throw new IIOException JavaDoc("I/O error writing Plain Text Extension!", e);
1170            }
1171        }
1172    }
1173    
1174    private void writeApplicationExtension(GIFWritableImageMetadata im)
1175      throws IOException JavaDoc {
1176        if (im.applicationIDs != null) {
1177            Iterator JavaDoc iterIDs = im.applicationIDs.iterator();
1178            Iterator JavaDoc iterCodes = im.authenticationCodes.iterator();
1179            Iterator JavaDoc 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 JavaDoc e) {
1194                    throw new IIOException JavaDoc("I/O error writing Application Extension!", e);
1195                }
1196            }
1197        }
1198    }
1199
1200    private void writeCommentExtension(GIFWritableImageMetadata im)
1201      throws IOException JavaDoc {
1202        if (im.comments != null) {
1203            try {
1204                Iterator JavaDoc 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 JavaDoc e) {
1212                throw new IIOException JavaDoc("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 JavaDoc {
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 JavaDoc e) {
1249            throw new IIOException JavaDoc("I/O error writing Image Descriptor!", e);
1250        }
1251    }
1252    
1253    private void writeImageDescriptor(GIFWritableImageMetadata imageMetadata,
1254                                      int bitsPerPixel)
1255      throws IOException JavaDoc {
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 JavaDoc {
1268        stream.write(0x3b);
1269    }
1270}
1271
1272class GIFImageWriteParam extends ImageWriteParam JavaDoc {
1273    GIFImageWriteParam(Locale JavaDoc locale) {
1274        super(locale);
1275        this.canWriteCompressed = true;
1276        this.canWriteProgressive = true;
1277        this.compressionTypes = new String JavaDoc[] {"LZW", "lzw"};
1278        this.compressionType = compressionTypes[0];
1279    }
1280
1281    public void setCompressionMode(int mode) {
1282        if (mode == MODE_DISABLED) {
1283            throw new UnsupportedOperationException JavaDoc("MODE_DISABLED is not supported.");
1284        }
1285        super.setCompressionMode(mode);
1286    }
1287}
1288
Popular Tags