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