All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlgraphics.image.codec.tiff.TIFFDirectory Maven / Gradle / Ivy

There is a newer version: 1.2.2.1-jre17
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: TIFFDirectory.java 1732018 2016-02-24 04:51:06Z gadams $ */

package org.apache.xmlgraphics.image.codec.tiff;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.xmlgraphics.image.codec.util.PropertyUtil;
import org.apache.xmlgraphics.image.codec.util.SeekableStream;

// CSOFF: ConstantName
// CSOFF: EmptyStatement
// CSOFF: InnerAssignment
// CSOFF: LocalVariableName
// CSOFF: MemberName
// CSOFF: MultipleVariableDeclarations
// CSOFF: NeedBraces
// CSOFF: ParameterName
// CSOFF: WhitespaceAround

/**
 * A class representing an Image File Directory (IFD) from a TIFF 6.0
 * stream.  The TIFF file format is described in more detail in the
 * comments for the TIFFDescriptor class.
 *
 * 

A TIFF IFD consists of a set of TIFFField tags. Methods are * provided to query the set of tags and to obtain the raw field * array. In addition, convenience methods are provided for acquiring * the values of tags that contain a single value that fits into a * byte, int, long, float, or double. * *

Every TIFF file is made up of one or more public IFDs that are * joined in a linked list, rooted in the file header. A file may * also contain so-called private IFDs that are referenced from * tag data and do not appear in the main list. * *

This class is not a committed part of the JAI API. It may * be removed or changed in future releases of JAI. * * @see TIFFField * @version $Id: TIFFDirectory.java 1732018 2016-02-24 04:51:06Z gadams $ */ public class TIFFDirectory implements Serializable { private static final long serialVersionUID = 2007844835460959003L; /** A boolean storing the endianness of the stream. */ boolean isBigEndian; /** The number of entries in the IFD. */ int numEntries; /** An array of TIFFFields. */ TIFFField[] fields; /** A Hashtable indexing the fields by tag number. */ Map fieldIndex = new HashMap(); /** The offset of this IFD. */ long ifdOffset = 8; /** The offset of the next IFD. */ long nextIFDOffset; /** The default constructor. */ TIFFDirectory() { } private static boolean isValidEndianTag(int endian) { return ((endian == 0x4949) || (endian == 0x4d4d)); } /** * Constructs a TIFFDirectory from a SeekableStream. * The directory parameter specifies which directory to read from * the linked list present in the stream; directory 0 is normally * read but it is possible to store multiple images in a single * TIFF file by maintaing multiple directories. * * @param stream a SeekableStream to read from. * @param directory the index of the directory to read. */ public TIFFDirectory(SeekableStream stream, int directory) throws IOException { long globalSaveOffset = stream.getFilePointer(); long ifdOffset; // Read the TIFF header stream.seek(0L); int endian = stream.readUnsignedShort(); if (!isValidEndianTag(endian)) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory1")); } isBigEndian = (endian == 0x4d4d); int magic = readUnsignedShort(stream); if (magic != 42) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory2")); } // Get the initial ifd offset as an unsigned int (using a long) ifdOffset = readUnsignedInt(stream); for (int i = 0; i < directory; i++) { if (ifdOffset == 0L) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory3")); } stream.seek(ifdOffset); long entries = readUnsignedShort(stream); stream.skip(12 * entries); ifdOffset = readUnsignedInt(stream); } if (ifdOffset == 0L) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory3")); } stream.seek(ifdOffset); initialize(stream); stream.seek(globalSaveOffset); } /** * Constructs a TIFFDirectory by reading a SeekableStream. * The ifd_offset parameter specifies the stream offset from which * to begin reading; this mechanism is sometimes used to store * private IFDs within a TIFF file that are not part of the normal * sequence of IFDs. * * @param stream a SeekableStream to read from. * @param ifdOffset the long byte offset of the directory. * @param directory the index of the directory to read beyond the * one at the current stream offset; zero indicates the IFD * at the current offset. */ public TIFFDirectory(SeekableStream stream, long ifdOffset, int directory) throws IOException { long globalSaveOffset = stream.getFilePointer(); stream.seek(0L); int endian = stream.readUnsignedShort(); if (!isValidEndianTag(endian)) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory1")); } isBigEndian = (endian == 0x4d4d); // Seek to the first IFD. stream.seek(ifdOffset); // Seek to desired IFD if necessary. int dirNum = 0; while (dirNum < directory) { // Get the number of fields in the current IFD. long numEntries = readUnsignedShort(stream); // Skip to the next IFD offset value field. stream.seek(ifdOffset + 12 * numEntries); // Read the offset to the next IFD beyond this one. ifdOffset = readUnsignedInt(stream); // Seek to the next IFD. stream.seek(ifdOffset); // Increment the directory. dirNum++; } initialize(stream); stream.seek(globalSaveOffset); } private static final int[] SIZE_OF_TYPE = { 0, // 0 = n/a 1, // 1 = byte 1, // 2 = ascii 2, // 3 = short 4, // 4 = long 8, // 5 = rational 1, // 6 = sbyte 1, // 7 = undefined 2, // 8 = sshort 4, // 9 = slong 8, // 10 = srational 4, // 11 = float 8 // 12 = double }; private void initialize(SeekableStream stream) throws IOException { long nextTagOffset; int i; int j; ifdOffset = stream.getFilePointer(); numEntries = readUnsignedShort(stream); fields = new TIFFField[numEntries]; for (i = 0; i < numEntries; i++) { int tag = readUnsignedShort(stream); int type = readUnsignedShort(stream); int count = (int)(readUnsignedInt(stream)); int value = 0; // The place to return to to read the next tag nextTagOffset = stream.getFilePointer() + 4; try { // If the tag data can't fit in 4 bytes, the next 4 bytes // contain the starting offset of the data if (count * SIZE_OF_TYPE[type] > 4) { value = (int)(readUnsignedInt(stream)); stream.seek(value); } } catch (ArrayIndexOutOfBoundsException ae) { // System.err.println(tag + " " + "TIFFDirectory4"); TODO - log this message // if the data type is unknown we should skip this TIFF Field stream.seek(nextTagOffset); continue; } fieldIndex.put(tag, i); Object obj = null; switch (type) { case TIFFField.TIFF_BYTE: case TIFFField.TIFF_SBYTE: case TIFFField.TIFF_UNDEFINED: case TIFFField.TIFF_ASCII: byte[] bvalues = new byte[count]; stream.readFully(bvalues, 0, count); if (type == TIFFField.TIFF_ASCII) { // Can be multiple strings int index = 0; int prevIndex = 0; List v = new ArrayList(); while (index < count) { while ((index < count) && (bvalues[index++] != 0)) { // NOP } // When we encountered zero, means one string has ended v.add(new String(bvalues, prevIndex, (index - prevIndex), "UTF-8")); prevIndex = index; } count = v.size(); String[] strings = new String[count]; v.toArray(strings); obj = strings; } else { obj = bvalues; } break; case TIFFField.TIFF_SHORT: char[] cvalues = new char[count]; for (j = 0; j < count; j++) { cvalues[j] = (char)(readUnsignedShort(stream)); } obj = cvalues; break; case TIFFField.TIFF_LONG: long[] lvalues = new long[count]; for (j = 0; j < count; j++) { lvalues[j] = readUnsignedInt(stream); } obj = lvalues; break; case TIFFField.TIFF_RATIONAL: long[][] llvalues = new long[count][2]; for (j = 0; j < count; j++) { llvalues[j][0] = readUnsignedInt(stream); llvalues[j][1] = readUnsignedInt(stream); } obj = llvalues; break; case TIFFField.TIFF_SSHORT: short[] svalues = new short[count]; for (j = 0; j < count; j++) { svalues[j] = readShort(stream); } obj = svalues; break; case TIFFField.TIFF_SLONG: int[] ivalues = new int[count]; for (j = 0; j < count; j++) { ivalues[j] = readInt(stream); } obj = ivalues; break; case TIFFField.TIFF_SRATIONAL: int[][] iivalues = new int[count][2]; for (j = 0; j < count; j++) { iivalues[j][0] = readInt(stream); iivalues[j][1] = readInt(stream); } obj = iivalues; break; case TIFFField.TIFF_FLOAT: float[] fvalues = new float[count]; for (j = 0; j < count; j++) { fvalues[j] = readFloat(stream); } obj = fvalues; break; case TIFFField.TIFF_DOUBLE: double[] dvalues = new double[count]; for (j = 0; j < count; j++) { dvalues[j] = readDouble(stream); } obj = dvalues; break; default: throw new RuntimeException(PropertyUtil.getString("TIFFDirectory0")); } fields[i] = new TIFFField(tag, type, count, obj); stream.seek(nextTagOffset); } // Read the offset of the next IFD. nextIFDOffset = readUnsignedInt(stream); } /** Returns the number of directory entries. */ public int getNumEntries() { return numEntries; } /** * Returns the value of a given tag as a TIFFField, * or null if the tag is not present. */ public TIFFField getField(int tag) { Integer i = (Integer)fieldIndex.get(tag); if (i == null) { return null; } else { return fields[i]; } } /** * Returns true if a tag appears in the directory. */ public boolean isTagPresent(int tag) { return fieldIndex.containsKey(tag); } /** * Returns an ordered array of ints indicating the tag * values. */ public int[] getTags() { int[] tags = new int[fieldIndex.size()]; Iterator iter = fieldIndex.keySet().iterator(); int i = 0; while (iter.hasNext()) { tags[i++] = (Integer) iter.next(); } return tags; } /** * Returns an array of TIFFFields containing all the fields * in this directory. */ public TIFFField[] getFields() { return fields; } /** * Returns the value of a particular index of a given tag as a * byte. The caller is responsible for ensuring that the tag is * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or * TIFF_UNDEFINED. */ public byte getFieldAsByte(int tag, int index) { Integer i = (Integer)fieldIndex.get(tag); byte [] b = (fields[i]).getAsBytes(); return b[index]; } /** * Returns the value of index 0 of a given tag as a * byte. The caller is responsible for ensuring that the tag is * present and has type TIFFField.TIFF_SBYTE, TIFF_BYTE, or * TIFF_UNDEFINED. */ public byte getFieldAsByte(int tag) { return getFieldAsByte(tag, 0); } /** * Returns the value of a particular index of a given tag as a * long. The caller is responsible for ensuring that the tag is * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG. */ public long getFieldAsLong(int tag, int index) { Integer i = (Integer)fieldIndex.get(tag); return (fields[i]).getAsLong(index); } /** * Returns the value of index 0 of a given tag as a * long. The caller is responsible for ensuring that the tag is * present and has type TIFF_BYTE, TIFF_SBYTE, TIFF_UNDEFINED, * TIFF_SHORT, TIFF_SSHORT, TIFF_SLONG or TIFF_LONG. */ public long getFieldAsLong(int tag) { return getFieldAsLong(tag, 0); } /** * Returns the value of a particular index of a given tag as a * float. The caller is responsible for ensuring that the tag is * present and has numeric type (all but TIFF_UNDEFINED and * TIFF_ASCII). */ public float getFieldAsFloat(int tag, int index) { Integer i = (Integer)fieldIndex.get(tag); return fields[i].getAsFloat(index); } /** * Returns the value of index 0 of a given tag as a float. The * caller is responsible for ensuring that the tag is present and * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII). */ public float getFieldAsFloat(int tag) { return getFieldAsFloat(tag, 0); } /** * Returns the value of a particular index of a given tag as a * double. The caller is responsible for ensuring that the tag is * present and has numeric type (all but TIFF_UNDEFINED and * TIFF_ASCII). */ public double getFieldAsDouble(int tag, int index) { Integer i = (Integer)fieldIndex.get(tag); return fields[i].getAsDouble(index); } /** * Returns the value of index 0 of a given tag as a double. The * caller is responsible for ensuring that the tag is present and * has numeric type (all but TIFF_UNDEFINED and TIFF_ASCII). */ public double getFieldAsDouble(int tag) { return getFieldAsDouble(tag, 0); } // Methods to read primitive data types from the stream private short readShort(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readShort(); } else { return stream.readShortLE(); } } private int readUnsignedShort(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readUnsignedShort(); } else { return stream.readUnsignedShortLE(); } } private int readInt(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readInt(); } else { return stream.readIntLE(); } } private long readUnsignedInt(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readUnsignedInt(); } else { return stream.readUnsignedIntLE(); } } // private long readLong(SeekableStream stream) // throws IOException { // if (isBigEndian) { // return stream.readLong(); // } else { // return stream.readLongLE(); // } // } private float readFloat(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readFloat(); } else { return stream.readFloatLE(); } } private double readDouble(SeekableStream stream) throws IOException { if (isBigEndian) { return stream.readDouble(); } else { return stream.readDoubleLE(); } } private static int readUnsignedShort(SeekableStream stream, boolean isBigEndian) throws IOException { if (isBigEndian) { return stream.readUnsignedShort(); } else { return stream.readUnsignedShortLE(); } } private static long readUnsignedInt(SeekableStream stream, boolean isBigEndian) throws IOException { if (isBigEndian) { return stream.readUnsignedInt(); } else { return stream.readUnsignedIntLE(); } } // Utilities /** * Returns the number of image directories (subimages) stored in a * given TIFF file, represented by a SeekableStream. */ public static int getNumDirectories(SeekableStream stream) throws IOException { long pointer = stream.getFilePointer(); // Save stream pointer stream.seek(0L); int endian = stream.readUnsignedShort(); if (!isValidEndianTag(endian)) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory1")); } boolean isBigEndian = (endian == 0x4d4d); int magic = readUnsignedShort(stream, isBigEndian); if (magic != 42) { throw new IllegalArgumentException(PropertyUtil.getString("TIFFDirectory2")); } stream.seek(4L); long offset = readUnsignedInt(stream, isBigEndian); int numDirectories = 0; while (offset != 0L) { ++numDirectories; stream.seek(offset); long entries = readUnsignedShort(stream, isBigEndian); stream.skip(12 * entries); offset = readUnsignedInt(stream, isBigEndian); } stream.seek(pointer); // Reset stream pointer return numDirectories; } /** * Returns a boolean indicating whether the byte order used in the * the TIFF file is big-endian. That is, whether the byte order is from * the most significant to the least significant. */ public boolean isBigEndian() { return isBigEndian; } /** * Returns the offset of the IFD corresponding to this * TIFFDirectory. */ public long getIFDOffset() { return ifdOffset; } /** * Returns the offset of the next IFD after the IFD corresponding to this * TIFFDirectory. */ public long getNextIFDOffset() { return nextIFDOffset; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy