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.Arrays;
048import java.util.List;
049import java.util.Locale;
050
051import javax.imageio.ImageWriteParam;
052
053import com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriter;
054
055/**
056 * A subclass of {@link ImageWriteParam <code>ImageWriteParam</code>}
057 * allowing control over the TIFF writing process. The set of innately
058 * supported compression types is listed in the following table:
059 *
060 * <p>
061 * <table border=1>
062 * <caption><b>Supported Compression Types</b></caption>
063 * <tr><th>Compression Type</th> <th>Description</th> <th>Reference</th></tr>
064 * <tr>
065 * <td>CCITT RLE</td>
066 * <td>Modified Huffman compression</td>
067 * <td>TIFF 6.0 Specification, Section 10</td>
068 * </tr>
069 * <tr>
070 * <td>CCITT T.4</td>
071 * <td>CCITT T.4 bilevel encoding/Group 3 facsimile compression</td>
072 * <td>TIFF 6.0 Specification, Section 11</td>
073 * </tr>
074 * <tr>
075 * <td>CCITT T.6</td>
076 * <td>CCITT T.6 bilevel encoding/Group 4 facsimile compression</td>
077 * <td>TIFF 6.0 Specification, Section 11</td></tr>
078 * <tr>
079 * <td>LZW</td>
080 * <td>LZW compression</td>
081 * <td>TIFF 6.0 Specification, Section 13</td></tr>
082 * <tr>
083 * <td>JPEG</td>
084 * <td>"New" JPEG-in-TIFF compression</td>
085 * <td><a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">TIFF
086 * Technical Note #2</a></td>
087 * </tr>
088 * <tr>
089 * <td>ZLib</td>
090 * <td>"Deflate/Inflate" compression (see note following this table)</td>
091 * <td><a href="http://partners.adobe.com/asn/developer/pdfs/tn/TIFFphotoshop.pdf">
092 * Adobe Photoshop&#174; TIFF Technical Notes</a> (PDF)</td>
093 * </tr>
094 * <tr>
095 * <td>PackBits</td>
096 * <td>Byte-oriented, run length compression</td>
097 * <td>TIFF 6.0 Specification, Section 9</td>
098 * </tr>
099 * <tr>
100 * <td>Deflate</td>
101 * <td>"Zip-in-TIFF" compression (see note following this table)</td>
102 * <td><a href="http://www.isi.edu/in-notes/rfc1950.txt">
103 * ZLIB Compressed Data Format Specification</a>,
104 * <a href="http://www.isi.edu/in-notes/rfc1951.txt">
105 * DEFLATE Compressed Data Format Specification</a></td>
106 * </tr>
107 * <tr>
108 * <td>EXIF JPEG</td>
109 * <td>EXIF-specific JPEG compression (see note following this table)</td>
110 * <td><a href="http://www.exif.org/Exif2-2.PDF">EXIF 2.2 Specification</a>
111 * (PDF), section 4.5.5, "Basic Structure of Thumbnail Data"</td>
112 * </table>
113 * </p>
114 * <p>
115 * Old-style JPEG compression as described in section 22 of the TIFF 6.0
116 * Specification is <i>not</i> supported.
117 * </p>
118 *
119 * <p> The CCITT compression types are applicable to bilevel (1-bit)
120 * images only.  The JPEG compression type is applicable to byte
121 * grayscale (1-band) and RGB (3-band) images only.</p>
122 *
123 * <p>
124 * ZLib and Deflate compression are identical except for the value of the
125 * TIFF Compression field: for ZLib the Compression field has value 8
126 * whereas for Deflate it has value 32946 (0x80b2). In both cases each
127 * image segment (strip or tile) is written as a single complete zlib data
128 * stream.
129 * </p>
130 *
131 * <p>
132 * "EXIF JPEG" is a compression type used when writing the contents of an
133 * APP1 EXIF marker segment for inclusion in a JPEG native image metadata
134 * tree. The contents appended to the output when this compression type is
135 * used are a function of whether an empty or non-empty image is written.
136 * If the image is empty, then a TIFF IFD adhering to the specification of
137 * a compressed EXIF primary IFD is appended. If the image is non-empty,
138 * then a complete IFD and image adhering to the specification of a
139 * compressed EXIF thumbnail IFD and image are appended. Note that the
140 * data of the empty image may <i>not</i> later be appended using the pixel
141 * replacement capability of the TIFF writer.
142 * </p>
143 *
144 * <p> If ZLib/Deflate or JPEG compression is used, the compression quality
145 * may be set. For ZLib/Deflate the supplied floating point quality value is
146 * rescaled to the range <tt>[1,&nbsp;9]</tt> and truncated to an integer
147 * to derive the Deflate compression level. For JPEG the floating point
148 * quality value is passed directly to the JPEG writer plug-in which
149 * interprets it in the usual way.</p>
150 *
151 * <p> The <code>canWriteTiles</code> and
152 * <code>canWriteCompressed</code> methods will return
153 * <code>true</code>; the <code>canOffsetTiles</code> and
154 * <code>canWriteProgressive</code> methods will return
155 * <code>false</code>.</p>
156 *
157 * <p> If tiles are being written, then each of their dimensions will be
158 * rounded to the nearest multiple of 16 per the TIFF specification. If
159 * JPEG-in-TIFF compression is being used, and tiles are being written
160 * each tile dimension will be rounded to the nearest multiple of 8 times
161 * the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF
162 * compression is being used and strips are being written, the number of
163 * rows per strip is rounded to a multiple of 8 times the maximum MCU over
164 * both dimensions.</p>
165 */
166public class TIFFImageWriteParam extends ImageWriteParam {
167
168    TIFFCompressor compressor = null;
169
170    TIFFColorConverter colorConverter = null;
171    int photometricInterpretation;
172
173    private boolean appendedCompressionType = false;
174
175    /**
176     * Constructs a <code>TIFFImageWriteParam</code> instance
177     * for a given <code>Locale</code>.
178     *
179     * @param locale the <code>Locale</code> for which messages
180     * should be localized.
181     */
182    public TIFFImageWriteParam(Locale locale) {
183        super(locale);
184        this.canWriteCompressed = true;
185        this.canWriteTiles = true;
186        this.compressionTypes = TIFFImageWriter.TIFFCompressionTypes;
187    };
188
189    public boolean isCompressionLossless() {
190        if (getCompressionMode() != MODE_EXPLICIT) {
191            throw new IllegalStateException
192                ("Compression mode not MODE_EXPLICIT!");
193        }
194
195        if (compressionType == null) {
196            throw new IllegalStateException("No compression type set!");
197        }
198
199        if(compressor != null &&
200           compressionType.equals(compressor.getCompressionType())) {
201            return compressor.isCompressionLossless();
202        }
203
204        for (int i = 0; i < compressionTypes.length; i++) {
205            if (compressionType.equals(compressionTypes[i])) {
206                return TIFFImageWriter.isCompressionLossless[i];
207            }
208        }
209
210        return false;
211    }
212
213    /**
214     * Sets the <code>TIFFCompressor</code> object to be used by the
215     * <code>ImageWriter</code> to encode each image strip or tile.
216     * A value of <code>null</code> allows the writer to choose its
217     * own TIFFCompressor.
218     *
219     * <p>Note that invoking this method is not sufficient to set
220     * the compression type:
221     * {@link ImageWriteParam#setCompressionType(String) <code>setCompressionType()</code>}
222     * must be invoked explicitly for this purpose. The following
223     * code illustrates the correct procedure:
224     * <pre>
225     * TIFFImageWriteParam writeParam;
226     * TIFFCompressor compressor;
227     * writeParam.setCompressionMode(writeParam.MODE_EXPLICIT);
228     * writeParam.setTIFFCompressor(compressor);
229     * writeParam.setCompressionType(compressor.getCompressionType());
230     * </pre>
231     * If <code>compressionType</code> is set to a value different from
232     * that supported by the <code>TIFFCompressor</code> then the
233     * compressor object will not be used.
234     * </p>
235     *
236     * <p>If the compression type supported by the supplied
237     * <code>TIFFCompressor</code> is not among those in
238     * {@link ImageWriteParam#compressionTypes <code>compressionTypes</code>},
239     * then it will be appended to this array after removing any previously
240     * appended compression type. If <code>compressor</code> is
241     * <code>null</code> this will also cause any previously appended
242     * type to be removed from the array.</p>
243     *
244     * @param compressor the <code>TIFFCompressor</code> to be
245     * used for encoding, or <code>null</code> to allow the writer to
246     * choose its own.
247     *
248     * @throws IllegalStateException if the compression mode is not
249     * <code>MODE_EXPLICIT</code>.
250     *
251     * @see #getTIFFCompressor
252     */
253    public void setTIFFCompressor(TIFFCompressor compressor) {
254        if (getCompressionMode() != MODE_EXPLICIT) {
255            throw new IllegalStateException
256                ("Compression mode not MODE_EXPLICIT!");
257        }
258
259        this.compressor = compressor;
260
261        if(appendedCompressionType) {
262            // Remove previously appended compression type.
263            int len = compressionTypes.length - 1;
264            String[] types = new String[len];
265            System.arraycopy(compressionTypes, 0, types, 0, len);
266            compressionTypes = types;
267            appendedCompressionType = false;
268        }
269
270        if(compressor != null) {
271            // Check whether compressor's type is already in the list.
272            String compressorType = compressor.getCompressionType();
273            int len = compressionTypes.length;
274            boolean appendCompressionType = true;
275            for(int i = 0; i < len; i++) {
276                if(compressorType.equals(compressionTypes[i])) {
277                    appendCompressionType = false;
278                    break;
279                }
280            }
281
282            if(appendCompressionType) {
283                // Compressor's compression type not in the list; append it.
284                String[] types = new String[len + 1];
285                System.arraycopy(compressionTypes, 0, types, 0, len);
286                types[len] = compressorType;
287                compressionTypes = types;
288                appendedCompressionType = true;
289            }
290        }
291    }
292
293    /**
294     * Returns the <code>TIFFCompressor</code> that is currently set
295     * to be used by the <code>ImageWriter</code> to encode each image
296     * strip or tile, or <code>null</code> if none has been set.
297     *
298     * @return compressor the <code>TIFFCompressor</code> to be
299     * used for encoding, or <code>null</code> if none has been set
300     * (allowing the writer to choose its own).
301     *
302     * @throws IllegalStateException if the compression mode is not
303     * <code>MODE_EXPLICIT</code>.
304     *
305     * @see #setTIFFCompressor(TIFFCompressor)
306     */
307    public TIFFCompressor getTIFFCompressor() {
308        if (getCompressionMode() != MODE_EXPLICIT) {
309            throw new IllegalStateException
310                ("Compression mode not MODE_EXPLICIT!");
311        }
312        return this.compressor;
313    }
314
315    /**
316     * Sets the <code>TIFFColorConverter</code> object describing the
317     * color space to which the input data should be converted for
318     * storage in the input stream.  In addition, the value to be
319     * written to the <code>PhotometricInterpretation</code> tag is
320     * supplied.
321     *
322     * @param colorConverter a <code>TIFFColorConverter</code> object,
323     * or <code>null</code>.
324     * @param photometricInterpretation the value to be written to the
325     * <code>PhotometricInterpretation</code> tag in the root IFD.
326     *
327     * @see #getColorConverter
328     * @see #getPhotometricInterpretation
329     */
330    public void setColorConverter(TIFFColorConverter colorConverter,
331                                  int photometricInterpretation) {
332        this.colorConverter = colorConverter;
333        this.photometricInterpretation = photometricInterpretation;
334    }
335
336    /**
337     * Returns the current <code>TIFFColorConverter</code> object that
338     * will be used to perform color conversion when writing the
339     * image, or <code>null</code> if none is set.
340     *
341     * @return a <code>TIFFColorConverter</code> object, or
342     * <code>null</code>.
343     *
344     * @see #setColorConverter(TIFFColorConverter, int)
345     */
346    public TIFFColorConverter getColorConverter() {
347        return colorConverter;
348    }
349
350    /**
351     * Returns the current value that will be written to the
352     * <code>Photometricinterpretation</code> tag.  This method should
353     * only be called if a value has been set using the
354     * <code>setColorConverter</code> method.
355     *
356     * @return an <code>int</code> to be used as the value of the
357     * <code>PhotometricInterpretation</code> tag.
358     *
359     * @see #setColorConverter(TIFFColorConverter, int)
360     *
361     * @throws IllegalStateException if no value is set.
362     */
363    public int getPhotometricInterpretation() {
364        if (colorConverter == null) {
365            throw new IllegalStateException("Color converter not set!");
366        }
367        return photometricInterpretation;
368    }
369
370    /**
371     * Removes any currently set <code>ColorConverter</code> object and
372     * <code>PhotometricInterpretation</code> tag value.
373     *
374     * @see #setColorConverter(TIFFColorConverter, int)
375     */
376    public void unsetColorConverter() {
377        this.colorConverter = null;
378    }
379}