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