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 long readLong(SeekableStream stream) throws IOException {
   if (isBigEndian) {
     return stream.readLong();
   } else {
     return stream.readLongLE();
   }
 }
 private long readUnsignedInt(SeekableStream stream) throws IOException {
   if (isBigEndian) {
     return stream.readUnsignedInt();
   } else {
     return stream.readUnsignedIntLE();
   }
 }
 private short readShort(SeekableStream stream) throws IOException {
   if (isBigEndian) {
     return stream.readShort();
   } else {
     return stream.readShortLE();
   }
 }
 private static int readUnsignedShort(SeekableStream stream, boolean isBigEndian)
     throws IOException {
   if (isBigEndian) {
     return stream.readUnsignedShort();
   } else {
     return stream.readUnsignedShortLE();
   }
 }
  /**
   * Returns the number of image directories (subimages) stored in a given TIFF file, represented by
   * a <code>SeekableStream</code>.
   */
  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("TIFFDirectory1");
    }
    boolean isBigEndian = (endian == 0x4d4d);
    int magic = readUnsignedShort(stream, isBigEndian);
    if (magic != 42) {
      throw new IllegalArgumentException("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.skipFully(12 * entries);
      offset = readUnsignedInt(stream, isBigEndian);
    }

    stream.seek(pointer); // Reset stream pointer
    return numDirectories;
  }
  /**
   * 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 ifd_offset 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 ifd_offset, int directory) throws IOException {

    long global_save_offset = stream.getFilePointer();
    stream.seek(0L);
    int endian = stream.readUnsignedShort();
    if (!isValidEndianTag(endian)) {
      throw new IllegalArgumentException("TIFFDirectory1");
    }
    isBigEndian = (endian == 0x4d4d);

    // Seek to the first IFD.
    stream.seek(ifd_offset);

    // 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(ifd_offset + 12 * numEntries);

      // Read the offset to the next IFD beyond this one.
      ifd_offset = readUnsignedInt(stream);

      // Seek to the next IFD.
      stream.seek(ifd_offset);

      // Increment the directory.
      dirNum++;
    }

    initialize(stream);
    stream.seek(global_save_offset);
  }
  /**
   * 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 global_save_offset = stream.getFilePointer();
    long ifd_offset;

    // Read the TIFF header
    stream.seek(0L);
    int endian = stream.readUnsignedShort();
    if (!isValidEndianTag(endian)) {
      throw new IllegalArgumentException("TIFFDirectory1");
    }
    isBigEndian = (endian == 0x4d4d);

    int magic = readUnsignedShort(stream);
    if (magic != 42) {
      throw new IllegalArgumentException("TIFFDirectory2");
    }

    // Get the initial ifd offset as an unsigned int (using a long)
    ifd_offset = readUnsignedInt(stream);

    for (int i = 0; i < directory; i++) {
      if (ifd_offset == 0L) {
        throw new IllegalArgumentException("TIFFDirectory3");
      }

      stream.seek(ifd_offset);
      long entries = readUnsignedShort(stream);
      stream.skipFully(12 * entries);

      ifd_offset = readUnsignedInt(stream);
    }

    stream.seek(ifd_offset);
    initialize(stream);
    stream.seek(global_save_offset);
  }
  private void initialize(SeekableStream stream) throws IOException {
    long nextTagOffset;
    int i, 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 * sizeOfType[type] > 4) {
          value = (int) (readUnsignedInt(stream));
          stream.seek(value);
        }
      } catch (ArrayIndexOutOfBoundsException ae) {

        System.err.println(tag + " " + "TIFFDirectory4");
        // 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, prevIndex = 0;
            List v = new ArrayList();

            while (index < count) {

              while ((index < count) && (bvalues[index++] != 0)) ;

              // When we encountered zero, means one string has ended
              v.add(new String(bvalues, prevIndex, (index - prevIndex)));
              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:
          System.err.println("TIFFDirectory0");
          break;
      }

      fields[i] = new TIFFField(tag, type, count, obj);
      stream.seek(nextTagOffset);
    }

    // Read the offset of the next IFD.
    nextIFDOffset = readUnsignedInt(stream);
  }