001/*
002 * $RCSfile: TIFFImageWriteParam.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.3 $
042 * $Date: 2006/04/28 01:01:59 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.plugins.tiff;
046
047import java.util.Locale;
048
049import javax.imageio.ImageWriteParam;
050
051import com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriter;
052
053/**
054 * A subclass of {@link ImageWriteParam <code>ImageWriteParam</code>}
055 * allowing control over the TIFF writing process. The set of innately
056 * supported compression types is listed in the following table:
057 *
058 * <p>
059 * <table border=1>
060 * <caption><b>Supported Compression Types</b></caption>
061 * <tr><th>Compression Type</th> <th>Description</th> <th>Reference</th></tr>
062 * <tr>
063 * <td>CCITT RLE</td>
064 * <td>Modified Huffman compression</td>
065 * <td>TIFF 6.0 Specification, Section 10</td>
066 * </tr>
067 * <tr>
068 * <td>CCITT T.4</td>
069 * <td>CCITT T.4 bilevel encoding/Group 3 facsimile compression</td>
070 * <td>TIFF 6.0 Specification, Section 11</td>
071 * </tr>
072 * <tr>
073 * <td>CCITT T.6</td>
074 * <td>CCITT T.6 bilevel encoding/Group 4 facsimile compression</td>
075 * <td>TIFF 6.0 Specification, Section 11</td></tr>
076 * <tr>
077 * <td>LZW</td>
078 * <td>LZW compression</td>
079 * <td>TIFF 6.0 Specification, Section 13</td></tr>
080 * <tr>
081 * <td>JPEG</td>
082 * <td>"New" JPEG-in-TIFF compression</td>
083 * <td><a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">TIFF
084 * Technical Note #2</a></td>
085 * </tr>
086 * <tr>
087 * <td>ZLib</td>
088 * <td>"Deflate/Inflate" compression (see note following this table)</td>
089 * <td><a href="http://partners.adobe.com/asn/developer/pdfs/tn/TIFFphotoshop.pdf">
090 * Adobe Photoshop&#174; TIFF Technical Notes</a> (PDF)</td>
091 * </tr>
092 * <tr>
093 * <td>PackBits</td>
094 * <td>Byte-oriented, run length compression</td>
095 * <td>TIFF 6.0 Specification, Section 9</td>
096 * </tr>
097 * <tr>
098 * <td>Deflate</td>
099 * <td>"Zip-in-TIFF" compression (see note following this table)</td>
100 * <td><a href="http://www.isi.edu/in-notes/rfc1950.txt">
101 * ZLIB Compressed Data Format Specification</a>,
102 * <a href="http://www.isi.edu/in-notes/rfc1951.txt">
103 * DEFLATE Compressed Data Format Specification</a></td>
104 * </tr>
105 * <tr>
106 * <td>EXIF JPEG</td>
107 * <td>EXIF-specific JPEG compression (see note following this table)</td>
108 * <td><a href="http://www.exif.org/Exif2-2.PDF">EXIF 2.2 Specification</a>
109 * (PDF), section 4.5.5, "Basic Structure of Thumbnail Data"</td>
110 * </table>
111 * </p>
112 * <p>
113 * Old-style JPEG compression as described in section 22 of the TIFF 6.0
114 * Specification is <i>not</i> supported.
115 * </p>
116 *
117 * <p> The CCITT compression types are applicable to bilevel (1-bit)
118 * images only.  The JPEG compression type is applicable to byte
119 * grayscale (1-band) and RGB (3-band) images only.</p>
120 *
121 * <p>
122 * ZLib and Deflate compression are identical except for the value of the
123 * TIFF Compression field: for ZLib the Compression field has value 8
124 * whereas for Deflate it has value 32946 (0x80b2). In both cases each
125 * image segment (strip or tile) is written as a single complete zlib data
126 * stream.
127 * </p>
128 *
129 * <p>
130 * "EXIF JPEG" is a compression type used when writing the contents of an
131 * APP1 EXIF marker segment for inclusion in a JPEG native image metadata
132 * tree. The contents appended to the output when this compression type is
133 * used are a function of whether an empty or non-empty image is written.
134 * If the image is empty, then a TIFF IFD adhering to the specification of
135 * a compressed EXIF primary IFD is appended. If the image is non-empty,
136 * then a complete IFD and image adhering to the specification of a
137 * compressed EXIF thumbnail IFD and image are appended. Note that the
138 * data of the empty image may <i>not</i> later be appended using the pixel
139 * replacement capability of the TIFF writer.
140 * </p>
141 *
142 * <p> If ZLib/Deflate or JPEG compression is used, the compression quality
143 * may be set. For ZLib/Deflate the supplied floating point quality value is
144 * rescaled to the range <tt>[1,&nbsp;9]</tt> and truncated to an integer
145 * to derive the Deflate compression level. For JPEG the floating point
146 * quality value is passed directly to the JPEG writer plug-in which
147 * interprets it in the usual way.</p>
148 *
149 * <p> The <code>canWriteTiles</code> and
150 * <code>canWriteCompressed</code> methods will return
151 * <code>true</code>; the <code>canOffsetTiles</code> and
152 * <code>canWriteProgressive</code> methods will return
153 * <code>false</code>.</p>
154 *
155 * <p> If tiles are being written, then each of their dimensions will be
156 * rounded to the nearest multiple of 16 per the TIFF specification. If
157 * JPEG-in-TIFF compression is being used, and tiles are being written
158 * each tile dimension will be rounded to the nearest multiple of 8 times
159 * the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF
160 * compression is being used and strips are being written, the number of
161 * rows per strip is rounded to a multiple of 8 times the maximum MCU over
162 * both dimensions.</p>
163 */
164public class TIFFImageWriteParam extends ImageWriteParam {
165
166    TIFFCompressor compressor = null;
167
168    TIFFColorConverter colorConverter = null;
169    int photometricInterpretation;
170
171    private boolean appendedCompressionType = false;
172
173    /**
174     * Constructs a <code>TIFFImageWriteParam</code> instance
175     * for a given <code>Locale</code>.
176     *
177     * @param locale the <code>Locale</code> for which messages
178     * should be localized.
179     */
180    public TIFFImageWriteParam(Locale locale) {
181        super(locale);
182        this.canWriteCompressed = true;
183        this.canWriteTiles = true;
184        this.compressionTypes = TIFFImageWriter.TIFFCompressionTypes;
185    };
186
187    public boolean isCompressionLossless() {
188        if (getCompressionMode() != MODE_EXPLICIT) {
189            throw new IllegalStateException
190                ("Compression mode not MODE_EXPLICIT!");
191        }
192
193        if (compressionType == null) {
194            throw new IllegalStateException("No compression type set!");
195        }
196
197        if(compressor != null &&
198           compressionType.equals(compressor.getCompressionType())) {
199            return compressor.isCompressionLossless();
200        }
201
202        for (int i = 0; i < compressionTypes.length; i++) {
203            if (compressionType.equals(compressionTypes[i])) {
204                return TIFFImageWriter.isCompressionLossless[i];
205            }
206        }
207
208        return false;
209    }
210
211    /**
212     * Sets the <code>TIFFCompressor</code> object to be used by the
213     * <code>ImageWriter</code> to encode each image strip or tile.
214     * A value of <code>null</code> allows the writer to choose its
215     * own TIFFCompressor.
216     *
217     * <p>Note that invoking this method is not sufficient to set
218     * the compression type:
219     * {@link ImageWriteParam#setCompressionType(String) <code>setCompressionType()</code>}
220     * must be invoked explicitly for this purpose. The following
221     * code illustrates the correct procedure:
222     * <pre>
223     * TIFFImageWriteParam writeParam;
224     * TIFFCompressor compressor;
225     * writeParam.setCompressionMode(writeParam.MODE_EXPLICIT);
226     * writeParam.setTIFFCompressor(compressor);
227     * writeParam.setCompressionType(compressor.getCompressionType());
228     * </pre>
229     * If <code>compressionType</code> is set to a value different from
230     * that supported by the <code>TIFFCompressor</code> then the
231     * compressor object will not be used.
232     * </p>
233     *
234     * <p>If the compression type supported by the supplied
235     * <code>TIFFCompressor</code> is not among those in
236     * {@link ImageWriteParam#compressionTypes <code>compressionTypes</code>},
237     * then it will be appended to this array after removing any previously
238     * appended compression type. If <code>compressor</code> is
239     * <code>null</code> this will also cause any previously appended
240     * type to be removed from the array.</p>
241     *
242     * @param compressor the <code>TIFFCompressor</code> to be
243     * used for encoding, or <code>null</code> to allow the writer to
244     * choose its own.
245     *
246     * @throws IllegalStateException if the compression mode is not
247     * <code>MODE_EXPLICIT</code>.
248     *
249     * @see #getTIFFCompressor
250     */
251    public void setTIFFCompressor(TIFFCompressor compressor) {
252        if (getCompressionMode() != MODE_EXPLICIT) {
253            throw new IllegalStateException
254                ("Compression mode not MODE_EXPLICIT!");
255        }
256
257        this.compressor = compressor;
258
259        if(appendedCompressionType) {
260            // Remove previously appended compression type.
261            int len = compressionTypes.length - 1;
262            String[] types = new String[len];
263            System.arraycopy(compressionTypes, 0, types, 0, len);
264            compressionTypes = types;
265            appendedCompressionType = false;
266        }
267
268        if(compressor != null) {
269            // Check whether compressor's type is already in the list.
270            String compressorType = compressor.getCompressionType();
271            int len = compressionTypes.length;
272            boolean appendCompressionType = true;
273            for(int i = 0; i < len; i++) {
274                if(compressorType.equals(compressionTypes[i])) {
275                    appendCompressionType = false;
276                    break;
277                }
278            }
279
280            if(appendCompressionType) {
281                // Compressor's compression type not in the list; append it.
282                String[] types = new String[len + 1];
283                System.arraycopy(compressionTypes, 0, types, 0, len);
284                types[len] = compressorType;
285                compressionTypes = types;
286                appendedCompressionType = true;
287            }
288        }
289    }
290
291    /**
292     * Returns the <code>TIFFCompressor</code> that is currently set
293     * to be used by the <code>ImageWriter</code> to encode each image
294     * strip or tile, or <code>null</code> if none has been set.
295     *
296     * @return compressor the <code>TIFFCompressor</code> to be
297     * used for encoding, or <code>null</code> if none has been set
298     * (allowing the writer to choose its own).
299     *
300     * @throws IllegalStateException if the compression mode is not
301     * <code>MODE_EXPLICIT</code>.
302     *
303     * @see #setTIFFCompressor(TIFFCompressor)
304     */
305    public TIFFCompressor getTIFFCompressor() {
306        if (getCompressionMode() != MODE_EXPLICIT) {
307            throw new IllegalStateException
308                ("Compression mode not MODE_EXPLICIT!");
309        }
310        return this.compressor;
311    }
312
313    /**
314     * Sets the <code>TIFFColorConverter</code> object describing the
315     * color space to which the input data should be converted for
316     * storage in the input stream.  In addition, the value to be
317     * written to the <code>PhotometricInterpretation</code> tag is
318     * supplied.
319     *
320     * @param colorConverter a <code>TIFFColorConverter</code> object,
321     * or <code>null</code>.
322     * @param photometricInterpretation the value to be written to the
323     * <code>PhotometricInterpretation</code> tag in the root IFD.
324     *
325     * @see #getColorConverter
326     * @see #getPhotometricInterpretation
327     */
328    public void setColorConverter(TIFFColorConverter colorConverter,
329                                  int photometricInterpretation) {
330        this.colorConverter = colorConverter;
331        this.photometricInterpretation = photometricInterpretation;
332    }
333
334    /**
335     * Returns the current <code>TIFFColorConverter</code> object that
336     * will be used to perform color conversion when writing the
337     * image, or <code>null</code> if none is set.
338     *
339     * @return a <code>TIFFColorConverter</code> object, or
340     * <code>null</code>.
341     *
342     * @see #setColorConverter(TIFFColorConverter, int)
343     */
344    public TIFFColorConverter getColorConverter() {
345        return colorConverter;
346    }
347
348    /**
349     * Returns the current value that will be written to the
350     * <code>Photometricinterpretation</code> tag.  This method should
351     * only be called if a value has been set using the
352     * <code>setColorConverter</code> method.
353     *
354     * @return an <code>int</code> to be used as the value of the
355     * <code>PhotometricInterpretation</code> tag.
356     *
357     * @see #setColorConverter(TIFFColorConverter, int)
358     *
359     * @throws IllegalStateException if no value is set.
360     */
361    public int getPhotometricInterpretation() {
362        if (colorConverter == null) {
363            throw new IllegalStateException("Color converter not set!");
364        }
365        return photometricInterpretation;
366    }
367
368    /**
369     * Removes any currently set <code>ColorConverter</code> object and
370     * <code>PhotometricInterpretation</code> tag value.
371     *
372     * @see #setColorConverter(TIFFColorConverter, int)
373     */
374    public void unsetColorConverter() {
375        this.colorConverter = null;
376    }
377}