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