blob: 67dd60f8a79a4480a8c4cf99b288f920e0cf0582 [file] [log] [blame]
/*
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.imageio.plugins.tiff;
import java.io.IOException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
import javax.imageio.plugins.tiff.TIFFField;
/**
* {@code TIFFDecompressor} for "Old JPEG" compression.
*/
public class TIFFOldJPEGDecompressor extends TIFFJPEGDecompressor {
// Define Huffman Tables
private static final int DHT = 0xC4;
// Define Quantisation Tables
private static final int DQT = 0xDB;
// Define Restart Interval
private static final int DRI = 0xDD;
// Baseline DCT
private static final int SOF0 = 0xC0;
// Start of Scan
private static final int SOS = 0xDA;
// End of Image
// private static final int EOI = 0xD9; // now defined in superclass
// Whether the decompressor has been initialized.
private boolean isInitialized = false;
//
// Instance variables set by the initialize() method.
//
// Offset to a complete, contiguous JPEG stream.
private Long JPEGStreamOffset = null;
// Offset to the SOF marker.
private int SOFPosition = -1;
// Value of the SOS marker.
private byte[] SOSMarker = null;
// Horizontal chroma subsampling factor.
private int subsamplingX = 2;
// Vertical chroma subsampling factor.
private int subsamplingY = 2;
public TIFFOldJPEGDecompressor() {}
//
// Intialize instance variables according to an analysis of the
// TIFF field content. See bug 4929147 for test image information.
//
// Case 1: Image contains a single strip or tile and the offset to
// that strip or tile points to an SOI marker.
//
// Example:
// "Visionshape Inc. Compression Software, version 2.5"
// ColorTiffWithBarcode.tif
// Color2.tif (pages 2-5 (indexes 1-4)
// color3.tif (pages 2-5 (indexes 1-4)
//
// "Kofax standard Multi-Page TIFF Storage Filter v2.01.000"
// 01.tif (pages 1 and 3(indexes 0 and 2))
//
// Instance variables set: JPEGStreamOffset
//
// Case 2: Image contains a single strip or tile and a
// JPEGInterchangeFormat field is present but the
// JPEGInterchangeFormatLength is erroneously missing.
//
// Example:
// "Kofax standard Multi-Page TIFF Storage Filter v2.01.000"
// 01.tif (pages 1 and 3(indexes 0 and 2))
// (but this example also satisfies case 1)
//
// Instance variables set: JPEGStreamOffset
//
// Case 3: Image contains a single strip or tile, the
// JPEGInterchangeFormat and JPEGInterchangeFormatLength
// fields are both present, the value of JPEGInterchangeFormat
// is less than the offset to the strip or tile, and the sum
// of the values of JPEGInterchangeFormat and
// JPEGInterchangeFormatLength is greater than the offset to
// the strip or tile.
//
// Instance variables set: JPEGStreamOffset
//
// Example:
// "HP IL v1.1"
// smallliz.tif from libtiff test data.
//
// Instance variables set: JPEGStreamOffset
//
// Cases 4-5 apply if none of cases 1-3 applies or the image has multiple
// strips or tiles.
//
// Case 4: JPEGInterchangeFormat and JPEGInterchangeFormatLength are
// present, the value of JPEGInterchangeFormatLength is at least 2,
// and the sum of the values of these two fields is at most the
// value of the offset to the first strip or tile.
//
// Instance variables set: tables, SOFPosition, SOSMarker
//
// Example:
// "Oi/GFS, writer v00.06.00P, (c) Wang Labs, Inc. 1990, 1991"
// 03.tif (pages 1 and 3(indexes 0 and 2))
//
// "Oi/GFS, writer v00.06.02"
// Color2.tif (page 1 (index 0))
// color3.tif (page 1 (index 0))
//
// Case 5: If none of the foregoing cases apply. For this case the
// JPEGQTables, JPEGACTables, and JPEGDCTables must be valid.
//
// Instance variables set: tables, SOFPosition, SOSMarker
//
// Example:
// "NeXT"
// zackthecat.tif from libtiff test data.
//
private synchronized void initialize() throws IOException {
if(isInitialized) {
return;
}
// Get the TIFF metadata object.
TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
// Get the JPEGInterchangeFormat field.
TIFFField JPEGInterchangeFormatField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
// Get the tile or strip offsets.
TIFFField segmentOffsetField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
if(segmentOffsetField == null) {
segmentOffsetField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
if(segmentOffsetField == null) {
segmentOffsetField = JPEGInterchangeFormatField;
}
}
long[] segmentOffsets = segmentOffsetField.getAsLongs();
// Determine whether the image has more than one strip or tile.
boolean isTiled = segmentOffsets.length > 1;
if(!isTiled) {
//
// If the image has only a single strip or tile and it looks
// as if a complete JPEG stream is present then set the value
// of JPEGStreamOffset to the offset of the JPEG stream;
// otherwise leave JPEGStreamOffset set to null.
//
stream.seek(offset);
stream.mark();
if(stream.read() == 0xff && stream.read() == SOI) {
// Tile or strip offset points to SOI.
JPEGStreamOffset = Long.valueOf(offset);
// Set initialization flag and return.
((TIFFImageReader)reader).forwardWarningMessage("SOI marker detected at start of strip or tile.");
isInitialized = true;
return;
}
stream.reset();
if(JPEGInterchangeFormatField != null) {
// Get the value of JPEGInterchangeFormat.
long jpegInterchangeOffset =
JPEGInterchangeFormatField.getAsLong(0);
// Get the JPEGInterchangeFormatLength field.
TIFFField JPEGInterchangeFormatLengthField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
if(JPEGInterchangeFormatLengthField == null) {
// JPEGInterchangeFormat stream is of indeterminate
// length so use it as a complete JPEG stream.
JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset);
// Set initialization flag and return.
((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormatLength field is missing");
isInitialized = true;
return;
} else {
// Get the JPEGInterchangeFormatLength field's value.
long jpegInterchangeLength =
JPEGInterchangeFormatLengthField.getAsLong(0);
if(jpegInterchangeOffset < segmentOffsets[0] &&
(jpegInterchangeOffset + jpegInterchangeLength) >
segmentOffsets[0]) {
// JPEGInterchangeFormat points to a position
// below the segment start position and ends at
// a position after the segment start position.
JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset);
// Set initialization flag and return.
isInitialized = true;
return;
}
}
}
}
// Get the subsampling factors.
TIFFField YCbCrSubsamplingField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
if(YCbCrSubsamplingField != null) {
subsamplingX = YCbCrSubsamplingField.getAsChars()[0];
subsamplingY = YCbCrSubsamplingField.getAsChars()[1];
}
//
// Initialize the 'tables' instance variable either for later
// use in prepending to individual abbreviated strips or tiles.
//
if(JPEGInterchangeFormatField != null) {
// Get the value of JPEGInterchangeFormat.
long jpegInterchangeOffset =
JPEGInterchangeFormatField.getAsLong(0);
// Get the JPEGInterchangeFormatLength field.
TIFFField JPEGInterchangeFormatLengthField =
tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
if(JPEGInterchangeFormatLengthField != null) {
// Get the JPEGInterchangeFormatLength field's value.
long jpegInterchangeLength =
JPEGInterchangeFormatLengthField.getAsLong(0);
if(jpegInterchangeLength >= 2 &&
jpegInterchangeOffset + jpegInterchangeLength <=
segmentOffsets[0]) {
// Determine the length excluding any terminal EOI marker
// and allocate table memory.
stream.mark();
stream.seek(jpegInterchangeOffset+jpegInterchangeLength-2);
if(stream.read() == 0xff && stream.read() == EOI) {
this.tables = new byte[(int)(jpegInterchangeLength-2)];
} else {
this.tables = new byte[(int)jpegInterchangeLength];
}
stream.reset();
// Read the tables.
stream.mark();
stream.seek(jpegInterchangeOffset);
stream.readFully(tables);
stream.reset();
((TIFFImageReader)reader).forwardWarningMessage("Incorrect JPEG interchange format: using JPEGInterchangeFormat offset to derive tables.");
} else {
((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormat+JPEGInterchangeFormatLength > offset to first strip or tile.");
}
}
}
if(this.tables == null) {
//
// Create tables-only stream in tables[] consisting of
// SOI+DQTs+DHTs
//
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// Save stream length;
long streamLength = stream.length();
// SOI
baos.write(0xff);
baos.write(SOI);
// Quantization Tables
TIFFField f =
tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES);
if(f == null) {
throw new IIOException("JPEGQTables field missing!");
}
long[] off = f.getAsLongs();
for(int i = 0; i < off.length; i++) {
baos.write(0xff); // Marker ID
baos.write(DQT);
char markerLength = (char)67;
baos.write((markerLength >>> 8) & 0xff); // Length
baos.write(markerLength & 0xff);
baos.write(i); // Table ID and precision
byte[] qtable = new byte[64];
if(streamLength != -1 && off[i] > streamLength) {
throw new IIOException("JPEGQTables offset for index "+
i+" is not in the stream!");
}
stream.seek(off[i]);
stream.readFully(qtable);
baos.write(qtable); // Table data
}
// Huffman Tables (k == 0 ? DC : AC).
for(int k = 0; k < 2; k++) {
int tableTagNumber = k == 0 ?
BaselineTIFFTagSet.TAG_JPEG_DC_TABLES :
BaselineTIFFTagSet.TAG_JPEG_AC_TABLES;
f = tim.getTIFFField(tableTagNumber);
String fieldName =
tableTagNumber ==
BaselineTIFFTagSet.TAG_JPEG_DC_TABLES ?
"JPEGDCTables" : "JPEGACTables";
if(f == null) {
throw new IIOException(fieldName+" field missing!");
}
off = f.getAsLongs();
for(int i = 0; i < off.length; i++) {
baos.write(0xff); // Marker ID
baos.write(DHT);
byte[] blengths = new byte[16];
if(streamLength != -1 && off[i] > streamLength) {
throw new IIOException(fieldName+" offset for index "+
i+" is not in the stream!");
}
stream.seek(off[i]);
stream.readFully(blengths);
int numCodes = 0;
for(int j = 0; j < 16; j++) {
numCodes += blengths[j]&0xff;
}
char markerLength = (char)(19 + numCodes);
baos.write((markerLength >>> 8) & 0xff); // Length
baos.write(markerLength & 0xff);
baos.write(i | (k << 4)); // Table ID and type
baos.write(blengths); // Number of codes
byte[] bcodes = new byte[numCodes];
stream.readFully(bcodes);
baos.write(bcodes); // Codes
}
}
// SOF0
baos.write((byte)0xff); // Marker identifier
baos.write((byte)SOF0);
short sval = (short)(8 + 3*samplesPerPixel); // Length
baos.write((byte)((sval >>> 8) & 0xff));
baos.write((byte)(sval & 0xff));
baos.write((byte)8); // Data precision
sval = (short)srcHeight; // Tile/strip height
baos.write((byte)((sval >>> 8) & 0xff));
baos.write((byte)(sval & 0xff));
sval = (short)srcWidth; // Tile/strip width
baos.write((byte)((sval >>> 8) & 0xff));
baos.write((byte)(sval & 0xff));
baos.write((byte)samplesPerPixel); // Number of components
if(samplesPerPixel == 1) {
baos.write((byte)1); // Component ID
baos.write((byte)0x11); // Subsampling factor
baos.write((byte)0); // Quantization table ID
} else { // 3
for(int i = 0; i < 3; i++) {
baos.write((byte)(i + 1)); // Component ID
baos.write((i != 0) ?
(byte)0x11 :
(byte)(((subsamplingX & 0x0f) << 4) |
(subsamplingY & 0x0f)));
baos.write((byte)i); // Quantization table ID
}
};
// DRI (optional).
f = tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL);
if(f != null) {
char restartInterval = f.getAsChars()[0];
if(restartInterval != 0) {
baos.write((byte)0xff); // Marker identifier
baos.write((byte)DRI);
sval = 4;
baos.write((byte)((sval >>> 8) & 0xff)); // Length
baos.write((byte)(sval & 0xff));
// RestartInterval
baos.write((byte)((restartInterval >>> 8) & 0xff));
baos.write((byte)(restartInterval & 0xff));
}
}
tables = baos.toByteArray();
}
//
// Check for presence of SOF marker and save its position.
//
int idx = 0;
int idxMax = tables.length - 1;
while(idx < idxMax) {
if((tables[idx]&0xff) == 0xff &&
(tables[idx+1]&0xff) == SOF0) {
SOFPosition = idx;
break;
}
idx++;
}
//
// If no SOF marker, add one.
//
if(SOFPosition == -1) {
byte[] tmpTables =
new byte[tables.length + 10 + 3*samplesPerPixel];
System.arraycopy(tables, 0, tmpTables, 0, tables.length);
int tmpOffset = tables.length;
SOFPosition = tables.length;
tables = tmpTables;
tables[tmpOffset++] = (byte)0xff; // Marker identifier
tables[tmpOffset++] = (byte)SOF0;
short sval = (short)(8 + 3*samplesPerPixel); // Length
tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
tables[tmpOffset++] = (byte)(sval & 0xff);
tables[tmpOffset++] = (byte)8; // Data precision
sval = (short)srcHeight; // Tile/strip height
tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
tables[tmpOffset++] = (byte)(sval & 0xff);
sval = (short)srcWidth; // Tile/strip width
tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
tables[tmpOffset++] = (byte)(sval & 0xff);
tables[tmpOffset++] = (byte)samplesPerPixel; // Number of components
if(samplesPerPixel == 1) {
tables[tmpOffset++] = (byte)1; // Component ID
tables[tmpOffset++] = (byte)0x11; // Subsampling factor
tables[tmpOffset++] = (byte)0; // Quantization table ID
} else { // 3
for(int i = 0; i < 3; i++) {
tables[tmpOffset++] = (byte)(i + 1); // Component ID
tables[tmpOffset++] = (i != 0) ?
(byte)0x11 :
(byte)(((subsamplingX & 0x0f) << 4) |
(subsamplingY & 0x0f));
tables[tmpOffset++] = (byte)i; // Quantization table ID
}
};
}
//
// Initialize SOSMarker.
//
stream.mark();
stream.seek(segmentOffsets[0]);
if(stream.read() == 0xff && stream.read() == SOS) {
//
// If the first segment starts with an SOS marker save it.
//
int SOSLength = (stream.read()<<8)|stream.read();
SOSMarker = new byte[SOSLength+2];
SOSMarker[0] = (byte)0xff;
SOSMarker[1] = (byte)SOS;
SOSMarker[2] = (byte)((SOSLength & 0xff00) >> 8);
SOSMarker[3] = (byte)(SOSLength & 0xff);
stream.readFully(SOSMarker, 4, SOSLength - 2);
} else {
//
// Manufacture an SOS marker.
//
SOSMarker = new byte[2 + 6 + 2*samplesPerPixel];
int SOSMarkerIndex = 0;
SOSMarker[SOSMarkerIndex++] = (byte)0xff; // Marker identifier
SOSMarker[SOSMarkerIndex++] = (byte)SOS;
short sval = (short)(6 + 2*samplesPerPixel); // Length
SOSMarker[SOSMarkerIndex++] = (byte)((sval >>> 8) & 0xff);
SOSMarker[SOSMarkerIndex++] = (byte)(sval & 0xff);
// Number of components in scan
SOSMarker[SOSMarkerIndex++] = (byte)samplesPerPixel;
if(samplesPerPixel == 1) {
SOSMarker[SOSMarkerIndex++] = (byte)1; // Component ID
SOSMarker[SOSMarkerIndex++] = (byte)0; // Huffman table ID
} else { // 3
for(int i = 0; i < 3; i++) {
SOSMarker[SOSMarkerIndex++] =
(byte)(i + 1); // Component ID
SOSMarker[SOSMarkerIndex++] =
(byte)((i << 4) | i); // Huffman table IDs
}
};
SOSMarker[SOSMarkerIndex++] = (byte)0;
SOSMarker[SOSMarkerIndex++] = (byte)0x3f;
SOSMarker[SOSMarkerIndex++] = (byte)0;
}
stream.reset();
// Set initialization flag.
isInitialized = true;
}
//
// The strategy for reading cases 1-3 is to treat the data as a complete
// JPEG interchange stream located at JPEGStreamOffset.
//
// The strategy for cases 4-5 is to concatenate a tables stream created
// in initialize() with the entropy coded data in each strip or tile.
//
public void decodeRaw(byte[] b,
int dstOffset,
int bitsPerPixel,
int scanlineStride) throws IOException {
initialize();
TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
if(JPEGStreamOffset != null) {
stream.seek(JPEGStreamOffset.longValue());
JPEGReader.setInput(stream, false, true);
} else {
// Determine buffer length and allocate.
int tableLength = tables.length;
int bufLength =
tableLength + SOSMarker.length + byteCount + 2; // 2 for EOI.
byte[] buf = new byte[bufLength];
System.arraycopy(tables, 0, buf, 0, tableLength);
int bufOffset = tableLength;
// Update the SOF dimensions.
short sval = (short)srcHeight; // Tile/strip height
buf[SOFPosition + 5] = (byte)((sval >>> 8) & 0xff);
buf[SOFPosition + 6] = (byte)(sval & 0xff);
sval = (short)srcWidth; // Tile/strip width
buf[SOFPosition + 7] = (byte)((sval >>> 8) & 0xff);
buf[SOFPosition + 8] = (byte)(sval & 0xff);
// Seek to data.
stream.seek(offset);
// Add SOS marker if data segment does not start with one.
byte[] twoBytes = new byte[2];
stream.readFully(twoBytes);
if(!((twoBytes[0]&0xff) == 0xff && (twoBytes[1]&0xff) == SOS)) {
// Segment does not start with SOS marker;
// use the pre-calculated SOS marker.
System.arraycopy(SOSMarker, 0, buf, bufOffset,
SOSMarker.length);
bufOffset += SOSMarker.length;
}
// Copy the segment data into the buffer.
buf[bufOffset++] = twoBytes[0];
buf[bufOffset++] = twoBytes[1];
stream.readFully(buf, bufOffset, byteCount - 2);
bufOffset += byteCount - 2;
// EOI.
buf[bufOffset++] = (byte)0xff; // Marker identifier
buf[bufOffset++] = (byte)EOI;
ByteArrayInputStream bais =
new ByteArrayInputStream(buf, 0, bufOffset);
ImageInputStream is = new MemoryCacheImageInputStream(bais);
JPEGReader.setInput(is, true, true);
}
// Read real image
JPEGParam.setDestination(rawImage);
JPEGReader.read(0, JPEGParam);
}
@SuppressWarnings("deprecation")
protected void finalize() throws Throwable {
super.finalize();
JPEGReader.dispose();
}
}