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