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