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 javax.imageio.plugins.tiff.BaselineTIFFTagSet;
28import javax.imageio.plugins.tiff.TIFFField;
29import javax.imageio.plugins.tiff.TIFFTag;
30import java.io.ByteArrayInputStream;
31import java.io.ByteArrayOutputStream;
32import java.util.Iterator;
33import javax.imageio.ImageReader;
34import javax.imageio.ImageWriteParam;
35import javax.imageio.metadata.IIOMetadata;
36import javax.imageio.spi.IIORegistry;
37import javax.imageio.spi.ImageReaderSpi;
38import javax.imageio.spi.ServiceRegistry;
39import javax.imageio.stream.MemoryCacheImageInputStream;
40import javax.imageio.stream.MemoryCacheImageOutputStream;
41
42/**
43 * Compressor for encoding compression type 7, TTN2/Adobe JPEG-in-TIFF.
44 */
45public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor {
46
47    // Subsampling factor for chroma bands (Cb Cr).
48    static final int CHROMA_SUBSAMPLING = 2;
49
50    /**
51     * A filter which identifies the ImageReaderSpi of a JPEG reader
52     * which supports JPEG native stream metadata.
53     */
54    private static class JPEGSPIFilter implements ServiceRegistry.Filter {
55        JPEGSPIFilter() {}
56
57        public boolean filter(Object provider) {
58            ImageReaderSpi readerSPI = (ImageReaderSpi)provider;
59
60            if(readerSPI != null) {
61                String streamMetadataName =
62                    readerSPI.getNativeStreamMetadataFormatName();
63                if(streamMetadataName != null) {
64                    return streamMetadataName.equals(STREAM_METADATA_NAME);
65                } else {
66                    return false;
67                }
68            }
69
70            return false;
71        }
72    }
73
74    /**
75     * Retrieves a JPEG reader which supports native JPEG stream metadata.
76     */
77    private static ImageReader getJPEGTablesReader() {
78        ImageReader jpegReader = null;
79
80        try {
81            IIORegistry registry = IIORegistry.getDefaultInstance();
82            Iterator<?> readerSPIs =
83                registry.getServiceProviders(ImageReaderSpi.class,
84                                             new JPEGSPIFilter(),
85                                             true);
86            if(readerSPIs.hasNext()) {
87                ImageReaderSpi jpegReaderSPI =
88                    (ImageReaderSpi)readerSPIs.next();
89                jpegReader = jpegReaderSPI.createReaderInstance();
90            }
91        } catch(Exception e) {
92            // Ignore it ...
93        }
94
95        return jpegReader;
96    }
97
98    public TIFFJPEGCompressor(ImageWriteParam param) {
99        super("JPEG", BaselineTIFFTagSet.COMPRESSION_JPEG, false, param);
100    }
101
102    /**
103     * Sets the value of the {@code metadata} field.
104     *
105     * <p>The implementation in this class also adds the TIFF fields
106     * JPEGTables, YCbCrSubSampling, YCbCrPositioning, and
107     * ReferenceBlackWhite superseding any prior settings of those
108     * fields.</p>
109     *
110     * @param metadata the {@code IIOMetadata} object for the
111     * image being written.
112     *
113     * @see #getMetadata()
114     */
115    public void setMetadata(IIOMetadata metadata) {
116        super.setMetadata(metadata);
117
118        if (metadata instanceof TIFFImageMetadata) {
119            TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
120            TIFFIFD rootIFD = tim.getRootIFD();
121            BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
122
123            TIFFField f =
124                tim.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
125            int numBands = f.getAsInt(0);
126
127            if(numBands == 1) {
128                // Remove YCbCr fields not relevant for grayscale.
129
130                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
131                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
132                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
133            } else { // numBands == 3
134                // Replace YCbCr fields.
135
136                // YCbCrSubSampling
137                TIFFField YCbCrSubSamplingField = new TIFFField
138                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
139                     TIFFTag.TIFF_SHORT, 2,
140                     new char[] {CHROMA_SUBSAMPLING, CHROMA_SUBSAMPLING});
141                rootIFD.addTIFFField(YCbCrSubSamplingField);
142
143                // YCbCrPositioning
144                TIFFField YCbCrPositioningField = new TIFFField
145                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
146                     TIFFTag.TIFF_SHORT, 1,
147                     new char[]
148                        {BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED});
149                rootIFD.addTIFFField(YCbCrPositioningField);
150
151                // ReferenceBlackWhite
152                TIFFField referenceBlackWhiteField = new TIFFField
153                    (base.getTag(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE),
154                     TIFFTag.TIFF_RATIONAL, 6,
155                     new long[][] { // no headroon/footroom
156                         {0, 1}, {255, 1},
157                         {128, 1}, {255, 1},
158                         {128, 1}, {255, 1}
159                     });
160                rootIFD.addTIFFField(referenceBlackWhiteField);
161            }
162
163            // JPEGTables field is written if and only if one is
164            // already present in the metadata. If one is present
165            // and has either zero length or does not represent a
166            // valid tables-only stream, then a JPEGTables field
167            // will be written initialized to the standard tables-
168            // only stream written by the JPEG writer.
169
170            // Retrieve the JPEGTables field.
171            TIFFField JPEGTablesField =
172                tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
173
174            // Initialize JPEG writer to one supporting abbreviated streams.
175            if(JPEGTablesField != null) {
176                // Intialize the JPEG writer to one that supports stream
177                // metadata, i.e., abbreviated streams, and may or may not
178                // support image metadata.
179                initJPEGWriter(true, false);
180            }
181
182            // Write JPEGTables field if a writer supporting abbreviated
183            // streams was available.
184            if(JPEGTablesField != null && JPEGWriter != null) {
185                // Set the abbreviated stream flag.
186                this.writeAbbreviatedStream = true;
187
188                //Branch based on field value count.
189                if(JPEGTablesField.getCount() > 0) {
190                    // Derive the stream metadata from the field.
191
192                    // Get the field values.
193                    byte[] tables = JPEGTablesField.getAsBytes();
194
195                    // Create an input stream for the tables.
196                    ByteArrayInputStream bais =
197                        new ByteArrayInputStream(tables);
198                    MemoryCacheImageInputStream iis =
199                        new MemoryCacheImageInputStream(bais);
200
201                    // Read the tables stream using the JPEG reader.
202                    ImageReader jpegReader = getJPEGTablesReader();
203                    jpegReader.setInput(iis);
204
205                    // Initialize the stream metadata object.
206                    try {
207                        JPEGStreamMetadata = jpegReader.getStreamMetadata();
208                    } catch(Exception e) {
209                        // Fall back to default tables.
210                        JPEGStreamMetadata = null;
211                    } finally {
212                        jpegReader.reset();
213                    }
214                }
215
216                if(JPEGStreamMetadata == null) {
217                    // Derive the field from default stream metadata.
218
219                    // Get default stream metadata.
220                    JPEGStreamMetadata =
221                        JPEGWriter.getDefaultStreamMetadata(JPEGParam);
222
223                    // Create an output stream for the tables.
224                    ByteArrayOutputStream tableByteStream =
225                        new ByteArrayOutputStream();
226                    MemoryCacheImageOutputStream tableStream =
227                        new MemoryCacheImageOutputStream(tableByteStream);
228
229                    // Write a tables-only stream.
230                    JPEGWriter.setOutput(tableStream);
231                    try {
232                        JPEGWriter.prepareWriteSequence(JPEGStreamMetadata);
233                        tableStream.flush();
234                        JPEGWriter.endWriteSequence();
235
236                        // Get the tables-only stream content.
237                        byte[] tables = tableByteStream.toByteArray();
238
239                        // Add the JPEGTables field.
240                        JPEGTablesField = new TIFFField
241                            (base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
242                             TIFFTag.TIFF_UNDEFINED,
243                             tables.length,
244                             tables);
245                        rootIFD.addTIFFField(JPEGTablesField);
246                    } catch(Exception e) {
247                        // Do not write JPEGTables field.
248                        rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
249                        this.writeAbbreviatedStream = false;
250                    }
251                }
252            } else { // Do not write JPEGTables field.
253                // Remove any field present.
254                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
255
256                // Initialize the writer preferring codecLib.
257                initJPEGWriter(false, false);
258            }
259        }
260    }
261}
262