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

edu.harvard.hul.ois.jhove.module.tiff.IFD Maven / Gradle / Ivy

The newest version!
/**********************************************************************
 * Jhove - JSTOR/Harvard Object Validation Environment
 * Copyright 2004 by JSTOR and the President and Fellows of Harvard College
 **********************************************************************/

package edu.harvard.hul.ois.jhove.module.tiff;

import edu.harvard.hul.ois.jhove.*;
import java.io.*;
import java.text.*;
import java.util.*;

/**
 * Encapsulation of a TIFF image file directory (IFD).
 */
public abstract class IFD
{
    
    /******************************************************************
     * DEBUGGING FIELDS.
     * All debugging fields should be set to false for release code.
     ******************************************************************/
    
    /* Set to true to allow out-of-sequence tags. */
    private static final boolean debug_allowoutofsequence = false;
    
    /******************************************************************
     * PUBLIC CLASS FIELDS.
     ******************************************************************/

    /** Standard TIFF IFD. */
    public static final int TIFF = 0;
    /** Exif IFD. */
    public static final int EXIF = 1;
    /** Exif Interoperability IFD. */
    public static final int INTEROPERABILITY = 2;
    /** GPSInfo IFD. */
    public static final int GPSINFO = 3;
    /** Global parameters IFD. */
    public static final int GLOBALPARAMETERS = 4;
    

    /** Undefined value for integer tags. */
    public static final int NULL = -1;

    /******************************************************************
     * PRIVATE CLASS FIELDS.
     ******************************************************************/

    /* TIFF data types. */

    /** TIFF BYTE (unsigned 8-bit) type. */
    public static final int BYTE = 1;
    /** TIFF ASCII type. */
    public static final int ASCII = 2;
    /** TIFF SHORT (unsigned 16-bit) type. */
    public static final int SHORT = 3;
    /** TIFF LONG (unsigned 32-bit) type. */
    public static final int LONG = 4;
    /** TIFF RATIONAL (two LONGs) type. */
    public static final int RATIONAL = 5;
    /** TIFF SBYTE (signed 8-bit) type. */
    public static final int SBYTE = 6;
    /** TIFF UNDEFINED (unsigned 8-bit) type. */
    public static final int UNDEFINED = 7;
    /** TIFF SSHORT (signed 16-bit) type. */
    public static final int SSHORT = 8;
    /** TIFF SLONG (signed 32-bit) type. */
    public static final int SLONG = 9;
    /** TIFF SRATIONAL (two SLONGs) type. */
    public static final int SRATIONAL = 10;
    /** TIFF FLOAT (32-bit IEEE floating point) type. */
    public static final int FLOAT = 11;
    /** TIFF DOUBLE (64-bit IEEE floating point) type. */
    public static final int DOUBLE = 12;
    /** TIFF IFD (LONG) type. */
    public static final int IFD = 13;

    /** TIFF type labels. */
    public static final String TYPE [] = {
        "", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "SBYTE", "UNDEFINED",
        "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", "IFD"
    };

    /******************************************************************
     * PRIVATE INSTANCE FIELDS.
     ******************************************************************/

    /** True if big-endian file. */
    protected boolean _bigEndian;

    /** List of errors. */
    private List _errors;

    /** True if this is the first IFD. */
    private boolean _first;
    
    /** True if the is the "thumbnail" IFD. */
    private boolean _thumbnail;

    /** Format for converting float to string. */
    private NumberFormat _format;

    /** Representation information. */
    protected RepInfo _info;

    /** Offset of next IFD. */
    protected long _next;

    /** IFD offset. */
    protected long _offset;
    
    /** Open random access TIFF file. */
    private RandomAccessFile _raf;

    /** TIFF version. */
    protected int _version;

    /******************************************************************
     * CLASS CONSTRUCTOR.
     ******************************************************************/

    /** Instantiate an IFD object.
     * @param offset IFD offset
     * @param info Representation information
     * @param raf TIFF file
     * @param bigEndian True if big-endian file
     */
    public IFD(long offset, RepInfo info, RandomAccessFile raf,
                boolean bigEndian)
    {
        _offset    = offset;
        _info      = info;
        _raf       = raf;
        _bigEndian = bigEndian;

        _first   = false;
        _thumbnail = false;
        _next    = 0L;
        _version = 4;

        _errors = new LinkedList();

        _format = NumberFormat.getInstance();
        _format.setGroupingUsed(false);
        _format.setMinimumFractionDigits(0);
    }

