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