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

edu.ucr.cs.bdlab.io.tiff.Raster Maven / Gradle / Ivy

/*
 * Copyright 2018 University of California, Riverside
 *
 * Licensed 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.
 */
package edu.ucr.cs.bdlab.io.tiff;

import edu.ucr.cs.bdlab.util.MathUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * A raster layer from the TIFF file.
 */
public class Raster {
  private static final Log LOG = LogFactory.getLog(Raster.class);

  /**
   * No compression, but pack data into bytes as tightly as possible, leaving no unused
   * bits (except at the end of a row). The component values are stored as an array of
   * type BYTE. Each scan line (row) is padded to the next BYTE boundary.
   */
  public static final int COMPRESSION_NONE = 1;

  /**LZW compression scheme*/
  public static final int COMPRESSION_LZW = 5;

  /**JPEG compression*/
  public static final int COMPRESSION_JPEG = 7;

  /**The associated TIFF file*/
  protected final ITiffReader reader;

  /**
   * A predictor is a mathematical operator that is applied to the image data before an encoding scheme is applied.
   * 1 = No prediction scheme used before coding.
   * 2 = Horizontal differencing.
   */
  protected final int predictor;

  /**IDF entries associated with this layer in the TIFF file*/
  protected AbstractIFDEntry[] entries;

  /**Width of the raster. Number of pixels per row.*/
  protected int width;

  /**Height of the raster. Number of rows (scan lines).*/
  protected int height;

  /**
   * Width of each tile in pixels. If the file is stored in a strip representation, tileWidth = width
   */
  protected int tileWidth;

  /**
   * Height of each tile in pixels. If the file is stored in a strip representation, tileHeight = rowsPerStrip
   */
  protected int tileHeight;

  /**
   * Offsets of tiles if the file is stored in a tile format; or offsets of strips if strip format
   */
  protected long[] tileOffsets;

  /**
   * Sizes of tiles in number of bytes (number of compressed bytes if compressed); if the file is stored in a strip
   * representation, this will be the strip lengths.
   */
  protected int[] tileByteCounts;

  /**Index of the tile that is currently loaded or -1 if not tile is currently loaded.*/
  protected int currentTileIndex = -1;

  /**Number of bits for each sample. A sample is like a component in the pixel.*/
  protected int[] bitsPerSample;

  /**The sum of the entries in the field {@link #bitsPerSample}*/
  protected transient int bitsPerPixel;

  /** A reusable buffer for reading contents of IFD entries*/
  protected transient ByteBuffer buffer;

  /**
   * This field specifies how to interpret each data sample in a pixel. Possible values are:
   * 1 = unsigned integer data
   * 2 = two's complement signed integer data
   * 3 = IEEE floating point data [IEEE]
   * 4 = undefined data format
   */
  protected int[] sampleFormats;

  public static final int SAMPLE_UNSIGNED_INT = 1;
  public static final int SAMPLE_SIGNED_INT = 2;
  public static final int SAMPLE_IEEE_FLOAT = 3;
  public static final int SAMPLE_UNDEFINED = 4;

  /**The decoded (decompressed) data of the current strip.*/
  protected ByteBuffer currentTileData;

  /**
   * How the components of each pixel are stored.
   * 1 = {@link #ChunkyFormat}
   * 2 = {@link #PlanarFormat}
   */
  protected int planarConfiguration;

  /**
   * 1 = Chunky format. The component values for each pixel are stored contiguously.
   * The order of the components within the pixel is specified by
   * PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB...
   */
  public static final int ChunkyFormat = 1;

  /**
   * 2 = Planar format. The components are stored in separate "component planes." The
   * values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional
   * array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns for row 0 are stored first,
   * followed by the columns of row 1, and so on.)
   * PhotometricInterpretation describes the type of data stored in each component plane.
   * For example, RGB data is stored with the Red components in one component plane, the Green in another,
   * and the Blue in another.
   */
  public static final int PlanarFormat = 2;