    /******************************************************************
     * PUBLIC INSTANCE METHODS.
     ******************************************************************/

    /** Get any errors discovered during parsing. */
    public List getErrors()
    {
        return _errors;
    }

    /** Get the offset of the next IFD. */
    public long getNext()
    {
        return _next;
    }

    /** Get the IFD offset. */
    public long getOffset()
    {
        return _offset;
    }

    /** Get the IFD properties. */
    public abstract Property getProperty(boolean rawOutput)
                        throws TiffException;

    /** Get the TIFF version. */
    public int getVersion()
    {
        return _version;
    }

    /** Return true if this is the first IFD. */
    public boolean isFirst()
    {
        return _first;
    }
    
    /** Return true if this is the thumbnail IFD. */
    public boolean isThumbnail()
    {
        return _thumbnail;
    }
    

    /** Lookup IFD tag. */
    public abstract void lookupTag(int tag, int type, long count, long value)
        throws TiffException;

    /** Parse the IFD. Errors are not suppressed, and odd byte offsets for
     *  tags not allowed.
     *  
     * @return The offset of the next IFD
     */
    public long parse()
        throws TiffException
    {
	return parse(false, false);
    }

    /** Parse the IFD.
     * @param byteOffsetIsValid   If true, allow offsets on odd byte boundaries
     * @param suppressErrors      If true, return IFD even with errors
     * @return The offset of the next IFD
     */
    public long parse(boolean byteOffsetIsValid, boolean suppressErrors)
        throws TiffException
    {
        try {
            return parse(byteOffsetIsValid);
        }
        catch (TiffException e) {
            // If we got a TiffException and we're suppressing errors,
            // cover over the exception and issue an info message; 
            // but we can't follow the IFD chain further.
            if (suppressErrors) {
                _info.setMessage
                    (new InfoMessage(e.getMessage(), e.getOffset()));
                return 0;
            }
            throw e;
        }
    }
    
    
    /** Parse the IFD. Errors are not suppressed. 
     * 
     * @param byteOffsetIsValid   If true, allow offsets on odd byte boundaries
     * @return The offset of the next IFD
     */
    public long parse(boolean byteOffsetIsValid)
        throws TiffException
    {
        /* Start at the IFD offset, read the number of entries, then
         * read the entire IFD.
         */
        long offset = _offset;
        _next = 0L;
        byte [] buffer;
        int nFields = 0;
        try {
            _raf.seek(offset);
            nFields = ModuleBase.readUnsignedShort(_raf, _bigEndian);
            offset += 2;

            int len = 12*nFields;
            buffer = new byte[len];
            _raf.read(buffer, 0, len);

            /* Read the offset of the next IFD (or 0 if none). */
            offset += len;
            _next = ModuleBase.readUnsignedInt(_raf, _bigEndian);
        }
        catch (Exception e) {
            throw new TiffException(MessageConstants.ERR_TIFF_PREM_EOF, offset);
        }

        DataInputStream ifdStream =
            new DataInputStream(new ByteArrayInputStream(buffer));

        try {
            int prevTag = 0;
            for (int i=0; i IFD) {
                    _info.setMessage(new ErrorMessage(MessageConstants.ERR_UNK_DATA_TYPE,
                    		                          MessageConstants.ERR_UNK_DATA_TYPE_SUB_1 + type +
                    		                          MessageConstants.ERR_UNK_DATA_TYPE_SUB_2 + tag,
                    		                          _offset + 4 + 12*i));
                }
                else {
                    /* Type gives indication of the TIFF version. */
                    if (SBYTE <= type && type <= IFD) {
                        _version = 6;
                    }

                    long count = ModuleBase.readUnsignedInt(ifdStream,
                                                            _bigEndian, null);
                    long value = ModuleBase.readUnsignedInt(ifdStream,
                                                            _bigEndian, null);
                    if (calcValueSize(type, count) > 4) {
                        /* Value is the word-aligned offset of the actual
                         * value. */
			if ((value & 1) != 0) {
				final String message = MessageConstants.INF_VALOFF_NOT_WORD_ALIGN + value;
			    if (byteOffsetIsValid) {
				_info.setMessage(new InfoMessage(message, _offset + 10 + 12*i));
			    }
			    else {
				throw new TiffException(message,_offset + 10 + 12*i);
			    }
                        }
                    }
                    else {
                        /* Value is the actual value; pass the offset of
                         * the value. */
                        value = _offset + 10 + 12*i;
                    }
                    lookupTag(tag, type, count, value);
                }
            }
        }
        catch (IOException e) {
            throw new TiffException(MessageConstants.ERR_IO_READ, _offset + 2);
        }
        postParseInitialization();

