001/*
002 * $RCSfile: TIFFImageWriter.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.24 $
042 * $Date: 2007/09/01 00:27:20 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.tiff;
046
047import java.awt.Point;
048import java.awt.Rectangle;
049import java.awt.color.ColorSpace;
050import java.awt.color.ICC_ColorSpace;
051import java.awt.image.BufferedImage;
052import java.awt.image.ColorModel;
053import java.awt.image.ComponentSampleModel;
054import java.awt.image.DataBuffer;
055import java.awt.image.DataBufferByte;
056import java.awt.image.IndexColorModel;
057import java.awt.image.Raster;
058import java.awt.image.RenderedImage;
059import java.awt.image.SampleModel;
060import java.awt.image.WritableRaster;
061import java.io.EOFException;
062import java.io.IOException;
063import java.nio.ByteOrder;
064import java.util.ArrayList;
065import java.util.Arrays;
066import java.util.List;
067
068import javax.imageio.IIOException;
069import javax.imageio.IIOImage;
070import javax.imageio.ImageTypeSpecifier;
071import javax.imageio.ImageWriteParam;
072import javax.imageio.ImageWriter;
073import javax.imageio.metadata.IIOInvalidTreeException;
074import javax.imageio.metadata.IIOMetadata;
075import javax.imageio.metadata.IIOMetadataFormatImpl;
076import javax.imageio.spi.ImageWriterSpi;
077import javax.imageio.stream.ImageOutputStream;
078
079import org.w3c.dom.Node;
080
081import com.github.jaiimageio.impl.common.ImageUtil;
082import com.github.jaiimageio.impl.common.SimpleRenderedImage;
083import com.github.jaiimageio.impl.common.SingleTileRenderedImage;
084import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
085import com.github.jaiimageio.plugins.tiff.EXIFParentTIFFTagSet;
086import com.github.jaiimageio.plugins.tiff.EXIFTIFFTagSet;
087import com.github.jaiimageio.plugins.tiff.TIFFColorConverter;
088import com.github.jaiimageio.plugins.tiff.TIFFCompressor;
089import com.github.jaiimageio.plugins.tiff.TIFFField;
090import com.github.jaiimageio.plugins.tiff.TIFFImageWriteParam;
091import com.github.jaiimageio.plugins.tiff.TIFFTag;
092import com.github.jaiimageio.plugins.tiff.TIFFTagSet;
093
094public class TIFFImageWriter extends ImageWriter {
095
096    private static final boolean DEBUG = false; // XXX false for release!
097
098    static final String EXIF_JPEG_COMPRESSION_TYPE = "EXIF JPEG";
099
100    public static final int DEFAULT_BYTES_PER_STRIP = 8192;
101
102    /**
103     * Supported TIFF compression types.
104     */
105    public static final String[] TIFFCompressionTypes = {
106        "CCITT RLE",
107        "CCITT T.4",
108        "CCITT T.6",
109        "LZW",
110        // "Old JPEG",
111        "JPEG",
112        "ZLib",
113        "PackBits",
114        "Deflate",
115        EXIF_JPEG_COMPRESSION_TYPE
116    };
117
118    //
119    // !!! The lengths of the arrays 'compressionTypes',
120    // !!! 'isCompressionLossless', and 'compressionNumbers'
121    // !!! must be equal.
122    //
123
124    /**
125     * Known TIFF compression types.
126     */
127    public static final String[] compressionTypes = {
128        "CCITT RLE",
129        "CCITT T.4",
130        "CCITT T.6",
131        "LZW",
132        "Old JPEG",
133        "JPEG",
134        "ZLib",
135        "PackBits",
136        "Deflate",
137        EXIF_JPEG_COMPRESSION_TYPE
138    };
139
140    /**
141     * Lossless flag for known compression types.
142     */
143    public static final boolean[] isCompressionLossless = {
144        true,  // RLE
145        true,  // T.4
146        true,  // T.6
147        true,  // LZW
148        false, // Old JPEG
149        false, // JPEG
150        true,  // ZLib
151        true,  // PackBits
152        true,  // DEFLATE
153        false  // EXIF JPEG
154    };
155
156    /**
157     * Compression tag values for known compression types.
158     */
159    public static final int[] compressionNumbers = {
160        BaselineTIFFTagSet.COMPRESSION_CCITT_RLE,
161        BaselineTIFFTagSet.COMPRESSION_CCITT_T_4,
162        BaselineTIFFTagSet.COMPRESSION_CCITT_T_6,
163        BaselineTIFFTagSet.COMPRESSION_LZW,
164        BaselineTIFFTagSet.COMPRESSION_OLD_JPEG,
165        BaselineTIFFTagSet.COMPRESSION_JPEG,
166        BaselineTIFFTagSet.COMPRESSION_ZLIB,
167        BaselineTIFFTagSet.COMPRESSION_PACKBITS,
168        BaselineTIFFTagSet.COMPRESSION_DEFLATE,
169        BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // EXIF JPEG
170    };
171
172    ImageOutputStream stream;
173    long headerPosition;
174    RenderedImage image;
175    ImageTypeSpecifier imageType;
176    ByteOrder byteOrder;
177    ImageWriteParam param;
178    TIFFCompressor compressor;
179    TIFFColorConverter colorConverter;
180
181    TIFFStreamMetadata streamMetadata;
182    TIFFImageMetadata imageMetadata;
183
184    int sourceXOffset;
185    int sourceYOffset;
186    int sourceWidth;
187    int sourceHeight;
188    int[] sourceBands;
189    int periodX;
190    int periodY;
191
192    int bitDepth; // bits per channel
193    int numBands;
194    int tileWidth;
195    int tileLength;
196    int tilesAcross;
197    int tilesDown;
198
199    int[] sampleSize = null; // Input sample size per band, in bits
200    int scalingBitDepth = -1; // Output bit depth of the scaling tables
201    boolean isRescaling = false; // Whether rescaling is needed.
202
203    boolean isBilevel; // Whether image is bilevel
204    boolean isImageSimple; // Whether image can be copied into directly
205    boolean isInverted; // Whether photometric inversion is required
206
207    boolean isTiled; // Whether the image is tiled (true) or stipped (false).
208
209    int nativePhotometricInterpretation;
210    int photometricInterpretation;
211
212    char[] bitsPerSample; // Output sample size per band
213    int sampleFormat =
214        BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format
215
216    // Tables for 1, 2, 4, or 8 bit output
217    byte[][] scale = null; // 8 bit table
218    byte[] scale0 = null; // equivalent to scale[0]
219
220    // Tables for 16 bit output
221    byte[][] scaleh = null; // High bytes of output
222    byte[][] scalel = null; // Low bytes of output
223
224    int compression;
225    int predictor;
226
227    int totalPixels;
228    int pixelsDone;
229
230    long nextIFDPointerPos;
231
232    // Next available space.
233    long nextSpace = 0L;
234
235    // Whether a sequence is being written.
236    boolean isWritingSequence = false;
237
238    /**
239     * Converts a pixel's X coordinate into a horizontal tile index
240     * relative to a given tile grid layout specified by its X offset
241     * and tile width.
242     *
243     * <p> If <code>tileWidth < 0</code>, the results of this method
244     * are undefined.  If <code>tileWidth == 0</code>, an
245     * <code>ArithmeticException</code> will be thrown.
246     *
247     * @throws ArithmeticException  If <code>tileWidth == 0</code>.
248     */
249    public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
250        x -= tileGridXOffset;
251        if (x < 0) {
252            x += 1 - tileWidth;         // force round to -infinity (ceiling)
253        }
254        return x/tileWidth;
255    }
256
257    /**
258     * Converts a pixel's Y coordinate into a vertical tile index
259     * relative to a given tile grid layout specified by its Y offset
260     * and tile height.
261     *
262     * <p> If <code>tileHeight < 0</code>, the results of this method
263     * are undefined.  If <code>tileHeight == 0</code>, an
264     * <code>ArithmeticException</code> will be thrown.
265     *
266     * @throws ArithmeticException  If <code>tileHeight == 0</code>.
267     */
268    public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
269        y -= tileGridYOffset;
270        if (y < 0) {
271            y += 1 - tileHeight;         // force round to -infinity (ceiling)
272        }
273        return y/tileHeight;
274    }
275
276    public TIFFImageWriter(ImageWriterSpi originatingProvider) {
277        super(originatingProvider);
278    }
279    
280    public ImageWriteParam getDefaultWriteParam() {
281        return new TIFFImageWriteParam(getLocale());
282    }
283
284    public void setOutput(Object output) {
285        super.setOutput(output);
286
287        if (output != null) {
288            if (!(output instanceof ImageOutputStream)) {
289                throw new IllegalArgumentException
290                    ("output not an ImageOutputStream!");
291            }
292            this.stream = (ImageOutputStream)output;
293
294            //
295            // The output is expected to be positioned at a TIFF header
296            // or at some arbitrary location which may or may not be
297            // the EOF. In the former case the writer should be able
298            // either to overwrite the existing sequence or append to it.
299            //
300
301            // Set the position of the header and the next available space.
302            try {
303                headerPosition = this.stream.getStreamPosition();
304                try {
305                    // Read byte order and magic number.
306                    byte[] b = new byte[4];
307                    stream.readFully(b);
308
309                    // Check bytes for TIFF header.
310                    if((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
311                        b[2] == (byte)0x2a && b[3] == (byte)0x00) ||
312                       (b[0] == (byte)0x4d && b[1] == (byte)0x4d &&
313                        b[2] == (byte)0x00 && b[3] == (byte)0x2a)) {
314                        // TIFF header.
315                        this.nextSpace = stream.length();
316                    } else {
317                        // Neither TIFF header nor EOF: overwrite.
318                        this.nextSpace = headerPosition;
319                    }
320                } catch(IOException io) { // thrown by readFully()
321                    // At EOF or not at a TIFF header.
322                    this.nextSpace = headerPosition;
323                }
324                stream.seek(headerPosition);
325            } catch(IOException ioe) { // thrown by getStreamPosition()
326                // Assume it's at zero.
327                this.nextSpace = headerPosition = 0L;
328            }
329        } else {
330            this.stream = null;
331        }
332    }
333
334    public IIOMetadata
335        getDefaultStreamMetadata(ImageWriteParam param) {
336        return new TIFFStreamMetadata();
337    }
338
339    public IIOMetadata
340        getDefaultImageMetadata(ImageTypeSpecifier imageType,
341                                ImageWriteParam param) {
342        
343        List tagSets = new ArrayList(1);
344        tagSets.add(BaselineTIFFTagSet.getInstance());
345        // XXX Should add Fax/EXIF/GeoTIFF TagSets?
346        TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets);
347
348        if(imageType != null) {
349            TIFFImageMetadata im =
350                (TIFFImageMetadata)convertImageMetadata(imageMetadata,
351                                                        imageType,
352                                                        param);
353            if(im != null) {
354                imageMetadata = im;
355            }
356        }
357
358        return imageMetadata;
359    }
360
361    public IIOMetadata convertStreamMetadata(IIOMetadata inData,
362                                             ImageWriteParam param) {
363        // Check arguments.
364        if(inData == null) {
365            throw new IllegalArgumentException("inData == null!");
366        }
367
368        // Note: param is irrelevant as it does not contain byte order.
369
370        TIFFStreamMetadata outData = null;
371        if(inData instanceof TIFFStreamMetadata) {
372            outData = new TIFFStreamMetadata();
373            outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder;
374            return outData;
375        } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
376                      TIFFStreamMetadata.nativeMetadataFormatName)) {
377            outData = new TIFFStreamMetadata();
378            String format = TIFFStreamMetadata.nativeMetadataFormatName;
379            try {
380                outData.mergeTree(format, inData.getAsTree(format));
381            } catch(IIOInvalidTreeException e) {
382                // XXX Warning
383            }
384        }
385
386        return outData;
387    }
388
389    public IIOMetadata
390        convertImageMetadata(IIOMetadata inData,
391                             ImageTypeSpecifier imageType,
392                             ImageWriteParam param) {
393        // Check arguments.
394        if(inData == null) {
395            throw new IllegalArgumentException("inData == null!");
396        }
397        if(imageType == null) {
398            throw new IllegalArgumentException("imageType == null!");
399        }
400
401        TIFFImageMetadata outData = null;
402
403        // Obtain a TIFFImageMetadata object.
404        if(inData instanceof TIFFImageMetadata) {
405            // Create a new metadata object from a clone of the input IFD.
406            TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD();
407            outData = new TIFFImageMetadata(inIFD.getShallowClone());
408        } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
409                      TIFFImageMetadata.nativeMetadataFormatName)) {
410            // Initialize from the native metadata form of the input tree.
411            try {
412                outData = convertNativeImageMetadata(inData);
413            } catch(IIOInvalidTreeException e) {
414                // XXX Warning
415            }
416        } else if(inData.isStandardMetadataFormatSupported()) {
417            // Initialize from the standard metadata form of the input tree.
418            try {
419                outData = convertStandardImageMetadata(inData);
420            } catch(IIOInvalidTreeException e) {
421                // XXX Warning
422            }
423        }
424
425        // Update the metadata per the image type and param.
426        if(outData != null) {
427            TIFFImageWriter bogusWriter =
428                new TIFFImageWriter(this.originatingProvider);
429            bogusWriter.imageMetadata = outData;
430            bogusWriter.param = param;
431            SampleModel sm = imageType.getSampleModel();
432            try {
433                bogusWriter.setupMetadata(imageType.getColorModel(), sm,
434                                          sm.getWidth(), sm.getHeight());
435                return bogusWriter.imageMetadata;
436            } catch(IIOException e) {
437                // XXX Warning
438                return null;
439            } finally {
440              bogusWriter.dispose();
441            }
442        }
443
444        return outData;
445    }
446
447    /**
448     * Converts a standard <code>javax_imageio_1.0</code> tree to a
449     * <code>TIFFImageMetadata</code> object.
450     *
451     * @param inData The metadata object.
452     * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
453     * the standard tree derived from the input object is <code>null</code>.
454     * @throws IllegalArgumentException if <code>inData</code> is
455     * <code>null</code> or does not support the standard metadata format.
456     * @throws IIOInvalidTreeException if <code>inData</code> generates an
457     * invalid standard metadata tree.
458     */
459    private TIFFImageMetadata convertStandardImageMetadata(IIOMetadata inData)
460        throws IIOInvalidTreeException {
461
462        if(inData == null) {
463            throw new IllegalArgumentException("inData == null!");
464        } else if(!inData.isStandardMetadataFormatSupported()) {
465            throw new IllegalArgumentException
466                ("inData does not support standard metadata format!");
467        }
468
469        TIFFImageMetadata outData = null;
470
471        String formatName = IIOMetadataFormatImpl.standardMetadataFormatName;
472        Node tree = inData.getAsTree(formatName);
473        if (tree != null) {
474            List tagSets = new ArrayList(1);
475            tagSets.add(BaselineTIFFTagSet.getInstance());
476            outData = new TIFFImageMetadata(tagSets);
477            outData.setFromTree(formatName, tree);
478        }
479
480        return outData;
481    }
482
483    /**
484     * Converts a native
485     * <code>com_sun_media_imageio_plugins_tiff_image_1.0</code> tree to a
486     * <code>TIFFImageMetadata</code> object.
487     *
488     * @param inData The metadata object.
489     * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
490     * the native tree derived from the input object is <code>null</code>.
491     * @throws IllegalArgumentException if <code>inData</code> is
492     * <code>null</code> or does not support the native metadata format.
493     * @throws IIOInvalidTreeException if <code>inData</code> generates an
494     * invalid native metadata tree.
495     */
496    private TIFFImageMetadata convertNativeImageMetadata(IIOMetadata inData)
497        throws IIOInvalidTreeException {
498
499        if(inData == null) {
500            throw new IllegalArgumentException("inData == null!");
501        } else if(!Arrays.asList(inData.getMetadataFormatNames()).contains(
502                      TIFFImageMetadata.nativeMetadataFormatName)) {
503            throw new IllegalArgumentException
504                ("inData does not support native metadata format!");
505        }
506
507        TIFFImageMetadata outData = null;
508
509        String formatName = TIFFImageMetadata.nativeMetadataFormatName;
510        Node tree = inData.getAsTree(formatName);
511        if (tree != null) {
512            List tagSets = new ArrayList(1);
513            tagSets.add(BaselineTIFFTagSet.getInstance());
514            outData = new TIFFImageMetadata(tagSets);
515            outData.setFromTree(formatName, tree);
516        }
517
518        return outData;
519    }
520
521    /**
522     * Sets up the output metadata adding, removing, and overriding fields
523     * as needed. The destination image dimensions are provided as parameters
524     * because these might differ from those of the source due to subsampling.
525     * 
526     * @param cm The <code>ColorModel</code> of the image being written.
527     * @param sm The <code>SampleModel</code> of the image being written.
528     * @param destWidth The width of the written image after subsampling.
529     * @param destHeight The height of the written image after subsampling.
530     */
531    void setupMetadata(ColorModel cm, SampleModel sm,
532                       int destWidth, int destHeight)
533        throws IIOException {
534        // Get initial IFD from metadata
535
536        // Always emit these fields:
537        //
538        // Override values from metadata:
539        //
540        //  planarConfiguration -> chunky (planar not supported on output)
541        //
542        // Override values from metadata with image-derived values:
543        // 
544        //  bitsPerSample (if not bilivel)
545        //  colorMap (if palette color)
546        //  photometricInterpretation (derive from image)
547        //  imageLength
548        //  imageWidth
549        // 
550        //  rowsPerStrip     \      /   tileLength
551        //  stripOffsets      | OR |   tileOffsets
552        //  stripByteCounts  /     |   tileByteCounts
553        //                          \   tileWidth
554        //                    
555        //
556        // Override values from metadata with write param values:
557        //
558        //  compression
559
560        // Use values from metadata if present for these fields,
561        // otherwise use defaults:
562        //
563        //  resolutionUnit
564        //  XResolution (take from metadata if present)
565        //  YResolution
566        //  rowsPerStrip
567        //  sampleFormat
568
569        TIFFIFD rootIFD = imageMetadata.getRootIFD();
570
571        BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
572
573        // If PlanarConfiguration field present, set value to chunky.
574
575        TIFFField f =
576            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
577        if(f != null &&
578           f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) {
579            // XXX processWarningOccurred()
580            TIFFField planarConfigurationField =
581                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION),
582                              BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY);
583            rootIFD.addTIFFField(planarConfigurationField);
584        }
585
586        char[] extraSamples = null;
587
588        this.photometricInterpretation = -1;
589        boolean forcePhotometricInterpretation = false;
590
591        f =
592       rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
593        if (f != null) {
594            photometricInterpretation = f.getAsInt(0);
595            if(photometricInterpretation ==
596               BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
597               !(cm instanceof IndexColorModel)) {
598                photometricInterpretation = -1;
599            } else {
600                forcePhotometricInterpretation = true;
601            }
602        }
603
604//         f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
605//         if (f != null) {
606//             extraSamples = f.getAsChars();
607//         }
608
609//         f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
610//         if (f != null) {
611//             bitsPerSample = f.getAsChars();
612//         }
613
614        int[] sampleSize = sm.getSampleSize();
615
616        int numBands = sm.getNumBands();
617        int numExtraSamples = 0;
618
619        // Check that numBands > 1 here because TIFF requires that
620        // SamplesPerPixel = numBands + numExtraSamples and numBands
621        // cannot be zero.
622        if (numBands > 1 && cm != null && cm.hasAlpha()) {
623            --numBands;
624            numExtraSamples = 1;
625            extraSamples = new char[1];
626            if (cm.isAlphaPremultiplied()) {
627                extraSamples[0] =
628                    BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA;
629            } else {
630                extraSamples[0] =
631                    BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA;
632            }
633        }
634
635        if (numBands == 3) {
636            this.nativePhotometricInterpretation =
637                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
638            if (photometricInterpretation == -1) {
639                photometricInterpretation =
640                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
641            }
642        } else if (sm.getNumBands() == 1 && cm instanceof IndexColorModel) {
643            IndexColorModel icm = (IndexColorModel)cm;
644            int r0 = icm.getRed(0);
645            int r1 = icm.getRed(1);
646            if (icm.getMapSize() == 2 &&
647                (r0 == icm.getGreen(0)) && (r0 == icm.getBlue(0)) &&
648                (r1 == icm.getGreen(1)) && (r1 == icm.getBlue(1)) &&
649                (r0 == 0 || r0 == 255) &&
650                (r1 == 0 || r1 == 255) &&
651                (r0 != r1)) {
652                // Black/white image
653
654                if (r0 == 0) {
655                    nativePhotometricInterpretation =
656                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
657                } else {
658                    nativePhotometricInterpretation =
659                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
660                }
661
662
663                // If photometricInterpretation is already set to
664                // WhiteIsZero or BlackIsZero, leave it alone
665                if (photometricInterpretation !=
666                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
667                    photometricInterpretation !=
668                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
669                    photometricInterpretation =
670                        r0 == 0 ?
671                  BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO :
672                  BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
673                }
674            } else {
675                nativePhotometricInterpretation =
676                photometricInterpretation =
677                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
678            }
679        } else {
680            if(cm != null) {
681                switch(cm.getColorSpace().getType()) {
682                case ColorSpace.TYPE_Lab:
683                    nativePhotometricInterpretation =
684                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
685                    break;
686                case ColorSpace.TYPE_YCbCr:
687                    nativePhotometricInterpretation =
688                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
689                    break;
690                case ColorSpace.TYPE_CMYK:
691                    nativePhotometricInterpretation =
692                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
693                    break;
694                default:
695                    nativePhotometricInterpretation =
696                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
697                }
698            } else {
699                nativePhotometricInterpretation =
700                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
701            }
702            if (photometricInterpretation == -1) {
703                photometricInterpretation = nativePhotometricInterpretation;
704            }
705        }
706
707        // Set the compressor and color converter.
708
709        this.compressor = null;
710        this.colorConverter = null;
711        if (param instanceof TIFFImageWriteParam) {
712            TIFFImageWriteParam tparam = (TIFFImageWriteParam)param;
713            if(tparam.getCompressionMode() == tparam.MODE_EXPLICIT) {
714                compressor = tparam.getTIFFCompressor();
715                String compressionType = param.getCompressionType();
716                if(compressor != null &&
717                   !compressor.getCompressionType().equals(compressionType)) {
718                    // Unset the TIFFCompressor if its compression type is
719                    // not the one selected.
720                    compressor = null;
721                }
722            } else {
723                // Compression mode not MODE_EXPLICIT.
724                compressor = null;
725            }
726            colorConverter = tparam.getColorConverter();
727            if (colorConverter != null) {
728                photometricInterpretation =
729                    tparam.getPhotometricInterpretation();
730            }
731        }
732
733        // Emit compression tag
734
735        int compressionMode = param instanceof TIFFImageWriteParam ?
736            param.getCompressionMode() : ImageWriteParam.MODE_DEFAULT;
737        switch(compressionMode) {
738        case ImageWriteParam.MODE_EXPLICIT:
739            {
740                String compressionType = param.getCompressionType();
741                if (compressionType == null) {
742                    this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
743                } else {
744                    // Determine corresponding compression tag value.
745                    int len = compressionTypes.length;
746                    for (int i = 0; i < len; i++) {
747                        if (compressionType.equals(compressionTypes[i])) {
748                            this.compression = compressionNumbers[i];
749                        }
750                    }
751                }
752
753                // Ensure the compressor, if any, matches compression setting
754                // with the precedence described in TIFFImageWriteParam.
755                if(compressor != null &&
756                   compressor.getCompressionTagValue() != this.compression) {
757                    // Does not match: unset the compressor.
758                    compressor = null;
759                }
760            }
761            break;
762        case ImageWriteParam.MODE_COPY_FROM_METADATA:
763            {
764                TIFFField compField =
765                    rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
766                if(compField != null) {
767                    this.compression = compField.getAsInt(0);
768                    break;
769                }
770            }
771        case ImageWriteParam.MODE_DEFAULT:
772        case ImageWriteParam.MODE_DISABLED:
773        default:
774            this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
775        }
776
777        TIFFField predictorField =
778            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
779        if (predictorField != null) {
780            this.predictor = predictorField.getAsInt(0);
781            
782            // We only support Horizontal Predictor for a bitDepth of 8
783            if (sampleSize[0] != 8 || 
784                // Check the value of the tag for validity
785                (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && 
786                 predictor != 
787                 BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
788                // XXX processWarningOccured ???
789                // Set to default
790                predictor = BaselineTIFFTagSet.PREDICTOR_NONE;          
791
792                // Emit this changed predictor value to metadata
793                TIFFField newPredictorField =
794                   new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PREDICTOR),
795                                 predictor);
796                rootIFD.addTIFFField(newPredictorField);
797            }
798
799            // XXX Do we need to ensure that predictor is not passed on if
800            // the compression is not either Deflate or LZW?
801        }
802
803        TIFFField compressionField =
804            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
805                          compression);
806        rootIFD.addTIFFField(compressionField);
807
808        // Set EXIF flag. Note that there is no way to determine definitively
809        // when an uncompressed thumbnail is being written as the EXIF IFD
810        // pointer field is optional for thumbnails.
811        boolean isEXIF = false;
812        if(numBands == 3 &&
813           sampleSize[0] == 8 && sampleSize[1] == 8 && sampleSize[2] == 8) {
814            // Three bands with 8 bits per sample.
815            if(rootIFD.getTIFFField(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER)
816               != null) {
817                // EXIF IFD pointer present.
818                if(compression == BaselineTIFFTagSet.COMPRESSION_NONE &&
819                   (photometricInterpretation ==
820                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB ||
821                    photometricInterpretation ==
822                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) {
823                    // Uncompressed RGB or YCbCr.
824                    isEXIF = true;
825                } else if(compression ==
826                          BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
827                    // Compressed.
828                    isEXIF = true;
829                }
830            } else if(compressionMode == ImageWriteParam.MODE_EXPLICIT &&
831                      EXIF_JPEG_COMPRESSION_TYPE.equals
832                      (param.getCompressionType())) {
833                // EXIF IFD pointer absent but EXIF JPEG compression set.
834                isEXIF = true;
835            }
836        }
837
838        // Initialize JPEG interchange format flag which is used to
839        // indicate that the image is stored as a single JPEG stream.
840        // This flag is separated from the 'isEXIF' flag in case JPEG
841        // interchange format is eventually supported for non-EXIF images.
842        boolean isJPEGInterchange =
843            isEXIF && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG;
844
845        if (compressor == null) {
846            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
847
848                if(compressor == null) {
849                    compressor = new TIFFRLECompressor();
850                    if(DEBUG) {
851                        System.out.println("Using Java RLE compressor");
852                    }
853                }
854
855                if (!forcePhotometricInterpretation) {
856                    photometricInterpretation =
857                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
858                }
859            } else if (compression ==
860                       BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
861                
862
863                if(compressor == null) {
864                    compressor = new TIFFT4Compressor();
865                    if(DEBUG) {
866                        System.out.println("Using Java T.4 compressor");
867                    }
868                }
869
870                if (!forcePhotometricInterpretation) {
871                    photometricInterpretation =
872                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
873                }
874            } else if (compression ==
875                       BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
876               
877
878                if(compressor == null) {
879                    compressor = new TIFFT6Compressor();
880                    if(DEBUG) {
881                        System.out.println("Using Java T.6 compressor");
882                    }
883                }
884
885                if (!forcePhotometricInterpretation) {
886                    photometricInterpretation =
887                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
888                }
889            } else if (compression ==
890                       BaselineTIFFTagSet.COMPRESSION_LZW) {
891                compressor = new TIFFLZWCompressor(predictor);
892            } else if (compression ==
893                       BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
894                if(isEXIF) {
895                    compressor = new TIFFEXIFJPEGCompressor(param);
896                } else {
897                    throw new IIOException
898                        ("Old JPEG compression not supported!");
899                }
900            } else if (compression ==
901                       BaselineTIFFTagSet.COMPRESSION_JPEG) {
902                if(numBands == 3 && sampleSize[0] == 8 &&
903                   sampleSize[1] == 8 && sampleSize[2] == 8) {
904                    photometricInterpretation =
905                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
906                } else if(numBands == 1 && sampleSize[0] == 8) {
907                    photometricInterpretation =
908                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
909                } else {
910                    throw new IIOException
911                        ("JPEG compression supported for 1- and 3-band byte images only!");
912                }
913                compressor = new TIFFJPEGCompressor(param);
914            } else if (compression ==
915                       BaselineTIFFTagSet.COMPRESSION_ZLIB) {
916                compressor = new TIFFZLibCompressor(param, predictor);
917            } else if (compression ==
918                       BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
919                compressor = new TIFFPackBitsCompressor();
920            } else if (compression ==
921                       BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
922                compressor = new TIFFDeflateCompressor(param, predictor);
923            } else {
924                // Determine inverse fill setting.
925                f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
926                boolean inverseFill = (f != null && f.getAsInt(0) == 2);
927
928                if(inverseFill) {
929                    compressor = new TIFFLSBCompressor();
930                } else {
931                    compressor = new TIFFNullCompressor();
932                }
933            } // compression == ?
934        } // compressor == null
935
936        if(DEBUG) {
937            if(param != null &&
938               param.getCompressionMode() == param.MODE_EXPLICIT) {
939                System.out.println("compressionType = "+
940                                   param.getCompressionType());
941            }
942            if(compressor != null) {
943                System.out.println("compressor = "+
944                                   compressor.getClass().getName());
945            }
946        }
947
948        if (colorConverter == null) {
949            if(cm != null &&
950               cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
951                //
952                // Perform color conversion only if image has RGB color space.
953                //
954                if (photometricInterpretation ==
955                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
956                    compression !=
957                    BaselineTIFFTagSet.COMPRESSION_JPEG) {
958                    //
959                    // Convert RGB to YCbCr only if compression type is not
960                    // JPEG in which case this is handled implicitly by the
961                    // compressor.
962                    //
963                    colorConverter =
964                        new TIFFYCbCrColorConverter(imageMetadata);
965                } else if (photometricInterpretation ==
966                           BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) {
967                    colorConverter = new TIFFCIELabColorConverter();
968                }
969            }
970        }
971
972        //
973        // Cannot at this time do YCbCr subsampling so set the
974        // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning
975        // field value to "cosited".
976        //
977        if(photometricInterpretation ==
978           BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
979           compression !=
980           BaselineTIFFTagSet.COMPRESSION_JPEG) {
981            // Remove old subsampling and positioning fields.
982            rootIFD.removeTIFFField
983                (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
984            rootIFD.removeTIFFField
985                (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
986
987            // Add unity chrominance subsampling factors.
988            rootIFD.addTIFFField
989                (new TIFFField
990                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
991                     TIFFTag.TIFF_SHORT,
992                     2,
993                     new char[] {(char)1, (char)1}));
994
995            // Add cosited positioning.
996            rootIFD.addTIFFField
997                (new TIFFField
998                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
999                     TIFFTag.TIFF_SHORT,
1000                     1,
1001                     new char[] {
1002                         (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED
1003                     }));
1004        }
1005
1006        TIFFField photometricInterpretationField =
1007            new TIFFField(
1008                base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
1009                          photometricInterpretation);
1010        rootIFD.addTIFFField(photometricInterpretationField);
1011
1012        this.bitsPerSample = new char[numBands + numExtraSamples];
1013        this.bitDepth = 0;
1014        for (int i = 0; i < numBands; i++) {
1015            this.bitDepth = Math.max(bitDepth, sampleSize[i]);
1016        }
1017        if (bitDepth == 3) {
1018            bitDepth = 4;
1019        } else if (bitDepth > 4 && bitDepth < 8) {
1020            bitDepth = 8;
1021        } else if (bitDepth > 8 && bitDepth < 16) {
1022            bitDepth = 16;
1023        } else if (bitDepth > 16) {
1024            bitDepth = 32;
1025        }
1026
1027        for (int i = 0; i < bitsPerSample.length; i++) {
1028            bitsPerSample[i] = (char)bitDepth;
1029        }
1030
1031        // Emit BitsPerSample. If the image is bilevel, emit if and only
1032        // if already in the metadata and correct (count and value == 1).
1033        if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
1034            TIFFField bitsPerSampleField =
1035                new TIFFField(
1036                           base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
1037                           TIFFTag.TIFF_SHORT,
1038                           bitsPerSample.length,
1039                           bitsPerSample);
1040            rootIFD.addTIFFField(bitsPerSampleField);
1041        } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1
1042            TIFFField bitsPerSampleField =
1043                rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1044            if(bitsPerSampleField != null) {
1045                int[] bps = bitsPerSampleField.getAsInts();
1046                if(bps == null || bps.length != 1 || bps[0] != 1) {
1047                    rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1048                }
1049            }
1050        }
1051
1052        // Prepare SampleFormat field.
1053        f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
1054        if(f == null && (bitDepth == 16 || bitDepth == 32)) {
1055            // Set up default content for 16- and 32-bit cases.
1056            char sampleFormatValue;
1057            int dataType = sm.getDataType();
1058            if(bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) {
1059               sampleFormatValue =
1060                   (char)BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
1061            } else if(bitDepth == 32 && dataType == DataBuffer.TYPE_FLOAT) {
1062                sampleFormatValue =
1063                    (char)BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
1064            } else {
1065                sampleFormatValue =
1066                    BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
1067            }
1068            this.sampleFormat = (int)sampleFormatValue;
1069            char[] sampleFormatArray = new char[bitsPerSample.length];
1070            Arrays.fill(sampleFormatArray, sampleFormatValue);
1071
1072            // Update the metadata.
1073            TIFFTag sampleFormatTag =
1074                base.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
1075
1076            TIFFField sampleFormatField =
1077                new TIFFField(sampleFormatTag, TIFFTag.TIFF_SHORT,
1078                              sampleFormatArray.length, sampleFormatArray);
1079
1080            rootIFD.addTIFFField(sampleFormatField);
1081        } else if(f != null) {
1082            // Get whatever was provided.
1083            sampleFormat = f.getAsInt(0);
1084        } else {
1085            // Set default value for internal use only.
1086            sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
1087        }
1088
1089        if (extraSamples != null) {
1090            TIFFField extraSamplesField =
1091                new TIFFField(
1092                           base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1093                           TIFFTag.TIFF_SHORT,
1094                           extraSamples.length,
1095                           extraSamples);
1096            rootIFD.addTIFFField(extraSamplesField);
1097        } else {
1098            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
1099        }
1100
1101        TIFFField samplesPerPixelField =
1102            new TIFFField(
1103                         base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
1104                         bitsPerSample.length);
1105        rootIFD.addTIFFField(samplesPerPixelField);
1106
1107        // Emit ColorMap if image is of palette color type
1108        if (photometricInterpretation ==
1109            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
1110            cm instanceof IndexColorModel) {
1111            char[] colorMap = new char[3*(1 << bitsPerSample[0])];
1112
1113            IndexColorModel icm = (IndexColorModel)cm;
1114
1115            // mapSize is determined by BitsPerSample, not by incoming ICM.
1116            int mapSize = 1 << bitsPerSample[0];
1117            int indexBound = Math.min(mapSize, icm.getMapSize());
1118            for (int i = 0; i < indexBound; i++) {
1119                colorMap[i] = (char)((icm.getRed(i)*65535)/255);
1120                colorMap[mapSize + i] = (char)((icm.getGreen(i)*65535)/255);
1121                colorMap[2*mapSize + i] = (char)((icm.getBlue(i)*65535)/255);
1122            }
1123
1124            TIFFField colorMapField =
1125                new TIFFField(
1126                           base.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP),
1127                           TIFFTag.TIFF_SHORT,
1128                           colorMap.length,
1129                           colorMap);
1130            rootIFD.addTIFFField(colorMapField);
1131        } else {
1132            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
1133        }
1134
1135        // Emit ICCProfile if there is no ICCProfile field already in the
1136        // metadata and the ColorSpace is non-standard ICC.
1137        if(cm != null &&
1138           rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null &&
1139           ImageUtil.isNonStandardICCColorSpace(cm.getColorSpace())) {
1140            ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)cm.getColorSpace();
1141            byte[] iccProfileData = iccColorSpace.getProfile().getData();
1142            TIFFField iccProfileField =
1143                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE),
1144                              TIFFTag.TIFF_UNDEFINED,
1145                              iccProfileData.length,
1146                              iccProfileData);
1147            rootIFD.addTIFFField(iccProfileField);
1148        }
1149
1150        // Always emit XResolution and YResolution.
1151
1152        TIFFField XResolutionField =
1153            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
1154        TIFFField YResolutionField =
1155            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
1156
1157        if(XResolutionField == null && YResolutionField == null) {
1158            long[][] resRational = new long[1][2];
1159            resRational[0] = new long[2];
1160
1161            TIFFField ResolutionUnitField =
1162                rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
1163
1164            // Don't force dimensionless if one of the other dimensional
1165            // quantities is present.
1166            if(ResolutionUnitField == null &&
1167               rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null &&
1168               rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) {
1169                // Set resolution to unit and units to dimensionless.
1170                resRational[0][0] = 1;
1171                resRational[0][1] = 1;
1172
1173                ResolutionUnitField =
1174                    new TIFFField(rootIFD.getTag
1175                                  (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1176                                  BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
1177                rootIFD.addTIFFField(ResolutionUnitField);
1178            } else {
1179                // Set resolution to a value which would make the maximum
1180                // image dimension equal to 4 inches as arbitrarily stated
1181                // in the description of ResolutionUnit in the TIFF 6.0
1182                // specification. If the ResolutionUnit field specifies
1183                // "none" then set the resolution to unity (1/1).
1184                int resolutionUnit = ResolutionUnitField != null ?
1185                    ResolutionUnitField.getAsInt(0) :
1186                    BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
1187                int maxDimension = Math.max(destWidth, destHeight);
1188                switch(resolutionUnit) {
1189                case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH:
1190                    resRational[0][0] = maxDimension;
1191                    resRational[0][1] = 4;
1192                    break;
1193                case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER:
1194                    resRational[0][0] = 100L*maxDimension; // divide out 100
1195                    resRational[0][1] = 4*254; // 2.54 cm/inch * 100
1196                    break;
1197                default:
1198                    resRational[0][0] = 1;
1199                    resRational[0][1] = 1;
1200                }
1201            }
1202
1203            XResolutionField =
1204                new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1205                              TIFFTag.TIFF_RATIONAL,
1206                              1,
1207                              resRational);
1208            rootIFD.addTIFFField(XResolutionField);
1209
1210            YResolutionField =
1211                new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1212                              TIFFTag.TIFF_RATIONAL,
1213                              1,
1214                              resRational);
1215            rootIFD.addTIFFField(YResolutionField);
1216        } else if(XResolutionField == null && YResolutionField != null) {
1217            // Set XResolution to YResolution.
1218            long[] yResolution =
1219                (long[])YResolutionField.getAsRational(0).clone();
1220            XResolutionField =
1221             new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1222                              TIFFTag.TIFF_RATIONAL,
1223                              1,
1224                              yResolution);
1225            rootIFD.addTIFFField(XResolutionField);
1226        } else if(XResolutionField != null && YResolutionField == null) {
1227            // Set YResolution to XResolution.
1228            long[] xResolution =
1229                (long[])XResolutionField.getAsRational(0).clone();
1230            YResolutionField =
1231             new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1232                              TIFFTag.TIFF_RATIONAL,
1233                              1,
1234                              xResolution);
1235            rootIFD.addTIFFField(YResolutionField);
1236        }
1237
1238        // Set mandatory fields, overriding metadata passed in
1239
1240        int width = destWidth;
1241        TIFFField imageWidthField =
1242            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
1243                          width);
1244        rootIFD.addTIFFField(imageWidthField);
1245
1246        int height = destHeight;
1247        TIFFField imageLengthField =
1248            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
1249                          height);
1250        rootIFD.addTIFFField(imageLengthField);
1251
1252        // Determine rowsPerStrip
1253
1254        int rowsPerStrip;
1255
1256        TIFFField rowsPerStripField =
1257            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1258        if (rowsPerStripField != null) {
1259            rowsPerStrip = rowsPerStripField.getAsInt(0);
1260            if(rowsPerStrip < 0) {
1261                rowsPerStrip = height;
1262            }
1263        } else {
1264            int bitsPerPixel = bitDepth*(numBands + numExtraSamples);
1265            int bytesPerRow = (bitsPerPixel*width + 7)/8;
1266            rowsPerStrip =
1267                Math.max(Math.max(DEFAULT_BYTES_PER_STRIP/bytesPerRow, 1), 8);
1268        }
1269        rowsPerStrip = Math.min(rowsPerStrip, height);
1270
1271        // Tiling flag.
1272        boolean useTiling = false;
1273
1274        // Analyze tiling parameters
1275        int tilingMode = param instanceof TIFFImageWriteParam ?
1276            param.getTilingMode() : ImageWriteParam.MODE_DEFAULT;
1277        if (tilingMode == ImageWriteParam.MODE_DISABLED ||
1278            tilingMode == ImageWriteParam.MODE_DEFAULT) {
1279            this.tileWidth = width;
1280            this.tileLength = rowsPerStrip;
1281            useTiling = false;
1282        } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
1283            tileWidth = param.getTileWidth();
1284            tileLength = param.getTileHeight();
1285            useTiling = true;
1286        } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
1287            f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
1288            if (f == null) {
1289                tileWidth = width;
1290                useTiling = false;
1291            } else {
1292                tileWidth = f.getAsInt(0);
1293                useTiling = true;
1294            }
1295
1296            f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
1297            if (f == null) {
1298                tileLength = rowsPerStrip;
1299            } else {
1300                tileLength = f.getAsInt(0);
1301                useTiling = true;
1302            }
1303        } else {
1304            throw new IIOException("Illegal value of tilingMode!");
1305        }
1306
1307        if(compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
1308            // Reset tile size per TTN2 spec for JPEG compression.
1309            int subX;
1310            int subY;
1311            if(numBands == 1) {
1312                subX = subY = 1;
1313            } else {
1314                subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING;
1315            }
1316            if(useTiling) {
1317                int MCUMultipleX = 8*subX;
1318                int MCUMultipleY = 8*subY;
1319                tileWidth =
1320                    Math.max(MCUMultipleX*((tileWidth +
1321                                            MCUMultipleX/2)/MCUMultipleX),
1322                             MCUMultipleX);
1323                tileLength =
1324                    Math.max(MCUMultipleY*((tileLength +
1325                                            MCUMultipleY/2)/MCUMultipleY),
1326                             MCUMultipleY);
1327            } else if(rowsPerStrip < height) {
1328                int MCUMultiple = 8*Math.max(subX, subY);
1329                rowsPerStrip = tileLength =
1330                    Math.max(MCUMultiple*((tileLength +
1331                                           MCUMultiple/2)/MCUMultiple),
1332                             MCUMultiple);
1333            }
1334        } else if(isJPEGInterchange) {
1335            // Force tile size to equal image size.
1336            tileWidth = width;
1337            tileLength = height;
1338        } else if(useTiling) {
1339            // Round tile size to multiple of 16 per TIFF 6.0 specification
1340            // (see pages 67-68 of version 6.0.1 from Adobe).
1341            int tileWidthRemainder = tileWidth % 16;
1342            if(tileWidthRemainder != 0) {
1343                // Round to nearest multiple of 16 not less than 16.
1344                tileWidth = Math.max(16*((tileWidth + 8)/16), 16);
1345                // XXX insert processWarningOccurred(int,String);
1346            }
1347
1348            int tileLengthRemainder = tileLength % 16;
1349            if(tileLengthRemainder != 0) {
1350                // Round to nearest multiple of 16 not less than 16.
1351                tileLength = Math.max(16*((tileLength + 8)/16), 16);
1352                // XXX insert processWarningOccurred(int,String);
1353            }
1354        }
1355
1356        this.tilesAcross = (width + tileWidth - 1)/tileWidth;
1357        this.tilesDown = (height + tileLength - 1)/tileLength;
1358 
1359        if (!useTiling) {
1360            this.isTiled = false;
1361
1362            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
1363            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
1364            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
1365            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
1366
1367            rowsPerStripField =
1368              new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP),
1369                            rowsPerStrip);
1370            rootIFD.addTIFFField(rowsPerStripField);
1371
1372            TIFFField stripOffsetsField =
1373                new TIFFField(
1374                         base.getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS),
1375                         TIFFTag.TIFF_LONG,
1376                         tilesDown);
1377            rootIFD.addTIFFField(stripOffsetsField);
1378
1379            TIFFField stripByteCountsField =
1380                new TIFFField(
1381                         base.getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS),
1382                         TIFFTag.TIFF_LONG,
1383                         tilesDown);
1384            rootIFD.addTIFFField(stripByteCountsField);
1385        } else {
1386            this.isTiled = true;
1387
1388            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1389            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
1390            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
1391
1392            TIFFField tileWidthField =
1393                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH),
1394                              tileWidth);
1395            rootIFD.addTIFFField(tileWidthField);
1396
1397            TIFFField tileLengthField =
1398                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH),
1399                              tileLength);
1400            rootIFD.addTIFFField(tileLengthField);
1401
1402            TIFFField tileOffsetsField =
1403                new TIFFField(
1404                         base.getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS),
1405                         TIFFTag.TIFF_LONG,
1406                         tilesDown*tilesAcross);
1407            rootIFD.addTIFFField(tileOffsetsField);
1408
1409            TIFFField tileByteCountsField =
1410                new TIFFField(
1411                         base.getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS),
1412                         TIFFTag.TIFF_LONG,
1413                         tilesDown*tilesAcross);
1414            rootIFD.addTIFFField(tileByteCountsField);
1415        }
1416
1417        if(isEXIF) {
1418            //
1419            // Ensure presence of mandatory fields and absence of prohibited
1420            // fields and those that duplicate information in JPEG marker
1421            // segments per tables 14-18 of the EXIF 2.2 specification.
1422            //
1423
1424            // If an empty image is being written or inserted then infer
1425            // that the primary IFD is being set up.
1426            boolean isPrimaryIFD = isEncodingEmpty();
1427
1428            // Handle TIFF fields in order of increasing tag number.
1429            if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1430                // ImageWidth
1431                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
1432
1433                // ImageLength
1434                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
1435
1436                // BitsPerSample
1437                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1438                // Compression
1439                if(isPrimaryIFD) {
1440                    rootIFD.removeTIFFField
1441                        (BaselineTIFFTagSet.TAG_COMPRESSION);
1442                }
1443
1444                // PhotometricInterpretation
1445                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
1446
1447                // StripOffsets
1448                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
1449
1450                // SamplesPerPixel
1451                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
1452
1453                // RowsPerStrip
1454                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1455
1456                // StripByteCounts
1457                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
1458                // XResolution and YResolution are handled above for all TIFFs.
1459
1460                // PlanarConfiguration
1461                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
1462
1463                // ResolutionUnit
1464                if(rootIFD.getTIFFField
1465                   (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
1466                    f = new TIFFField(base.getTag
1467                                      (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1468                                      BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
1469                    rootIFD.addTIFFField(f);
1470                }
1471
1472                if(isPrimaryIFD) {
1473                    // JPEGInterchangeFormat
1474                    rootIFD.removeTIFFField
1475                        (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
1476
1477                    // JPEGInterchangeFormatLength
1478                    rootIFD.removeTIFFField
1479                        (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
1480
1481                    // YCbCrSubsampling
1482                    rootIFD.removeTIFFField
1483                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1484
1485                    // YCbCrPositioning
1486                    if(rootIFD.getTIFFField
1487                       (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) {
1488                        f = new TIFFField
1489                            (base.getTag
1490                             (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
1491                             TIFFTag.TIFF_SHORT,
1492                             1,
1493                             new char[] {
1494                                 (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED
1495                             });
1496                        rootIFD.addTIFFField(f);
1497                    }
1498                } else { // Thumbnail IFD
1499                    // JPEGInterchangeFormat
1500                    f = new TIFFField
1501                        (base.getTag
1502                         (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT),
1503                         TIFFTag.TIFF_LONG,
1504                         1);
1505                    rootIFD.addTIFFField(f);
1506
1507                    // JPEGInterchangeFormatLength
1508                    f = new TIFFField
1509                        (base.getTag
1510                         (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
1511                         TIFFTag.TIFF_LONG,
1512                         1);
1513                    rootIFD.addTIFFField(f);
1514
1515                    // YCbCrSubsampling
1516                    rootIFD.removeTIFFField
1517                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1518                }
1519            } else { // Uncompressed
1520                // ImageWidth through PlanarConfiguration are set above.
1521                // XResolution and YResolution are handled above for all TIFFs.
1522
1523                // ResolutionUnit
1524                if(rootIFD.getTIFFField
1525                   (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
1526                    f = new TIFFField(base.getTag
1527                                      (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1528                                      BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
1529                    rootIFD.addTIFFField(f);
1530                }
1531
1532
1533                // JPEGInterchangeFormat
1534                rootIFD.removeTIFFField
1535                    (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
1536
1537                // JPEGInterchangeFormatLength
1538                rootIFD.removeTIFFField
1539                    (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
1540
1541                if(photometricInterpretation ==
1542                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) {
1543                    // YCbCrCoefficients
1544                    rootIFD.removeTIFFField
1545                        (BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
1546
1547                    // YCbCrSubsampling
1548                    rootIFD.removeTIFFField
1549                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1550
1551                    // YCbCrPositioning
1552                    rootIFD.removeTIFFField
1553                        (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
1554                }
1555            }
1556
1557            // Get EXIF tags.
1558            TIFFTagSet exifTags = EXIFTIFFTagSet.getInstance();
1559
1560            // Retrieve or create the EXIF IFD.
1561            TIFFIFD exifIFD = null;
1562            f = rootIFD.getTIFFField
1563                (EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
1564            if(f != null) {
1565                // Retrieve the EXIF IFD.
1566                exifIFD = (TIFFIFD)f.getData();
1567            } else if(isPrimaryIFD) {
1568                // Create the EXIF IFD.
1569                List exifTagSets = new ArrayList(1);
1570                exifTagSets.add(exifTags);
1571                exifIFD = new TIFFIFD(exifTagSets);
1572
1573                // Add it to the root IFD.
1574                TIFFTagSet tagSet = EXIFParentTIFFTagSet.getInstance();
1575                TIFFTag exifIFDTag =
1576                    tagSet.getTag(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
1577                rootIFD.addTIFFField(new TIFFField(exifIFDTag,
1578                                                   TIFFTag.TIFF_LONG,
1579                                                   1,
1580                                                   exifIFD));
1581            }
1582
1583            if(exifIFD != null) {
1584                // Handle EXIF private fields in order of increasing
1585                // tag number.
1586
1587                // ExifVersion
1588                if(exifIFD.getTIFFField
1589                   (EXIFTIFFTagSet.TAG_EXIF_VERSION) == null) {
1590                    f = new TIFFField
1591                        (exifTags.getTag(EXIFTIFFTagSet.TAG_EXIF_VERSION),
1592                         TIFFTag.TIFF_UNDEFINED,
1593                         4,
1594                         EXIFTIFFTagSet.EXIF_VERSION_2_2);
1595                    exifIFD.addTIFFField(f);
1596                }
1597
1598                if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1599                    // ComponentsConfiguration
1600                    if(exifIFD.getTIFFField
1601                       (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION) == null) {
1602                        f = new TIFFField
1603                            (exifTags.getTag
1604                             (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION),
1605                             TIFFTag.TIFF_UNDEFINED,
1606                             4,
1607                             new byte[] {
1608                                 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_Y,
1609                                 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CB,
1610                                 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CR,
1611                                 (byte)0
1612                             });
1613                        exifIFD.addTIFFField(f);
1614                    }
1615                } else {
1616                    // ComponentsConfiguration
1617                    exifIFD.removeTIFFField
1618                        (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION);
1619
1620                    // CompressedBitsPerPixel
1621                    exifIFD.removeTIFFField
1622                        (EXIFTIFFTagSet.TAG_COMPRESSED_BITS_PER_PIXEL);
1623                }
1624
1625                // FlashpixVersion
1626                if(exifIFD.getTIFFField
1627                   (EXIFTIFFTagSet.TAG_FLASHPIX_VERSION) == null) {
1628                    f = new TIFFField
1629                        (exifTags.getTag(EXIFTIFFTagSet.TAG_FLASHPIX_VERSION),
1630                         TIFFTag.TIFF_UNDEFINED,
1631                         4,
1632                     new byte[] {(byte)'0', (byte)'1', (byte)'0', (byte)'0'});
1633                    exifIFD.addTIFFField(f);
1634                }
1635
1636                // ColorSpace
1637                if(exifIFD.getTIFFField
1638                   (EXIFTIFFTagSet.TAG_COLOR_SPACE) == null) {
1639                    f = new TIFFField
1640                        (exifTags.getTag(EXIFTIFFTagSet.TAG_COLOR_SPACE),
1641                         TIFFTag.TIFF_SHORT,
1642                         1,
1643                         new char[] {
1644                             (char)EXIFTIFFTagSet.COLOR_SPACE_SRGB
1645                         });
1646                    exifIFD.addTIFFField(f);
1647                }
1648
1649                if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1650                    // PixelXDimension
1651                    if(exifIFD.getTIFFField
1652                       (EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION) == null) {
1653                        f = new TIFFField
1654                            (exifTags.getTag(EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION),
1655                             width);
1656                        exifIFD.addTIFFField(f);
1657                    }
1658
1659                    // PixelYDimension
1660                    if(exifIFD.getTIFFField
1661                       (EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION) == null) {
1662                        f = new TIFFField
1663                            (exifTags.getTag(EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION),
1664                             height);
1665                        exifIFD.addTIFFField(f);
1666                    }
1667                } else {
1668                    exifIFD.removeTIFFField
1669                        (EXIFTIFFTagSet.TAG_INTEROPERABILITY_IFD_POINTER);
1670                }
1671            }
1672
1673        } // if(isEXIF)
1674    }
1675
1676    /**
1677       @param tileRect The area to be written which might be outside the image.
1678     */
1679    private int writeTile(Rectangle tileRect, TIFFCompressor compressor)
1680        throws IOException {
1681        // Determine the rectangle which will actually be written 
1682        // and set the padding flag. Padding will occur only when the
1683        // image is written as a tiled TIFF and the tile bounds are not
1684        // contained within the image bounds.
1685        Rectangle activeRect;
1686        boolean isPadded;
1687        Rectangle imageBounds =
1688            new Rectangle(image.getMinX(), image.getMinY(),
1689                          image.getWidth(), image.getHeight());
1690        if(!isTiled) {
1691            // Stripped
1692            activeRect = tileRect.intersection(imageBounds);
1693            tileRect = activeRect;
1694            isPadded = false;
1695        } else if(imageBounds.contains(tileRect)) {
1696            // Tiled, tile within image bounds
1697            activeRect = tileRect;
1698            isPadded = false;
1699        } else {
1700            // Tiled, tile not within image bounds
1701            activeRect = imageBounds.intersection(tileRect);
1702            isPadded = true;
1703        }
1704
1705        // Shouldn't happen, but return early if empty intersection.
1706        if(activeRect.isEmpty()) {
1707            return 0;
1708        }
1709
1710        int minX = tileRect.x;
1711        int minY = tileRect.y;
1712        int width = tileRect.width;
1713        int height = tileRect.height;
1714
1715        if(isImageSimple) {
1716
1717            SampleModel sm = image.getSampleModel();
1718
1719            // Read only data from the active rectangle.
1720            Raster raster = image.getData(activeRect);
1721
1722            // If padding is required, create a larger Raster and fill
1723            // it from the active rectangle.
1724            if(isPadded) {
1725                WritableRaster wr =
1726                    raster.createCompatibleWritableRaster(minX, minY,
1727                                                          width, height);
1728                wr.setRect(raster);
1729                raster = wr;
1730            }
1731
1732            if(isBilevel) {
1733                /* XXX
1734                MultiPixelPackedSampleModel mppsm =
1735                    (MultiPixelPackedSampleModel)raster.getSampleModel();
1736
1737                byte[] buf;
1738                int off;
1739                int lineStride;
1740                if(mppsm.getDataBitOffset() == 0 &&
1741                   raster.getDataBuffer() instanceof DataBufferByte) {
1742                    buf = ((DataBufferByte)raster.getDataBuffer()).getData();
1743                    off = mppsm.getOffset(tileRect.x -
1744                                          raster.getSampleModelTranslateX(),
1745                                          tileRect.y -
1746                                          raster.getSampleModelTranslateY());
1747                    lineStride = mppsm.getScanlineStride();
1748                } else {
1749                    buf = ImageUtil.getPackedBinaryData(raster,
1750                                                        tileRect);
1751                    off = 0;
1752                    lineStride = (tileRect.width + 7)/8;
1753                }
1754                */
1755                byte[] buf = ImageUtil.getPackedBinaryData(raster,
1756                                                           tileRect);
1757
1758                if(isInverted) {
1759                    DataBuffer dbb = raster.getDataBuffer();
1760                    if(dbb instanceof DataBufferByte &&
1761                       buf == ((DataBufferByte)dbb).getData()) {
1762                        byte[] bbuf = new byte[buf.length];
1763                        int len = buf.length;
1764                        for(int i = 0; i < len; i++) {
1765                            bbuf[i] = (byte)(buf[i] ^ 0xff);
1766                        }
1767                        buf = bbuf;
1768                    } else {
1769                        int len = buf.length;
1770                        for(int i = 0; i < len; i++) {
1771                            buf[i] ^= 0xff;
1772                        }
1773                    }
1774                }
1775
1776                if(DEBUG) {
1777                    System.out.println("Optimized bilevel case");
1778                }
1779
1780                return compressor.encode(buf, 0,
1781                                         width, height, sampleSize,
1782                                         (tileRect.width + 7)/8);
1783            } else if(bitDepth == 8 &&
1784                      sm.getDataType() == DataBuffer.TYPE_BYTE) {
1785                ComponentSampleModel csm =
1786                    (ComponentSampleModel)raster.getSampleModel();
1787
1788                byte[] buf =
1789                    ((DataBufferByte)raster.getDataBuffer()).getData();
1790
1791                int off =
1792                    csm.getOffset(minX -
1793                                  raster.getSampleModelTranslateX(),
1794                                  minY -
1795                                  raster.getSampleModelTranslateY());
1796
1797                if(DEBUG) {
1798                    System.out.println("Optimized component case");
1799                }
1800
1801                return compressor.encode(buf, off,
1802                                         width, height, sampleSize,
1803                                         csm.getScanlineStride());
1804            }
1805        }
1806        
1807        // Set offsets and skips based on source subsampling factors
1808        int xOffset = minX;
1809        int xSkip = periodX;
1810        int yOffset = minY;
1811        int ySkip = periodY;
1812
1813        // Early exit if no data for this pass
1814        int hpixels = (width + xSkip - 1)/xSkip;
1815        int vpixels = (height + ySkip - 1)/ySkip;
1816        if (hpixels == 0 || vpixels == 0) {
1817            return 0;
1818        }
1819
1820        // Convert X offset and skip from pixels to samples
1821        xOffset *= numBands;
1822        xSkip *= numBands;
1823
1824        // Initialize sizes
1825        int samplesPerByte = 8/bitDepth;
1826        int numSamples = width*numBands;
1827        int bytesPerRow = hpixels*numBands;
1828
1829        // Update number of bytes per row.
1830        if (bitDepth < 8) {
1831            bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
1832        } else if (bitDepth == 16) {
1833            bytesPerRow *= 2;
1834        } else if (bitDepth == 32) {
1835            bytesPerRow *= 4;
1836        }
1837
1838        // Create row buffers
1839        int[] samples = null;
1840        float[] fsamples = null;
1841        if(sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
1842            fsamples = new float[numSamples];
1843        } else {
1844            samples = new int[numSamples];
1845        }
1846
1847        // Create tile buffer
1848        byte[] currTile = new byte[bytesPerRow*vpixels];
1849
1850        // Sub-optimal case: shy of "isImageSimple" only by virtue of
1851        // not being contiguous.
1852        if(!isInverted &&                  // no inversion
1853           !isRescaling &&                 // no value rescaling
1854           sourceBands == null &&          // no subbanding
1855           periodX == 1 && periodY == 1 && // no subsampling
1856           colorConverter == null) {
1857
1858            SampleModel sm = image.getSampleModel();
1859
1860            if(sm instanceof ComponentSampleModel &&       // component
1861               bitDepth == 8 &&                            // 8 bits/sample
1862               sm.getDataType() == DataBuffer.TYPE_BYTE) { // byte type
1863
1864                if(DEBUG) {
1865                    System.out.println("Sub-optimal byte component case");
1866                    System.out.println(sm.getClass().getName());
1867                }
1868
1869                // Read only data from the active rectangle.
1870                Raster raster = image.getData(activeRect);
1871
1872                // If padding is required, create a larger Raster and fill
1873                // it from the active rectangle.
1874                if(isPadded) {
1875                    WritableRaster wr =
1876                        raster.createCompatibleWritableRaster(minX, minY,
1877                                                              width, height);
1878                    wr.setRect(raster);
1879                    raster = wr;
1880                }
1881
1882                // Get SampleModel info.
1883                ComponentSampleModel csm =
1884                    (ComponentSampleModel)raster.getSampleModel();
1885                int[] bankIndices = csm.getBankIndices();
1886                byte[][] bankData =
1887                    ((DataBufferByte)raster.getDataBuffer()).getBankData();
1888                int lineStride = csm.getScanlineStride();
1889                int pixelStride = csm.getPixelStride();
1890
1891                // Copy the data into a contiguous pixel interleaved buffer.
1892                for(int k = 0; k < numBands; k++) {
1893                    byte[] bandData = bankData[bankIndices[k]];
1894                    int lineOffset =
1895                        csm.getOffset(raster.getMinX() -
1896                                      raster.getSampleModelTranslateX(),
1897                                      raster.getMinY() -
1898                                      raster.getSampleModelTranslateY(), k);
1899                    int idx = k;
1900                    for(int j = 0; j < vpixels; j++) {
1901                        int offset = lineOffset;
1902                        for(int i = 0; i < hpixels; i++) {
1903                            currTile[idx] = bandData[offset];
1904                            idx += numBands;
1905                            offset += pixelStride;
1906                        }
1907                        lineOffset += lineStride;
1908                    }
1909                }
1910
1911                // Compressor and return.
1912                return compressor.encode(currTile, 0,
1913                                         width, height, sampleSize,
1914                                         width*numBands);
1915            }
1916        }
1917
1918        if(DEBUG) {
1919            System.out.println("Unoptimized case for bit depth "+bitDepth);
1920            SampleModel sm = image.getSampleModel();
1921            System.out.println("isRescaling = "+isRescaling);
1922            System.out.println("sourceBands = "+sourceBands);
1923            System.out.println("periodX = "+periodX);
1924            System.out.println("periodY = "+periodY);
1925            System.out.println(sm.getClass().getName());
1926            System.out.println(sm.getDataType());
1927            if(sm instanceof ComponentSampleModel) {
1928                ComponentSampleModel csm = (ComponentSampleModel)sm;
1929                System.out.println(csm.getNumBands());
1930                System.out.println(csm.getPixelStride());
1931                int[] bankIndices = csm.getBankIndices();
1932                for(int b = 0; b < numBands; b++) {
1933                    System.out.print(bankIndices[b]+" ");
1934                }
1935                int[] bandOffsets = csm.getBandOffsets();
1936                for(int b = 0; b < numBands; b++) {
1937                    System.out.print(bandOffsets[b]+" ");
1938                }
1939                System.out.println("");
1940            }
1941        }
1942
1943        int tcount = 0;
1944
1945        // Save active rectangle variables.
1946        int activeMinX = activeRect.x;
1947        int activeMinY = activeRect.y;
1948        int activeMaxY = activeMinY + activeRect.height - 1;
1949        int activeWidth = activeRect.width;
1950
1951        // Set a SampleModel for use in padding.
1952        SampleModel rowSampleModel = null;
1953        if(isPadded) {
1954           rowSampleModel =
1955               image.getSampleModel().createCompatibleSampleModel(width, 1);
1956        }
1957
1958        for (int row = yOffset; row < yOffset + height; row += ySkip) {
1959            Raster ras = null;
1960            if(isPadded) {
1961                // Create a raster for the entire row.
1962                WritableRaster wr =
1963                    Raster.createWritableRaster(rowSampleModel,
1964                                                new Point(minX, row));
1965
1966                // Populate the raster from the active sub-row, if any.
1967                if(row >= activeMinY && row <= activeMaxY) {
1968                    Rectangle rect =
1969                        new Rectangle(activeMinX, row, activeWidth, 1);
1970                    ras = image.getData(rect);
1971                    wr.setRect(ras);
1972                }
1973
1974                // Update the raster variable.
1975                ras = wr;
1976            } else {
1977                Rectangle rect = new Rectangle(minX, row, width, 1);
1978                ras = image.getData(rect);
1979            }
1980            if (sourceBands != null) {
1981                ras = ras.createChild(minX, row, width, 1, minX, row,
1982                                      sourceBands);
1983            }
1984
1985            if(sampleFormat ==
1986               BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
1987                ras.getPixels(minX, row, width, 1, fsamples);
1988            } else {
1989                ras.getPixels(minX, row, width, 1, samples);
1990
1991                if ((nativePhotometricInterpretation ==
1992                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
1993                     photometricInterpretation ==
1994                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
1995                    (nativePhotometricInterpretation ==
1996                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
1997                     photometricInterpretation ==
1998                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) {
1999                    int bitMask = (1 << bitDepth) - 1;
2000                    for (int s = 0; s < numSamples; s++) {
2001                        samples[s] ^= bitMask;
2002                    }
2003                }
2004            }
2005
2006            if (colorConverter != null) {
2007                int idx = 0;
2008                float[] result = new float[3];
2009
2010                if(sampleFormat ==
2011                   BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
2012                    for (int i = 0; i < width; i++) {
2013                        float r = fsamples[idx];
2014                        float g = fsamples[idx + 1];
2015                        float b = fsamples[idx + 2];
2016
2017                        colorConverter.fromRGB(r, g, b, result);
2018
2019                        fsamples[idx] = result[0];
2020                        fsamples[idx + 1] = result[1];
2021                        fsamples[idx + 2] = result[2];
2022
2023                        idx += 3;
2024                    }
2025                } else {
2026                    for (int i = 0; i < width; i++) {
2027                        float r = (float)samples[idx];
2028                        float g = (float)samples[idx + 1];
2029                        float b = (float)samples[idx + 2];
2030
2031                        colorConverter.fromRGB(r, g, b, result);
2032
2033                        samples[idx] = (int)(result[0]);
2034                        samples[idx + 1] = (int)(result[1]);
2035                        samples[idx + 2] = (int)(result[2]);
2036
2037                        idx += 3;
2038                    }
2039                }
2040            }
2041
2042            int tmp = 0;
2043            int pos = 0;
2044            
2045            switch (bitDepth) {
2046            case 1: case 2: case 4:
2047                // Image can only have a single band
2048                
2049                if(isRescaling) {
2050                    for (int s = 0; s < numSamples; s += xSkip) {
2051                        byte val = scale0[samples[s]];
2052                        tmp = (tmp << bitDepth) | val;
2053
2054                        if (++pos == samplesPerByte) {
2055                            currTile[tcount++] = (byte)tmp;
2056                            tmp = 0;
2057                            pos = 0;
2058                        }
2059                    }
2060                } else {
2061                    for (int s = 0; s < numSamples; s += xSkip) {
2062                        byte val = (byte)samples[s];
2063                        tmp = (tmp << bitDepth) | val;
2064
2065                        if (++pos == samplesPerByte) {
2066                            currTile[tcount++] = (byte)tmp;
2067                            tmp = 0;
2068                            pos = 0;
2069                        }
2070                    }
2071                }
2072
2073                // Left shift the last byte
2074                if (pos != 0) {
2075                    tmp <<= ((8/bitDepth) - pos)*bitDepth;
2076                    currTile[tcount++] = (byte)tmp;
2077                }
2078                break;
2079
2080            case 8:
2081                if (numBands == 1) {
2082                    if(isRescaling) {
2083                        for (int s = 0; s < numSamples; s += xSkip) {
2084                            currTile[tcount++] = scale0[samples[s]];
2085                        }
2086                    } else {
2087                        for (int s = 0; s < numSamples; s += xSkip) {
2088                            currTile[tcount++] = (byte)samples[s];
2089                        }
2090                    }
2091                } else {
2092                    if(isRescaling) {
2093                        for (int s = 0; s < numSamples; s += xSkip) {
2094                            for (int b = 0; b < numBands; b++) {
2095                                currTile[tcount++] = scale[b][samples[s + b]];
2096                            }
2097                        }
2098                    } else {
2099                        for (int s = 0; s < numSamples; s += xSkip) {
2100                            for (int b = 0; b < numBands; b++) {
2101                                currTile[tcount++] = (byte)samples[s + b];
2102                            }
2103                        }
2104                    }
2105                }
2106                break;
2107
2108            case 16:
2109                // XXX Need to verify this rescaling for signed vs. unsigned.
2110                if(isRescaling) {
2111                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2112                        for (int s = 0; s < numSamples; s += xSkip) {
2113                            for (int b = 0; b < numBands; b++) {
2114                                int sample = samples[s + b];
2115                                currTile[tcount++] = scaleh[b][sample];
2116                                currTile[tcount++] = scalel[b][sample];
2117                            }
2118                        }
2119                    } else { // ByteOrder.LITLE_ENDIAN
2120                        for (int s = 0; s < numSamples; s += xSkip) {
2121                            for (int b = 0; b < numBands; b++) {
2122                                int sample = samples[s + b];
2123                                currTile[tcount++] = scalel[b][sample];
2124                                currTile[tcount++] = scaleh[b][sample];
2125                            }
2126                        }
2127                    }
2128                } else {
2129                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2130                        for (int s = 0; s < numSamples; s += xSkip) {
2131                            for (int b = 0; b < numBands; b++) {
2132                                int sample = samples[s + b];
2133                                currTile[tcount++] =
2134                                    (byte)((sample >>> 8) & 0xff);
2135                                currTile[tcount++] =
2136                                    (byte)(sample & 0xff);
2137                            }
2138                        }
2139                    } else { // ByteOrder.LITLE_ENDIAN
2140                        for (int s = 0; s < numSamples; s += xSkip) {
2141                            for (int b = 0; b < numBands; b++) {
2142                                int sample = samples[s + b];
2143                                currTile[tcount++] =
2144                                    (byte)(sample & 0xff);
2145                                currTile[tcount++] =
2146                                    (byte)((sample >>> 8) & 0xff);
2147                            }
2148                        }
2149                    }
2150                }
2151                break;
2152
2153            case 32:
2154                if(sampleFormat ==
2155                   BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
2156                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2157                        for (int s = 0; s < numSamples; s += xSkip) {
2158                            for (int b = 0; b < numBands; b++) {
2159                                float fsample = fsamples[s + b];
2160                                int isample = Float.floatToIntBits(fsample);
2161                                currTile[tcount++] =
2162                                    (byte)((isample & 0xff000000) >> 24);
2163                                currTile[tcount++] =
2164                                    (byte)((isample & 0x00ff0000) >> 16);
2165                                currTile[tcount++] =
2166                                    (byte)((isample & 0x0000ff00) >> 8);
2167                                currTile[tcount++] =
2168                                    (byte)(isample & 0x000000ff);
2169                            }
2170                        }
2171                    } else { // ByteOrder.LITLE_ENDIAN
2172                        for (int s = 0; s < numSamples; s += xSkip) {
2173                            for (int b = 0; b < numBands; b++) {
2174                                float fsample = fsamples[s + b];
2175                                int isample = Float.floatToIntBits(fsample);
2176                                currTile[tcount++] =
2177                                    (byte)(isample & 0x000000ff);
2178                                currTile[tcount++] =
2179                                    (byte)((isample & 0x0000ff00) >> 8);
2180                                currTile[tcount++] =
2181                                    (byte)((isample & 0x00ff0000) >> 16);
2182                                currTile[tcount++] =
2183                                    (byte)((isample & 0xff000000) >> 24);
2184                            }
2185                        }
2186                    }
2187                } else {
2188                    if(isRescaling) {
2189                        // XXX Need to verify this for signed vs. unsigned.
2190                        // XXX The following gives saturated results when the
2191                        // original data are in the signed integer range.
2192                        long[] maxIn = new long[numBands];
2193                        long[] halfIn = new long[numBands];
2194                        long maxOut = (1L << (long)bitDepth) - 1L;
2195
2196                        for (int b = 0; b < numBands; b++) {
2197                            maxIn[b] = ((1L << (long)sampleSize[b]) - 1L);
2198                            halfIn[b] = maxIn[b]/2;
2199                        }
2200
2201                        if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2202                            for (int s = 0; s < numSamples; s += xSkip) {
2203                                for (int b = 0; b < numBands; b++) {
2204                                    long sampleOut =
2205                                        (samples[s + b]*maxOut + halfIn[b])/
2206                                        maxIn[b];
2207                                    currTile[tcount++] =
2208                                        (byte)((sampleOut & 0xff000000) >> 24);
2209                                    currTile[tcount++] =
2210                                        (byte)((sampleOut & 0x00ff0000) >> 16);
2211                                    currTile[tcount++] =
2212                                        (byte)((sampleOut & 0x0000ff00) >> 8);
2213                                    currTile[tcount++] =
2214                                        (byte)(sampleOut & 0x000000ff);
2215                                }
2216                            }
2217                        } else { // ByteOrder.LITLE_ENDIAN
2218                            for (int s = 0; s < numSamples; s += xSkip) {
2219                                for (int b = 0; b < numBands; b++) {
2220                                    long sampleOut =
2221                                        (samples[s + b]*maxOut + halfIn[b])/
2222                                        maxIn[b];
2223                                    currTile[tcount++] =
2224                                        (byte)(sampleOut & 0x000000ff);
2225                                    currTile[tcount++] =
2226                                        (byte)((sampleOut & 0x0000ff00) >> 8);
2227                                    currTile[tcount++] =
2228                                        (byte)((sampleOut & 0x00ff0000) >> 16);
2229                                    currTile[tcount++] =
2230                                        (byte)((sampleOut & 0xff000000) >> 24);
2231                                }
2232                            }
2233                        }
2234                    } else {
2235                        if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2236                            for (int s = 0; s < numSamples; s += xSkip) {
2237                                for (int b = 0; b < numBands; b++) {
2238                                    int isample = samples[s + b];
2239                                    currTile[tcount++] =
2240                                        (byte)((isample & 0xff000000) >> 24);
2241                                    currTile[tcount++] =
2242                                        (byte)((isample & 0x00ff0000) >> 16);
2243                                    currTile[tcount++] =
2244                                        (byte)((isample & 0x0000ff00) >> 8);
2245                                    currTile[tcount++] =
2246                                        (byte)(isample & 0x000000ff);
2247                                }
2248                            }
2249                        } else { // ByteOrder.LITLE_ENDIAN
2250                            for (int s = 0; s < numSamples; s += xSkip) {
2251                                for (int b = 0; b < numBands; b++) {
2252                                    int isample = samples[s + b];
2253                                    currTile[tcount++] =
2254                                        (byte)(isample & 0x000000ff);
2255                                    currTile[tcount++] =
2256                                        (byte)((isample & 0x0000ff00) >> 8);
2257                                    currTile[tcount++] =
2258                                        (byte)((isample & 0x00ff0000) >> 16);
2259                                    currTile[tcount++] =
2260                                        (byte)((isample & 0xff000000) >> 24);
2261                                }
2262                            }
2263                        }
2264                    }
2265                }
2266                break;
2267            }
2268        }
2269
2270        int[] bitsPerSample = new int[numBands];
2271        for (int i = 0; i < bitsPerSample.length; i++) {
2272            bitsPerSample[i] = bitDepth;
2273        }
2274
2275        int byteCount = compressor.encode(currTile, 0,
2276                                          hpixels, vpixels,
2277                                          bitsPerSample,
2278                                          bytesPerRow);
2279        return byteCount;
2280    }
2281
2282    // Check two int arrays for value equality, always returns false
2283    // if either array is null
2284    private boolean equals(int[] s0, int[] s1) {
2285        if (s0 == null || s1 == null) {
2286            return false;
2287        }
2288        if (s0.length != s1.length) {
2289            return false;
2290        }
2291        for (int i = 0; i < s0.length; i++) {
2292            if (s0[i] != s1[i]) {
2293                return false;
2294            }
2295        }
2296        return true;
2297    }
2298
2299    // Initialize the scale/scale0 or scaleh/scalel arrays to
2300    // hold the results of scaling an input value to the desired
2301    // output bit depth
2302    // XXX Need to verify this rescaling for signed vs. unsigned.
2303    private void initializeScaleTables(int[] sampleSize) {
2304        // Save the sample size in the instance variable.
2305
2306        // If the existing tables are still valid, just return.
2307        if (bitDepth == scalingBitDepth &&
2308            equals(sampleSize, this.sampleSize)) {
2309            if(DEBUG) {
2310                System.out.println("Returning from initializeScaleTables()");
2311            }
2312            return;
2313        }
2314
2315        // Reset scaling variables.
2316        isRescaling = false;
2317        scalingBitDepth = -1;
2318        scale = scalel = scaleh = null;
2319        scale0 = null;
2320
2321        // Set global sample size to parameter.
2322        this.sampleSize = sampleSize;
2323
2324        // Check whether rescaling is called for.
2325        if(bitDepth <= 16) {
2326            for(int b = 0; b < numBands; b++) {
2327                if(sampleSize[b] != bitDepth) {
2328                    isRescaling = true;
2329                    break;
2330                }
2331            }
2332        }
2333
2334        if(DEBUG) {
2335            System.out.println("isRescaling = "+isRescaling);
2336        }
2337
2338        // If not rescaling then return after saving the sample size.
2339        if(!isRescaling) {
2340            return;
2341        }
2342        
2343        // Compute new tables
2344        this.scalingBitDepth = bitDepth;
2345        int maxOutSample = (1 << bitDepth) - 1;
2346        if (bitDepth <= 8) {
2347            scale = new byte[numBands][];
2348            for (int b = 0; b < numBands; b++) {
2349                int maxInSample = (1 << sampleSize[b]) - 1;
2350                int halfMaxInSample = maxInSample/2;
2351                scale[b] = new byte[maxInSample + 1];
2352                for (int s = 0; s <= maxInSample; s++) {
2353                    scale[b][s] =
2354                        (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
2355                }
2356            }
2357            scale0 = scale[0];
2358            scaleh = scalel = null;
2359        } else if(bitDepth <= 16) {
2360            // Divide scaling table into high and low bytes
2361            scaleh = new byte[numBands][];
2362            scalel = new byte[numBands][];
2363
2364            for (int b = 0; b < numBands; b++) {
2365                int maxInSample = (1 << sampleSize[b]) - 1;
2366                int halfMaxInSample = maxInSample/2;
2367                scaleh[b] = new byte[maxInSample + 1];
2368                scalel[b] = new byte[maxInSample + 1];
2369                for (int s = 0; s <= maxInSample; s++) {
2370                    int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
2371                    scaleh[b][s] = (byte)(val >> 8);
2372                    scalel[b][s] = (byte)(val & 0xff);
2373                }
2374            }
2375            scale = null;
2376            scale0 = null;
2377        }
2378    }
2379
2380    public void write(IIOMetadata sm,
2381                      IIOImage iioimage,
2382                      ImageWriteParam p) throws IOException {
2383        write(sm, iioimage, p, true, true);
2384    }
2385    
2386    private void writeHeader() throws IOException {
2387        if (streamMetadata != null) {
2388            this.byteOrder = streamMetadata.byteOrder;
2389        } else {
2390            this.byteOrder = ByteOrder.BIG_ENDIAN;
2391        }
2392
2393        stream.setByteOrder(byteOrder);
2394        if (byteOrder == ByteOrder.BIG_ENDIAN) {
2395            stream.writeShort(0x4d4d);
2396        } else {
2397            stream.writeShort(0x4949);
2398        }
2399        
2400        stream.writeShort(42); // Magic number
2401        stream.writeInt(0); // Offset of first IFD (0 == none)
2402
2403        nextSpace = stream.getStreamPosition();
2404        headerPosition = nextSpace - 8;
2405    }
2406
2407    private void write(IIOMetadata sm,
2408                       IIOImage iioimage,
2409                       ImageWriteParam p,
2410                       boolean writeHeader,
2411                       boolean writeData) throws IOException {
2412        if (stream == null) {
2413            throw new IllegalStateException("output == null!");
2414        }
2415        if (iioimage == null) {
2416            throw new IllegalArgumentException("image == null!");
2417        }
2418        if(iioimage.hasRaster() && !canWriteRasters()) {
2419            throw new UnsupportedOperationException
2420                ("TIFF ImageWriter cannot write Rasters!");
2421        }
2422
2423        this.image = iioimage.getRenderedImage();
2424        SampleModel sampleModel = image.getSampleModel();
2425
2426        this.sourceXOffset = image.getMinX();
2427        this.sourceYOffset = image.getMinY();
2428        this.sourceWidth = image.getWidth();
2429        this.sourceHeight = image.getHeight();
2430
2431        Rectangle imageBounds = new Rectangle(sourceXOffset,
2432                                              sourceYOffset,
2433                                              sourceWidth,
2434                                              sourceHeight);
2435
2436        ColorModel colorModel = null;
2437        if (p == null) {
2438            this.param = getDefaultWriteParam();
2439            this.sourceBands = null;
2440            this.periodX = 1;
2441            this.periodY = 1;
2442            this.numBands = sampleModel.getNumBands();
2443            colorModel = image.getColorModel();
2444        } else {
2445            this.param = p;
2446
2447            // Get source region and subsampling factors
2448            Rectangle sourceRegion = param.getSourceRegion();
2449            if (sourceRegion != null) {
2450                // Clip to actual image bounds
2451                sourceRegion = sourceRegion.intersection(imageBounds);
2452
2453                sourceXOffset = sourceRegion.x;
2454                sourceYOffset = sourceRegion.y;
2455                sourceWidth = sourceRegion.width;
2456                sourceHeight = sourceRegion.height;
2457            }
2458
2459            // Adjust for subsampling offsets
2460            int gridX = param.getSubsamplingXOffset();
2461            int gridY = param.getSubsamplingYOffset();
2462            this.sourceXOffset += gridX;
2463            this.sourceYOffset += gridY;
2464            this.sourceWidth -= gridX;
2465            this.sourceHeight -= gridY;
2466            
2467            // Get subsampling factors
2468            this.periodX = param.getSourceXSubsampling();
2469            this.periodY = param.getSourceYSubsampling();
2470            
2471            int[] sBands = param.getSourceBands();
2472            if (sBands != null) {
2473                sourceBands = sBands;
2474                this.numBands = sourceBands.length;
2475            } else {
2476                this.numBands = sampleModel.getNumBands();
2477            }
2478
2479            ImageTypeSpecifier destType = p.getDestinationType();
2480            if(destType != null) {
2481                ColorModel cm = destType.getColorModel();
2482                if(cm.getNumComponents() == numBands) {
2483                    colorModel = cm;
2484                }
2485            }
2486
2487            if(colorModel == null) {
2488                colorModel = image.getColorModel();
2489            }
2490        }
2491            
2492        this.imageType = new ImageTypeSpecifier(colorModel, sampleModel);
2493
2494        ImageUtil.canEncodeImage(this, this.imageType);
2495
2496        // Compute output dimensions
2497        int destWidth = (sourceWidth + periodX - 1)/periodX;
2498        int destHeight = (sourceHeight + periodY - 1)/periodY;
2499        if (destWidth <= 0 || destHeight <= 0) {
2500            throw new IllegalArgumentException("Empty source region!");
2501        }
2502
2503        // this.bitDepth = 8; // XXX fix?
2504
2505        clearAbortRequest();
2506        processImageStarted(0);
2507
2508        // Optionally write the header.
2509        if (writeHeader) {
2510            // Clear previous stream metadata.
2511            this.streamMetadata = null;
2512
2513            // Try to convert non-null input stream metadata.
2514            if (sm != null) {
2515                this.streamMetadata =
2516                    (TIFFStreamMetadata)convertStreamMetadata(sm, param);
2517            }
2518
2519            // Set to default if not converted.
2520            if(this.streamMetadata == null) {
2521                this.streamMetadata =
2522                    (TIFFStreamMetadata)getDefaultStreamMetadata(param);
2523            }
2524
2525            // Write the header.
2526            writeHeader();
2527
2528            // Seek to the position of the IFD pointer in the header.
2529            stream.seek(headerPosition + 4);
2530
2531            // Ensure IFD is written on a word boundary
2532            nextSpace = (nextSpace + 3) & ~0x3;
2533
2534            // Write the pointer to the first IFD after the header.
2535            stream.writeInt((int)nextSpace);
2536        }
2537
2538        // Write out the IFD and any sub IFDs, followed by a zero
2539
2540        // Clear previous image metadata.
2541        this.imageMetadata = null;
2542
2543        // Initialize the metadata object.
2544        IIOMetadata im = iioimage.getMetadata();
2545        if(im != null) {
2546            if (im instanceof TIFFImageMetadata) {
2547                // Clone the one passed in.
2548                this.imageMetadata = ((TIFFImageMetadata)im).getShallowClone();
2549            } else if(Arrays.asList(im.getMetadataFormatNames()).contains(
2550                   TIFFImageMetadata.nativeMetadataFormatName)) {
2551                this.imageMetadata = convertNativeImageMetadata(im);
2552            } else if(im.isStandardMetadataFormatSupported()) {
2553                try {
2554                    // Convert standard metadata.
2555                    this.imageMetadata = convertStandardImageMetadata(im);
2556                } catch(IIOInvalidTreeException e) {
2557                    // XXX Warning
2558                }
2559            }
2560        }
2561
2562        // Use default metadata if still null.
2563        if(this.imageMetadata == null) {
2564            this.imageMetadata =
2565                (TIFFImageMetadata)getDefaultImageMetadata(this.imageType,
2566                                                           this.param);
2567        }
2568
2569        // Set or overwrite mandatory fields in the root IFD
2570        setupMetadata(colorModel, sampleModel, destWidth, destHeight);
2571
2572        // Set compressor fields.
2573        compressor.setWriter(this);
2574        // Metadata needs to be set on the compressor before the IFD is
2575        // written as the compressor could modify the metadata.
2576        compressor.setMetadata(imageMetadata);
2577        compressor.setStream(stream);
2578        
2579        // Initialize scaling tables for this image
2580        int[] sampleSize = sampleModel.getSampleSize();
2581        initializeScaleTables(sampleModel.getSampleSize());
2582
2583        // Determine whether bilevel.
2584        this.isBilevel = ImageUtil.isBinary(this.image.getSampleModel());
2585
2586        // Check for photometric inversion.
2587        this.isInverted =
2588            (nativePhotometricInterpretation ==
2589             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
2590             photometricInterpretation ==
2591             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
2592            (nativePhotometricInterpretation ==
2593             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
2594             photometricInterpretation ==
2595             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
2596
2597        // Analyze image data suitability for direct copy.
2598        this.isImageSimple = 
2599            (isBilevel ||
2600             (!isInverted && ImageUtil.imageIsContiguous(this.image))) &&
2601            !isRescaling &&                 // no value rescaling
2602            sourceBands == null &&          // no subbanding
2603            periodX == 1 && periodY == 1 && // no subsampling
2604            colorConverter == null;
2605
2606        TIFFIFD rootIFD = imageMetadata.getRootIFD();
2607
2608        rootIFD.writeToStream(stream);
2609
2610        this.nextIFDPointerPos = stream.getStreamPosition();
2611        stream.writeInt(0);
2612
2613        // Seek to end of IFD data
2614        long lastIFDPosition = rootIFD.getLastPosition();
2615        stream.seek(lastIFDPosition);
2616        if(lastIFDPosition > this.nextSpace) {
2617            this.nextSpace = lastIFDPosition;
2618        }
2619
2620        // If not writing the image data, i.e., if writing or inserting an
2621        // empty image, return.
2622        if(!writeData) {
2623            return;
2624        }
2625
2626        // Get positions of fields within the IFD to update as we write
2627        // each strip or tile
2628        long stripOrTileByteCountsPosition =
2629            rootIFD.getStripOrTileByteCountsPosition();
2630        long stripOrTileOffsetsPosition =
2631            rootIFD.getStripOrTileOffsetsPosition();
2632
2633        // Compute total number of pixels for progress notification
2634        this.totalPixels = tileWidth*tileLength*tilesDown*tilesAcross;
2635        this.pixelsDone = 0;
2636
2637        // Write the image, a strip or tile at a time
2638        for (int tj = 0; tj < tilesDown; tj++) {
2639            for (int ti = 0; ti < tilesAcross; ti++) {
2640                long pos = stream.getStreamPosition();
2641
2642                // Write the (possibly compressed) tile data
2643
2644                Rectangle tileRect =
2645                    new Rectangle(sourceXOffset + ti*tileWidth*periodX,
2646                                  sourceYOffset + tj*tileLength*periodY,
2647                                  tileWidth*periodX,
2648                                  tileLength*periodY);
2649                // tileRect = tileRect.intersection(imageBounds); // XXX
2650
2651                try {
2652                    int byteCount = writeTile(tileRect, compressor);
2653
2654                    if(pos + byteCount > nextSpace) {
2655                        nextSpace = pos + byteCount;
2656                    }
2657
2658                    pixelsDone += tileRect.width*tileRect.height;
2659                    processImageProgress(100.0F*pixelsDone/totalPixels);
2660        
2661                    // Fill in the offset and byte count for the file
2662                    stream.mark();
2663                    stream.seek(stripOrTileOffsetsPosition);
2664                    stream.writeInt((int)pos);
2665                    stripOrTileOffsetsPosition += 4;
2666                    
2667                    stream.seek(stripOrTileByteCountsPosition);
2668                    stream.writeInt(byteCount);
2669                    stripOrTileByteCountsPosition += 4;
2670                    stream.reset();
2671                } catch (IOException e) {
2672                    throw new IIOException("I/O error writing TIFF file!", e);
2673                }
2674
2675                if (abortRequested()) {
2676                    processWriteAborted();
2677                    return;
2678                }
2679            }
2680        }
2681        
2682        processImageComplete();
2683    }
2684
2685    public boolean canWriteSequence() {
2686        return true;
2687    }
2688
2689    public void prepareWriteSequence(IIOMetadata streamMetadata)
2690        throws IOException {
2691        if (getOutput() == null) {
2692            throw new IllegalStateException("getOutput() == null!");
2693        }
2694
2695        // Set up stream metadata.
2696        if (streamMetadata != null) {
2697            streamMetadata = convertStreamMetadata(streamMetadata, null);
2698        }
2699        if(streamMetadata == null) {
2700            streamMetadata = getDefaultStreamMetadata(null);
2701        }
2702        this.streamMetadata = (TIFFStreamMetadata)streamMetadata;
2703
2704        // Write the header.
2705        writeHeader();
2706
2707        // Set the sequence flag.
2708        this.isWritingSequence = true;
2709    }
2710
2711    public void writeToSequence(IIOImage image, ImageWriteParam param)
2712        throws IOException {
2713        // Check sequence flag.
2714        if(!this.isWritingSequence) {
2715            throw new IllegalStateException
2716                ("prepareWriteSequence() has not been called!");
2717        }
2718
2719        // Append image.
2720        writeInsert(-1, image, param);
2721    }
2722
2723    public void endWriteSequence() throws IOException {
2724        // Check output.
2725        if (getOutput() == null) {
2726            throw new IllegalStateException("getOutput() == null!");
2727        }
2728
2729        // Check sequence flag.
2730        if(!isWritingSequence) {
2731            throw new IllegalStateException
2732                ("prepareWriteSequence() has not been called!");
2733        }
2734
2735        // Unset sequence flag.
2736        this.isWritingSequence = false;
2737    }
2738
2739    public boolean canInsertImage(int imageIndex) throws IOException {
2740        if (getOutput() == null) {
2741            throw new IllegalStateException("getOutput() == null!");
2742        }
2743
2744        // Mark position as locateIFD() will seek to IFD at imageIndex.
2745        stream.mark();
2746
2747        // locateIFD() will throw an IndexOutOfBoundsException if
2748        // imageIndex is < -1 or is too big thereby satisfying the spec.
2749        long[] ifdpos = new long[1];
2750        long[] ifd = new long[1];
2751        locateIFD(imageIndex, ifdpos, ifd);
2752
2753        // Reset to position before locateIFD().
2754        stream.reset();
2755
2756        return true;
2757    }
2758
2759    // Locate start of IFD for image.
2760    // Throws IIOException if not at a TIFF header and
2761    // IndexOutOfBoundsException if imageIndex is < -1 or is too big.
2762    private void locateIFD(int imageIndex, long[] ifdpos, long[] ifd) 
2763        throws IOException {
2764
2765        if(imageIndex < -1) {
2766            throw new IndexOutOfBoundsException("imageIndex < -1!");
2767        }
2768
2769        long startPos = stream.getStreamPosition();
2770
2771        stream.seek(headerPosition);
2772        int byteOrder = stream.readUnsignedShort();
2773        if (byteOrder == 0x4d4d) {
2774            stream.setByteOrder(ByteOrder.BIG_ENDIAN);
2775        } else if (byteOrder == 0x4949) {
2776            stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
2777        } else {
2778            stream.seek(startPos);
2779            throw new IIOException("Illegal byte order");
2780        }
2781        if (stream.readUnsignedShort() != 42) {
2782            stream.seek(startPos);
2783            throw new IIOException("Illegal magic number");
2784        }
2785
2786        ifdpos[0] = stream.getStreamPosition();
2787        ifd[0] = stream.readUnsignedInt();
2788        if (ifd[0] == 0) {
2789            // imageIndex has to be >= -1 due to check above.
2790            if(imageIndex > 0) {
2791                stream.seek(startPos);
2792                throw new IndexOutOfBoundsException
2793                    ("imageIndex is greater than the largest available index!");
2794            }
2795            return;
2796        }
2797        stream.seek(ifd[0]);
2798
2799        for (int i = 0; imageIndex == -1 || i < imageIndex; i++) {
2800            int numFields;
2801            try {
2802                numFields = stream.readShort();
2803            } catch (EOFException eof) {
2804                stream.seek(startPos);
2805                ifd[0] = 0;
2806                return;
2807            }
2808
2809            stream.skipBytes(12*numFields);
2810
2811            ifdpos[0] = stream.getStreamPosition();
2812            ifd[0] = stream.readUnsignedInt();
2813            if (ifd[0] == 0) {
2814                if (imageIndex != -1 && i < imageIndex - 1) {
2815                    stream.seek(startPos);
2816                    throw new IndexOutOfBoundsException(
2817                    "imageIndex is greater than the largest available index!");
2818                }
2819                break;
2820            }
2821            stream.seek(ifd[0]);
2822        }
2823    }
2824
2825    public void writeInsert(int imageIndex,
2826                            IIOImage image,
2827                            ImageWriteParam param) throws IOException {
2828        insert(imageIndex, image, param, true);
2829    }
2830
2831    private void insert(int imageIndex,
2832                        IIOImage image,
2833                        ImageWriteParam param,
2834                        boolean writeData) throws IOException {
2835        if (stream == null) {
2836            throw new IllegalStateException("Output not set!");
2837        }
2838        if (image == null) {
2839            throw new IllegalArgumentException("image == null!");
2840        }
2841
2842        // Locate the position of the old IFD (ifd) and the location
2843        // of the pointer to that position (ifdpos).
2844        long[] ifdpos = new long[1];
2845        long[] ifd = new long[1];
2846
2847        // locateIFD() will throw an IndexOutOfBoundsException if
2848        // imageIndex is < -1 or is too big thereby satisfying the spec.
2849        locateIFD(imageIndex, ifdpos, ifd);
2850
2851        // Seek to the position containing the pointer to the old IFD.
2852        stream.seek(ifdpos[0]);
2853
2854        // Update next space pointer in anticipation of next write.
2855        if(ifdpos[0] + 4 > nextSpace) {
2856            nextSpace = ifdpos[0] + 4;
2857        }
2858
2859        // Ensure IFD is written on a word boundary
2860        nextSpace = (nextSpace + 3) & ~0x3;
2861
2862        // Update the value to point to the next available space.
2863        stream.writeInt((int)nextSpace);
2864
2865        // Seek to the next available space.
2866        stream.seek(nextSpace);
2867
2868        // Write the image (IFD and data).
2869        write(null, image, param, false, writeData);
2870
2871        // Seek to the position containing the pointer in the new IFD.
2872        stream.seek(nextIFDPointerPos);
2873
2874        // Update the new IFD to point to the old IFD.
2875        stream.writeInt((int)ifd[0]);
2876        // Don't need to update nextSpace here as already done in write().
2877    }
2878
2879    // ----- BEGIN insert/writeEmpty methods -----
2880
2881    // XXX Move local variable(s) up.
2882    private boolean isInsertingEmpty = false;
2883    private boolean isWritingEmpty = false;
2884
2885    private boolean isEncodingEmpty() {
2886        return isInsertingEmpty || isWritingEmpty;
2887    }
2888
2889    public boolean canInsertEmpty(int imageIndex) throws IOException {
2890        return canInsertImage(imageIndex);
2891    }
2892
2893    public boolean canWriteEmpty() throws IOException {
2894        if (getOutput() == null) {
2895            throw new IllegalStateException("getOutput() == null!");
2896        }
2897        return true;
2898    }
2899
2900    // Check state and parameters for writing or inserting empty images.
2901    private void checkParamsEmpty(ImageTypeSpecifier imageType,
2902                                  int width,
2903                                  int height,
2904                                  List thumbnails) {
2905        if (getOutput() == null) {
2906            throw new IllegalStateException("getOutput() == null!");
2907        }
2908
2909        if(imageType == null) {
2910            throw new IllegalArgumentException("imageType == null!");
2911        }
2912
2913        if(width < 1 || height < 1) {
2914            throw new IllegalArgumentException("width < 1 || height < 1!");
2915        }
2916
2917        if(thumbnails != null) {
2918            int numThumbs = thumbnails.size();
2919            for(int i = 0; i < numThumbs; i++) {
2920                Object thumb = thumbnails.get(i);
2921                if(thumb == null || !(thumb instanceof BufferedImage)) {
2922                    throw new IllegalArgumentException
2923                        ("thumbnails contains null references or objects other than BufferedImages!");
2924                }
2925            }
2926        }
2927
2928        if(this.isInsertingEmpty) {
2929            throw new IllegalStateException
2930                ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2931        }
2932
2933        if(this.isWritingEmpty) {
2934            throw new IllegalStateException
2935                ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2936        }
2937    }
2938
2939    public void prepareInsertEmpty(int imageIndex,
2940                                   ImageTypeSpecifier imageType,
2941                                   int width,
2942                                   int height,
2943                                   IIOMetadata imageMetadata,
2944                                   List thumbnails,
2945                                   ImageWriteParam param) throws IOException {
2946        checkParamsEmpty(imageType, width, height, thumbnails);
2947
2948        this.isInsertingEmpty = true;
2949
2950        SampleModel emptySM = imageType.getSampleModel();
2951        RenderedImage emptyImage =
2952            new EmptyImage(0, 0, width, height,
2953                           0, 0, emptySM.getWidth(), emptySM.getHeight(),
2954                           emptySM, imageType.getColorModel());
2955
2956        insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata),
2957               param, false);
2958    }
2959
2960    public void prepareWriteEmpty(IIOMetadata streamMetadata,
2961                                  ImageTypeSpecifier imageType,
2962                                  int width,
2963                                  int height,
2964                                  IIOMetadata imageMetadata,
2965                                  List thumbnails,
2966                                  ImageWriteParam param) throws IOException {
2967        checkParamsEmpty(imageType, width, height, thumbnails);
2968
2969        this.isWritingEmpty = true;
2970
2971        SampleModel emptySM = imageType.getSampleModel();
2972        RenderedImage emptyImage =
2973            new EmptyImage(0, 0, width, height,
2974                           0, 0, emptySM.getWidth(), emptySM.getHeight(),
2975                           emptySM, imageType.getColorModel());
2976
2977        write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata),
2978              param, true, false);
2979    }
2980
2981    public void endInsertEmpty() throws IOException {
2982        if (getOutput() == null) {
2983            throw new IllegalStateException("getOutput() == null!");
2984        }
2985
2986        if(!this.isInsertingEmpty) {
2987            throw new IllegalStateException
2988                ("No previous call to prepareInsertEmpty()!");
2989        }
2990
2991        if(this.isWritingEmpty) {
2992            throw new IllegalStateException
2993                ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2994        }
2995
2996        if (inReplacePixelsNest) {
2997            throw new IllegalStateException
2998                ("In nested call to prepareReplacePixels!");
2999        }
3000
3001        this.isInsertingEmpty = false;
3002    }
3003
3004    public void endWriteEmpty() throws IOException {
3005        if (getOutput() == null) {
3006            throw new IllegalStateException("getOutput() == null!");
3007        }
3008
3009        if(!this.isWritingEmpty) {
3010            throw new IllegalStateException
3011                ("No previous call to prepareWriteEmpty()!");
3012        }
3013
3014        if(this.isInsertingEmpty) {
3015            throw new IllegalStateException
3016                ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
3017        }
3018
3019        if (inReplacePixelsNest) {
3020            throw new IllegalStateException
3021                ("In nested call to prepareReplacePixels!");
3022        }
3023
3024        this.isWritingEmpty = false;
3025    }
3026
3027    // ----- END insert/writeEmpty methods -----
3028
3029    // ----- BEGIN replacePixels methods -----
3030
3031    private TIFFIFD readIFD(int imageIndex) throws IOException {
3032        if (stream == null) {
3033            throw new IllegalStateException("Output not set!");
3034        }
3035        if (imageIndex < 0) {
3036            throw new IndexOutOfBoundsException("imageIndex < 0!");
3037        }
3038
3039        stream.mark();
3040        long[] ifdpos = new long[1];
3041        long[] ifd = new long[1];
3042        locateIFD(imageIndex, ifdpos, ifd);
3043        if (ifd[0] == 0) {
3044            stream.reset();
3045            throw new IndexOutOfBoundsException
3046                ("imageIndex out of bounds!");
3047        }
3048
3049        List tagSets = new ArrayList(1);
3050        tagSets.add(BaselineTIFFTagSet.getInstance());
3051        TIFFIFD rootIFD = new TIFFIFD(tagSets);
3052        // XXX Ignore unknown fields in metadata presumably because
3053        // any fields needed to write pixels would be known?
3054        rootIFD.initialize(stream, true);
3055        stream.reset();
3056
3057        return rootIFD;
3058    }
3059
3060    public boolean canReplacePixels(int imageIndex) throws IOException {
3061        if (getOutput() == null) {
3062            throw new IllegalStateException("getOutput() == null!");
3063        }
3064
3065        TIFFIFD rootIFD = readIFD(imageIndex);
3066        TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
3067        int compression = f.getAsInt(0);
3068
3069        return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
3070    }
3071
3072    private Object replacePixelsLock = new Object();
3073
3074    private int replacePixelsIndex = -1;
3075    private TIFFImageMetadata replacePixelsMetadata = null;
3076    private long[] replacePixelsTileOffsets = null;
3077    private long[] replacePixelsByteCounts = null;
3078    private long replacePixelsOffsetsPosition = 0L;
3079    private long replacePixelsByteCountsPosition = 0L;
3080    private Rectangle replacePixelsRegion = null;
3081    private boolean inReplacePixelsNest = false;
3082
3083    private TIFFImageReader reader = null;
3084
3085    public void prepareReplacePixels(int imageIndex,
3086                                     Rectangle region) throws IOException {
3087        synchronized(replacePixelsLock) {
3088            // Check state and parameters vis-a-vis ImageWriter specification.
3089            if (stream == null) {
3090                throw new IllegalStateException("Output not set!");
3091            }
3092            if (region == null) {
3093                throw new IllegalArgumentException("region == null!");
3094            }
3095            if (region.getWidth() < 1) {
3096                throw new IllegalArgumentException("region.getWidth() < 1!");
3097            }
3098            if (region.getHeight() < 1) {
3099                throw new IllegalArgumentException("region.getHeight() < 1!");
3100            }
3101            if (inReplacePixelsNest) {
3102                throw new IllegalStateException
3103                    ("In nested call to prepareReplacePixels!");
3104            }
3105
3106            // Read the IFD for the pixel replacement index.
3107            TIFFIFD replacePixelsIFD = readIFD(imageIndex);
3108
3109            // Ensure that compression is "none".
3110            TIFFField f =
3111                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
3112            int compression = f.getAsInt(0);
3113            if (compression != BaselineTIFFTagSet.COMPRESSION_NONE) {
3114                throw new UnsupportedOperationException
3115                    ("canReplacePixels(imageIndex) == false!");
3116            }
3117
3118            // Get the image dimensions.
3119            f =
3120                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
3121            if(f == null) {
3122                throw new IIOException("Cannot read ImageWidth field.");
3123            }
3124            int w = f.getAsInt(0);
3125
3126            f =
3127                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
3128            if(f == null) {
3129                throw new IIOException("Cannot read ImageHeight field.");
3130            }
3131            int h = f.getAsInt(0);
3132
3133            // Create image bounds.
3134            Rectangle bounds = new Rectangle(0, 0, w, h);
3135
3136            // Intersect region with bounds.
3137            region = region.intersection(bounds);
3138
3139            // Check for empty intersection.
3140            if(region.isEmpty()) {
3141                throw new IIOException("Region does not intersect image bounds");
3142            }
3143
3144            // Save the region.
3145            replacePixelsRegion = region;
3146
3147            // Get the tile offsets.
3148            f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
3149            if(f == null) {
3150                f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
3151            }
3152            replacePixelsTileOffsets = f.getAsLongs();
3153
3154            // Get the byte counts.
3155            f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
3156            if(f == null) {
3157                f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
3158            }
3159            replacePixelsByteCounts = f.getAsLongs();
3160
3161            replacePixelsOffsetsPosition =
3162                replacePixelsIFD.getStripOrTileOffsetsPosition();
3163            replacePixelsByteCountsPosition =
3164                replacePixelsIFD.getStripOrTileByteCountsPosition();
3165
3166            // Get the image metadata.
3167            replacePixelsMetadata = new TIFFImageMetadata(replacePixelsIFD);
3168
3169            // Save the image index.
3170            replacePixelsIndex = imageIndex;
3171
3172            // Set the pixel replacement flag.
3173            inReplacePixelsNest = true;
3174        }
3175    }
3176
3177    private Raster subsample(Raster raster, int[] sourceBands,
3178                             int subOriginX, int subOriginY,
3179                             int subPeriodX, int subPeriodY,
3180                             int dstOffsetX, int dstOffsetY,
3181                             Rectangle target) {
3182
3183        int x = raster.getMinX();
3184        int y = raster.getMinY();
3185        int w = raster.getWidth();
3186        int h = raster.getHeight();
3187        int b = raster.getSampleModel().getNumBands();
3188        int t = raster.getSampleModel().getDataType();
3189
3190        int outMinX = XToTileX(x, subOriginX, subPeriodX) + dstOffsetX;
3191        int outMinY = YToTileY(y, subOriginY, subPeriodY) + dstOffsetY;
3192        int outMaxX = XToTileX(x + w - 1, subOriginX, subPeriodX) + dstOffsetX;
3193        int outMaxY = YToTileY(y + h - 1, subOriginY, subPeriodY) + dstOffsetY;
3194        int outWidth = outMaxX - outMinX + 1;
3195        int outHeight = outMaxY - outMinY + 1;
3196
3197        if(outWidth <= 0 || outHeight <= 0) return null;
3198
3199        int inMinX = (outMinX - dstOffsetX)*subPeriodX + subOriginX;
3200        int inMaxX = (outMaxX - dstOffsetX)*subPeriodX + subOriginX;
3201        int inWidth = inMaxX - inMinX + 1;
3202        int inMinY = (outMinY - dstOffsetY)*subPeriodY + subOriginY;
3203        int inMaxY = (outMaxY - dstOffsetY)*subPeriodY + subOriginY;
3204        int inHeight = inMaxY - inMinY + 1;
3205
3206        WritableRaster wr =
3207            raster.createCompatibleWritableRaster(outMinX, outMinY,
3208                                                  outWidth, outHeight);
3209
3210        int jMax = inMinY + inHeight;
3211
3212        if(t == DataBuffer.TYPE_FLOAT || t == DataBuffer.TYPE_DOUBLE) {
3213            float[] fsamples = new float[inWidth];
3214            float[] fsubsamples = new float[outWidth];
3215
3216            for(int k = 0; k < b; k++) {
3217                int outY = outMinY;
3218                for(int j = inMinY; j < jMax; j += subPeriodY) {
3219                    raster.getSamples(inMinX, j, inWidth, 1, k, fsamples);
3220                    int s = 0;
3221                    for(int i = 0; i < inWidth; i += subPeriodX) {
3222                        fsubsamples[s++] = fsamples[i];
3223                    }
3224                    wr.setSamples(outMinX, outY++, outWidth, 1, k,
3225                                  fsubsamples);
3226                }
3227            }
3228        } else {
3229            int[] samples = new int[inWidth];
3230            int[] subsamples = new int[outWidth];
3231
3232            for(int k = 0; k < b; k++) {
3233                int outY = outMinY;
3234                for(int j = inMinY; j < jMax; j += subPeriodY) {
3235                    raster.getSamples(inMinX, j, inWidth, 1, k, samples);
3236                    int s = 0;
3237                    for(int i = 0; i < inWidth; i += subPeriodX) {
3238                        subsamples[s++] = samples[i];
3239                    }
3240                    wr.setSamples(outMinX, outY++, outWidth, 1, k,
3241                                  subsamples);
3242                }
3243            }
3244        }
3245
3246        return wr.createChild(outMinX, outMinY,
3247                              target.width, target.height,
3248                              target.x, target.y,
3249                              sourceBands);
3250    }
3251
3252    public void replacePixels(RenderedImage image, ImageWriteParam param) 
3253        throws IOException {
3254
3255        synchronized(replacePixelsLock) {
3256            // Check state and parameters vis-a-vis ImageWriter specification.
3257            if (stream == null) {
3258                throw new IllegalStateException("stream == null!");
3259            }
3260
3261            if (image == null) {
3262                throw new IllegalArgumentException("image == null!");
3263            }
3264
3265            if (!inReplacePixelsNest) {
3266                throw new IllegalStateException
3267                    ("No previous call to prepareReplacePixels!");
3268            }
3269
3270            // Subsampling values.
3271            int stepX = 1, stepY = 1, gridX = 0, gridY = 0;
3272
3273            // Initialize the ImageWriteParam.
3274            if (param == null) {
3275                // Use the default.
3276                param = getDefaultWriteParam();
3277            } else {
3278                // Make a copy of the ImageWriteParam.
3279                ImageWriteParam paramCopy = getDefaultWriteParam();
3280
3281                // Force uncompressed.
3282                paramCopy.setCompressionMode(ImageWriteParam.MODE_DISABLED);
3283
3284                // Force tiling to remain as in the already written image.
3285                paramCopy.setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
3286
3287                // Retain source and destination region and band settings.
3288                paramCopy.setDestinationOffset(param.getDestinationOffset());
3289                paramCopy.setSourceBands(param.getSourceBands());
3290                paramCopy.setSourceRegion(param.getSourceRegion());
3291
3292                // Save original subsampling values for subsampling the
3293                // replacement data - not the data re-read from the image.
3294                stepX = param.getSourceXSubsampling();
3295                stepY = param.getSourceYSubsampling();
3296                gridX = param.getSubsamplingXOffset();
3297                gridY = param.getSubsamplingYOffset();
3298
3299                // Replace the param.
3300                param = paramCopy;
3301            }
3302
3303            // Check band count and bit depth compatibility.
3304            TIFFField f =
3305                replacePixelsMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
3306            if(f == null) {
3307                throw new IIOException
3308                    ("Cannot read destination BitsPerSample");
3309            }
3310            int[] dstBitsPerSample = f.getAsInts();
3311            int[] srcBitsPerSample = image.getSampleModel().getSampleSize();
3312            int[] sourceBands = param.getSourceBands();
3313            if(sourceBands != null) {
3314                if(sourceBands.length != dstBitsPerSample.length) {
3315                    throw new IIOException
3316                        ("Source and destination have different SamplesPerPixel");
3317                }
3318                for(int i = 0; i < sourceBands.length; i++) {
3319                    if(dstBitsPerSample[i] !=
3320                       srcBitsPerSample[sourceBands[i]]) {
3321                        throw new IIOException
3322                            ("Source and destination have different BitsPerSample");
3323                    }
3324                }
3325            } else {
3326                int srcNumBands = image.getSampleModel().getNumBands();
3327                if(srcNumBands != dstBitsPerSample.length) {
3328                    throw new IIOException
3329                        ("Source and destination have different SamplesPerPixel");
3330                }
3331                for(int i = 0; i < srcNumBands; i++) {
3332                    if(dstBitsPerSample[i] != srcBitsPerSample[i]) {
3333                        throw new IIOException
3334                            ("Source and destination have different BitsPerSample");
3335                    }
3336                }
3337            }
3338
3339            // Get the source image bounds.
3340            Rectangle srcImageBounds =
3341                new Rectangle(image.getMinX(), image.getMinY(),
3342                              image.getWidth(), image.getHeight());
3343
3344            // Initialize the source rect.
3345            Rectangle srcRect = param.getSourceRegion();
3346            if(srcRect == null) {
3347                srcRect = srcImageBounds;
3348            }
3349
3350            // Set subsampling grid parameters.
3351            int subPeriodX = stepX;
3352            int subPeriodY = stepY;
3353            int subOriginX = gridX + srcRect.x;
3354            int subOriginY = gridY + srcRect.y;
3355
3356            // Intersect with the source bounds.
3357            if(!srcRect.equals(srcImageBounds)) {
3358                srcRect = srcRect.intersection(srcImageBounds);
3359                if(srcRect.isEmpty()) {
3360                    throw new IllegalArgumentException
3361                        ("Source region does not intersect source image!");
3362                }
3363            }
3364
3365            // Get the destination offset.
3366            Point dstOffset = param.getDestinationOffset();
3367
3368            // Forward map source rectangle to determine destination width.
3369            int dMinX = XToTileX(srcRect.x, subOriginX, subPeriodX) +
3370                dstOffset.x;
3371            int dMinY = YToTileY(srcRect.y, subOriginY, subPeriodY) +
3372                dstOffset.y;
3373            int dMaxX = XToTileX(srcRect.x + srcRect.width,
3374                                 subOriginX, subPeriodX) + dstOffset.x;
3375            int dMaxY = YToTileY(srcRect.y + srcRect.height,
3376                                 subOriginY, subPeriodY) + dstOffset.y;
3377
3378            // Initialize the destination rectangle.
3379            Rectangle dstRect =
3380                new Rectangle(dstOffset.x, dstOffset.y,
3381                              dMaxX - dMinX, dMaxY - dMinY);
3382
3383            // Intersect with the replacement region.
3384            dstRect = dstRect.intersection(replacePixelsRegion);
3385            if(dstRect.isEmpty()) {
3386                throw new IllegalArgumentException
3387                    ("Forward mapped source region does not intersect destination region!");
3388            }
3389
3390            // Backward map to the active source region.
3391            int activeSrcMinX = (dstRect.x - dstOffset.x)*subPeriodX +
3392                subOriginX;
3393            int sxmax = 
3394                (dstRect.x + dstRect.width - 1 - dstOffset.x)*subPeriodX +
3395                subOriginX;
3396            int activeSrcWidth = sxmax - activeSrcMinX + 1;
3397        
3398            int activeSrcMinY = (dstRect.y - dstOffset.y)*subPeriodY +
3399                subOriginY;
3400            int symax =
3401                (dstRect.y + dstRect.height - 1 - dstOffset.y)*subPeriodY +
3402                subOriginY;
3403            int activeSrcHeight = symax - activeSrcMinY + 1;
3404            Rectangle activeSrcRect =
3405                new Rectangle(activeSrcMinX, activeSrcMinY,
3406                              activeSrcWidth, activeSrcHeight);
3407            if(activeSrcRect.intersection(srcImageBounds).isEmpty()) {
3408                throw new IllegalArgumentException
3409                    ("Backward mapped destination region does not intersect source image!");
3410            }
3411
3412            if(reader == null) {
3413                reader = new TIFFImageReader(new TIFFImageReaderSpi());
3414            } else {
3415                reader.reset();
3416            }
3417
3418            stream.mark();
3419
3420            try {
3421                stream.seek(headerPosition);
3422                reader.setInput(stream);
3423
3424                this.imageMetadata = replacePixelsMetadata;
3425                this.param = param;
3426                SampleModel sm = image.getSampleModel();
3427                ColorModel cm = image.getColorModel();
3428                this.numBands = sm.getNumBands();
3429                this.imageType = new ImageTypeSpecifier(image);
3430                this.periodX = param.getSourceXSubsampling();
3431                this.periodY = param.getSourceYSubsampling();
3432                this.sourceBands = null;
3433                int[] sBands = param.getSourceBands();
3434                if (sBands != null) {
3435                    this.sourceBands = sBands;
3436                    this.numBands = sourceBands.length;
3437                }
3438                setupMetadata(cm, sm,
3439                              reader.getWidth(replacePixelsIndex),
3440                              reader.getHeight(replacePixelsIndex));
3441                int[] scaleSampleSize = sm.getSampleSize();
3442                initializeScaleTables(scaleSampleSize);
3443
3444                // Determine whether bilevel.
3445                this.isBilevel = ImageUtil.isBinary(image.getSampleModel());
3446
3447                // Check for photometric inversion.
3448                this.isInverted =
3449                    (nativePhotometricInterpretation ==
3450                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
3451                     photometricInterpretation ==
3452                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
3453                    (nativePhotometricInterpretation ==
3454                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
3455                     photometricInterpretation ==
3456                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
3457
3458                // Analyze image data suitability for direct copy.
3459                this.isImageSimple = 
3460                    (isBilevel ||
3461                     (!isInverted && ImageUtil.imageIsContiguous(image))) &&
3462                    !isRescaling &&                 // no value rescaling
3463                    sourceBands == null &&          // no subbanding
3464                    periodX == 1 && periodY == 1 && // no subsampling
3465                    colorConverter == null;
3466
3467                int minTileX = XToTileX(dstRect.x, 0, tileWidth);
3468                int minTileY = YToTileY(dstRect.y, 0, tileLength);
3469                int maxTileX = XToTileX(dstRect.x + dstRect.width - 1,
3470                                        0, tileWidth);
3471                int maxTileY = YToTileY(dstRect.y + dstRect.height - 1,
3472                                        0, tileLength);
3473
3474                TIFFCompressor encoder = new TIFFNullCompressor();
3475                encoder.setWriter(this);
3476                encoder.setStream(stream);
3477                encoder.setMetadata(this.imageMetadata);
3478
3479                Rectangle tileRect = new Rectangle();
3480                for(int ty = minTileY; ty <= maxTileY; ty++) {
3481                    for(int tx = minTileX; tx <= maxTileX; tx++) {
3482                        int tileIndex = ty*tilesAcross + tx;
3483                        boolean isEmpty =
3484                            replacePixelsByteCounts[tileIndex] == 0L;
3485                        WritableRaster raster;
3486                        if(isEmpty) {
3487                            SampleModel tileSM =
3488                                sm.createCompatibleSampleModel(tileWidth,
3489                                                               tileLength);
3490                            raster = Raster.createWritableRaster(tileSM, null);
3491                        } else {
3492                            BufferedImage tileImage =
3493                                reader.readTile(replacePixelsIndex, tx, ty);
3494                            raster = tileImage.getRaster();
3495                        }
3496
3497                        tileRect.setLocation(tx*tileWidth,
3498                                             ty*tileLength);
3499                        tileRect.setSize(raster.getWidth(),
3500                                         raster.getHeight());
3501                        raster =
3502                            raster.createWritableTranslatedChild(tileRect.x,
3503                                                                 tileRect.y);
3504
3505                        Rectangle replacementRect =
3506                            tileRect.intersection(dstRect);
3507
3508                        int srcMinX =
3509                            (replacementRect.x - dstOffset.x)*subPeriodX +
3510                            subOriginX;
3511                        int srcXmax = 
3512                            (replacementRect.x + replacementRect.width - 1 -
3513                             dstOffset.x)*subPeriodX + subOriginX;
3514                        int srcWidth = srcXmax - srcMinX + 1;
3515        
3516                        int srcMinY =
3517                            (replacementRect.y - dstOffset.y)*subPeriodY +
3518                            subOriginY;
3519                        int srcYMax =
3520                            (replacementRect.y + replacementRect.height - 1 -
3521                             dstOffset.y)*subPeriodY + subOriginY;
3522                        int srcHeight = srcYMax - srcMinY + 1;
3523                        Rectangle srcTileRect =
3524                            new Rectangle(srcMinX, srcMinY,
3525                                          srcWidth, srcHeight);
3526
3527                        Raster replacementData = image.getData(srcTileRect);
3528                        if(subPeriodX == 1 && subPeriodY == 1 &&
3529                           subOriginX == 0 && subOriginY == 0) {
3530                            replacementData =
3531                                replacementData.createChild(srcTileRect.x,
3532                                                            srcTileRect.y,
3533                                                            srcTileRect.width,
3534                                                            srcTileRect.height,
3535                                                            replacementRect.x,
3536                                                            replacementRect.y,
3537                                                            sourceBands);
3538                        } else {
3539                            replacementData = subsample(replacementData,
3540                                                        sourceBands,
3541                                                        subOriginX,
3542                                                        subOriginY,
3543                                                        subPeriodX,
3544                                                        subPeriodY,
3545                                                        dstOffset.x,
3546                                                        dstOffset.y,
3547                                                        replacementRect);
3548                            if(replacementData == null) {
3549                                continue;
3550                            }
3551                        }
3552
3553                        raster.setRect(replacementData);
3554
3555                        if(isEmpty) {
3556                            stream.seek(nextSpace);
3557                        } else {
3558                            stream.seek(replacePixelsTileOffsets[tileIndex]);
3559                        }
3560
3561                        this.image = new SingleTileRenderedImage(raster, cm);
3562
3563                        int numBytes = writeTile(tileRect, encoder);
3564
3565                        if(isEmpty) {
3566                            // Update Strip/TileOffsets and
3567                            // Strip/TileByteCounts fields.
3568                            stream.mark();
3569                            stream.seek(replacePixelsOffsetsPosition +
3570                                        4*tileIndex);
3571                            stream.writeInt((int)nextSpace);
3572                            stream.seek(replacePixelsByteCountsPosition +
3573                                        4*tileIndex);
3574                            stream.writeInt(numBytes);
3575                            stream.reset();
3576
3577                            // Increment location of next available space.
3578                            nextSpace += numBytes;
3579                        }
3580                    }
3581                }
3582
3583            } catch(IOException e) {
3584                throw e;
3585            } finally {
3586                stream.reset();
3587            }
3588        }
3589    }
3590
3591    public void replacePixels(Raster raster, ImageWriteParam param)
3592        throws IOException {
3593        if (raster == null) {
3594            throw new IllegalArgumentException("raster == null!");
3595        }
3596
3597        replacePixels(new SingleTileRenderedImage(raster,
3598                                                  image.getColorModel()),
3599                      param);
3600    }
3601
3602    public void endReplacePixels() throws IOException {
3603        synchronized(replacePixelsLock) {
3604            if(!this.inReplacePixelsNest) {
3605                throw new IllegalStateException
3606                    ("No previous call to prepareReplacePixels()!");
3607            }
3608            replacePixelsIndex = -1;
3609            replacePixelsMetadata = null;
3610            replacePixelsTileOffsets = null;
3611            replacePixelsByteCounts = null;
3612            replacePixelsOffsetsPosition = 0L;
3613            replacePixelsByteCountsPosition = 0L;
3614            replacePixelsRegion = null;
3615            inReplacePixelsNest = false;
3616        }
3617    }
3618
3619    // ----- END replacePixels methods -----
3620
3621    public void reset() {
3622        super.reset();
3623
3624        stream = null;
3625        image = null;
3626        imageType = null;
3627        byteOrder = null;
3628        param = null;
3629        if(compressor != null){
3630          compressor.dispose();
3631        }
3632        compressor = null;
3633        colorConverter = null;
3634        streamMetadata = null;
3635        imageMetadata = null;
3636
3637        isWritingSequence = false;
3638        isWritingEmpty = false;
3639        isInsertingEmpty = false;
3640
3641        replacePixelsIndex = -1;
3642        replacePixelsMetadata = null;
3643        replacePixelsTileOffsets = null;
3644        replacePixelsByteCounts = null;
3645        replacePixelsOffsetsPosition = 0L;
3646        replacePixelsByteCountsPosition = 0L;
3647        replacePixelsRegion = null;
3648        inReplacePixelsNest = false;
3649    }
3650    
3651    public void dispose() {
3652        reset();
3653        super.dispose();
3654    }
3655}
3656
3657class EmptyImage extends SimpleRenderedImage {
3658    EmptyImage(int minX, int minY, int width, int height,
3659               int tileGridXOffset, int tileGridYOffset,
3660               int tileWidth, int tileHeight,
3661               SampleModel sampleModel, ColorModel colorModel) {
3662        this.minX = minX;
3663        this.minY = minY;
3664        this.width = width;
3665        this.height = height;
3666        this.tileGridXOffset = tileGridXOffset;
3667        this.tileGridYOffset = tileGridYOffset;
3668        this.tileWidth = tileWidth;
3669        this.tileHeight = tileHeight;
3670        this.sampleModel = sampleModel;
3671        this.colorModel = colorModel;
3672    }
3673
3674    public Raster getTile(int tileX, int tileY) {
3675        return null;
3676    }
3677}