  /**
   * Initializes a raster layer for the given TIFF file and the given layer number in it.
   * @param reader
   * @param iLayer
   */
  public Raster(ITiffReader reader, int iLayer) throws IOException {
    this.entries = reader.getDirectoryEntries(iLayer);
    this.reader = reader;
    this.width = getEntry(IFDEntry.TAG_IMAGE_WIDTH).getOffsetAsInt();
    this.height = getEntry(IFDEntry.TAG_IMAGE_LENGTH).getOffsetAsInt();
    // Retrieve the tiling information depending on the representation type
    AbstractIFDEntry rowsPerStrip = getEntry(IFDEntry.TAG_ROWS_PER_STRIP);
    if (rowsPerStrip != null) {
      // File stored in stripped format
      this.tileWidth = this.width;
      this.tileHeight = rowsPerStrip.getOffsetAsInt();
      int numStrips = getNumTilesY();
      // Retrieve offsets of strips
      AbstractIFDEntry ifdStripOffsets = getEntry(IFDEntry.TAG_STRIP_OFFSETS);
      assert ifdStripOffsets.getCountAsInt() == numStrips;
      this.tileOffsets = getLongValues(ifdStripOffsets);
      // Retrieve lengths of strips
      AbstractIFDEntry ifdStripLengths = getEntry(IFDEntry.TAG_STRIP_BYTE_COUNTS);
      assert ifdStripLengths.getCountAsInt() == numStrips;
      this.tileByteCounts = getIntValues(ifdStripLengths);
    } else {
      // File stored in grid format
      this.tileWidth = getEntry(IFDEntry.TAG_TILE_WIDTH).getOffsetAsInt();
      this.tileHeight = getEntry(IFDEntry.TAG_TILE_LENGTH).getOffsetAsInt();
      int numTiles = getNumTilesX() * getNumTilesY();
      // Retrieve tile offsets
      AbstractIFDEntry ifdTileOffsets = getEntry(IFDEntry.TAG_TILE_OFFSETS);
      assert ifdTileOffsets.getCountAsInt() == numTiles;
      this.tileOffsets = getLongValues(ifdTileOffsets);
      // Retrieve tile lengths (sizes in bytes)
      AbstractIFDEntry ifdTileByteCounts = getEntry(IFDEntry.TAG_TILE_BYTE_COUNTS);
      assert ifdTileByteCounts.getCountAsInt() == numTiles;
      this.tileByteCounts = getIntValues(ifdTileByteCounts);
    }
    int samplesPerPixel = getEntry(IFDEntry.TAG_SAMPLES_PER_PIXEL).getOffsetAsInt();
    this.bitsPerSample = getIntValues(getEntry(IFDEntry.TAG_BITS_PER_SAMPLE));
    AbstractIFDEntry sampleFormatEntry = getEntry(IFDEntry.TAG_SAMPLE_FORMAT);
    if (sampleFormatEntry != null) {
      this.sampleFormats = getIntValues(sampleFormatEntry);
    } else {
      // Use default values
      this.sampleFormats = new int[samplesPerPixel];
      Arrays.fill(this.sampleFormats, 1);
    }
    assert this.bitsPerSample.length == this.sampleFormats.length;
    assert this.bitsPerSample.length == samplesPerPixel;
    for (int x : bitsPerSample)
      bitsPerPixel += x;
    this.planarConfiguration = getEntry(IFDEntry.TAG_PLANAR_CONFIGURATION).getOffsetAsInt();
    AbstractIFDEntry predictorEntry = getEntry(IFDEntry.TAG_PREDICTOR);
    this.predictor = predictorEntry == null ? 1 : predictorEntry.getOffsetAsInt();
  }

  /**
   * Width of the raster layer in pixels
   * @return
   */
  public int getWidth() {
    return width;
  }

  /**
   * Height of the raster layer in pixels.
   * @return
   */
  public int getHeight() {
    return height;
  }

  /**
   * Number of tiles along the x-axis = ⌈image width / tile width⌉
   * @return
   */
  public int getNumTilesX() {
    return (width + tileWidth - 1) / tileWidth;
  }

  /**
   * Number of tiles along the y-axis = ⌈ image height / tile height ⌉
   * @return
   */
  public int getNumTilesY() {
    return (height + tileHeight - 1) / tileHeight;
  }