        return _next;
    }

    /** Sets flag indicating whether this is the first IFD. */
    public void setFirst(boolean first)
    {
        _first = first;
    }
    
    /** Sets flag indicating whether this is the "thumbnail" IFD.
     *  The second IFD in the top-level chain is assumed to be
     *  the Thumbnail IFD. */
    public void setThumbnail(boolean thumbnail)
    {
        _thumbnail = thumbnail;
    }

    /** 
     *  Returns a Property representing a bitmask.
     *  If rawOutput is true, returns a LIST
     *  property whose elements are STRING properties.  The
     *  string values of these STRING properties are the
     *  elements of labels whose indices
     *  correspond to 1 bits in the bitmask, counting
     *  the low-order bit as bit 0.
     *  if rawOutput is false, returns a LONG
     *  property whose numeric value is value.
     */
    protected Property addBitmaskProperty(String name, long value,
                                          String [] labels, boolean rawOutput)
    {
        Property prop = null;
        if (!rawOutput) {
            List list = new LinkedList();
            try {
                for (int i=0; irawOutput is true, returns
     *  an INTEGER property, and labels and
     *  index are unused.  Otherwise,
     *  returns a STRING property, with the
     *  string being the element of labels
     *  whose index is value.
     */
    protected Property addIntegerProperty(String name, int value,
                                           String [] labels, boolean rawOutput)
    {
        Property prop = null;
        if (!rawOutput) {
            try {
                prop = new Property(name, PropertyType.STRING, labels[value]);
            }
            catch (ArrayIndexOutOfBoundsException aioobe) {
                _errors.add(name + MessageConstants.ERR_VAL_OUT_OF_RANGE + value);
            }
        }
        if (prop == null) {
            prop = new Property(name, PropertyType.INTEGER,
                                 new Integer(value));
        }

        return prop;
    }

    /** 
     *  Returns an Property representing an integer value.
     *  If rawOutput is true, returns
     *  an INTEGER property, and labels and
     *  index are unused.  Otherwise,
     *  returns a STRING property, with the
     *  string being the element of labels
     *  whose index is the index of
     *  value in index.
     */
    protected Property addIntegerProperty(String name, int value,
                                          String [] labels, int [] index,
                                          boolean rawOutput)
    {
        Property prop = null;
        if (!rawOutput) {
            int n = -1;
            for (int i = 0; i < index.length; i++) {
                if (value == index[i]) {
                    n = i;
                    break;
                }
            }
            if (n > -1) {
                prop = new Property(name, PropertyType.STRING, labels[n]);
            }
            else {
                _errors.add(name + MessageConstants.ERR_VAL_OUT_OF_RANGE + value);
            }
        }
        if (prop == null) {
            prop = new Property(name, PropertyType.INTEGER,
                                 new Integer(value));
        }

        return prop;
    }

    /** 
     *  Returns an ARRAY Property representing an integer array.
     *  If rawOutput is true, the elements of the property array
     *  are INTEGER properties, and labels is unused.  Otherwise,
     *  the elements of the array are STRING properties, with the
     *  elements of value used as indices into
     *  labels.
     */
    protected Property addIntegerArrayProperty(String name, int [] value,
                                                String [] labels,
                                                boolean rawOutput)
    {
        Property prop = null;
        if (!rawOutput) {
            String [] s = new String[value.length];
            for (int i=0; i 127) {
                sb.append(byteToHex(c));
            }
            else {
                sb.append((char) c);
            }
        }
        return sb.toString();
    }

    /** Reads an array of strings from the TIFF file.
     * 
     *  @param  count  Number of strings to read
     *  @param  value  Offset from which to read
     *  
     */
    protected String [] readASCIIArray(long count, long value)
        throws IOException
    {
        _raf.seek(value);

        int nstrs = 0;
        List list = new LinkedList();
        byte[] buf = new byte[(int) count];
        _raf.read(buf);
        StringBuffer strbuf = new StringBuffer();
        for (int i=0; i 127) {
                    strbuf.append(byteToHex((byte) b));
                }
                else {
                    strbuf.append((char) b);
                }
            }
        }
        /* We can't use ArrayList.toArray because that returns an 
           Object[], not a String[] ... sigh. */
        String [] strs = new String[nstrs];
        ListIterator iter = list.listIterator();
        for (int i=0; itrue if file is big-endian, 
     *  false if little-endian.
     */
    public boolean isBigEndian()
    {
        return _bigEndian;
    }



    /******************************************************************
     * PRIVATE CLASS METHODS.
     ******************************************************************/

    /**
     * Check the tag entry count.
     * @param tag Tag entry value
     * @param count Tag entry count
     * @param minCount Tag count
     */
    protected static void checkCount(int tag, long count, int minCount)
        throws TiffException
    {
        if (count < minCount) {
            throw new TiffException(MessageConstants.ERR_TAG_COUNT_MISMATCH + tag +
            						MessageConstants.MISMATCH_SUB_1 + minCount +
            						MessageConstants.MISMATCH_SUB_2 + count);
        }
    }

    /**
     * Check the tag entry type.
     * @param tag Tag entry value
     * @param type Tag entry type
     * @param expected Tag type
     */
    protected static void checkType(int tag, int type, int expected)
        throws TiffException
    {
        /* Readers are supposed to accept BYTE, SHORT or LONG for any
         * unsigned integer field. */
        if (type == BYTE || type == SHORT || type == LONG || type == IFD) {
            if (expected == BYTE || expected == SHORT || expected == LONG ||
		expected == IFD) {
                return;    // it's OK
            }
        }
        if (type != expected) {
            throw new TiffException(MessageConstants.ERR_TAG_TYPE_MISMATCH + tag +
            						MessageConstants.MISMATCH_SUB_1 + expected +
            						MessageConstants.MISMATCH_SUB_2 + type);
        }
    }

    /**
     * Check the tag entry type.
     * @param tag Tag entry value
     * @param type Tag entry type
     * @param type1 Tag type
     * @param type2 Alternate tag type
     */
    protected static void checkType(int tag, int type, int type1, int type2)
        throws TiffException
    {
        if (type != type1 && type != type2) {
            throw new TiffException(MessageConstants.ERR_TAG_TYPE_MISMATCH + tag +
            						MessageConstants.MISMATCH_SUB_1 + type1 +
            						MessageConstants.MISMATCH_SUB_3 + type2 +
            						MessageConstants.MISMATCH_SUB_2 + type);
        }
    }

    protected static Rational average(Rational r1, Rational r2)
    {
        long d1 = r1.getDenominator();
        long d2 = r2.getDenominator();

        Rational f1 = new Rational(r1.getNumerator()*d2,
                                   r1.getDenominator()*d2);
        Rational f2 = new Rational(r2.getNumerator()*d1,
                                   r2.getDenominator()*d1);

        return new Rational((f1.getNumerator() + f2.getNumerator())/2,
                             f1.getDenominator());
    }


    /******************************************************************
     * PRIVATE INSTANCE METHODS.
     ******************************************************************/

    /** Represent a byte value as %XX */
    private String byteToHex(byte c) {
        int[] nibbles = new int[2];
        nibbles[0] = ((int) c & 0XF0) >> 4;
        nibbles[1] = (int) c & 0X0F;
        StringBuffer retval = new StringBuffer("%");
        for (int i = 0; i <= 1; i++) {
            int b = nibbles[i];
            if (b >= 10) {
                b += (int) 'A' - 10;
            }
            else {
                b += (int) '0';
            }
            retval.append((char) b);
        }
        return retval.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy