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