  /**
   * Returns the value of the pixel at column i and row j as an integer. If it has more than one component,
   * they are concatenated together into one integer. If the components cannot fit into one integer (i.e., more than
   * 32 bits), an exception is thrown.
   * @param iPixel column-coordinate of the pixel
   * @param jPixel row-coordinate of the pixel
   * @return
   */
  public int getPixel(int iPixel, int jPixel) throws IOException {
    if (bitsPerPixel > 32)
      throw new RuntimeException(String.format("The pixel value is too big to fit in a 32-bit integer (%d > 32)", bitsPerPixel));
    loadTileAtPixel(iPixel, jPixel);
    int val = 0;
    int pixelOffset = (jPixel % tileHeight) * tileWidth + (iPixel % tileWidth);
    switch (planarConfiguration) {
      case ChunkyFormat:
        int bitOffset = pixelOffset * bitsPerPixel;
        for (int iSample = 0; iSample < bitsPerSample.length; iSample++) {
          val = (val << bitsPerSample[iSample]) | MathUtil.getBits(currentTileData.array(), bitOffset, bitsPerSample[iSample]);
          bitOffset += bitsPerSample[iSample];
        }
        break;
      case PlanarFormat:
        throw new RuntimeException("Planar format is not yet supported");
    }
    return val;
  }

  /**
   * Loads the data of the tile that contains the given pixel
   * @param iPixel
   * @param jPixel
   * @throws IOException
   */
  public void loadTileAtPixel(int iPixel, int jPixel) throws IOException {
    int iTile = iPixel / tileWidth;
    int jTile = jPixel / tileHeight;
    int requiredStripIndex = jTile * getNumTilesX() + iTile;
//    if(requiredStripIndex==89662)
//      throw new RuntimeException(iPixel + "," + jPixel + "," + tileWidth + "," + tileHeight + "," + iTile + "," + jTile);
    if (requiredStripIndex != currentTileIndex)
      readTileData(requiredStripIndex);
  }

  /**
   * Returns the value of the given pixel and band as an integer
   * @param iPixel
   * @param jPixel
   * @param iSample which band (or sample) to read for the given pixel
   * @return
   */
  public int getSampleValueAsInt(int iPixel, int jPixel, int iSample) throws IOException {
    int val = getRawSampleValue(iPixel, jPixel, iSample);
    switch (sampleFormats[iSample]) {
      case SAMPLE_IEEE_FLOAT:
        return Math.round(Float.intBitsToFloat(val));
      case SAMPLE_SIGNED_INT:
        //diff return value
        return (short) val;
      case SAMPLE_UNSIGNED_INT:
      case SAMPLE_UNDEFINED:
      default:
        return val;
    }
  }

  /**
   * Returns the value of the given pixel and band as an double-precision floating point value
   * @param iPixel
   * @param jPixel
   * @param iSample
   * @return
   */
  protected float getSampleValueAsFloat(int iPixel, int jPixel, int iSample) throws IOException {
    int val = getRawSampleValue(iPixel, jPixel, iSample);
    switch (sampleFormats[iSample]) {
      case SAMPLE_IEEE_FLOAT:
        return Float.intBitsToFloat(val);
      case SAMPLE_SIGNED_INT:
        return (short)val;
      case SAMPLE_UNSIGNED_INT:
      case SAMPLE_UNDEFINED:
      default:
        return (float) val;
    }
  }

  /**
   * Get the sample value bits as an integer without intepreting the sample format.
   * @param iPixel
   * @param jPixel
   * @param iSample
   * @return
   * @throws IOException
   */
  public int getRawSampleValue(int iPixel, int jPixel, int iSample) throws IOException {
    loadTileAtPixel(iPixel, jPixel);
    int val = 0;
    int pixelOffset = (jPixel % tileHeight) * tileWidth + (iPixel % tileWidth);
    switch (planarConfiguration) {
      case ChunkyFormat:
        int bitOffset = pixelOffset * bitsPerPixel;
        int i = 0;
        while (i < iSample)
          bitOffset += bitsPerSample[i++];
        val = MathUtil.getBits(currentTileData.array(), bitOffset, bitsPerSample[iSample]);
        if (reader.isLittleEndian())
          val = reverseValue(val, bitsPerSample[iSample]);
        break;
      case PlanarFormat:
        throw new RuntimeException("Planar format is not yet supported");
    }
    return val;
  }

  /**
   * Reverse a value (Little Endian < = > Big Endian)
   * @param value
   * @param numBits
   * @return
   */
  protected static int reverseValue(int value, int numBits) {
    if (numBits == 32) {
      int valReversed = value >>> 24;
      valReversed |= (value >>> 8) & 0xff00;
      valReversed |= (value << 8) & 0xff0000;
      valReversed |= (value << 24) & 0xff000000;
      value = valReversed;
    } else if (numBits == 16) {
      int valReversed = value >>> 8;
      valReversed |= (value << 8) & 0xff00;
      value = valReversed;
    }
    return value;
  }

  /**
   * Loads the data of the given tile (or strip) in the this raster.
   * If the data is compressed, this function decompresses it. The loaded data is stored in the
   * {@link #currentTileData} field. If the given tile is already loaded, this function does nothing.
   * The field {@link #currentTileIndex} is updated to point to the given tile index.
   * on the fly.
   * @param desiredTileIndex
   */
  protected void readTileData(int desiredTileIndex) throws IOException {
    if (this.currentTileIndex == desiredTileIndex)
      return;
    this.currentTileIndex = desiredTileIndex;
    byte[] rawData = new byte[tileByteCounts[desiredTileIndex]];
    reader.readRawData(tileOffsets[desiredTileIndex], rawData);
    int compressionScheme = getEntry(IFDEntry.TAG_COMPRESSION).getOffsetAsInt();
    switch (compressionScheme) {
      case COMPRESSION_LZW:
          byte[] decompressedData = LZWDecoder.decode(rawData);
          currentTileData = ByteBuffer.wrap(decompressedData);
        break;
      case COMPRESSION_NONE:
        currentTileData = ByteBuffer.wrap(rawData);
        break;
      default:
        throw new RuntimeException(String.format("Unsupported compression scheme %d", compressionScheme));
    }
    if (predictor == 2) {
      // Apply the differencing algorithm
      // Special case when all components are 8-bits which make the differencing simpler
      boolean allSamplesEightBits = true;
      int iSample = 0;
      while (allSamplesEightBits && iSample < getNumSamples() && bitsPerSample[iSample] == 8)
        iSample++;
      if (allSamplesEightBits) {
        // This is the easiest case to handle with an efficient algorithm
        byte[] data = currentTileData.array();
        int numSamples = getNumSamples();
        for (int jPixel = 0; jPixel < tileHeight; jPixel++) {
          int offset = (jPixel * width + 1) * numSamples;
          int endOffset = ((jPixel + 1) * width) * numSamples;
          while (offset < endOffset) {
            data[offset] += data[offset - numSamples];
            offset++;
          }
        }
      } else {
        throw new RuntimeException("Not implemented yet for samples of sizes "+Arrays.asList(bitsPerSample));
      }
    }

  }

  public int getTileWidth() {
    return tileWidth;
  }

  public int getTileHeight() {
    return tileHeight;
  }

  public int getNumSamples() {
    return bitsPerSample.length;
  }

  /**
   * Returns all the sample values at the given pixel location as integer. If these samples are stored in other formats,
   * e.g., byte or short, they are converted to integer with the same value. If the samples are represented as
   * floating-point numbers, they are rounded to the nearest integer.
   * @param iPixel
   * @param jPixel
   * @param value
   * @throws IOException
   */
  public void getPixelSamplesAsInt(int iPixel, int jPixel, int[] value) throws IOException {
    assert value.length == getNumSamples();
    for (int iSample = 0; iSample < bitsPerSample.length; iSample++)
      value[iSample] = getSampleValueAsInt(iPixel, jPixel, iSample);
  }

  public void getPixelSamplesAsFloat(int iPixel, int jPixel, float[] value) throws IOException {
    assert value.length == getNumSamples();
    for (int iSample = 0; iSample < bitsPerSample.length; iSample++)
      value[iSample] = getSampleValueAsFloat(iPixel, jPixel, iSample);
  }

  /**
   * Returns the list of IFD entries for this raster.
   * @return
   */
  public AbstractIFDEntry[] getEntries() {
    return entries;
  }

  /**
   * Returns the IFD Entry for the given tag and associated with this raster.
   * @param tag
   * @return
   */
  public AbstractIFDEntry getEntry(short tag) {
    // TODO if the list is long, use binary search as the entries are already sorted by tag
    for (AbstractIFDEntry entry : entries) {
      if (entry.tag == tag)
        return entry;
    }
    return null;
  }

  public static final Map TagNames = new HashMap<>();
  static {
    TagNames.put(IFDEntry.TAG_NEW_SUBFILE_TYPE, "New Subfile Type");
    TagNames.put(IFDEntry.TAG_IMAGE_WIDTH, "Image Width");
    TagNames.put(IFDEntry.TAG_IMAGE_LENGTH, "Image Length");
    TagNames.put(IFDEntry.TAG_BITS_PER_SAMPLE, "Bits per sample");
    TagNames.put(IFDEntry.TAG_COMPRESSION, "Compression");
    TagNames.put(IFDEntry.TAG_PHOTOMETRIC_INTERPRETATION, "Photometric Interpretation");
    TagNames.put(IFDEntry.TAG_DOCUMENT_NAME, "Document Name");
    TagNames.put(IFDEntry.TAG_STRIP_OFFSETS, "Strip Offsets");
    TagNames.put(IFDEntry.TAG_ORIENTATION, "Orientation");
    TagNames.put(IFDEntry.TAG_SAMPLES_PER_PIXEL, "Samples per Pixel");
    TagNames.put(IFDEntry.TAG_ROWS_PER_STRIP, "Rows pre Strip");
    TagNames.put(IFDEntry.TAG_STRIP_BYTE_COUNTS, "Strip Byte Counts");
    TagNames.put(IFDEntry.TAG_XRESOLUTION, "X Resolution");
    TagNames.put(IFDEntry.TAG_YRESOLUTION, "Y Resolution");
    TagNames.put(IFDEntry.TAG_PLANAR_CONFIGURATION, "Planar Configuration");
    TagNames.put(IFDEntry.TAG_RESOLUTION_UNIT, "Resolution Unit");
    TagNames.put(IFDEntry.TAG_PAGE_NUMBER, "Page Number");
    TagNames.put(IFDEntry.TAG_PREDICTOR, "Predictor");
    TagNames.put(IFDEntry.TAG_COLOR_MAP, "Color Map");
    TagNames.put(IFDEntry.TAG_TILE_WIDTH, "Tile Width");
    TagNames.put(IFDEntry.TAG_TILE_LENGTH, "Tile Length");
    TagNames.put(IFDEntry.TAG_TILE_OFFSETS, "Tile Offsets");
    TagNames.put(IFDEntry.TAG_TILE_BYTE_COUNTS, "Tile Byte Counts");
    TagNames.put(IFDEntry.TAG_EXTRA_SAMPLES, "Extra Samples");
    TagNames.put(IFDEntry.TAG_SAMPLE_FORMAT, "Sample Format");
    TagNames.put(IFDEntry.TAG_FILL_ORDER, "Fill Order");
    TagNames.put(IFDEntry.TAG_WHITE_POINT, "White Point");
    TagNames.put(IFDEntry.TAG_PRIMARY_CHROMATICITIES, "Primary Chromaticities");
    TagNames.put(IFDEntry.TAG_JPEG_TABLES, "JPEG Tables");
    TagNames.put(IFDEntry.TAG_REFERENCE_BLACK_WHITE, "Reference black white");
  }

  /**
   * Write all entry data to the given print stream; used for debugging purposes.
   * @param out
   */
  public void dumpEntries(PrintStream out) throws IOException {
    ByteBuffer buffer = null;
    for (AbstractIFDEntry entry : entries) {
      buffer = reader.readEntry(entry, buffer);
      out.printf("#%d ", entry.tag & 0xffff);
      String tagName = TagNames.get(entry.tag);
      out.print(tagName != null? tagName : "Unknown Tag");
      out.print(" - ");
      if (entry.getCountAsInt() > 1)
        out.print("[");
      switch (entry.type) {
        case IFDEntry.TYPE_BYTE:
        default:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.get() & 0xff);
          }
          break;
        case IFDEntry.TYPE_SBYTE:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.get());
          }
          break;
        case IFDEntry.TYPE_SHORT:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.getShort() & 0xffff);
          }
          break;
        case IFDEntry.TYPE_SSHORT:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.getShort());
          }
          break;
        case IFDEntry.TYPE_LONG:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.getInt() & 0xffffffffL);
          }
          break;
        case IFDEntry.TYPE_SLONG:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%d", buffer.getInt());
          }
          break;
        case IFDEntry.TYPE_ASCII:
          out.print('"');
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            char c = (char) buffer.get();
            if (c == 0) {
              out.print('"');
              if ($i < entry.getCountAsInt() - 1) {
                out.print(", \"");
              }
            } else {
              out.print(c);
            }
          }
          break;
        case IFDEntry.TYPE_FLOAT:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%f", Float.intBitsToFloat(buffer.getInt()));
          }
          break;
        case IFDEntry.TYPE_DOUBLE:
          for (int $i = 0; $i < entry.getCountAsInt(); $i++) {
            if ($i > 0)
              out.print(", ");
            out.printf("%f", Double.longBitsToDouble(buffer.getLong()));
          }
          break;
      }
      if (entry.getCountAsInt() > 1)
        out.print("]");
      out.println();
    }
  }

  /**
   * Returns all the values in the given entry as an integer array. This method has to implemented at the TiffReader
   * level as it might need to read the underlying file.
   * @return
   */
  public int[] getIntValues(AbstractIFDEntry entry) throws IOException {
    int[] values = new int[entry.getCountAsInt()];
    buffer = reader.readEntry(entry, buffer);
    switch (entry.type) {
      case IFDEntry.TYPE_BYTE:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = 0xff & buffer.get();
        break;
      case IFDEntry.TYPE_SBYTE:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.get();
        break;
      case IFDEntry.TYPE_SHORT:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = 0xffff & buffer.getShort();
        break;
      case IFDEntry.TYPE_SSHORT:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.getShort();
        break;
      case IFDEntry.TYPE_LONG:
        // TODO we should handle unsigned 32-bit integers that are larger than INT_MAX
      case IFDEntry.TYPE_SLONG:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.getInt();
        break;
      case IFDEntry.TYPE_LONG8:
      case IFDEntry.TYPE_SLONG8:
      case IFDEntry.TYPE_IFD8:
        for (int $i = 0; $i < values.length; $i++) {
          long value = buffer.getLong();
          if (value > Integer.MAX_VALUE)
            throw new RuntimeException("Value too big");
          values[$i] = (int) value;
        }
        break;
      default:
        throw new RuntimeException(String.format("Unsupported type %d", entry.type));
    }
    return values;
  }


  /**
   * Returns all the values in the given entry as an integer array. This method has to implemented at the TiffReader
   * level as it might need to read the underlying file.
   * @return
   */
  public long[] getLongValues(AbstractIFDEntry entry) throws IOException {
    long[] values = new long[entry.getCountAsInt()];
    buffer = reader.readEntry(entry, buffer);
    switch (entry.type) {
      case IFDEntry.TYPE_BYTE:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = 0xff & buffer.get();
        break;
      case IFDEntry.TYPE_SBYTE:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.get();
        break;
      case IFDEntry.TYPE_SHORT:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = 0xffff & buffer.getShort();
        break;
      case IFDEntry.TYPE_SSHORT:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.getShort();
        break;
      case IFDEntry.TYPE_LONG:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = (long)buffer.getInt() & 0xffffffffL;
        break;
        // TODO we should handle unsigned 32-bit integers that are larger than INT_MAX
      case IFDEntry.TYPE_SLONG:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.getInt();
        break;
      case IFDEntry.TYPE_LONG8:
      case IFDEntry.TYPE_SLONG8:
      case IFDEntry.TYPE_IFD8:
        for (int $i = 0; $i < values.length; $i++)
          values[$i] = buffer.getLong();
        break;
      default:
        throw new RuntimeException(String.format("Unsupported type %d", entry.type));
    }
    return values;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy