blob: 1210e3ee92514941ecbdd80327939a0a01b12cef [file] [log] [blame]
/*
* Copyright (c) 2005, 2021, 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.EOFException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
import javax.imageio.plugins.tiff.TIFFDirectory;
import javax.imageio.plugins.tiff.TIFFField;
import javax.imageio.plugins.tiff.TIFFTag;
import javax.imageio.plugins.tiff.TIFFTagSet;
public class TIFFIFD extends TIFFDirectory {
private static final long MAX_SAMPLES_PER_PIXEL = 0xffff;
private static final long MAX_ASCII_SIZE = 0xffff;
private long stripOrTileByteCountsPosition = -1;
private long stripOrTileOffsetsPosition = -1;
private long lastPosition = -1;
//
// A set of tag numbers corresponding to tags essential to decoding
// the image and metadata required to interpret its samples.
//
private static volatile Set<Integer> essentialTags = null;
private static void initializeEssentialTags() {
Set<Integer> tags = essentialTags;
if (tags == null) {
essentialTags = tags = Set.of(
BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE,
BaselineTIFFTagSet.TAG_COLOR_MAP,
BaselineTIFFTagSet.TAG_COMPRESSION,
BaselineTIFFTagSet.TAG_EXTRA_SAMPLES,
BaselineTIFFTagSet.TAG_FILL_ORDER,
BaselineTIFFTagSet.TAG_ICC_PROFILE,
BaselineTIFFTagSet.TAG_IMAGE_LENGTH,
BaselineTIFFTagSet.TAG_IMAGE_WIDTH,
BaselineTIFFTagSet.TAG_JPEG_AC_TABLES,
BaselineTIFFTagSet.TAG_JPEG_DC_TABLES,
BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT,
BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
BaselineTIFFTagSet.TAG_JPEG_PROC,
BaselineTIFFTagSet.TAG_JPEG_Q_TABLES,
BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL,
BaselineTIFFTagSet.TAG_JPEG_TABLES,
BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION,
BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION,
BaselineTIFFTagSet.TAG_PREDICTOR,
BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE,
BaselineTIFFTagSet.TAG_ROWS_PER_STRIP,
BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL,
BaselineTIFFTagSet.TAG_SAMPLE_FORMAT,
BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS,
BaselineTIFFTagSet.TAG_STRIP_OFFSETS,
BaselineTIFFTagSet.TAG_T4_OPTIONS,
BaselineTIFFTagSet.TAG_T6_OPTIONS,
BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS,
BaselineTIFFTagSet.TAG_TILE_LENGTH,
BaselineTIFFTagSet.TAG_TILE_OFFSETS,
BaselineTIFFTagSet.TAG_TILE_WIDTH,
BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS,
BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING
);
}
}
/**
* Converts a {@code TIFFDirectory} to a {@code TIFFIFD}.
*/
public static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
if(dir instanceof TIFFIFD) {
return (TIFFIFD)dir;
}
TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
dir.getParentTag());
TIFFField[] fields = dir.getTIFFFields();
int numFields = fields.length;
for(int i = 0; i < numFields; i++) {
TIFFField f = fields[i];
TIFFTag tag = f.getTag();
if(tag.isIFDPointer()) {
TIFFDirectory subDir = null;
if (f.hasDirectory()) {
subDir = f.getDirectory();
} else if (f.getData() instanceof TIFFDirectory) {
subDir = (TIFFDirectory)f.getData();
}
if (subDir != null) {
TIFFDirectory subIFD = getDirectoryAsIFD(subDir);
f = new TIFFField(tag, f.getType(), (long)f.getCount(),
subIFD);
} else {
f = null;
}
}
if (f != null) {
ifd.addTIFFField(f);
}
}
return ifd;
}
public static TIFFTag getTag(int tagNumber, List<TIFFTagSet> tagSets) {
Iterator<TIFFTagSet> iter = tagSets.iterator();
while (iter.hasNext()) {
TIFFTagSet tagSet = iter.next();
TIFFTag tag = tagSet.getTag(tagNumber);
if (tag != null) {
return tag;
}
}
return null;
}
public static TIFFTag getTag(String tagName, List<TIFFTagSet> tagSets) {
Iterator<TIFFTagSet> iter = tagSets.iterator();
while (iter.hasNext()) {
TIFFTagSet tagSet = iter.next();
TIFFTag tag = tagSet.getTag(tagName);
if (tag != null) {
return tag;
}
}
return null;
}
private static void writeTIFFFieldToStream(TIFFField field,
ImageOutputStream stream)
throws IOException {
int count = field.getCount();
Object data = field.getData();
switch (field.getType()) {
case TIFFTag.TIFF_ASCII:
for (int i = 0; i < count; i++) {
String s = ((String[])data)[i];
int length = s.length();
for (int j = 0; j < length; j++) {
stream.writeByte(s.charAt(j) & 0xff);
}
stream.writeByte(0);
}
break;
case TIFFTag.TIFF_UNDEFINED:
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
stream.write((byte[])data);
break;
case TIFFTag.TIFF_SHORT:
stream.writeChars((char[])data, 0, ((char[])data).length);
break;
case TIFFTag.TIFF_SSHORT:
stream.writeShorts((short[])data, 0, ((short[])data).length);
break;
case TIFFTag.TIFF_SLONG:
stream.writeInts((int[])data, 0, ((int[])data).length);
break;
case TIFFTag.TIFF_LONG:
for (int i = 0; i < count; i++) {
stream.writeInt((int)(((long[])data)[i]));
}
break;
case TIFFTag.TIFF_IFD_POINTER:
stream.writeInt(0); // will need to be backpatched
break;
case TIFFTag.TIFF_FLOAT:
stream.writeFloats((float[])data, 0, ((float[])data).length);
break;
case TIFFTag.TIFF_DOUBLE:
stream.writeDoubles((double[])data, 0, ((double[])data).length);
break;
case TIFFTag.TIFF_SRATIONAL:
for (int i = 0; i < count; i++) {
stream.writeInt(((int[][])data)[i][0]);
stream.writeInt(((int[][])data)[i][1]);
}
break;
case TIFFTag.TIFF_RATIONAL:
for (int i = 0; i < count; i++) {
long num = ((long[][])data)[i][0];
long den = ((long[][])data)[i][1];
stream.writeInt((int)num);
stream.writeInt((int)den);
}
break;
default:
// error
}
}
public TIFFIFD(List<TIFFTagSet> tagSets, TIFFTag parentTag) {
super(tagSets.toArray(new TIFFTagSet[tagSets.size()]),
parentTag);
}
public TIFFIFD(List<TIFFTagSet> tagSets) {
this(tagSets, null);
}
public List<TIFFTagSet> getTagSetList() {
return Arrays.asList(getTagSets());
}
/**
* Returns an {@code Iterator} over the TIFF fields. The
* traversal is in the order of increasing tag number.
*/
// Note: the sort is guaranteed for low fields by the use of an
// array wherein the index corresponds to the tag number and for
// the high fields by the use of a TreeMap with tag number keys.
public Iterator<TIFFField> iterator() {
return Arrays.asList(getTIFFFields()).iterator();
}
/**
* Read the value of a field. The {@code data} parameter should be
* an array of length 1 of Object.
*
* @param stream the input stream
* @param type the type as read from the stream
* @param count the count read from the stream
* @param data a container for the data
* @return the updated count
* @throws IOException
*/
private static int readFieldValue(ImageInputStream stream,
int type, int count, Object[] data) throws IOException {
Object obj;
final int UNIT_SIZE = 1024000;
switch (type) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
case TIFFTag.TIFF_UNDEFINED:
case TIFFTag.TIFF_ASCII:
if (type == TIFFTag.TIFF_ASCII) {
byte[] bvalues = new byte[count];
stream.readFully(bvalues, 0, count);
// Can be multiple strings
ArrayList<String> v = new ArrayList<>();
boolean inString = false;
int prevIndex = 0;
for (int index = 0; index <= count; index++) {
if (index < count && bvalues[index] != 0) {
if (!inString) {
// start of string
prevIndex = index;
inString = true;
}
} else { // null or special case at end of string
if (inString) {
// end of string
String s = new String(bvalues, prevIndex,
index - prevIndex,
StandardCharsets.US_ASCII);
v.add(s);
inString = false;
}
}
}
count = v.size();
String[] strings;
if (count != 0) {
strings = new String[count];
for (int c = 0; c < count; c++) {
strings[c] = v.get(c);
}
} else {
// This case has been observed when the value of
// 'count' recorded in the field is non-zero but
// the value portion contains all nulls.
count = 1;
strings = new String[]{""};
}
obj = strings;
} else {
if (count < UNIT_SIZE) {
byte[] bvalues = new byte[count];
stream.readFully(bvalues, 0, count);
obj = bvalues;
} else {
int bytesToRead = count;
int bytesRead = 0;
List<byte[]> bufs = new ArrayList<>();
while (bytesToRead != 0) {
int sz = Math.min(bytesToRead, UNIT_SIZE);
byte[] unit = new byte[sz];
stream.readFully(unit, 0, sz);
bufs.add(unit);
bytesRead += sz;
bytesToRead -= sz;
}
byte[] tagData = new byte[bytesRead];
int copiedBytes = 0;
for (byte[] ba : bufs) {
System.arraycopy(ba, 0, tagData, copiedBytes, ba.length);
copiedBytes += ba.length;
}
obj = tagData;
}
}
break;
case TIFFTag.TIFF_SHORT:
final int SHORT_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_SHORT);
if (count < SHORT_TILE_SIZE) {
char[] cvalues = new char[count];
for (int j = 0; j < count; j++) {
cvalues[j] = (char) (stream.readUnsignedShort());
}
obj = cvalues;
} else {
int charsToRead = count;
int charsRead = 0;
List<char[]> bufs = new ArrayList<>();
while (charsToRead != 0) {
int sz = Math.min(charsToRead, SHORT_TILE_SIZE);
char[] unit = new char[sz];
for (int i = 0; i < sz ; i++) {
unit[i] = (char) (stream.readUnsignedShort());
}
bufs.add(unit);
charsRead += sz;
charsToRead -= sz;
}
char[] tagData = new char[charsRead];
int copiedChars = 0;
for (char[] ca : bufs) {
System.arraycopy(ca, 0, tagData, copiedChars, ca.length);
copiedChars += ca.length;
}
obj = tagData;
}
break;
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
final int LONG_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_LONG);
if (count < LONG_TILE_SIZE) {
long[] lvalues = new long[count];
for (int j = 0; j < count; j++) {
lvalues[j] = stream.readUnsignedInt();
}
obj = lvalues;
} else {
int longsToRead = count;
int longsRead = 0;
List<long[]> bufs = new ArrayList<>();
while (longsToRead != 0) {
int sz = Math.min(longsToRead, LONG_TILE_SIZE);
long[] unit = new long[sz];
for (int i = 0; i < sz ; i++) {
unit[i] = stream.readUnsignedInt();
}
bufs.add(unit);
longsRead += sz;
longsToRead -= sz;
}
long[] tagData = new long[longsRead];
int copiedLongs = 0;
for (long[] la : bufs) {
System.arraycopy(la, 0, tagData, copiedLongs, la.length);
copiedLongs += la.length;
}
obj = tagData;
}
break;
case TIFFTag.TIFF_RATIONAL:
final int RATIONAL_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_RATIONAL);
if (count < RATIONAL_TILE_SIZE) {
long[][] llvalues = new long[count][2];
for (int j = 0; j < count; j++) {
llvalues[j][0] = stream.readUnsignedInt();
llvalues[j][1] = stream.readUnsignedInt();
}
obj = llvalues;
} else {
int rationalsToRead = count;
int rationalsRead = 0;
List<long[]> bufs = new ArrayList<>();
while (rationalsToRead != 0) {
int sz = Math.min(rationalsToRead, RATIONAL_TILE_SIZE);
long[] unit = new long[sz * 2];
for (int i = 0; i < (sz * 2) ; i++) {
unit[i] = stream.readUnsignedInt();
}
bufs.add(unit);
rationalsRead += sz;
rationalsToRead -= sz;
}
long[][] tagData = new long[rationalsRead][2];
int copiedRationals = 0;
for (long[] la : bufs) {
for (int i = 0; i < la.length; i = i + 2) {
tagData[copiedRationals + i][0] = la[i];
tagData[copiedRationals + i][1] = la[i + 1];
}
copiedRationals += (la.length / 2);
}
obj = tagData;
}
break;
case TIFFTag.TIFF_SSHORT:
final int SSHORT_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_SSHORT);
if (count < SSHORT_TILE_SIZE) {
short[] svalues = new short[count];
for (int j = 0; j < count; j++) {
svalues[j] = stream.readShort();
}
obj = svalues;
} else {
int shortsToRead = count;
int shortsRead = 0;
List<short[]> bufs = new ArrayList<>();
while (shortsToRead != 0) {
int sz = Math.min(shortsToRead, SSHORT_TILE_SIZE);
short[] unit = new short[sz];
stream.readFully(unit, 0, sz);
bufs.add(unit);
shortsRead += sz;
shortsToRead -= sz;
}
short[] tagData = new short[shortsRead];
int copiedShorts = 0;
for (short[] sa : bufs) {
System.arraycopy(sa, 0, tagData, copiedShorts, sa.length);
copiedShorts += sa.length;
}
obj = tagData;
}
break;
case TIFFTag.TIFF_SLONG:
final int INT_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_SLONG);
if (count < INT_TILE_SIZE) {
int[] ivalues = new int[count];
for (int j = 0; j < count; j++) {
ivalues[j] = stream.readInt();
}
obj = ivalues;
} else {
int intsToRead = count;
int intsRead = 0;
List<int[]> bufs = new ArrayList<>();
while (intsToRead != 0) {
int sz = Math.min(intsToRead, INT_TILE_SIZE);
int[] unit = new int[sz];
stream.readFully(unit, 0, sz);
bufs.add(unit);
intsRead += sz;
intsToRead -= sz;
}
int[] tagData = new int[intsRead];
int copiedInts = 0;
for (int[] ia : bufs) {
System.arraycopy(ia, 0, tagData, copiedInts, ia.length);
copiedInts += ia.length;
}
obj = tagData;
}
break;
case TIFFTag.TIFF_SRATIONAL:
final int SRATIONAL_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_SRATIONAL);
if (count < SRATIONAL_TILE_SIZE) {
int[][] iivalues = new int[count][2];
for (int j = 0; j < count; j++) {
iivalues[j][0] = stream.readInt();
iivalues[j][1] = stream.readInt();
}
obj = iivalues;
} else {
int srationalsToRead = count;
int srationalsRead = 0;
List<int[]> bufs = new ArrayList<>();
while (srationalsToRead != 0) {
int sz = Math.min(srationalsToRead, SRATIONAL_TILE_SIZE);
int[] unit = new int[sz * 2];
stream.readFully(unit, 0, (sz * 2));
bufs.add(unit);
srationalsRead += sz;
srationalsToRead -= sz;
}
int[][] tagData = new int[srationalsRead][2];
int copiedSrationals = 0;
for (int[] ia : bufs) {
for (int i = 0; i < ia.length; i = i + 2) {
tagData[copiedSrationals + i][0] = ia[i];
tagData[copiedSrationals + i][1] = ia[i + 1];
}
copiedSrationals += (ia.length / 2);
}
obj = tagData;
}
break;
case TIFFTag.TIFF_FLOAT:
final int FLOAT_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_FLOAT);
if (count < FLOAT_TILE_SIZE) {
float[] fvalues = new float[count];
for (int j = 0; j < count; j++) {
fvalues[j] = stream.readFloat();
}
obj = fvalues;
} else {
int floatsToRead = count;
int floatsRead = 0;
List<float[]> bufs = new ArrayList<>();
while (floatsToRead != 0) {
int sz = Math.min(floatsToRead, FLOAT_TILE_SIZE);
float[] unit = new float[sz];
stream.readFully(unit, 0, sz);
bufs.add(unit);
floatsRead += sz;
floatsToRead -= sz;
}
float[] tagData = new float[floatsRead];
int copiedFloats = 0;
for (float[] fa : bufs) {
System.arraycopy(fa, 0, tagData, copiedFloats, fa.length);
copiedFloats += fa.length;
}
obj = tagData;
}
break;
case TIFFTag.TIFF_DOUBLE:
final int DOUBLE_TILE_SIZE =
UNIT_SIZE / TIFFTag.getSizeOfType(TIFFTag.TIFF_DOUBLE);
if (count < DOUBLE_TILE_SIZE) {
double[] dvalues = new double[count];
for (int j = 0; j < count; j++) {
dvalues[j] = stream.readDouble();
}
obj = dvalues;
} else {
int doublesToRead = count;
int doublesRead = 0;
List<double[]> bufs = new ArrayList<>();
while (doublesToRead != 0) {
int sz = Math.min(doublesToRead, DOUBLE_TILE_SIZE);
double[] unit = new double[sz];
stream.readFully(unit, 0, sz);
bufs.add(unit);
doublesRead += sz;
doublesToRead -= sz;
}
double[] tagData = new double[doublesRead];
int copiedDoubles = 0;
for (double[] da : bufs) {
System.arraycopy(da, 0, tagData, copiedDoubles, da.length);
copiedDoubles += da.length;
}
obj = tagData;
}
break;
default:
obj = null;
break;
}
data[0] = obj;
return count;
}
//
// Class to represent an IFD entry where the actual content is at an offset
// in the stream somewhere outside the IFD itself. This occurs when the
// value cannot be contained within four bytes. Seeking is required to read
// such field values.
//
private static class TIFFIFDEntry {
public final TIFFTag tag;
public final int type;
public final int count;
public final long offset;
TIFFIFDEntry(TIFFTag tag, int type, int count, long offset) {
this.tag = tag;
this.type = type;
this.count = count;
this.offset = offset;
}
}
//
// Retrieve the value of a baseline field as a long.
//
private long getFieldAsLong(int tagNumber) {
TIFFField f = getTIFFField(tagNumber);
return f == null ? -1 : f.getAsLong(0);
}
//
// Retrieve the value of a baseline field as an int.
//
private int getFieldAsInt(int tagNumber) {
TIFFField f = getTIFFField(tagNumber);
return f == null ? -1 : f.getAsInt(0);
}
//
// Calculate the number of bytes in each strip or tile. This method
// is to be used if and only if no fields exist which provide this
// information. The parameter must be empty and if the method succeeds
// will contain a single element.
//
private boolean calculateByteCounts(int expectedSize,
List<TIFFField> byteCounts) {
if (!byteCounts.isEmpty()) {
throw new IllegalArgumentException("byteCounts is not empty");
}
// must be interleaved
if (getFieldAsInt(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION) ==
BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
return false;
}
// must be uncompressed
if (getFieldAsInt(BaselineTIFFTagSet.TAG_COMPRESSION) !=
BaselineTIFFTagSet.COMPRESSION_NONE) {
return false;
}
// must have image dimensions
long w = getFieldAsLong(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
if (w < 0) {
return false;
}
long h = getFieldAsLong(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
if (h < 0) {
return false;
}
long tw = getFieldAsLong(BaselineTIFFTagSet.TAG_TILE_WIDTH);
if (tw < 0) {
tw = w;
}
long th = getFieldAsLong(BaselineTIFFTagSet.TAG_TILE_LENGTH);
if (th < 0) {
th = getFieldAsLong(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
if (th < 0) {
th = h;
}
}
int[] bitsPerSample = null;
TIFFField f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
if (f != null) {
bitsPerSample = f.getAsInts();
} else {
int samplesPerPixel =
getFieldAsInt(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
if (samplesPerPixel < 0) {
samplesPerPixel = 1;
}
bitsPerSample = new int[samplesPerPixel];
Arrays.fill(bitsPerSample, 8);
}
int bitsPerPixel = 0;
for (int bps : bitsPerSample) {
bitsPerPixel += bps;
}
int bytesPerRow = (int)(tw*bitsPerPixel + 7)/8;
int bytesPerPacket = (int)th*bytesPerRow;
long nx = (w + tw - 1)/tw;
long ny = (h + th - 1)/th;
if (nx*ny != expectedSize) {
return false;
}
boolean isTiled =
getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS) != null;
int tagNumber;
if (isTiled) {
tagNumber = BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS;
} else {
tagNumber = BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS;
}
TIFFTag t = BaselineTIFFTagSet.getInstance().getTag(tagNumber);
f = getTIFFField(tagNumber);
if (f != null) {
removeTIFFField(tagNumber);
}
int numPackets = (int)(nx*ny);
long[] packetByteCounts = new long[numPackets];
Arrays.fill(packetByteCounts, bytesPerPacket);
// if the strip or tile width does not exceed the image width and the
// image height is not a multiple of the strip or tile height, then
// truncate the estimate of the byte count of the last strip to avoid
// reading past the end of the data
if (tw <= w && h % th != 0) {
int numRowsInLastStrip = (int)(h - (ny - 1)*th);
packetByteCounts[numPackets - 1] = numRowsInLastStrip*bytesPerRow;
}
f = new TIFFField(t, TIFFTag.TIFF_LONG, numPackets, packetByteCounts);
addTIFFField(f);
byteCounts.add(f);
return true;
}
//
// Verify that data pointed to outside of the IFD itself are within the
// stream. To be called after all fields have been read and populated.
//
private void checkFieldOffsets(long streamLength) throws IIOException {
if (streamLength < 0) {
return;
}
// StripOffsets
List<TIFFField> offsets = new ArrayList<>();
TIFFField f = getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
int count = 0;
if (f != null) {
count = f.getCount();
offsets.add(f);
}
// TileOffsets
f = getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
if (f != null) {
int sz = offsets.size();
int newCount = f.getCount();
if (sz > 0 && newCount != count) {
throw new IIOException
("StripOffsets count != TileOffsets count");
}
if (sz == 0) {
count = newCount;
}
offsets.add(f);
}
List<TIFFField> byteCounts = new ArrayList<>();
if (offsets.size() > 0) {
// StripByteCounts
f = getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
if (f != null) {
if (f.getCount() != count) {
throw new IIOException
("StripByteCounts count != number of offsets");
}
byteCounts.add(f);
}
// TileByteCounts
f = getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
if (f != null) {
if (f.getCount() != count) {
throw new IIOException
("TileByteCounts count != number of offsets");
}
byteCounts.add(f);
}
if (byteCounts.size() > 0) {
for (TIFFField offset : offsets) {
for (TIFFField byteCount : byteCounts) {
for (int i = 0; i < count; i++) {
long dataOffset = offset.getAsLong(i);
long dataByteCount = byteCount.getAsLong(i);
if (dataOffset + dataByteCount > streamLength) {
throw new IIOException
("Data segment out of stream");
}
}
}
}
}
}
// JPEGInterchangeFormat and JPEGInterchangeFormatLength
TIFFField jpegOffset =
getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
if (jpegOffset != null) {
TIFFField jpegLength =
getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
if (jpegLength != null) {
if (jpegOffset.getAsLong(0) + jpegLength.getAsLong(0)
> streamLength) {
throw new IIOException
("JPEGInterchangeFormat data out of stream");
}
}
}
// Ensure there is at least a data pointer for JPEG interchange format or
// both data offsets and byte counts for other compression types.
if (jpegOffset == null
&& (offsets.size() == 0 || byteCounts.size() == 0)) {
boolean throwException = true;
if (offsets.size() != 0 && byteCounts.size() == 0) {
// Attempt to calculate missing byte counts
int expectedSize = offsets.get(0).getCount();
throwException =
!calculateByteCounts(expectedSize, byteCounts);
}
if (throwException) {
throw new IIOException
("Insufficient data offsets or byte counts");
}
}
// JPEGQTables - one 64-byte table for each offset.
f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES);
if (f != null) {
long[] tableOffsets = f.getAsLongs();
for (long off : tableOffsets) {
if (off + 64 > streamLength) {
throw new IIOException("JPEGQTables data out of stream");
}
}
}
// JPEGDCTables
f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_DC_TABLES);
if (f != null) {
long[] tableOffsets = f.getAsLongs();
for (long off : tableOffsets) {
if (off + 16 > streamLength) {
throw new IIOException("JPEGDCTables data out of stream");
}
}
}
// JPEGACTables
f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_AC_TABLES);
if (f != null) {
long[] tableOffsets = f.getAsLongs();
for (long off : tableOffsets) {
if (off + 16 > streamLength) {
throw new IIOException("JPEGACTables data out of stream");
}
}
}
}
// Stream position initially at beginning, left at end
// if readUnknownTags is false, do not load fields for which
// a tag cannot be found in an allowed TagSet.
public void initialize(ImageInputStream stream, boolean isPrimaryIFD,
boolean ignoreMetadata, boolean readUnknownTags) throws IOException {
removeTIFFFields();
long streamLength = stream.length();
boolean haveStreamLength = streamLength != -1;
List<TIFFTagSet> tagSetList = getTagSetList();
// Configure essential tag variables if this is the primary IFD and
// either all metadata are being ignored, or metadata are not being
// ignored but both unknown tags are being ignored and the tag set
// list does not contain the baseline tags.
boolean ensureEssentialTags = false;
TIFFTagSet baselineTagSet = null;
if (isPrimaryIFD &&
(ignoreMetadata ||
(!readUnknownTags &&
!tagSetList.contains(BaselineTIFFTagSet.getInstance())))) {
ensureEssentialTags = true;
initializeEssentialTags();
baselineTagSet = BaselineTIFFTagSet.getInstance();
}
List<Object> entries = new ArrayList<>();
Object[] entryData = new Object[1]; // allocate once for later reuse.
// Read the IFD entries, loading the field values which are no more than
// four bytes long, and storing the 4-byte offsets for the others.
int numEntries = stream.readUnsignedShort();
for (int i = 0; i < numEntries; i++) {
// Read tag number, value type, and value count.
int tagNumber = stream.readUnsignedShort();
int type = stream.readUnsignedShort();
int sizeOfType;
try {
sizeOfType = TIFFTag.getSizeOfType(type);
} catch (IllegalArgumentException ignored) {
// Continue with the next IFD entry.
stream.skipBytes(4);
continue;
}
long longCount = stream.readUnsignedInt();
// Get the associated TIFFTag.
TIFFTag tag = getTag(tagNumber, tagSetList);
if (tag == null && ensureEssentialTags
&& essentialTags.contains(tagNumber)) {
tag = baselineTagSet.getTag(tagNumber);
}
// Ignore non-essential fields, unknown fields unless forcibly
// being read, fields with unknown type, and fields
// with count out of int range.
if((ignoreMetadata &&
(!ensureEssentialTags || !essentialTags.contains(tagNumber)))
|| (tag == null && !readUnknownTags)
|| (tag != null && !tag.isDataTypeOK(type))
|| longCount > Integer.MAX_VALUE) {
// Skip the value/offset so as to leave the stream
// position at the start of the next IFD entry.
stream.skipBytes(4);
// Continue with the next IFD entry.
continue;
}
int count = (int)longCount;
if (tag == null) {
tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, tagNumber,
1 << type, count);
} else {
int expectedCount = tag.getCount();
if (expectedCount > 0) {
// If the tag count is positive then the tag defines a
// specific, fixed count that the field must match.
if (count != expectedCount) {
throw new IIOException("Unexpected count "
+ count + " for " + tag.getName() + " field");
}
} else if (type == TIFFTag.TIFF_ASCII) {
// Clamp the size of ASCII fields of unspecified length
// to a maximum value.
int asciiSize = TIFFTag.getSizeOfType(TIFFTag.TIFF_ASCII);
if (count*asciiSize > MAX_ASCII_SIZE) {
count = (int)(MAX_ASCII_SIZE/asciiSize);
}
}
}
long longSize = longCount*sizeOfType;
if (longSize > Integer.MAX_VALUE) {
// Continue with the next IFD entry.
stream.skipBytes(4);
continue;
}
int size = (int)longSize;
if (size > 4 || tag.isIFDPointer()) {
// The IFD entry value is a pointer to the actual field value.
long offset = stream.readUnsignedInt();
// Check whether the the field value is within the stream.
if (haveStreamLength && offset + size > streamLength) {
continue;
}
// Add a TIFFIFDEntry as a placeholder. This avoids a mark,
// seek to the data, and a reset.
entries.add(new TIFFIFDEntry(tag, type, count, offset));
} else {
// The IFD entry value is the actual field value of no more than
// four bytes.
Object obj = null;
try {
// Read the field value and update the count.
count = readFieldValue(stream, type, count, entryData);
obj = entryData[0];
} catch (EOFException eofe) {
// The TIFF 6.0 fields have tag numbers less than or equal
// to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright).
// If there is an error reading a baseline tag, then re-throw
// the exception and fail; otherwise continue with the next
// field.
if (BaselineTIFFTagSet.getInstance().getTag(tagNumber) == null) {
throw eofe;
}
}
// If the field value is smaller than four bytes then skip
// the remaining, unused bytes.
if (size < 4) {
stream.skipBytes(4 - size);
}
// Add the populated TIFFField to the list of entries.
entries.add(new TIFFField(tag, type, count, obj));
}
}
// After reading the IFD entries the stream is positioned at an unsigned
// four byte integer containing either the offset of the next IFD or
// zero if this is the last IFD.
long nextIFDOffset = stream.getStreamPosition();
Object[] fieldData = new Object[1];
for (Object entry : entries) {
if (entry instanceof TIFFField) {
// Add the populated field directly.
addTIFFField((TIFFField)entry);
} else {
TIFFIFDEntry e = (TIFFIFDEntry)entry;
TIFFTag tag = e.tag;
int tagNumber = tag.getNumber();
int type = e.type;
int count = e.count;
stream.seek(e.offset);
if (tag.isIFDPointer()) {
List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
tagSets.add(tag.getTagSet());
TIFFIFD subIFD = new TIFFIFD(tagSets);
subIFD.initialize(stream, false, ignoreMetadata,
readUnknownTags);
TIFFField f = new TIFFField(tag, type, e.offset, subIFD);
addTIFFField(f);
} else {
if (tagNumber == BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS
|| tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS
|| tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {
this.stripOrTileByteCountsPosition
= stream.getStreamPosition();
} else if (tagNumber == BaselineTIFFTagSet.TAG_STRIP_OFFSETS
|| tagNumber == BaselineTIFFTagSet.TAG_TILE_OFFSETS
|| tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) {
this.stripOrTileOffsetsPosition
= stream.getStreamPosition();
}
Object obj = null;
try {
count = readFieldValue(stream, type, count, fieldData);
obj = fieldData[0];
} catch (EOFException eofe) {
// The TIFF 6.0 fields have tag numbers less than or equal
// to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright).
// If there is an error reading a baseline tag, then re-throw
// the exception and fail; otherwise continue with the next
// field.
if (BaselineTIFFTagSet.getInstance().getTag(tagNumber) != null) {
throw eofe;
}
}
if (obj == null) {
continue;
}
TIFFField f = new TIFFField(tag, type, count, obj);
addTIFFField(f);
}
}
}
if(isPrimaryIFD && haveStreamLength) {
checkFieldOffsets(streamLength);
}
stream.seek(nextIFDOffset);
this.lastPosition = stream.getStreamPosition();
}
public void writeToStream(ImageOutputStream stream)
throws IOException {
int numFields = getNumTIFFFields();
stream.writeShort(numFields);
long nextSpace = stream.getStreamPosition() + 12*numFields + 4;
Iterator<TIFFField> iter = iterator();
while (iter.hasNext()) {
TIFFField f = iter.next();
TIFFTag tag = f.getTag();
int type = f.getType();
int count = f.getCount();
// Deal with unknown tags
if (type == 0) {
type = TIFFTag.TIFF_UNDEFINED;
}
int size = count*TIFFTag.getSizeOfType(type);
if (type == TIFFTag.TIFF_ASCII) {
int chars = 0;
for (int i = 0; i < count; i++) {
chars += f.getAsString(i).length() + 1;
}
count = chars;
size = count;
}
int tagNumber = f.getTagNumber();
stream.writeShort(tagNumber);
stream.writeShort(type);
stream.writeInt(count);
// Write a dummy value to fill space
stream.writeInt(0);
stream.mark(); // Mark beginning of next field
stream.skipBytes(-4);
long pos;
if (size > 4 || tag.isIFDPointer()) {
// Ensure IFD or value is written on a word boundary
nextSpace = (nextSpace + 3) & ~0x3;
stream.writeInt((int)nextSpace);
stream.seek(nextSpace);
pos = nextSpace;
if (tag.isIFDPointer() && f.hasDirectory()) {
TIFFIFD subIFD = getDirectoryAsIFD(f.getDirectory());
subIFD.writeToStream(stream);
nextSpace = subIFD.lastPosition;
} else {
writeTIFFFieldToStream(f, stream);
nextSpace = stream.getStreamPosition();
}
} else {
pos = stream.getStreamPosition();
writeTIFFFieldToStream(f, stream);
}
// If we are writing the data for the
// StripByteCounts, TileByteCounts, StripOffsets,
// TileOffsets, JPEGInterchangeFormat, or
// JPEGInterchangeFormatLength fields, record the current stream
// position for backpatching
if (tagNumber ==
BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS ||
tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS ||
tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {
this.stripOrTileByteCountsPosition = pos;
} else if (tagNumber ==
BaselineTIFFTagSet.TAG_STRIP_OFFSETS ||
tagNumber ==
BaselineTIFFTagSet.TAG_TILE_OFFSETS ||
tagNumber ==
BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) {
this.stripOrTileOffsetsPosition = pos;
}
stream.reset(); // Go to marked position of next field
}
this.lastPosition = nextSpace;
}
public long getStripOrTileByteCountsPosition() {
return stripOrTileByteCountsPosition;
}
public long getStripOrTileOffsetsPosition() {
return stripOrTileOffsetsPosition;
}
public long getLastPosition() {
return lastPosition;
}
void setPositions(long stripOrTileOffsetsPosition,
long stripOrTileByteCountsPosition,
long lastPosition) {
this.stripOrTileOffsetsPosition = stripOrTileOffsetsPosition;
this.stripOrTileByteCountsPosition = stripOrTileByteCountsPosition;
this.lastPosition = lastPosition;
}
/**
* Returns a {@code TIFFIFD} wherein all fields from the
* {@code BaselineTIFFTagSet} are copied by value and all other
* fields copied by reference.
*/
public TIFFIFD getShallowClone() {
// Get the baseline TagSet.
TIFFTagSet baselineTagSet = BaselineTIFFTagSet.getInstance();
// If the baseline TagSet is not included just return.
List<TIFFTagSet> tagSetList = getTagSetList();
if(!tagSetList.contains(baselineTagSet)) {
return this;
}
// Create a new object.
TIFFIFD shallowClone = new TIFFIFD(tagSetList, getParentTag());
// Get the tag numbers in the baseline set.
Set<Integer> baselineTagNumbers = baselineTagSet.getTagNumbers();
// Iterate over the fields in this IFD.
Iterator<TIFFField> fields = iterator();
while(fields.hasNext()) {
// Get the next field.
TIFFField field = fields.next();
// Get its tag number.
Integer tagNumber = Integer.valueOf(field.getTagNumber());
// Branch based on membership in baseline set.
TIFFField fieldClone;
if(baselineTagNumbers.contains(tagNumber)) {
// Copy by value.
Object fieldData = field.getData();
int fieldType = field.getType();
try {
switch (fieldType) {
case TIFFTag.TIFF_BYTE:
case TIFFTag.TIFF_SBYTE:
case TIFFTag.TIFF_UNDEFINED:
fieldData = ((byte[])fieldData).clone();
break;
case TIFFTag.TIFF_ASCII:
fieldData = ((String[])fieldData).clone();
break;
case TIFFTag.TIFF_SHORT:
fieldData = ((char[])fieldData).clone();
break;
case TIFFTag.TIFF_LONG:
case TIFFTag.TIFF_IFD_POINTER:
fieldData = ((long[])fieldData).clone();
break;
case TIFFTag.TIFF_RATIONAL:
fieldData = ((long[][])fieldData).clone();
break;
case TIFFTag.TIFF_SSHORT:
fieldData = ((short[])fieldData).clone();
break;
case TIFFTag.TIFF_SLONG:
fieldData = ((int[])fieldData).clone();
break;
case TIFFTag.TIFF_SRATIONAL:
fieldData = ((int[][])fieldData).clone();
break;
case TIFFTag.TIFF_FLOAT:
fieldData = ((float[])fieldData).clone();
break;
case TIFFTag.TIFF_DOUBLE:
fieldData = ((double[])fieldData).clone();
break;
default:
// Shouldn't happen but do nothing ...
}
} catch(Exception e) {
// Ignore it and copy by reference ...
}
fieldClone = new TIFFField(field.getTag(), fieldType,
field.getCount(), fieldData);
} else {
// Copy by reference.
fieldClone = field;
}
// Add the field to the clone.
shallowClone.addTIFFField(fieldClone);
}
// Set positions.
shallowClone.setPositions(stripOrTileOffsetsPosition,
stripOrTileByteCountsPosition,
lastPosition);
return shallowClone;
}
}