// // IFD.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.tiff; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashMap; import loci.formats.FormatException; import loci.formats.FormatTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Data structure for working with TIFF Image File Directories (IFDs). * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/tiff/IFD.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/tiff/IFD.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Curtis Rueden ctrueden at wisc.edu * @author Eric Kjellman egkjellman at wisc.edu * @author Melissa Linkert melissa at glencoesoftware.com * @author Chris Allan callan at blackcat.ca */ public class IFD extends HashMap<Integer, Object> { // -- Constants -- private static final Logger LOGGER = LoggerFactory.getLogger(IFD.class); // non-IFD tags (for internal use) public static final int LITTLE_ENDIAN = 0; public static final int BIG_TIFF = 1; public static final int REUSE = 3; // IFD tags public static final int NEW_SUBFILE_TYPE = 254; public static final int SUBFILE_TYPE = 255; public static final int IMAGE_WIDTH = 256; public static final int IMAGE_LENGTH = 257; public static final int BITS_PER_SAMPLE = 258; public static final int COMPRESSION = 259; public static final int PHOTOMETRIC_INTERPRETATION = 262; public static final int THRESHHOLDING = 263; public static final int CELL_WIDTH = 264; public static final int CELL_LENGTH = 265; public static final int FILL_ORDER = 266; public static final int DOCUMENT_NAME = 269; public static final int IMAGE_DESCRIPTION = 270; public static final int MAKE = 271; public static final int MODEL = 272; public static final int STRIP_OFFSETS = 273; public static final int ORIENTATION = 274; public static final int SAMPLES_PER_PIXEL = 277; public static final int ROWS_PER_STRIP = 278; public static final int STRIP_BYTE_COUNTS = 279; public static final int MIN_SAMPLE_VALUE = 280; public static final int MAX_SAMPLE_VALUE = 281; public static final int X_RESOLUTION = 282; public static final int Y_RESOLUTION = 283; public static final int PLANAR_CONFIGURATION = 284; public static final int PAGE_NAME = 285; public static final int X_POSITION = 286; public static final int Y_POSITION = 287; public static final int FREE_OFFSETS = 288; public static final int FREE_BYTE_COUNTS = 289; public static final int GRAY_RESPONSE_UNIT = 290; public static final int GRAY_RESPONSE_CURVE = 291; public static final int T4_OPTIONS = 292; public static final int T6_OPTIONS = 293; public static final int RESOLUTION_UNIT = 296; public static final int PAGE_NUMBER = 297; public static final int TRANSFER_FUNCTION = 301; public static final int SOFTWARE = 305; public static final int DATE_TIME = 306; public static final int ARTIST = 315; public static final int HOST_COMPUTER = 316; public static final int PREDICTOR = 317; public static final int WHITE_POINT = 318; public static final int PRIMARY_CHROMATICITIES = 319; public static final int COLOR_MAP = 320; public static final int HALFTONE_HINTS = 321; public static final int TILE_WIDTH = 322; public static final int TILE_LENGTH = 323; public static final int TILE_OFFSETS = 324; public static final int TILE_BYTE_COUNTS = 325; public static final int SUB_IFD = 330; public static final int INK_SET = 332; public static final int INK_NAMES = 333; public static final int NUMBER_OF_INKS = 334; public static final int DOT_RANGE = 336; public static final int TARGET_PRINTER = 337; public static final int EXTRA_SAMPLES = 338; public static final int SAMPLE_FORMAT = 339; public static final int S_MIN_SAMPLE_VALUE = 340; public static final int S_MAX_SAMPLE_VALUE = 341; public static final int TRANSFER_RANGE = 342; public static final int JPEG_TABLES = 347; public static final int JPEG_PROC = 512; public static final int JPEG_INTERCHANGE_FORMAT = 513; public static final int JPEG_INTERCHANGE_FORMAT_LENGTH = 514; public static final int JPEG_RESTART_INTERVAL = 515; public static final int JPEG_LOSSLESS_PREDICTORS = 517; public static final int JPEG_POINT_TRANSFORMS = 518; public static final int JPEG_Q_TABLES = 519; public static final int JPEG_DC_TABLES = 520; public static final int JPEG_AC_TABLES = 521; public static final int Y_CB_CR_COEFFICIENTS = 529; public static final int Y_CB_CR_SUB_SAMPLING = 530; public static final int Y_CB_CR_POSITIONING = 531; public static final int REFERENCE_BLACK_WHITE = 532; public static final int COPYRIGHT = 33432; public static final int EXIF = 34665; /** EXIF tags. */ public static final int EXPOSURE_TIME = 33434; public static final int F_NUMBER = 33437; public static final int EXPOSURE_PROGRAM = 34850; public static final int SPECTRAL_SENSITIVITY = 34852; public static final int ISO_SPEED_RATINGS = 34855; public static final int OECF = 34856; public static final int EXIF_VERSION = 36864; public static final int DATE_TIME_ORIGINAL = 36867; public static final int DATE_TIME_DIGITIZED = 36868; public static final int COMPONENTS_CONFIGURATION = 37121; public static final int COMPRESSED_BITS_PER_PIXEL = 37122; public static final int SHUTTER_SPEED_VALUE = 37377; public static final int APERTURE_VALUE = 37378; public static final int BRIGHTNESS_VALUE = 37379; public static final int EXPOSURE_BIAS_VALUE = 37380; public static final int MAX_APERTURE_VALUE = 37381; public static final int SUBJECT_DISTANCE = 37382; public static final int METERING_MODE = 37383; public static final int LIGHT_SOURCE = 37384; public static final int FLASH = 37385; public static final int FOCAL_LENGTH = 37386; public static final int MAKER_NOTE = 37500; public static final int USER_COMMENT = 37510; public static final int SUB_SEC_TIME = 37520; public static final int SUB_SEC_TIME_ORIGINAL = 37521; public static final int SUB_SEC_TIME_DIGITIZED = 37522; public static final int FLASH_PIX_VERSION = 40960; public static final int COLOR_SPACE = 40961; public static final int PIXEL_X_DIMENSION = 40962; public static final int PIXEL_Y_DIMENSION = 40963; public static final int RELATED_SOUND_FILE = 40964; public static final int FLASH_ENERGY = 41483; public static final int SPATIAL_FREQUENCY_RESPONSE = 41484; public static final int FOCAL_PLANE_X_RESOLUTION = 41486; public static final int FOCAL_PLANE_Y_RESOLUTION = 41487; public static final int FOCAL_PLANE_RESOLUTION_UNIT = 41488; public static final int SUBJECT_LOCATION = 41492; public static final int EXPOSURE_INDEX = 41493; public static final int SENSING_METHOD = 41495; public static final int FILE_SOURCE = 41728; public static final int SCENE_TYPE = 41729; public static final int CFA_PATTERN = 41730; public static final int CUSTOM_RENDERED = 41985; public static final int EXPOSURE_MODE = 41986; public static final int WHITE_BALANCE = 41987; public static final int DIGITAL_ZOOM_RATIO = 41988; public static final int FOCAL_LENGTH_35MM_FILM = 41989; public static final int SCENE_CAPTURE_TYPE = 41990; public static final int GAIN_CONTROL = 41991; public static final int CONTRAST = 41992; public static final int SATURATION = 41993; public static final int SHARPNESS = 41994; public static final int SUBJECT_DISTANCE_RANGE = 41996; // -- Constructors -- public IFD() { super(); } public IFD(IFD ifd) { super(ifd); } // -- Tag retrieval methods -- /** Gets whether this is a BigTIFF IFD. */ public boolean isBigTiff() throws FormatException { return ((Boolean) getIFDValue(BIG_TIFF, Boolean.class)).booleanValue(); } /** Gets whether the TIFF information in this IFD is little-endian. */ public boolean isLittleEndian() throws FormatException { return ((Boolean) getIFDValue(LITTLE_ENDIAN, Boolean.class)).booleanValue(); } /** Gets the given directory entry value from this IFD. */ public Object getIFDValue(int tag) { return get(new Integer(tag)); } /** * Gets the given directory entry value from this IFD, * performing some error checking. */ public Object getIFDValue(int tag, Class checkClass) throws FormatException { Object value = get(new Integer(tag)); if (checkClass != null && value != null && !checkClass.isInstance(value)) { // wrap object in array of length 1, if appropriate Class cType = checkClass.getComponentType(); Object array = Array.newInstance(cType == null ? value.getClass() : cType, 1); if (cType == value.getClass()) { Array.set(array, 0, value); } else if (cType == boolean.class && value instanceof Boolean) { Array.setBoolean(array, 0, ((Boolean) value).booleanValue()); } else if (cType == byte.class && value instanceof Byte) { Array.setByte(array, 0, ((Byte) value).byteValue()); } else if (cType == char.class && value instanceof Character) { Array.setChar(array, 0, ((Character) value).charValue()); } else if (cType == double.class && value instanceof Double) { Array.setDouble(array, 0, ((Double) value).doubleValue()); } else if (cType == float.class && value instanceof Float) { Array.setFloat(array, 0, ((Float) value).floatValue()); } else if (cType == int.class && value instanceof Integer) { Array.setInt(array, 0, ((Integer) value).intValue()); } else if (cType == long.class && value instanceof Long) { Array.setLong(array, 0, ((Long) value).longValue()); } else if (cType == short.class && value instanceof Short) { Array.setShort(array, 0, ((Short) value).shortValue()); } else { try { value = Array.get(value, 0); if (checkClass.isInstance(value)) return value; } catch (IllegalArgumentException exc) { } throw new FormatException(getIFDTagName(tag) + " directory entry is the wrong type (got " + value.getClass().getName() + ", expected " + checkClass.getName()); } return array; } return value; } /** * Gets the given directory entry value in long format from this IFD, * performing some error checking. */ public long getIFDLongValue(int tag, long defaultValue) throws FormatException { long value = defaultValue; Number number = (Number) getIFDValue(tag, Number.class); if (number != null) value = number.longValue(); return value; } /** * Gets the given directory entry value in int format from this IFD, * or -1 if the given directory does not exist. */ public int getIFDIntValue(int tag) { int value = -1; try { value = getIFDIntValue(tag, -1); } catch (FormatException exc) { } return value; } /** * Gets the given directory entry value in int format from this IFD, * performing some error checking. */ public int getIFDIntValue(int tag, int defaultValue) throws FormatException { int value = defaultValue; Number number = (Number) getIFDValue(tag, Number.class); if (number != null) value = number.intValue(); return value; } /** * Gets the given directory entry value in rational format from this IFD, * performing some error checking. */ public TiffRational getIFDRationalValue(int tag) throws FormatException { return (TiffRational) getIFDValue(tag, TiffRational.class); } /** * Gets the given directory entry value as a string from this IFD, * performing some error checking. */ public String getIFDStringValue(int tag) throws FormatException { return (String) getIFDValue(tag, String.class); } /** Gets the given directory entry value as a string (regardless of type). */ public String getIFDTextValue(int tag) { String value = null; Object o = getIFDValue(tag); if (o instanceof String[]) { StringBuilder sb = new StringBuilder(); String[] s = (String[]) o; for (int i=0; i<s.length; i++) { sb.append(s[i]); if (i < s.length - 1) sb.append("\n"); } value = sb.toString(); } else if (o instanceof short[]) { StringBuffer sb = new StringBuffer(); for (short s : ((short[]) o)) { if (!Character.isISOControl((char) s)) { sb.append((char) s); } else if (s != 0) sb.append("\n"); } value = sb.toString(); } else if (o != null) value = o.toString(); // sanitize line feeds if (value != null) { value = value.replaceAll("\r\n", "\n"); // CR-LF to LF value = value.replaceAll("\r", "\n"); // CR to LF } return value; } /** * Gets the given directory entry values in long format * from this IFD, performing some error checking. */ public long[] getIFDLongArray(int tag) throws FormatException { Object value = getIFDValue(tag); long[] results = null; if (value instanceof long[]) results = (long[]) value; else if (value instanceof Number) { results = new long[] {((Number) value).longValue()}; } else if (value instanceof Number[]) { Number[] numbers = (Number[]) value; results = new long[numbers.length]; for (int i=0; i<results.length; i++) results[i] = numbers[i].longValue(); } else if (value instanceof int[]) { // convert int[] to long[] int[] integers = (int[]) value; results = new long[integers.length]; for (int i=0; i<integers.length; i++) results[i] = integers[i]; } else if (value != null) { throw new FormatException(getIFDTagName(tag) + " directory entry is the wrong type (got " + value.getClass().getName() + ", expected Number, long[], Number[] or int[])"); } return results; } /** * Gets the given directory entry values in int format * from this IFD, performing some error checking. */ public int[] getIFDIntArray(int tag) throws FormatException { Object value = getIFDValue(tag); int[] results = null; if (value instanceof int[]) results = (int[]) value; else if (value instanceof long[]) { long[] v = (long[]) value; results = new int[v.length]; for (int i=0; i<v.length; i++) { results[i] = (int) v[i]; } } else if (value instanceof Number) { results = new int[] {((Number) value).intValue()}; } else if (value instanceof Number[]) { Number[] numbers = (Number[]) value; results = new int[numbers.length]; for (int i=0; i<results.length; i++) results[i] = numbers[i].intValue(); } else if (value != null) { throw new FormatException(getIFDTagName(tag) + " directory entry is the wrong type (got " + value.getClass().getName() + ", expected Number, int[] or Number[])"); } return results; } /** * Gets the given directory entry values in short format * from this IFD, performing some error checking. */ public short[] getIFDShortArray(int tag) throws FormatException { Object value = getIFDValue(tag); short[] results = null; if (value instanceof short[]) results = (short[]) value; else if (value instanceof int[]) { int[] v = (int[]) value; results = new short[v.length]; for (int i=0; i<v.length; i++) { results[i] = (short) v[i]; } } else if (value instanceof Number) { results = new short[] {((Number) value).shortValue()}; } else if (value instanceof Number[]) { Number[] numbers = (Number[]) value; results = new short[numbers.length]; for (int i=0; i<results.length; i++) { results[i] = numbers[i].shortValue(); } } else if (value != null) { throw new FormatException(getIFDTagName(tag) + " directory entry is the wrong type (got " + value.getClass().getName() + ", expected Number, short[] or Number[])"); } return results; } /** Convenience method for obtaining the ImageDescription from this IFD. */ public String getComment() { return getIFDTextValue(IMAGE_DESCRIPTION); } /** Returns the width of an image tile. */ public long getTileWidth() throws FormatException { long tileWidth = getIFDLongValue(TILE_WIDTH, 0); return tileWidth == 0 ? getImageWidth() : tileWidth; } /** Returns the length of an image tile. */ public long getTileLength() throws FormatException { long tileLength = getIFDLongValue(TILE_LENGTH, 0); return tileLength == 0 ? getRowsPerStrip()[0] : tileLength; } /** Returns the number of image tiles per row. */ public long getTilesPerRow() throws FormatException { long tileWidth = getTileWidth(); long imageWidth = getImageWidth(); long nTiles = imageWidth / tileWidth; if (nTiles * tileWidth < imageWidth) nTiles++; return nTiles; } /** Returns the number of image tiles per column. */ public long getTilesPerColumn() throws FormatException { long tileLength = getTileLength(); long imageLength = getImageLength(); long nTiles = imageLength / tileLength; if (nTiles * tileLength < imageLength) nTiles++; return nTiles; } public boolean isTiled() throws FormatException { Object offsets = get(new Integer(STRIP_OFFSETS)); Object tileWidth = get(new Integer(TILE_WIDTH)); return offsets == null && tileWidth != null; } /** * Retrieves the image's width (TIFF tag ImageWidth) from a given TIFF IFD. * @return the image's width. * @throws FormatException if there is a problem parsing the IFD metadata. */ public long getImageWidth() throws FormatException { long width = getIFDLongValue(IMAGE_WIDTH, 0); if (width > Integer.MAX_VALUE) { throw new FormatException("Sorry, ImageWidth > " + Integer.MAX_VALUE + " is not supported."); } return width; } /** * Retrieves the image's length (TIFF tag ImageLength) from a given TIFF IFD. * @return the image's length. * @throws FormatException if there is a problem parsing the IFD metadata. */ public long getImageLength() throws FormatException { long length = getIFDLongValue(IMAGE_LENGTH, 0); if (length > Integer.MAX_VALUE) { throw new FormatException("Sorry, ImageLength > " + Integer.MAX_VALUE + " is not supported."); } return length; } /** * Retrieves the image's bits per sample (TIFF tag BitsPerSample) from a given * TIFF IFD. * @return the image's bits per sample. The length of the array is equal to * the number of samples per pixel. * @throws FormatException if there is a problem parsing the IFD metadata. * @see #getSamplesPerPixel() */ public int[] getBitsPerSample() throws FormatException { int[] bitsPerSample = getIFDIntArray(BITS_PER_SAMPLE); if (bitsPerSample == null) bitsPerSample = new int[] {1}; int samplesPerPixel = getSamplesPerPixel(); if (bitsPerSample.length < samplesPerPixel) { LOGGER.debug("BitsPerSample length ({}) does not match " + "SamplesPerPixel ({})", bitsPerSample.length, samplesPerPixel); int bits = bitsPerSample[0]; bitsPerSample = new int[samplesPerPixel]; Arrays.fill(bitsPerSample, bits); } int nSamples = (int) Math.min(bitsPerSample.length, samplesPerPixel); for (int i=0; i<nSamples; i++) { if (bitsPerSample[i] < 1) { throw new FormatException("Illegal BitsPerSample (" + bitsPerSample[i] + ")"); } } return bitsPerSample; } /** * Retrieves the image's pixel type based on the BitsPerSample tag. * @return the pixel type. This is one of: * <li>FormatTools.INT8</li> * <li>FormatTools.UINT8</li> * <li>FormatTools.INT16</li> * <li>FormatTools.UINT16</li> * <li>FormatTools.INT32</li> * <li>FormatTools.UINT32</li> * <li>FormatTools.FLOAT</li> * <li>FormatTools.DOUBLE</li> * @throws FormatException if there is a problem parsing the IFD metadata. * @see #getBitsPerSample() */ public int getPixelType() throws FormatException { int bps = getBitsPerSample()[0]; int bitFormat = getIFDIntValue(SAMPLE_FORMAT); while (bps % 8 != 0) bps++; if (bps == 24 && bitFormat != 3) bps = 32; switch (bps) { case 16: if (bitFormat == 3) return FormatTools.FLOAT; return bitFormat == 2 ? FormatTools.INT16 : FormatTools.UINT16; case 24: return FormatTools.FLOAT; case 64: return FormatTools.DOUBLE; case 32: if (bitFormat == 3) return FormatTools.FLOAT; return bitFormat == 2 ? FormatTools.INT32 : FormatTools.UINT32; default: return bitFormat == 2 ? FormatTools.INT8 : FormatTools.UINT8; } } /** * Retrieves the image's bytes per sample (derived from tag BitsPerSample) * from this IFD. * @return the image's bytes per sample. The length of the array is equal to * the number of samples per pixel. * @throws FormatException if there is a problem parsing the IFD metadata. * @see #getSamplesPerPixel() * @see #getBitsPerSample() */ public int[] getBytesPerSample() throws FormatException { int[] bitsPerSample = getBitsPerSample(); int[] bps = new int[bitsPerSample.length]; for (int i=0; i<bitsPerSample.length; i++) { bps[i] = bitsPerSample[i]; while ((bps[i] % 8) != 0) bps[i]++; bps[i] /= 8; if (bps[i] == 0) bps[i] = 1; } return bps; } /** * Retrieves the number of samples per pixel for the image (TIFF tag * SamplesPerPixel) from this IFD. * @return the number of samples per pixel. * @throws FormatException if there is a problem parsing the IFD metadata. */ public int getSamplesPerPixel() throws FormatException { return getIFDIntValue(SAMPLES_PER_PIXEL, 1); } /** * Retrieves the image's compression type (TIFF tag Compression) from * this IFD. * * @return the image's compression type. As of TIFF 6.0 this is one of: * <ul> * <li>Uncompressed (1)</li> * <li>CCITT 1D (2)</li> * <li>Group 3 Fax (3)</li> * <li>Group 4 Fax (4)</li> * <li>LZW (5)</li> * <li>JPEG (6)</li> * <li>PackBits (32773)</li> * </ul> * Other proprietary compression types are also possible; * see {@link TiffCompression} for more details. * * @throws FormatException if there is a problem parsing the IFD metadata. */ public TiffCompression getCompression() throws FormatException { return TiffCompression.get(getIFDIntValue( COMPRESSION, TiffCompression.UNCOMPRESSED.getCode())); } /** * Retrieves the image's photometric interpretation (TIFF tag * PhotometricInterpretation) from this IFD. * @return the image's photometric interpretation. As of TIFF 6.0 this is one * of: * <ul> * <li>WhiteIsZero (0)</li> * <li>BlackIsZero (1)</li> * <li>RGB (2)</li> * <li>RGB Palette (3)</li> * <li>Transparency mask (4)</li> * <li>CMYK (5)</li> * <li>YbCbCr (6)</li> * <li>CIELab (8)</li> * </ul> * * @throws FormatException if there is a problem parsing the IFD metadata. */ public PhotoInterp getPhotometricInterpretation() throws FormatException { Object photo = getIFDValue(PHOTOMETRIC_INTERPRETATION); if (photo instanceof PhotoInterp) return (PhotoInterp) photo; int pi = photo instanceof Number ? ((Number) photo).intValue() : ((int[]) photo)[0]; return PhotoInterp.get(pi); } /** * Retrieves the image's planar configuration (TIFF tag PlanarConfiguration) * from this IFD. * @return the image's planar configuration. As of TIFF 6.0 this is one of: * <ul> * <li>Chunky (1)</li> * <li>Planar (2)</li> * </ul> * * @throws FormatException if there is a problem parsing the IFD metadata. */ public int getPlanarConfiguration() throws FormatException { int planarConfig = getIFDIntValue(PLANAR_CONFIGURATION, 1); if (planarConfig != 1 && planarConfig != 2) { throw new FormatException("Sorry, PlanarConfiguration (" + planarConfig + ") not supported."); } return planarConfig; } /** * Retrieves the strip offsets for the image (TIFF tag StripOffsets) from * this IFD. * @return the strip offsets for the image. The length of the array is equal * to the number of strips per image. <i>StripsPerImage = * floor ((ImageLength + RowsPerStrip - 1) / RowsPerStrip)</i>. * @throws FormatException if there is a problem parsing the IFD metadata. * @see #getStripByteCounts() * @see #getRowsPerStrip() */ public long[] getStripOffsets() throws FormatException { int tag = isTiled() ? TILE_OFFSETS : STRIP_OFFSETS; long[] offsets = getIFDLongArray(tag); if (isTiled() && offsets == null) { offsets = getIFDLongArray(STRIP_OFFSETS); } if (offsets == null) return null; for (int i=0; i<offsets.length; i++) { if (offsets[i] < 0) { offsets[i] += 0x100000000L; } } if (isTiled()) return offsets; long rowsPerStrip = getRowsPerStrip()[0]; long numStrips = (getImageLength() + rowsPerStrip - 1) / rowsPerStrip; if (getPlanarConfiguration() == 2) numStrips *= getSamplesPerPixel(); if (offsets.length < numStrips) { throw new FormatException("StripOffsets length (" + offsets.length + ") does not match expected " + "number of strips (" + numStrips + ")"); } return offsets; } /** * Retrieves strip byte counts for the image (TIFF tag StripByteCounts) from * this IFD. * @return the byte counts for each strip. The length of the array is equal * to the number of strips per image. <i>StripsPerImage = * floor((ImageLength + RowsPerStrip - 1) / RowsPerStrip)</i>. * @throws FormatException if there is a problem parsing the IFD metadata. * @see #getStripOffsets() */ public long[] getStripByteCounts() throws FormatException { int tag = isTiled() ? TILE_BYTE_COUNTS : STRIP_BYTE_COUNTS; long[] byteCounts = getIFDLongArray(tag); if (isTiled() && byteCounts == null) { byteCounts = getIFDLongArray(STRIP_BYTE_COUNTS); } if (byteCounts == null) { // technically speaking, this shouldn't happen (since TIFF writers are // required to write the StripByteCounts tag), but we'll support it // anyway // don't rely on RowsPerStrip, since it's likely that if the file doesn't // have the StripByteCounts tag, it also won't have the RowsPerStrip tag long[] offsets = getStripOffsets(); if (offsets == null) return null; int bytesPerSample = getBytesPerSample()[0]; long imageWidth = getImageWidth(); long imageLength = getImageLength(); byteCounts = new long[offsets.length]; int samples = getSamplesPerPixel(); long imageSize = imageWidth * imageLength * bytesPerSample * (getPlanarConfiguration() == 2 ? 1 : samples); long count = imageSize / byteCounts.length; Arrays.fill(byteCounts, count); } long[] counts = new long[byteCounts.length]; if (getCompression() == TiffCompression.LZW) { for (int i=0; i<byteCounts.length; i++) { counts[i] = byteCounts[i] * 2; } } else System.arraycopy(byteCounts, 0, counts, 0, counts.length); if (isTiled()) return counts; long rowsPerStrip = getRowsPerStrip()[0]; long numStrips = (getImageLength() + rowsPerStrip - 1) / rowsPerStrip; if (getPlanarConfiguration() == 2) numStrips *= getSamplesPerPixel(); if (counts.length < numStrips) { throw new FormatException("StripByteCounts length (" + counts.length + ") does not match expected " + "number of strips (" + numStrips + ")"); } return counts; } /** * Retrieves the number of rows per strip for image (TIFF tag RowsPerStrip) * from this IFD. * @return the number of rows per strip. * @throws FormatException if there is a problem parsing the IFD metadata. */ public long[] getRowsPerStrip() throws FormatException { long[] rowsPerStrip = getIFDLongArray(ROWS_PER_STRIP); if (rowsPerStrip == null) { // create a fake RowsPerStrip entry if one is not present return new long[] {getImageLength()}; } // rowsPerStrip should never be more than the total number of rows long imageLength = getImageLength(); for (int i=0; i<rowsPerStrip.length; i++) { rowsPerStrip[i] = (long) Math.min(rowsPerStrip[i], imageLength); } long rows = rowsPerStrip[0]; for (int i=1; i<rowsPerStrip.length; i++) { if (rows != rowsPerStrip[i]) { throw new FormatException( "Sorry, non-uniform RowsPerStrip is not supported"); } } return rowsPerStrip; } /** * Retrieve the value by which to multiply the X and Y resolution so that * the resolutions are in microns per pixel. */ public int getResolutionMultiplier() { int resolutionUnit = getIFDIntValue(RESOLUTION_UNIT); int multiplier = 1; if (resolutionUnit == 2) { multiplier = 25400; } else if (resolutionUnit == 3) { multiplier = 10000; } return multiplier; } /** * Retrieve the X resolution (TIFF tag XResolution) from this IFD. * The resolution will be normalized to microns per pixel. * * @return the X resolution, in microns per pixel * @throws FormatException if there is a problem parsing the IFD metadata. */ public double getXResolution() throws FormatException { TiffRational xResolution = getIFDRationalValue(X_RESOLUTION); double x = xResolution == null ? 0 : 1 / xResolution.doubleValue(); int multiplier = getResolutionMultiplier(); return x * multiplier; } /** * Retrieve the Y resolution (TIFF tag YResolution) from this IFD. * The resolution will be normalized to microns per pixel. * * @return the Y resolution, in microns per pixel * @throws FormatException if there is a problem parsing the IFD metadata. */ public double getYResolution() throws FormatException { TiffRational yResolution = getIFDRationalValue(Y_RESOLUTION); double y = yResolution == null ? 0 : 1 / yResolution.doubleValue(); int resolutionUnit = getIFDIntValue(RESOLUTION_UNIT); int multiplier = getResolutionMultiplier(); return y * multiplier; } // -- IFD population methods -- /** Adds a directory entry to this IFD. */ public void putIFDValue(int tag, Object value) { put(new Integer(tag), value); } /** Adds a directory entry of type BYTE to this IFD. */ public void putIFDValue(int tag, short value) { putIFDValue(tag, new Short(value)); } /** Adds a directory entry of type SHORT to this IFD. */ public void putIFDValue(int tag, int value) { putIFDValue(tag, new Integer(value)); } /** Adds a directory entry of type LONG to this IFD. */ public void putIFDValue(int tag, long value) { putIFDValue(tag, new Long(value)); } // -- Debugging -- /** Prints the contents of this IFD. */ public void printIFD() { LOGGER.trace("IFD directory entry values:"); for (Integer tag : keySet()) { Object value = get(tag); String v = null; if (value == null) { LOGGER.trace("\t{}=null", getIFDTagName(tag.intValue())); } else if ((value instanceof Boolean) || (value instanceof Number) || (value instanceof String) || (value instanceof PhotoInterp) || (value instanceof TiffCompression) || (value instanceof TiffIFDEntry)) { v = value.toString(); LOGGER.trace("\t{}={}", getIFDTagName(tag.intValue()), v); } else { // this is an array of primitive types, Strings, or TiffRationals LOGGER.trace("\t{}=", getIFDTagName(tag.intValue())); int nElements = Array.getLength(value); for (int i=0; i<nElements; i++) { LOGGER.trace("\t\t{}", Array.get(value, i)); } } } } // -- Utility methods -- /** Gets the name of the IFD tag encoded by the given number. */ public static String getIFDTagName(int tag) { return getFieldName(tag); } /** * This method uses reflection to scan the values of this class's * static fields, returning the first matching field's name. It is * probably not very efficient, and is mainly intended for debugging. */ public static String getFieldName(int value) { Field[] fields = IFD.class.getFields(); for (int i=0; i<fields.length; i++) { try { if (fields[i].getInt(null) == value) return fields[i].getName(); } catch (IllegalAccessException exc) { } catch (IllegalArgumentException exc) { } } return "" + value; } }