001/*
002 * $RCSfile: TIFFJPEGCompressor.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/11 22:10:36 $
043 * $State: Exp $
044 */
045package com.github.jaiimageio.impl.plugins.tiff;
046
047import java.io.ByteArrayInputStream;
048import java.io.ByteArrayOutputStream;
049import java.util.Iterator;
050
051import javax.imageio.ImageReader;
052import javax.imageio.ImageWriteParam;
053import javax.imageio.metadata.IIOMetadata;
054import javax.imageio.spi.IIORegistry;
055import javax.imageio.spi.ImageReaderSpi;
056import javax.imageio.spi.ServiceRegistry;
057import javax.imageio.stream.MemoryCacheImageInputStream;
058import javax.imageio.stream.MemoryCacheImageOutputStream;
059
060import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet;
061import com.github.jaiimageio.plugins.tiff.TIFFField;
062import com.github.jaiimageio.plugins.tiff.TIFFTag;
063
064/**
065 * Compressor for encoding compression type 7, TTN2/Adobe JPEG-in-TIFF.
066 */
067public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor {
068
069    private static final boolean DEBUG = false; // XXX false for release.
070
071    // Subsampling factor for chroma bands (Cb Cr).
072    static final int CHROMA_SUBSAMPLING = 2;
073
074    /**
075     * A filter which identifies the ImageReaderSpi of a JPEG reader
076     * which supports JPEG native stream metadata.
077     */
078    private static class JPEGSPIFilter implements ServiceRegistry.Filter {
079        JPEGSPIFilter() {}
080
081        public boolean filter(Object provider) {
082            ImageReaderSpi readerSPI = (ImageReaderSpi)provider;
083
084            if(readerSPI != null) {
085                String streamMetadataName =
086                    readerSPI.getNativeStreamMetadataFormatName();
087                if(streamMetadataName != null) {
088                    return streamMetadataName.equals(STREAM_METADATA_NAME);
089                } else {
090                    return false;
091                }
092            }
093
094            return false;
095        }
096    }
097
098    /**
099     * Retrieves a JPEG reader which supports native JPEG stream metadata.
100     */
101    private static ImageReader getJPEGTablesReader() {
102        ImageReader jpegReader = null;
103
104        try {
105            IIORegistry registry = IIORegistry.getDefaultInstance();
106            Class imageReaderClass =
107                Class.forName("javax.imageio.spi.ImageReaderSpi");
108            Iterator readerSPIs =
109                registry.getServiceProviders(imageReaderClass,
110                                             new JPEGSPIFilter(),
111                                             true);
112            if(readerSPIs.hasNext()) {
113                ImageReaderSpi jpegReaderSPI =
114                    (ImageReaderSpi)readerSPIs.next();
115                jpegReader = jpegReaderSPI.createReaderInstance();
116            }
117        } catch(Exception e) {
118            // Ignore it ...
119        }
120
121        return jpegReader;
122    }
123
124    public TIFFJPEGCompressor(ImageWriteParam param) {
125        super("JPEG", BaselineTIFFTagSet.COMPRESSION_JPEG, false, param);
126    }
127
128    /**
129     * Sets the value of the <code>metadata</code> field.
130     *
131     * <p>The implementation in this class also adds the TIFF fields
132     * JPEGTables, YCbCrSubSampling, YCbCrPositioning, and
133     * ReferenceBlackWhite superseding any prior settings of those
134     * fields.</p>
135     *
136     * @param metadata the <code>IIOMetadata</code> object for the
137     * image being written.
138     *
139     * @see #getMetadata()
140     */
141    public void setMetadata(IIOMetadata metadata) {
142        super.setMetadata(metadata);
143
144        if (metadata instanceof TIFFImageMetadata) {
145            TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
146            TIFFIFD rootIFD = tim.getRootIFD();
147            BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
148
149            TIFFField f =
150                tim.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
151            int numBands = f.getAsInt(0);
152
153            if(numBands == 1) {
154                // Remove YCbCr fields not relevant for grayscale.
155
156                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
157                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
158                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
159            } else { // numBands == 3
160                // Replace YCbCr fields.
161
162                // YCbCrSubSampling
163                TIFFField YCbCrSubSamplingField = new TIFFField
164                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
165                     TIFFTag.TIFF_SHORT, 2,
166                     new char[] {CHROMA_SUBSAMPLING, CHROMA_SUBSAMPLING});
167                rootIFD.addTIFFField(YCbCrSubSamplingField);
168
169                // YCbCrPositioning
170                TIFFField YCbCrPositioningField = new TIFFField
171                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
172                     TIFFTag.TIFF_SHORT, 1,
173                     new char[]
174                        {BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED});
175                rootIFD.addTIFFField(YCbCrPositioningField);
176
177                // ReferenceBlackWhite
178                TIFFField referenceBlackWhiteField = new TIFFField
179                    (base.getTag(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE),
180                     TIFFTag.TIFF_RATIONAL, 6,
181                     new long[][] { // no headroon/footroom
182                         {0, 1}, {255, 1},
183                         {128, 1}, {255, 1},
184                         {128, 1}, {255, 1}
185                     });
186                rootIFD.addTIFFField(referenceBlackWhiteField);
187            }
188
189            // JPEGTables field is written if and only if one is
190            // already present in the metadata. If one is present
191            // and has either zero length or does not represent a
192            // valid tables-only stream, then a JPEGTables field
193            // will be written initialized to the standard tables-
194            // only stream written by the JPEG writer.
195
196            // Retrieve the JPEGTables field.
197            TIFFField JPEGTablesField =
198                tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
199
200            // Initialize JPEG writer to one supporting abbreviated streams.
201            if(JPEGTablesField != null) {
202                // Intialize the JPEG writer to one that supports stream
203                // metadata, i.e., abbreviated streams, and may or may not
204                // support image metadata.
205                initJPEGWriter(true, false);
206            }
207
208            // Write JPEGTables field if a writer supporting abbreviated
209            // streams was available.
210            if(JPEGTablesField != null && JPEGWriter != null) {
211                if(DEBUG) System.out.println("Has JPEGTables ...");
212
213                // Set the abbreviated stream flag.
214                this.writeAbbreviatedStream = true;
215
216                //Branch based on field value count.
217                if(JPEGTablesField.getCount() > 0) {
218                    if(DEBUG) System.out.println("JPEGTables > 0");
219
220                    // Derive the stream metadata from the field.
221
222                    // Get the field values.
223                    byte[] tables = JPEGTablesField.getAsBytes();
224
225                    // Create an input stream for the tables.
226                    ByteArrayInputStream bais =
227                        new ByteArrayInputStream(tables);
228                    MemoryCacheImageInputStream iis =
229                        new MemoryCacheImageInputStream(bais);
230
231                    // Read the tables stream using the JPEG reader.
232                    ImageReader jpegReader = getJPEGTablesReader();
233                    jpegReader.setInput(iis);
234
235                    // Initialize the stream metadata object.
236                    try {
237                        JPEGStreamMetadata = jpegReader.getStreamMetadata();
238                    } catch(Exception e) {
239                        // Fall back to default tables.
240                        JPEGStreamMetadata = null;
241                    } finally {
242                        jpegReader.reset();
243                    }
244                    if(DEBUG) System.out.println(JPEGStreamMetadata);
245                }
246
247                if(JPEGStreamMetadata == null) {
248                    if(DEBUG) System.out.println("JPEGTables == 0");
249
250                    // Derive the field from default stream metadata.
251
252                    // Get default stream metadata.
253                    JPEGStreamMetadata =
254                        JPEGWriter.getDefaultStreamMetadata(JPEGParam);
255
256                    // Create an output stream for the tables.
257                    ByteArrayOutputStream tableByteStream =
258                        new ByteArrayOutputStream();
259                    MemoryCacheImageOutputStream tableStream =
260                        new MemoryCacheImageOutputStream(tableByteStream);
261
262                    // Write a tables-only stream.
263                    JPEGWriter.setOutput(tableStream);
264                    try {
265                        JPEGWriter.prepareWriteSequence(JPEGStreamMetadata);
266                        tableStream.flush();
267                        JPEGWriter.endWriteSequence();
268
269                        // Get the tables-only stream content.
270                        byte[] tables = tableByteStream.toByteArray();
271                        if(DEBUG) System.out.println("tables.length = "+
272                                                     tables.length);
273
274                        // Add the JPEGTables field.
275                        JPEGTablesField = new TIFFField
276                            (base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
277                             TIFFTag.TIFF_UNDEFINED,
278                             tables.length,
279                             tables);
280                        rootIFD.addTIFFField(JPEGTablesField);
281                    } catch(Exception e) {
282                        // Do not write JPEGTables field.
283                        rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
284                        this.writeAbbreviatedStream = false;
285                    }
286                }
287            } else { // Do not write JPEGTables field.
288                // Remove any field present.
289                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
290
291                // Initialize the writer preferring codecLib.
292                initJPEGWriter(false, false);
293            }
294        }
295    }
296}