/**********************************************************************
 * 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 edu.harvard.hul.ois.jhove.messages.JhoveMessage;
import edu.harvard.hul.ois.jhove.messages.JhoveMessages;

import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import javax.xml.parsers.SAXParserFactory;

/**
 * Encapsulation of standard TIFF IFD.
 */
public class TiffIFD extends IFD {
	/******************************************************************
	 * PRIVATE CLASS FIELDS.
	 ******************************************************************/

	/** Standard TIFF 6.0 tags. */
	public static final int NEWSUBFILETYPE = 254, SUBFILETYPE = 255,
			IMAGEWIDTH = 256, IMAGELENGTH = 257, BITSPERSAMPLE = 258,
			COMPRESSION = 259, PHOTOMETRICINTERPRETATION = 262,
			THRESHHOLDING = 263, CELLWIDTH = 264, CELLLENGTH = 265,
			FILLORDER = 266, DOCUMENTNAME = 269, IMAGEDESCRIPTION = 270,
			MAKE = 271, MODEL = 272, STRIPOFFSETS = 273, ORIENTATION = 274,
			SAMPLESPERPIXEL = 277, ROWSPERSTRIP = 278, STRIPBYTECOUNTS = 279,
			MINSAMPLEVALUE = 280, MAXSAMPLEVALUE = 281, XRESOLUTION = 282,
			YRESOLUTION = 283, PLANARCONFIGURATION = 284, PAGENAME = 285,
			XPOSITION = 286, YPOSITION = 287, FREEOFFSETS = 288,
			FREEBYTECOUNTS = 289, GRAYRESPONSEUNIT = 290,
			GRAYRESPONSECURVE = 291, T4OPTIONS = 292, T6OPTIONS = 293,
			RESOLUTIONUNIT = 296, PAGENUMBER = 297, TRANSFERFUNCTION = 301,
			SOFTWARE = 305, DATETIME = 306, ARTIST = 315, HOSTCOMPUTER = 316,
			PREDICTOR = 317, WHITEPOINT = 318, PRIMARYCHROMATICITIES = 319,
			COLORMAP = 320, HALFTONEHINTS = 321, TILEWIDTH = 322,
			TILELENGTH = 323, TILEOFFSETS = 324, TILEBYTECOUNTS = 325,
			INKSET = 332, INKNAMES = 333, NUMBEROFINKS = 334, DOTRANGE = 336,
			TARGETPRINTER = 337, EXTRASAMPLES = 338, SAMPLEFORMAT = 339,
			SMINSAMPLEVALUE = 340, SMAXSAMPLEVALUE = 341, TRANSFERRANGE = 342,
			JPEGPROC = 512, JPEGINTERCHANGEFORMAT = 513,
			JPEGINTERCHANGEFORMATLENGTH = 514, JPEGRESTARTINTERVAL = 515,
			JPEGLOSSLESSPREDICTORS = 517, JPEGPOINTTRANSFORMS = 518,
			JPEGQTABLES = 519, JPEGDCTABLES = 520, JPEGACTABLES = 521,
			YCBCRCOEFFICIENTS = 529, YCBCRSUBSAMPLING = 530,
			YCBCRPOSITIONING = 531, REFERENCEBLACKWHITE = 532,
			COPYRIGHT = 33432;

	/** Fill order tag (266) labels. */
	private static final String[] FILLORDER_L = { "", "high-order",
			"low-order" };
	/** Indexed tag (346) labels. */
	private static final String[] INDEXED_L = { "not indexed", "indexed" };
	/** InkSet tag (332) labels. */
	private static final String[] INKSET_L = { "", "CMYK", "not CMYK" };
	/** JPEGLosslessPredictors tag (517) labels. */
	private static final String[] JPEGLOSSLESSPREDICTORS_L = { "", "A", "B",
			"C", "A+B+C", "A+((B-C)/2)", "B+((A-C)/2)", "(A+B)/2" };
	/** JPEGProc tag (512) labels. */
	private static final String[] JPEGPROC_L = { "baseline sequential process",
			"lossless process with Huffman coding" };
	private static final int[] JPEGPROC_INDEX = { 1, 14 };
	/** NewSubfileType tag (254) bit labels. */
	private static final String[] NEWSUBFILETYPE_L = {
			"reduced-resolution image of another image in this file",
			"single page of multi-page image",
			"transparency mask for another image in this file" };
	/** OPIProxy tag (351) labels. */
	private static final String[] OPIPROXY_L = {
			"no higher-resolution version exists",
			"higher-resolution version exists" };
	/** Predictor tag (317) labels. */
	private static final String[] PREDICTOR_L = { "", "no prediction scheme",
			"horizontal differencing" };
	/** SampleFormat tag (339) labels. */
	private static final String[] SAMPLEFORMAT_L = { "", "unsigned integer",
			"signed integer", "IEEE floating point", "undefined" };
	/** SubfileType tag (255) labels. */
	private static final String[] SUBFILETYPE_L = { "", "full-resolution image",
			"reduced-resolution image", "single page of multi-page image" };
	/** Threshholding tag (263) labels. */
	private static final String[] THRESHHOLDING_L = { "",
			"no dithering or halftoning", "ordered dithering or halftoning",
			"randomized process" };
	/** YCbCrPositioning tag (531) labels. */
	private static final String[] YCBCRPOSITIONING_L = { "", "centered",
			"cosited" };
	/** YCbCrSubSampling tag (530) labels. */
	private static final String[] YCBCRSUBSAMPLING_HORZ = { "",
			"width of chroma image is equal to width of associated luma image",
			"width of chroma image is 1/2 the width of associated luma image",
			"",
			"width of chroma image is 1/4 the width of associated luma image" };
	private static final String[] YCBCRSUBSAMPLING_VERT = { "",
			"length of chroma image is equal to length of associated luma image",
			"length of chroma image is 1/2 the length of associated luma image",
			"",
			"length of chroma image is 1/4 the length of associated luma image" };

	/** TIFF/IT tags. */
	private static final int SITE = 34016, COLORSEQUENCE = 34017,
			IT8HEADER = 34018, RASTERPADDING = 34019, BITSPERRUNLENGTH = 34020,
			BITSPEREXTENDEDRUNLENGTH = 34021, COLORTABLE = 34022,
			IMAGECOLORINDICATOR = 34023, BACKGROUNDCOLORINDICATOR = 34024,
			IMAGECOLORVALUE = 34025, BACKGROUNDCOLORVALUE = 34026,
			PIXELINTENSITYRANGE = 34027, TRANSPARENCYINDICATOR = 34028,
			COLORCHARACTERIZATION = 34029, HCUSAGE = 34030;

	public static final String[] BACKGROUNDCOLORINDICATOR_L = {
			"background not defined", "Background color defined",
			"full transparency, background color not defined" };
	public static final String[] HCUSAGE_L = {
			"high resolution CT contone information",
			"line art (line work) information", "trapping information" };
	public static final String[] IMAGECOLORINDICATOR_L = { "image not defined",
			"image color defined",
			"full transparency, image color not defined" };
	/* RasterPadding tag (34019) labels */
	private static final String[] RASTERPADDING_L = { "1 byte", "2 bytes",
			"4 bytes", "512 bytes", "1024 bytes" };
	private static final int[] RASTERPADDING_INDEX = { 0, 1, 2, 9, 10 };
	public static final String[] TRANSPARENCYINDICATOR_L = { "no transparency",
			"transparency used" };

	/** TIFF/EP tags. */
	private static final int CFAREPEATPATTERNDIM = 33421, CFAPATTERN = 33422,
			BATTERYLEVEL = 33423, EXPOSURETIME = 33434, FNUMBER = 33437,
			IPTCNAA = 33723, ICC_PROFILE = 34675, EXPOSUREPROGRAM = 34850,
			SPECTRALSENSITIVITY = 34852, ISOSPEEDRATINGS = 34855, OECF = 34856,
			INTERLACE = 34857, TIMEZONEOFFSET = 34858, SELFTIMERMODE = 34859,
			DATETIMEORIGINAL = 36867, COMPRESSEDBITSPERPIXEL = 37122,
			SHUTTERSPEEDVALUE = 37377, APERTUREVALUE = 37378,
			BRIGHTNESSVALUE = 37379, EXPOSUREBIASVALUE = 37380,
			MAXAPERTUREVALUE = 37381, SUBJECTDISTANCE = 37382,
			METERINGMODE = 37383, LIGHTSOURCE = 37384, FLASH = 37385,
			FOCALLENGTH = 37386, FLASHENERGY = 37387,
			SPATIALFREQUENCYRESPONSE = 37388, NOISE = 37389,
			FOCALPLANEXRESOLUTION = 37390, FOCALPLANEYRESOLUTION = 37391,
			FOCALPLANERESOLUTIONUNIT = 37392, IMAGENUMBER = 37393,
			SECURITYCLASSIFICATION = 37394, IMAGEHISTORY = 37395,
			SUBJECTLOCATION = 37396, EXPOSUREINDEX = 37397,
			TIFFEPSTANDARDID = 37398, SENSINGMETHOD = 37399;
	/** TIFF/EP tag labels. */
	private static final String[] EXPOSUREPROGRAM_L = { "unidentified",
			"manual", "program normal", "aperature priority",
			"shutter priority", "program creative", "program action",
			"portrait mode", "landscape mode" };
	private static final String[] FLASH_L = { "did not fire", "fired",
			"fired, return not sensed", "fired, return sensed",
			"fired, fill flash mode, camera has no flash return sensing capability",
			"fired, fill flash mode, return not sensed",
			"fired, fill flash mode, return sensed",
			"did not fire, flash 'off' mode", "did not fire, 'auto' mode",
			"fired, 'auto' mode, camera has no flash return sensing capability",
			"fired, 'auto' mode, return not sensed",
			"fired, 'auto' mode, return sensed",
			"camera does not have a flash unit" };
	private static final int[] FLASH_INDEX = { 0, 1, 5, 7, 9, 13, 15, 16, 24,
			25, 29, 31, 32 };
	// Official values in TIFF/EP (not compatible with EXIF or MIX) 
	// "", "inches", "meters", "centimeters", "millimeters", "micrometers" 
	protected static final String[] FOCALPLANERESOLUTIONUNIT_L = { "", "", 
			"inches", "centimeters", "millimeters", "micrometers" 
		 };

	/** Exif tags. */
	private static final int EXIFIFD = 34665, GPSINFOIFD = 34853,
			INTEROPERABILITYIFD = 40965;

	/** GeoTIFF tags. */
	private static final int GEOKEYDIRECTORYTAG = 34735,
			GEODOUBLEPARAMSTAG = 34736, GEOASCIIPARAMSTAG = 34737,
			MODELTIEPOINTTAG = 33922, MODELPIXELSCALETAG = 33550,
			MODELTRANSFORMATIONTAG = 34264;
	/** GeoTIFF key values. */
	public static final int GTMODELTYPEGEOKEY = 1024, GTRASTERTYPEGEOKEY = 1025,
			GTCITATIONGEOKEY = 1026, GEOGRAPHICTYPEGEOKEY = 2048,
			GEOGCITATIONGEOKEY = 2049, GEOGGEODETICDATUMGEOKEY = 2050,
			GEOGPRIMEMERIDIANGEOKEY = 2051, GEOGLINEARUNITSGEOKEY = 2052,
			GEOGLINEARUNITSIZEGEOKEY = 2053, GEOGANGULARUNITSGEOKEY = 2054,
			GEOGANGULARUNITSIZEGEOKEY = 2055, GEOGELLIPSOIDGEOKEY = 2056,
			GEOGSEMIMAJORAXISGEOKEY = 2057, GEOGSEMIMINORAXISGEOKEY = 2058,
			GEOGINVFLATTENINGGEOKEY = 2059, GEOGAZIMUTHUNITSGEOKEY = 2060,
			GEOGPRIMEMERIDIANLONGGEOKEY = 2061, PROJECTEDCSTYPEGEOKEY = 3072,
			PCSCITATIONGEOKEY = 3073, PROJECTIONGEOKEY = 3074,
			PROJCOORDTRANSGEOKEY = 3075, PROJLINEARUNITSGEOKEY = 3076,
			PROJLINEARUNITSIZEGEOKEY = 3077, PROJSTDPARALLEL1GEOKEY = 3078,
			PROJSTDPARALLEL2GEOKEY = 3079, PROJNATORIGINLONGGEOKEY = 3080,
			PROJNATORIGINLATGEOKEY = 3081, PROJFALSEEASTINGGEOKEY = 3082,
			PROJFALSENORTHINGGEOKEY = 3083, PROJFALSEORIGINLONGGEOKEY = 3084,
			PROJFALSEORIGINLATGEOKEY = 3085,
			PROJFALSEORIGINEASTINGGEOKEY = 3086,
			PROJFALSEORIGINNORTHINGGEOKEY = 3087, PROJCENTERLONGGEOKEY = 3088,
			PROJCENTERLATGEOKEY = 3089, PROJCENTEREASTINGGEOKEY = 3090,
			PROJFALSEORIGINNORTHINGGEOKEY_2 = 3091,
			PROJSCALEATNATORIGINGEOKEY = 3092, PROJSCALEATCENTERGEOKEY = 3093,
			PROJAZIMUTHANGLEGEOKEY = 3094, PROJSTRAIGHTVERTPOLELONGEOKEY = 3095,
			VERTICALCSTYPEGEOKEY = 4096, VERTICALCITATIONGEOKEY = 4097,
			VERTICALDATUMGEOKEY = 4098, VERTICALUNITSGEOKEY = 4099;

	/** PageMaker 6.0 tags. */
	private static final int SUBIFDS = 330, CLIPPATH = 343,
			XCLIPPATHUNITS = 344, YCLIPPATHUNITS = 345, INDEXED = 346,
			OPIPROXY = 351, IMAGEID = 32781;

	/** Photoshop 'Advanced Tiff' tags. */
	private static final int JPEGTABLES = 347, IMAGESOURCEDATA = 37724;

	/**
	 * More Photoshop TIFF tags.
	 */
	private static final int PHOTOSHOPPROPS = 34377, ANNOTATIONS = 50255;

	/** Class F tags. */
	private static final int BADFAXLINES = 326, CLEANFAXDATA = 327,
			CONSECUTIVEBADFAXLINES = 328;

	/** XMP tag. */
	private static final int XMP = 700;

	/** TIFF/FX tags. */
	private static final int GLOBALPARAMETERSIFD = 400, STRIPROWCOUNTS = 559,
			IMAGELAYER = 34732;

	public static final String[] IMAGELAYER_L = { "", "Background", "Mask",
			"Foreground" };

	/** DNG tags. */
	private static final int DNGVERSION = 50706, DNGBACKWARDVERSION = 50707,
			UNIQUECAMERAMODEL = 50708, LOCALIZEDCAMERAMODEL = 50709,
			CFAPLANECOLOR = 50710, CFALAYOUT = 50711,
			LINEARIZATIONTABLE = 50712, BLACKLEVELREPEATDIM = 50713,
			BLACKLEVEL = 50714, BLACKLEVELDELTAH = 50715,
			BLACKLEVELDELTAV = 50716, WHITELEVEL = 50717, DEFAULTSCALE = 50718,
			DEFAULTCROPORIGIN = 50719, DEFAULTCROPSIZE = 50720,
			COLORMATRIX1 = 50721, COLORMATRIX2 = 50722,
			CAMERACALIBRATION1 = 50723, CAMERACALIBRATION2 = 50724,
			REDUCTIONMATRIX1 = 50725, REDUCTIONMATRIX2 = 50726,
			ANALOGBALANCE = 50727, ASSHOTNEUTRAL = 50728, ASSHOTWHITEXY = 50729,
			BASELINEEXPOSURE = 50730, BASELINENOISE = 50731,
			BASELINESHARPNESS = 50732, BAYERGREENSPLIT = 50733,
			LINEARRESPONSELIMIT = 50734, CAMERASERIALNUMBER = 50735,
			LENSINFO = 50736, CHROMABLURRADIUS = 50737,
			ANTIALIASSTRENGTH = 50738, SHADOWSCALE = 50739,         // Undocumented
															         // tag
			DNGPRIVATEDATA = 50740, MAKERNOTESAFETY = 50741,
			CALIBRATIONILLUMINANT1 = 50778, CALIBRATIONILLUMINANT2 = 50779,
			BESTQUALITYSCALE = 50780;

	public static final String[] CFALAYOUT_L = { "", "Rectangular",
			"Staggered Layout A", "Staggered Layout B", "Staggered Layout C",
			"Staggered Layout D" };
	public static final String[] MAKERNOTESAFETY_L = { "Unsafe", "Safe" };

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

	/** NISO Z39.87/AIIM 20-2002 image metadata. */
	private NisoImageMetadata _niso;

	/** NewSubfileType tag (254). */
	private long _newSubfileType;
	/** SubfileType tag (255). */
	private int _subfileType;
	/** PhotometricInterpretation tag (262). */
	private int _photometricInterpretation;
	/** Threshholding tag (263). */
	private int _threshholding;
	/** Cell width tag (264). */
	private int _cellWidth;
	/** Cell length tag (265). */
	private int _cellLength;
	/** Fill order tag (266). */
	private int _fillOrder;
	/** Document name tag (269). */
	private String _documentName;
	/** Image description tag (270). */
	private String _imageDescription;
	/** Minimum sample value tag (280). */
	private int[] _minSampleValue;
	/** Maximum sample value tag (281). */
	private int[] _maxSampleValue;
	/** Page name tag (285). */
	private String _pageName;
	/** X position tag (286). */
	private Rational _xPosition;
	/** Y position tag (287). */
	private Rational _yPosition;
	/** Free offsets tag (288). */
	private long[] _freeOffsets;
	/** Free byte counts tag (289). */
	private long[] _freeByteCounts;
	/** CCITT Group 3 compression options tag (292). */
	private long _t4Options;
	/** CCITT Group 4 compression options tag (293). */
	private long _t6Options;
	/** Page number tag (297). */
	private int[] _pageNumber;
	/** Transfer function tag (301). */
	private boolean _transferFunction;
	/** Date/time tag (306). */
	private String _dateTime;
	/** Compression differencing predictor tag (317). */
	private int _predictor;
	/** Halftone hints tag (321). */
	private int[] _halftoneHints;
	/** Bad fax lines tag (326). */
	private long _badFaxLines;
	/** Clean fax data tag (327). */
	private short _cleanFaxData;
	/** Consecutive bad fax lines tag (328). */
	private long _consecutiveBadFaxLines;
	/** Ink set tag (332). */
	private int _inkSet;
	/** InkNames tag (322). */
	private String[] _inkNames;
	/** Sub IFDs tag (330). */
	private long[] _subIFDs;
	/** Number of inks tag (334). */
	private int _numberOfInks;
	/** Dot range tag (336). */
	private int[] _dotRange;
	/** Target printer tag (337). */
	private String _targetPrinter;
	/** Sample format tag (339). */
	private int[] _sampleFormat;
	/** Transfer range tag (342). */
	private int[] _transferRange;
	/** Clip path tag (343). */
	private int[] _clipPath;
	/** X clip path units tag (344). */
	private long _xClipPathUnits;
	/** Y clip path units tag (345). */
	private long _yClipPathUnits;
	/** Indexed tag (346). */
	private int _indexed;
	/** JPEG tables tag (347). */
	private int[] _jpegTables;
	/** OPI proxy tag (351). */
	private int _opiProxy;
	/** JPEG Proc tag (512). */
	private int _jpegProc;
	/** JPEG interchange format tag (513). */
	private long _jpegInterchangeFormat;
	/** JPEG interchange format length tag (514). */
	private long _jpegInterchangeFormatLength;
	/** JPEG restart interval tag (515). */
	private int _jpegRestartInterval;
	/** JPEG lossless predictors tag (517). */
	private int[] _jpegLosslessPredictors;
	/** JPEG point transforms tag (518). */
	private int[] _jpegPointTransforms;
	/** JPEG Q tables tag (519). */
	private long[] _jpegQTables;
	/** JPEG DC tables tag (520). */
	private long[] _jpegDCTables;
	/** JPEG AC tables tag (521). */
	private long[] _jpegACTables;
	/** Copyright tag (33432). */
	private String _copyright;
	/** Exif IFD tag (34665). */
	private long _exifIFD;
	/** GPSInfo IFD tag (34853). */
	private long _gpsInfoIFD;
	/** GlobalParametersIFD tag (400). */
	private long _globalParametersIFD;
	/** Photoshop Properties tag (34377). */
	private int[] _photoshopProperties;
	/** ImageSourceData tag (37724). */
	private int[] _imageSourceData;
	/** Exif Interoperability IFD tag (40965). */
	private long _interoperabilityIFD;
	/** Annotations tag (50255). */
	private int[] _annotations;

	/* TIFF/IT tags. */
	private int _backgroundColorIndicator;
	private int _backgroundColorValue;
	private int _bitsPerExtendedRunLength;
	private int _bitsPerRunLength;
	private String _colorCharacterization;
	private String _colorSequence;
	private int[] _colorTable;
	private long _hcUsage;
	private int _imageColorIndicator;
	private int _imageColorValue;
	private String _it8Header;
	private int[] _pixelIntensityRange;
	private int _rasterPadding;
	private String _site;
	private int _transparencyIndicator;

	/* TIFF/EP tags. */
	private Rational _aperatureValue;
	private String _batteryLevel;
	private int[] _cfaRepeatPatternDim;
	private int[] _cfaPattern;
	private Rational _compressedBitsPerPixel;
	private int _exposureProgram;
	private int _flash;
	private int _focalPlaneResolutionUnit;
	private Rational _focalPlaneXResolution;
	private Rational _focalPlaneYResolution;
	private byte[] _iccProfile;
	private String _imageHistory;
	private long _imageNumber;
	private int _interlace;
	private long[] _iptc;
	private int[] _isoSpeedRatings;
	private Rational _maxAperatureValue;
	private int[] _noise;
	private int[] _oecf;
	private String _securityClassification;
	private int _selfTimerMode;
	private Rational _shutterSpeedValue;
	private int[] _spatialFrequencyResponse;
	private String _spectralSensitivity;
	private int[] _subjectLocation;
	private String _tiffEPStandardID;
	private int[] _timeZoneOffset;

	/* GeoTIFF tags. */
	private String _geoAsciiParamsTag;
	private double[] _geoDoubleParamsTag;
	private int[] _geoKeyDirectoryTag;
	private double[] _modelPixelScaleTag;
	private double[] _modelTiepointTag;
	private double[] _modelTransformationTag;

	/* XMP property. */
	private Property _xmpProp;

	/* Tiff/FX tag values. */
	private long[] _stripRowCounts;
	private int[] _imageLayer;

	/** Exif IFD object. */
	private ExifIFD _theExifIFD;
	/** GPSInfo IFD object. */
	private GPSInfoIFD _theGPSInfoIFD;
	/** Exif Interoperability IFD. */
	private InteroperabilityIFD _theInteroperabilityIFD;
	/** GlobalParameters IFD. */
	private GlobalParametersIFD _theGlobalParametersIFD;

	/*
	 * DNG tag values. The spec says that some of these tags go into
	 * a "raw IFD," which isn't defined. Until this is explained,
	 * throw it all in here.
	 */
	private int[] _dngVersion;
	private int[] _dngBackwardVersion;
	private String _uniqueCameraModel;
	private String _localizedCameraModel;  // Note: This is specified as Unicode
	private int[] _cfaPlaneColor;
	private int _cfaLayout;
	private int[] _linearizationTable;
	private int[] _blackLevelRepeatDim;
	// BlackLevel can be SHORT or LONG or RATIONAL.
	// To avoid having to store multiple versions, we will convert
	// SHORT or LONG values to RATIONAL.
	private Rational[] _blackLevel;
	private Rational[] _blackLevelDeltaH;
	private Rational[] _blackLevelDeltaV;
	// Though BlackLevel can be RATIONAL, WhiteLevel can't.
	// There must be a rational explanation.
	private long[] _whiteLevel;
	private Rational[] _defaultScale;
	private Rational _bestQualityScale;
	private Rational[] _defaultCropOrigin;
	private Rational[] _defaultCropSize;
	private int _calibrationIlluminant1;
	private int _calibrationIlluminant2;
	private Rational[] _colorMatrix1;
	private Rational[] _colorMatrix2;   // for calculating revolutions, no doubt
	private Rational[] _cameraCalibration1;
	private Rational[] _cameraCalibration2;
	private Rational[] _reductionMatrix1;
	private Rational[] _reductionMatrix2;
	private Rational[] _analogBalance;
	private Rational[] _asShotNeutral;
	private Rational[] _asShotWhiteXY;
	private Rational _baselineExposure;
	private Rational _baselineNoise;
	private Rational _baselineSharpness;
	private int _bayerGreenSplit;
	private Rational _linearResponseLimit;
	private String _cameraSerialNumber;
	private Rational[] _lensInfo;
	private Rational _chromaBlurRadius;
	private Rational _antiAliasStrength;
	private int[] _dngPrivateData;
	private int _makerNoteSafety;

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

	/**
	 * Instantiate an <code>TiffIFD</code> object.
	 * 
	 * @param offset
	 *            IFD offset
	 * @param info
	 *            The RepInfo object
	 * @param raf
	 *            TIFF file
	 * @param bigEndian
	 *            True if big-endian file
	 */
	public TiffIFD(long offset, RepInfo info, RandomAccessFile raf,
			boolean bigEndian) {
		super(offset, info, raf, bigEndian);

		/* Define a NISO metadata object and set defaults. */
		_niso = new NisoImageMetadata();
		_niso.setMimeType("image/tiff");
		_niso.setCompressionScheme(1);
		_niso.setOrientation(1);
		_niso.setPlanarConfiguration(1);
		_niso.setRowsPerStrip(4294967295L);
		_niso.setSamplesPerPixel(1);
		_niso.setByteOrder(bigEndian ? "big-endian" : "little-endian");

		/* Set non-NISO defaults. */
		_photometricInterpretation = NULL;
		_cellLength = NULL;
		_cellWidth = NULL;
		_fillOrder = NULL;
		_indexed = 0;
		_inkSet = NULL;
		_jpegInterchangeFormat = NULL;
		_jpegInterchangeFormatLength = NULL;
		_jpegProc = NULL;
		_jpegRestartInterval = NULL;
		_newSubfileType = 0L;
		_numberOfInks = NULL;
		_opiProxy = NULL;
		_predictor = NULL;
		_subfileType = NULL;
		_t4Options = NULL;
		_t6Options = NULL;
		_threshholding = 1;
		_xClipPathUnits = NULL;
		_yClipPathUnits = NULL;

		/* TIFF/IT defaults. */
		_backgroundColorIndicator = 0;
		_backgroundColorValue = NULL;
		_bitsPerExtendedRunLength = 16;
		_bitsPerRunLength = 8;
		_hcUsage = NULL;
		_imageColorIndicator = 0;
		_imageColorValue = NULL;
		_rasterPadding = 0;
		_transparencyIndicator = 0;

		/* TIFF/EP defaults. */
		_exposureProgram = NULL;
		_flash = NULL;
		_focalPlaneResolutionUnit = NULL;
		_gpsInfoIFD = NULL;
		_imageNumber = NULL;
		_selfTimerMode = NULL;

		/* Exif defaults. */
		_exifIFD = NULL;
		_focalPlaneResolutionUnit = NULL;
		_imageNumber = NULL;
		_interlace = NULL;
		_interoperabilityIFD = NULL;
		_globalParametersIFD = NULL;

		/* Class F/RFC 1324 defaults. */
		_badFaxLines = NULL;
		_cleanFaxData = NULL;
		_consecutiveBadFaxLines = NULL;

		/* XMP default. */
		_xmpProp = null;

		/* Tiff/FX defaults. */
		_stripRowCounts = null;
		_imageLayer = null;

		/* DNG defaults. */
		_dngVersion = null;
		_dngBackwardVersion = null;
		_uniqueCameraModel = null;
		_localizedCameraModel = null;
		_cfaPlaneColor = null;
		_cfaLayout = NULL;
		_linearizationTable = null;
		_blackLevelRepeatDim = null;
		_blackLevel = null;
		_blackLevelDeltaH = null;
		_blackLevelDeltaV = null;
		_whiteLevel = null;
		_defaultScale = null;
		_bestQualityScale = null;
		_defaultCropOrigin = null;
		_defaultCropSize = null;
		_calibrationIlluminant1 = NULL;
		_calibrationIlluminant2 = NULL;
		_colorMatrix1 = null;
		_colorMatrix2 = null;
		_cameraCalibration1 = null;
		_cameraCalibration2 = null;
		_reductionMatrix1 = null;
		_reductionMatrix2 = null;
		_analogBalance = null;
		_asShotNeutral = null;
		_asShotWhiteXY = null;
		_baselineExposure = null;
		_baselineNoise = null;
		_baselineSharpness = null;
		_bayerGreenSplit = NULL;
		_linearResponseLimit = null;
		_cameraSerialNumber = null;
		_lensInfo = null;
		_chromaBlurRadius = null;
		_antiAliasStrength = null;
		_dngPrivateData = null;
		_makerNoteSafety = NULL;
	}

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

	/**
	 * Returns the value of the APERTUREVALUE (37378) tag. Note typo in
	 * function name.
	 */
	public Rational getAperatureValue() {
		return _aperatureValue;
	}

	/**
	 * Returns the value of the TIFF/IT BACKGROUNDCOLORINDICATOR
	 * (34024) tag.
	 */
	public int getBackgroundColorIndicator() {
		return _backgroundColorIndicator;
	}

	/**
	 * Returns the value of the BACKGROUNDCOLORVALUE
	 * (34026) tag.
	 */
	public int getBackgroundColorValue() {
		return _backgroundColorValue;
	}

	/** Returns the value of the BATTERYLEVEL (33423) tag. */
	public String getBatteryLevel() {
		return _batteryLevel;
	}

	/**
	 * Returns the value of the BITSPEREXTENDEDRUNLENGTH
	 * (34021) tag.
	 */
	public int getBitsPerExtendedRunLength() {
		return _bitsPerExtendedRunLength;
	}

	/** Returns the value of the BITSPERRUNLENGTH (34020) tag. */
	public int getBitsPerRunLength() {
		return _bitsPerRunLength;
	}

	/** Returns the value of the CELLLENGTH (265) tag. */
	public int getCellLength() {
		return _cellLength;
	}

	/** Returns the value of the CELLWIDTH (264) tag. */
	public int getCellWidth() {
		return _cellWidth;
	}

	/** Returns the value of the CFAPATTERN (33422) tag. */
	public int[] getCFAPattern() {
		return _cfaPattern;
	}

	/**
	 * Returns the value of the CFAREPEATPATTERNDIM
	 * (33421) tag.
	 */
	public int[] getCFARepeatPatternDim() {
		return _cfaRepeatPatternDim;
	}

	/** Returns the value of the CLIPPATH (343) tag. */
	public int[] getClipPath() {
		return _clipPath;
	}

	/**
	 * Returns the value of the COLORSEQUENCE
	 * (34017) tag.
	 */
	public String getColorSequence() {
		return _colorSequence;
	}

	/** Returns the value of the COLORTABLE (34022) tag. */
	public int[] getColorTable() {
		return _colorTable;
	}

	/**
	 * Returns the value of the COMPRESSEDBITSPERPIXEL
	 * (37122) tag.
	 */
	public Rational getCompressedBitsPerPixel() {
		return _compressedBitsPerPixel;
	}

	/** Returns the value of the COPYRIGHT (33432) tag. */
	public String getCopyright() {
		return _copyright;
	}

	/** Returns the value of the DATETIME (306) tag. */
	public String getDateTime() {
		return _dateTime;
	}

	/** Returns the value of the DOCUMENTNAME (269) tag. */
	public String getDocumentName() {
		return _documentName;
	}

	/** Returns the value of the DOTRANGE (336) tag. */
	public int[] getDotRange() {
		return _dotRange;
	}

	/** Return the offset of the Exif IFD. */
	public long getExifIFD() {
		return _exifIFD;
	}

	/** Return the offset of the GlobalParameters IFD. */
	public long getGlobalParametersIFD() {
		return _globalParametersIFD;
	}

	/** Returns the value of the EXPOSUREPROGRAM (34850) tag. */
	public int getExposureProgram() {
		return _exposureProgram;
	}

	/** Returns the value of the FILLORDER (266) tag. */
	public int getFillOrder() {
		return _fillOrder;
	}

	/**
	 * Returns the value of the FOCALPLANERESOLUTIONUNIT
	 * (37392) tag.
	 */
	public int getFocalPlaneResolutionUnit() {
		return _focalPlaneResolutionUnit;
	}

	/**
	 * Returns the value of the FOCALPLANEXRESOLUTION
	 * (37390) tag.
	 */
	public Rational getFocalPlaneXResolution() {
		return _focalPlaneXResolution;
	}

	/**
	 * Returns the value of the FOCALPLANEYRESOLUTION
	 * (37390) tag.
	 */
	public Rational getFocalPlaneYResolution() {
		return _focalPlaneYResolution;
	}

	/**
	 * Returns the value of the GEOKEYDIRECTORYTAG
	 * (34735) tag.
	 */
	public int[] getGeoKeyDirectoryTag() {
		return _geoKeyDirectoryTag;
	}

	/** Return the offset of the GPSInfo IFD. */
	public long getGPSInfoIFD() {
		return _gpsInfoIFD;
	}

	/**
	 * Returns the value of the IMAGECOLORINDICATOR
	 * (34023) tag.
	 */
	public int getImageColorIndicator() {
		return _imageColorIndicator;
	}

	/** Returns the value of the IMAGECOLORVALUE (34025) tag. */
	public int getImageColorValue() {
		return _imageColorValue;
	}

	/** Returns the value of the IMAGEDESCRIPTION (270) tag. */
	public String getImageDescription() {
		return _imageDescription;
	}

	/** Returns the value of the IMAGEHISTORY (37395) tag. */
	public String getImageHistory() {
		return _imageHistory;
	}

	/** Returns the value of the IMAGELAYER (34732) tag. */
	public int[] getImageLayer() {
		return _imageLayer;
	}

	/** Returns the value of the IMAGENUMBER (37393) tag. */
	public long getImageNumber() {
		return _imageNumber;
	}

	/**
	 * Returns the value of the IMAGESOURCEDATA
	 * (37724) tag.
	 */
	public int[] getImageSourceData() {
		return _imageSourceData;
	}

	/**
	 * Returns the value of the PHOTOSHOPPROPS
	 * (34377) tag.
	 */
	public int[] getPhotoshopProperties() {
		return _photoshopProperties;
	}

	/**
	 * Returns the value of the ANNOTATIONS
	 * (50255) tag.
	 */
	public int[] getAnnotations() {
		return _annotations;
	}

	/** Returns the value of the INKNAMES (333) tag. */
	public String[] getInkNames() {
		return _inkNames;
	}

	/** Returns the value of the INKSET (332) tag. */
	public int getInkSet() {
		return _inkSet;
	}

	/** Returns the value of the INTERLACE (34857) tag. */
	public int getInterlace() {
		return _interlace;
	}

	/** Returns the offset of the Exif Interoperability IFD. */
	public long getInteroperabilityIFD() {
		return _interoperabilityIFD;
	}

	/** Returns the value of the ICC_PROFILE tag. */
	public byte[] getICCProfile() {
		return _iccProfile;
	}

	/** Returns the value of the INDEXED (364) tag. */
	public int getIndexed() {
		return _indexed;
	}

	public long getJpegInterchangeFormat() {
		return _jpegInterchangeFormat;
	}

	/** Returns the value of the IPTCNAA (33723) tag. */
	public long[] getIPTCNAA() {
		return _iptc;
	}

	/**
	 * Returns the value of the ISOSPEEDRATINGS
	 * (34855) tag.
	 */
	public int[] getISOSpeedRatings() {
		return _isoSpeedRatings;
	}

	/** Returns the value of the IT8HEADER (34018) tag. */
	public String getIT8Header() {
		return _it8Header;
	}

	/** Returns the value of the JPEGPROC (512) tag. */
	public int getJPEGProc() {
		return _jpegProc;
	}

	/**
	 * Returns the value of the MAXAPERTUREVALUE (37381)
	 * tag. Note typo in function name.
	 */
	public Rational getMaxAperatureValue() {
		return _maxAperatureValue;
	}

	/**
	 * Returns the value of the MODELTIEPOINTTAG (33922)
	 * tag.
	 */
	public double[] getModelTiepointTag() {
		return _modelTiepointTag;
	}

	/**
	 * Returns the value of the MODELTRANSFORMATIONTAG
	 * (34264) tag.
	 */
	public double[] getModelTransformationTag() {
		return _modelTransformationTag;
	}

	/** Returns the value of the NEWSUBFILETYPE (254) tag. */
	public long getNewSubfileType() {
		return _newSubfileType;
	}

	/** Returns the constructed NisoImageMetadata. */
	public NisoImageMetadata getNisoImageMetadata() {
		return _niso;
	}

	/** Returns the value of the NOISE (37389) tag. */
	public int[] getNoise() {
		return _noise;
	}

	/** Returns the value of the NUMBEROFINKS (334) tag. */
	public int getNumberOfInks() {
		return _numberOfInks;
	}

	/** Returns the value of the OECF (34856) tag. */
	public int[] getOECF() {
		return _oecf;
	}

	/** Returns the value of the PAGENAME (285) tag. */
	public String getPageName() {
		return _pageName;
	}

	/** Returns the value of the PAGENUMBER (297) tag. */
	public int[] getPageNumber() {
		return _pageNumber;
	}

	/** Returns the value of the PIXELINTENSITYRANGE (34027) tag. */
	public int[] getPixelIntensityRange() {
		return _pixelIntensityRange;
	}

	/** Returns the value of the RASTERPADDING (34019) tag. */
	public int getRasterPadding() {
		return _rasterPadding;
	}

	/** Returns the value of the SECURITYCLASSIFICATION (37394) tag. */
	public String getSecurityClasssification() {
		return _securityClassification;
	}

	/** Returns the value of the SELFTIMERMODE (34859) tag. */
	public int getSelfTimerMode() {
		return _selfTimerMode;
	}

	/** Returns the value of the SHUTTERSPEEDVALUE (37377) tag. */
	public Rational getShutterSpeedValue() {
		return _shutterSpeedValue;
	}

	/** Returns the value of the SITE (34016) tag. */
	public String getSite() {
		return _site;
	}

	/** Returns the value of the SPATIALFREQUENCYRESPONSE (37388) tag. */
	public int[] getSpatialFrequencyResponse() {
		return _spatialFrequencyResponse;
	}

	/** Returns the value of the SPECTRALSENSITIVITY (34852) tag. */
	public String getSpectralSensitivity() {
		return _spectralSensitivity;
	}

	/** Returns the value of the STRIPROWCOUNTS (559) tag. */
	public long[] getStripRowCounts() {
		return _stripRowCounts;
	}

	/** Returns the value of the SUBIFDS (330) tag. */
	public long[] getSubIFDs() {
		return _subIFDs;
	}

	/** Returns the value of the SUBJECTLOCATION (37396) tag. */
	public int[] getSubjectLocation() {
		return _subjectLocation;
	}

	/** Returns the value of the T4OPTIONS (292) tag. */
	public long getT4Options() {
		return _t4Options;
	}

	/** Returns the value of the T6OPTIONS (293) tag. */
	public long getT6Options() {
		return _t6Options;
	}

	/** Returns the Exif IFD object, or null if none. */
	public ExifIFD getTheExifIFD() {
		return _theExifIFD;
	}

	/** Returns the GPS info IFD object, or null if none. */
	public GPSInfoIFD getTheGPSInfoIFD() {
		return _theGPSInfoIFD;
	}

	/**
	 * Returns the Interoperability IFD object,
	 * or null if none.
	 */
	public InteroperabilityIFD getTheInteroperabilityIFD() {
		return _theInteroperabilityIFD;
	}

	/** Returns the GlobalParameters IFD object, or null if none. */
	public GlobalParametersIFD getTheGlobalParametersIFD() {
		return _theGlobalParametersIFD;
	}

	/** Returns the value of the THRESHHOLDING (263) tag. */
	public int getThreshholding() {
		return _threshholding;
	}

	/** Returns the value of the TIFFEPSTANDARDID (37398) tag. */
	public String getTIFFEPStandardID() {
		return _tiffEPStandardID;
	}

	/** Returns the value of the TIMEZONEOFFSET (34858) tag. */
	public int[] getTimeZoneOffset() {
		return _timeZoneOffset;
	}

	/** Returns the value of the TRANSPARENCYINDICATOR (34028) tag. */
	public int getTransparencyIndicator() {
		return _transparencyIndicator;
	}

	/** Returns the value of the XCLIPPATHUNITS (344) tag. */
	public long getXClipPathUnits() {
		return _xClipPathUnits;
	}

	/** Returns the value of the XPOSITION (286) tag. */
	public Rational getXPosition() {
		return _xPosition;
	}

	/** Returns the value of the XPOSITION (287) tag. */
	public Rational getYPosition() {
		return _yPosition;
	}

	/** Returns the value of the DNGVERSION (50706) tag. */
	public int[] getDNGVersion() {
		return _dngVersion;
	}

	/** Returns the value of the DNG UNIQUECAMERAMODEL (50708) tag. */
	public String getUniqueCameraModel() {
		return _uniqueCameraModel;
	}

	/** Returns the value of the CFAPlaneColor (50710) tag. */
	public int[] getCFAPlaneColor() {
		return _cfaPlaneColor;
	}

	/** Returns the value of the AsShotNeutral (50728) tag. */
	public Rational[] getAsShotNeutral() {
		return _asShotNeutral;
	}

	/** Returns the value of the AsShotWhiteXY (50729) tag. */
	public Rational[] getAsShotWhiteXY() {
		return _asShotWhiteXY;
	}

	/** Get the IFD properties. */
	public Property getProperty(boolean rawOutput) throws TiffException {
		List<Property> entries = new LinkedList<Property>();
		// This function has gotten obscenely large. Split it up.
		addNisoProperties(entries, rawOutput);
		addMiscProperties(entries, rawOutput);
		addTiffITProperties(entries, rawOutput);
		addTiffEPProperties(entries, rawOutput);
		addGeoTiffProperties(entries, rawOutput);
		addTiffFXProperties(entries, rawOutput);
		addDNGProperties(entries, rawOutput);
		return propertyHeader("TIFF", entries);
	}

	private void addNisoProperties(List<Property> entries, boolean rawOutput) {
		entries.add(new Property("NisoImageMetadata",
				PropertyType.NISOIMAGEMETADATA, _niso));
	}

	/* Add non-NISO properties. */
	private void addMiscProperties(List<Property> entries, boolean rawOutput) {
		if (_imageDescription != null) {
			entries.add(new Property("ImageDescription", PropertyType.STRING,
					_imageDescription));
		}
		if (_dateTime != null) {
			entries.add(
					new Property("DateTime", PropertyType.STRING, _dateTime));
		}
		if (_newSubfileType != 0L || rawOutput) {
			entries.add(addBitmaskProperty("NewSubfileType", _newSubfileType,
					NEWSUBFILETYPE_L, rawOutput));
		} else {
			// if 0, always report as a raw number
			entries.add(new Property("NewSubfileType", PropertyType.LONG, _newSubfileType));
		}
		if (_subfileType != NULL && (_subfileType != 0 || rawOutput)) {
			entries.add(addIntegerProperty("SubfileType", _subfileType - 1,
					SUBFILETYPE_L, rawOutput));
		} else if (_subfileType != NULL) {
			// if 0, always report as a raw number
			entries.add(new Property("SubfileType", PropertyType.LONG,
					new Long(_subfileType)));
		}
		if (_documentName != null) {
			entries.add(new Property("DocumentName", PropertyType.STRING,
					_documentName));
		}
		if (_pageName != null) {
			entries.add(
					new Property("PageName", PropertyType.STRING, _pageName));
		}
		if (_pageNumber != null) {
			entries.add(new Property("PageNumber", PropertyType.INTEGER,
					PropertyArity.ARRAY, _pageNumber));
		}
		if (_xPosition != null) {
			entries.add(
					addRationalProperty("XPosition", _xPosition, rawOutput));
		}
		if (_yPosition != null) {
			entries.add(
					addRationalProperty("YPosition", _yPosition, rawOutput));
		}
		if (_copyright != null) {
			entries.add(
					new Property("Copyright", PropertyType.STRING, _copyright));
		}
		if (_fillOrder != NULL) {
			entries.add(addIntegerProperty("FillOrder", _fillOrder, FILLORDER_L,
					rawOutput));
		}
		entries.add(new Property("SampleFormat", PropertyType.INTEGER,
				PropertyArity.ARRAY, _sampleFormat));
		if (_minSampleValue != null) {
			entries.add(new Property("MinSampleValue", PropertyType.INTEGER,
					PropertyArity.ARRAY, _minSampleValue));
		}
		if (_maxSampleValue != null) {
			entries.add(new Property("MaxSampleValue", PropertyType.INTEGER,
					PropertyArity.ARRAY, _maxSampleValue));
		}
		if (_inkSet != NULL) {
			entries.add(
					addIntegerProperty("InkSet", _inkSet, INKSET_L, rawOutput));
		}
		if (_numberOfInks != NULL) {
			entries.add(new Property("NumberOfInks", PropertyType.INTEGER, _numberOfInks));
		}
		if (_inkNames != null) {
			entries.add(new Property("InkNames", PropertyType.STRING,
					PropertyArity.ARRAY, _inkNames));
		}
		if (_dotRange != null) {
			entries.add(new Property("DotRange", PropertyType.INTEGER,
					PropertyArity.ARRAY, _dotRange));
		}
		if (_targetPrinter != null) {
			entries.add(new Property("TargetPrinter", PropertyType.STRING,
					_targetPrinter));
		}
		if (_halftoneHints != null) {
			entries.add(new Property("HalftoneHints", PropertyType.INTEGER,
					PropertyArity.ARRAY, _halftoneHints));
		}
		if (_cellLength != NULL) {
			entries.add(new Property("CellLength", PropertyType.INTEGER, _cellLength));
		}
		if (_cellWidth != NULL) {
			entries.add(new Property("CellWidth", PropertyType.INTEGER, _cellWidth));
		}
		if (_transferFunction) {
			entries.add(new Property("TransferFunction", PropertyType.BOOLEAN,
					Boolean.TRUE));
		}
		if (_transferRange != null) {
			entries.add(new Property("TransferRange", PropertyType.INTEGER,
					PropertyArity.ARRAY, _transferRange));
		}
		entries.add(new Property("Threshholding", PropertyType.INTEGER, _threshholding));
		if (_predictor != NULL) {
			entries.add(addIntegerProperty("Predictor", _predictor, PREDICTOR_L,
					rawOutput));
		}
		if (_t4Options != NULL) {
			entries.add(new Property("T4Options", PropertyType.LONG, _t4Options));
		}
		if (_t6Options != NULL) {
			entries.add(new Property("T6Options", PropertyType.LONG, _t6Options));
		}
		if (_jpegProc != NULL) {
			entries.add(addIntegerProperty("JPEGProc", _jpegProc, JPEGPROC_L,
					JPEGPROC_INDEX, rawOutput));
		}
		if (_jpegInterchangeFormat != NULL) {
			entries.add(new Property("JPEGInterchangeFormat", PropertyType.LONG, _jpegInterchangeFormat));
		}
		if (_jpegInterchangeFormatLength != NULL) {
			entries.add(new Property("JPEGInterchangeFormatLength",
					PropertyType.LONG, _jpegInterchangeFormatLength));
		}
		if (_jpegRestartInterval != NULL) {
			entries.add(new Property("JPEGRestartInterval",
					PropertyType.INTEGER, _jpegRestartInterval));
		}
		if (_jpegLosslessPredictors != null) {
			entries.add(addIntegerArrayProperty("JPEGLosslessPredictors",
					_jpegLosslessPredictors, JPEGLOSSLESSPREDICTORS_L,
					rawOutput));
		}
		if (_jpegPointTransforms != null) {
			entries.add(
					new Property("JPEGPointTransforms", PropertyType.INTEGER,
							PropertyArity.ARRAY, _jpegPointTransforms));
		}
		if (_jpegQTables != null) {
			entries.add(new Property("JPEGQTables", PropertyType.LONG,
					PropertyArity.ARRAY, _jpegQTables));
		}
		if (_jpegDCTables != null) {
			entries.add(new Property("JPEGDCTables", PropertyType.LONG,
					PropertyArity.ARRAY, _jpegDCTables));
		}
		if (_jpegACTables != null) {
			entries.add(new Property("JPEGACTables", PropertyType.LONG,
					PropertyArity.ARRAY, _jpegACTables));
		}
		if (_jpegTables != null) {
			entries.add(new Property("JPEGTables", PropertyType.INTEGER,
					PropertyArity.ARRAY, _jpegTables));
		}
		if (_imageSourceData != null) {
			entries.add(new Property("ImageSourceData", PropertyType.INTEGER,
					PropertyArity.ARRAY, _imageSourceData));
		}
		if (_photoshopProperties != null) {
			entries.add(
					new Property("PhotoshopProperties", PropertyType.INTEGER,
							PropertyArity.ARRAY, _photoshopProperties));
		}
		if (_annotations != null) {
			entries.add(new Property("Annotations", PropertyType.INTEGER,
					PropertyArity.ARRAY, _annotations));
		}
		if (_clipPath != null) {
			entries.add(new Property("ClipPath", PropertyType.INTEGER,
					PropertyArity.ARRAY, _clipPath));
		}
		if (_xClipPathUnits != NULL) {
			entries.add(new Property("XClipPathUnits", PropertyType.LONG, _xClipPathUnits));
		}
		if (_yClipPathUnits != NULL) {
			entries.add(new Property("YClipPathUnits", PropertyType.LONG, _yClipPathUnits));
		}
		if (_cleanFaxData != NULL) {
			entries.add(new Property("CleanFaxData", PropertyType.LONG,
					new Long(_cleanFaxData)));
		}
		if (_badFaxLines != NULL) {
			entries.add(new Property("BadFaxLines", PropertyType.LONG, _badFaxLines));
		}
		if (_consecutiveBadFaxLines != NULL) {
			entries.add(new Property("ConsecutiveBadFaxLines",
					PropertyType.LONG, _consecutiveBadFaxLines));
		}
		if (_freeByteCounts != null) {
			entries.add(new Property("FreeByteCounts", PropertyType.LONG,
					PropertyArity.ARRAY, _freeByteCounts));
		}
		if (_freeOffsets != null) {
			entries.add(new Property("FreeOffsets", PropertyType.LONG,
					PropertyArity.ARRAY, _freeOffsets));
		}
	}

	private void addTiffITProperties(List<Property> entries,
			boolean rawOutput) {
		/* Add TIFF/IT properties. */

		List<Property> itList = new LinkedList<Property>();
		if (_site != null) {
			itList.add(new Property("Site", PropertyType.STRING, _site));
		}
		if (_colorTable != null) {
			itList.add(new Property("ColorTable", PropertyType.INTEGER,
					PropertyArity.ARRAY, _colorTable));
		}
		itList.add(addIntegerProperty("BackgroundColorIndicator",
				_backgroundColorIndicator, BACKGROUNDCOLORINDICATOR_L,
				rawOutput));
		if (_backgroundColorValue != NULL) {
			itList.add(new Property("BackgroundColorValue",
					PropertyType.INTEGER, _backgroundColorValue));
		}
		itList.add(addIntegerProperty("ImageColorIndicator",
				_imageColorIndicator, IMAGECOLORINDICATOR_L, rawOutput));
		itList.add(addIntegerProperty("TransparencyIndicator",
				_transparencyIndicator, TRANSPARENCYINDICATOR_L, rawOutput));
		if (_imageColorValue != NULL) {
			itList.add(new Property("ImageColorValue", PropertyType.INTEGER, _imageColorValue));
		}
		if (_colorCharacterization != null) {
			itList.add(new Property("ColorCharacterization",
					PropertyType.STRING, _colorCharacterization));
		}
		if (_colorSequence != null) {
			itList.add(new Property("ColorSequence", PropertyType.STRING,
					_colorSequence));
		}
		if (_hcUsage != NULL) {
			itList.add(addBitmaskProperty("HCUsage", _hcUsage, HCUSAGE_L,
					rawOutput));
		}
		if (_it8Header != null) {
			itList.add(
					new Property("IT8Header", PropertyType.STRING, _it8Header));
		}
		if (_pixelIntensityRange != null) {
			itList.add(new Property("PixelIntensityRange", PropertyType.INTEGER,
					PropertyArity.ARRAY, _pixelIntensityRange));
		}
		itList.add(addIntegerProperty("RasterPadding", _rasterPadding,
				RASTERPADDING_L, rawOutput));
		itList.add(new Property("BitsPerRunLength", PropertyType.INTEGER, _bitsPerRunLength));
		itList.add(new Property("BitsPerExtendedRunLength",
				PropertyType.INTEGER, _bitsPerExtendedRunLength));
		entries.add(new Property("TIFFITProperties", PropertyType.PROPERTY,
				PropertyArity.LIST, itList));
	}

	/* Add TIFF/EP properties. */
	private void addTiffEPProperties(List<Property> entries,
			boolean rawOutput) {
		List<Property> epList = new LinkedList<Property>();
		if (_cfaRepeatPatternDim != null) {
			epList.add(new Property("CFARepeatPatternDim", PropertyType.INTEGER,
					PropertyArity.ARRAY, _cfaRepeatPatternDim));
		}
		if (_cfaPattern != null) {
			epList.add(new Property("CFAPattern", PropertyType.INTEGER,
					PropertyArity.ARRAY, _cfaPattern));
		}
		if (_batteryLevel != null) {
			epList.add(new Property("BatteryLevel", PropertyType.STRING,
					_batteryLevel));
		}
		if (_iptc != null) {
			epList.add(new Property("IPTCNAA", PropertyType.LONG,
					PropertyArity.ARRAY, _iptc));
		}
		if (_iccProfile != null) {
			epList.add(new Property("ICCProfile", PropertyType.BOOLEAN,
					Boolean.TRUE));
		}
		if (_exposureProgram != NULL) {
			epList.add(addIntegerProperty("ExposureProgram", _exposureProgram,
					EXPOSUREPROGRAM_L, rawOutput));
		}
		if (_spectralSensitivity != null) {
			epList.add(new Property("SpectralSensitivity", PropertyType.STRING,
					_spectralSensitivity));
		}
		if (_isoSpeedRatings != null) {
			epList.add(new Property("ISOSpeedRatings", PropertyType.INTEGER,
					PropertyArity.ARRAY, _isoSpeedRatings));
		}
		if (_oecf != null) {
			epList.add(new Property("OECF", PropertyType.INTEGER,
					PropertyArity.ARRAY, _oecf));
		}
		if (_interlace != NULL) {
			epList.add(new Property("Interlace", PropertyType.INTEGER, _interlace));
		}
		if (_timeZoneOffset != null) {
			epList.add(new Property("TimeZoneOffset", PropertyType.INTEGER,
					PropertyArity.ARRAY, _timeZoneOffset));
		}
		if (_selfTimerMode != NULL) {
			epList.add(new Property("SelfTimerMode", PropertyType.INTEGER, _selfTimerMode));
		}
		if (_compressedBitsPerPixel != null) {
			epList.add(addRationalProperty("CompressedBitsPerPixel",
					_compressedBitsPerPixel, rawOutput));
		}
		if (_shutterSpeedValue != null) {
			epList.add(addRationalProperty("ShutterSpeedValue",
					_shutterSpeedValue, rawOutput));
		}
		if (_aperatureValue != null) {
			epList.add(addRationalProperty("AperatureValue", _aperatureValue,
					rawOutput));
		}
		if (_maxAperatureValue != null) {
			epList.add(addRationalProperty("MaxAperatureValue",
					_maxAperatureValue, rawOutput));
		}
		if (_flash != NULL) {
			epList.add(addIntegerProperty("FLASH", _flash, FLASH_L, FLASH_INDEX,
					rawOutput));
		}
		if (_spatialFrequencyResponse != null) {
			epList.add(new Property("SpatialFrequencyResponse",
					PropertyType.INTEGER, PropertyArity.ARRAY,
					_spatialFrequencyResponse));
		}
		if (_noise != null) {
			epList.add(new Property("Noise", PropertyType.INTEGER,
					PropertyArity.ARRAY, _noise));
		}
		if (_focalPlaneXResolution != null) {
			epList.add(addRationalProperty("FocalPlaneXResolution",
					_focalPlaneXResolution, rawOutput));
		}
		if (_focalPlaneYResolution != null) {
			epList.add(addRationalProperty("FocalPlaneYResolution",
					_focalPlaneYResolution, rawOutput));
		}
		if (_focalPlaneResolutionUnit != NULL) {
			epList.add(addIntegerProperty("FocalPlaneResolutionUnit",
					_focalPlaneResolutionUnit, FOCALPLANERESOLUTIONUNIT_L,
					rawOutput));
		}
		if (_imageNumber != NULL) {
			epList.add(new Property("ImageNumber", PropertyType.LONG, _imageNumber));
		}
		if (_securityClassification != null) {
			epList.add(new Property("SecurityClassification",
					PropertyType.STRING, _securityClassification));
		}
		if (_imageHistory != null) {
			epList.add(new Property("ImageHistory", PropertyType.STRING,
					_imageHistory));
		}
		if (_subjectLocation != null) {
			epList.add(new Property("SubjectLocation", PropertyType.INTEGER,
					PropertyArity.ARRAY, _subjectLocation));
		}
		if (_tiffEPStandardID != null) {
			epList.add(new Property("TIFFEPSStandardID", PropertyType.STRING,
					_tiffEPStandardID));
		}
		if (epList.size() > 0) {
			entries.add(new Property("TIFFEPProperties", PropertyType.PROPERTY,
					PropertyArity.LIST, epList));
		}

		if (_xmpProp != null) {
			entries.add(_xmpProp);
		}
	}

	private void addGeoTiffProperties(List<Property> entries, boolean rawOutput)
			throws TiffException {
		/* Add GeoTIFF properties. */

		List<Property> dirList = new LinkedList<Property>();
		if (_geoKeyDirectoryTag != null) {
			dirList.add(new Property("Version", PropertyType.INTEGER, _geoKeyDirectoryTag[0]));
			dirList.add(new Property("Revision", PropertyType.STRING,
					Integer.toString(_geoKeyDirectoryTag[1]) + "."
							+ Integer.toString(_geoKeyDirectoryTag[2])));
			dirList.add(new Property("NumberOfKeys", PropertyType.INTEGER, _geoKeyDirectoryTag[3]));
			for (int i = 0; i < _geoKeyDirectoryTag[3]; i++) {
				int j = i * 4 + 4;
				int key = _geoKeyDirectoryTag[j];
				int location = _geoKeyDirectoryTag[j + 1];
				int count = _geoKeyDirectoryTag[j + 2];
				int offset = _geoKeyDirectoryTag[j + 3];

				int ival = 0;
				double dval = 0.0;
				String sval = "NULL";
                            switch (location) {
                                case 0:
                                    ival = offset;
                                    break;
                                case 34736:
                                    dval = _geoDoubleParamsTag[offset];
                                    break;
                                case 34737:
                                    try {
                                        sval = _geoAsciiParamsTag.substring(offset,
                                                offset + count - 1);
                                    } catch (Exception e) {
                                        throw new TiffException(MessageConstants.TIFF_HUL_9);
                                    }
                                    break;
                                default:
                                    break;
                            }

                            switch (key) {
                                case GTMODELTYPEGEOKEY:
                                    dirList.add(addIntegerProperty("GTModelType", ival,
                                            GeoTiffStrings.MODELTYPE,
                                            GeoTiffStrings.MODELTYPE_INDEX, rawOutput));
                                    break;
                                case GTRASTERTYPEGEOKEY:
                                    dirList.add(addIntegerProperty("GTRasterType", ival,
                                            GeoTiffStrings.RASTERTYPE,
                                            GeoTiffStrings.RASTERTYPE_INDEX, rawOutput));
                                    break;
                                case GTCITATIONGEOKEY:
                                    dirList.add(new Property("GTCitation", PropertyType.STRING,
                                            sval));
                                    break;
                                case GEOGRAPHICTYPEGEOKEY:
                                    dirList.add(addIntegerProperty("GeographicType", ival,
                                            GeoTiffStrings.GEOGRAPHICS,
                                            GeoTiffStrings.GEOGRAPHICS_INDEX, rawOutput));
                                    break;
                                case GEOGCITATIONGEOKEY:
                                    dirList.add(new Property("GeogCitation",
                                            PropertyType.STRING, sval));
                                    break;
                                case GEOGGEODETICDATUMGEOKEY:
                                    dirList.add(addIntegerProperty("GeogGeodeticDatum", ival,
                                            GeoTiffStrings.GEODETICDATUM,
                                            GeoTiffStrings.GEODETICDATUM_INDEX, rawOutput));
                                    break;
                                case GEOGPRIMEMERIDIANGEOKEY:
                                    dirList.add(addIntegerProperty("GeogPrimeMeridian", ival,
                                            GeoTiffStrings.PRIMEMERIDIAN,
                                            GeoTiffStrings.PRIMEMERIDIAN_INDEX, rawOutput));
                                    break;
                                case GEOGPRIMEMERIDIANLONGGEOKEY:
                                    dirList.add(new Property("GeogPrimeMeridianLong",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGLINEARUNITSGEOKEY:
                                    dirList.add(addIntegerProperty("GeogLinearUnits", ival,
                                            GeoTiffStrings.LINEARUNITS,
                                            GeoTiffStrings.LINEARUNITS_INDEX, rawOutput));
                                    break;
                                case GEOGLINEARUNITSIZEGEOKEY:
                                    dirList.add(new Property("GeogLinearUnitSize",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGANGULARUNITSGEOKEY:
                                    dirList.add(addIntegerProperty("GeogAngularUnits", ival,
                                            GeoTiffStrings.ANGULARUNITS,
                                            GeoTiffStrings.ANGULARUNITS_INDEX, rawOutput));
                                    break;
                                case GEOGANGULARUNITSIZEGEOKEY:
                                    dirList.add(new Property("GeogAngularUnitSize",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGELLIPSOIDGEOKEY:
                                    dirList.add(addIntegerProperty("GeogEllipsoid", ival,
                                            GeoTiffStrings.ELLIPSOID,
                                            GeoTiffStrings.ELLIPSOID_INDEX, rawOutput));
                                    break;
                                case GEOGSEMIMAJORAXISGEOKEY:
                                    dirList.add(new Property("GeogSemiMajorAxis",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGSEMIMINORAXISGEOKEY:
                                    dirList.add(new Property("GeogSemiMinorAxis",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGINVFLATTENINGGEOKEY:
                                    dirList.add(new Property("GeogInvFlattening",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case GEOGAZIMUTHUNITSGEOKEY:
                                    dirList.add(addIntegerProperty("GeogAzimuthUnits", ival,
                                            GeoTiffStrings.ANGULARUNITS,
                                            GeoTiffStrings.ANGULARUNITS_INDEX, rawOutput));
                                    break;
                                case PROJECTEDCSTYPEGEOKEY:
                                    dirList.add(addIntegerProperty("ProjectedCSType", ival,
                                            GeoTiffStrings.PROJECTEDCSTYPE,
                                            GeoTiffStrings.PROJECTEDCSTYPE_INDEX, rawOutput));
                                    break;
                                case PCSCITATIONGEOKEY:
                                    dirList.add(new Property("PCSCitation", PropertyType.STRING,
                                            sval));
                                    break;
                                case PROJECTIONGEOKEY:
                                    dirList.add(addIntegerProperty("Projection", ival,
                                            GeoTiffStrings.PROJECTION,
                                            GeoTiffStrings.PROJECTION_INDEX, rawOutput));
                                    break;
                                case PROJCOORDTRANSGEOKEY:
                                    dirList.add(addIntegerProperty("ProjCoordTrans", ival,
                                            GeoTiffStrings.COORDINATETRANSFORMATION,
                                            GeoTiffStrings.COORDINATETRANSFORMATION_INDEX,
                                            rawOutput));
                                    break;
                                case PROJLINEARUNITSGEOKEY:
                                    dirList.add(addIntegerProperty("ProjLinearUnits", ival,
                                            GeoTiffStrings.LINEARUNITS,
                                            GeoTiffStrings.LINEARUNITS_INDEX, rawOutput));
                                    break;
                                case PROJLINEARUNITSIZEGEOKEY:
                                    dirList.add(new Property("ProjLinearUnitSize",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJSTDPARALLEL1GEOKEY:
                                    dirList.add(new Property("ProjStdParallel1",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJSTDPARALLEL2GEOKEY:
                                    dirList.add(new Property("ProjStdParallel2",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJNATORIGINLONGGEOKEY:
                                    dirList.add(new Property("ProjNatOriginLong",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJNATORIGINLATGEOKEY:
                                    dirList.add(new Property("ProjNatOriginLat",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSEEASTINGGEOKEY:
                                    dirList.add(new Property("ProjFalseEasting",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSENORTHINGGEOKEY:
                                    dirList.add(new Property("ProjFalseNorthing",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSEORIGINLONGGEOKEY:
                                    dirList.add(new Property("ProjFalseOriginLong",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSEORIGINLATGEOKEY:
                                    dirList.add(new Property("ProjFalseOriginLat",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSEORIGINEASTINGGEOKEY:
                                    dirList.add(new Property("ProjFalseOriginEasting",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJFALSEORIGINNORTHINGGEOKEY:
                                case PROJFALSEORIGINNORTHINGGEOKEY_2:
                                    dirList.add(new Property("ProjFalseOriginNorthing",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJCENTERLONGGEOKEY:
                                    dirList.add(new Property("ProjCenterLong",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJCENTERLATGEOKEY:
                                    dirList.add(new Property("ProjCenterLat",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJCENTEREASTINGGEOKEY:
                                    dirList.add(new Property("ProjCenterEasting",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJSCALEATNATORIGINGEOKEY:
                                    dirList.add(new Property("ProjScaleAtNatOrigin",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJSCALEATCENTERGEOKEY:
                                    dirList.add(new Property("ProjScaleAtCenter",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJAZIMUTHANGLEGEOKEY:
                                    dirList.add(new Property("ProjAzimuthAngle",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case PROJSTRAIGHTVERTPOLELONGEOKEY:
                                    dirList.add(new Property("ProjStraightVertPoleLong",
                                            PropertyType.DOUBLE, dval));
                                    break;
                                case VERTICALCSTYPEGEOKEY:
                                    dirList.add(addIntegerProperty("VerticalCSType", ival,
                                            GeoTiffStrings.VERTICALCSTYPE,
                                            GeoTiffStrings.VERTICALCSTYPE_INDEX, rawOutput));
                                    break;
                                case VERTICALCITATIONGEOKEY:
                                    dirList.add(new Property("VerticalCitation",
                                            PropertyType.STRING, sval));
                                    break;
                                case VERTICALDATUMGEOKEY:
                                    dirList.add(addIntegerProperty("VerticalDatum", ival,
                                            GeoTiffStrings.VERTICALCSDATUM,
                                            GeoTiffStrings.VERTICALCSDATUM_INDEX, rawOutput));
                                    break;
                                case VERTICALUNITSGEOKEY:
                                    dirList.add(addIntegerProperty("VerticalUnits", ival,
                                            GeoTiffStrings.LINEARUNITS,
                                            GeoTiffStrings.LINEARUNITS_INDEX, rawOutput));
                                    break;
                                default:
                                    break;
                            }
			}
		}
		List<Property> geoList = new LinkedList<Property>();
		if (dirList.size() > 0) {
			geoList.add(new Property("GeoKeyDirectory", PropertyType.PROPERTY,
					PropertyArity.LIST, dirList));
		}

		if (_modelTiepointTag != null) {
			geoList.add(new Property("ModelTiepointTag", PropertyType.DOUBLE,
					PropertyArity.ARRAY, _modelTiepointTag));
		}
		if (_modelPixelScaleTag != null) {
			geoList.add(new Property("ModelPixelScaleTag", PropertyType.DOUBLE,
					PropertyArity.ARRAY, _modelPixelScaleTag));
		}
		if (_modelTransformationTag != null) {
			geoList.add(
					new Property("ModelTransformationTag", PropertyType.DOUBLE,
							PropertyArity.ARRAY, _modelTransformationTag));
		}

		if (geoList.size() > 0) {
			entries.add(new Property("GeoTIFFProperties", PropertyType.PROPERTY,
					PropertyArity.LIST, geoList));
		}
	}

	/* Add Tiff/FX properties */
	private void addTiffFXProperties(List<Property> entries,
			boolean rawOutput) {
		if (_stripRowCounts != null) {
			entries.add(new Property("StripRowCounts", PropertyType.LONG,
					PropertyArity.ARRAY, _stripRowCounts));
		}
		if (_imageLayer != null) {
			// Do up ImageLayer as a property with two subproperties.
			Property[] layerProps = new Property[2];
			try {
				layerProps[0] = addIntegerProperty("LayerType", _imageLayer[0],
						IMAGELAYER_L, rawOutput);
				layerProps[1] = new Property("OrdinalNumber",
						PropertyType.INTEGER, _imageLayer[1]);

				entries.add(new Property("ImageLayer", PropertyType.PROPERTY,
						PropertyArity.ARRAY, layerProps));
			}
			// Don't blow up on incorrect array size
			catch (Exception e) {
			}
		}
	}

	/* Adds DNG properties. */
	private void addDNGProperties(List<Property> entries, boolean rawOutput) {
		setDNGDefaults();
		List<Property> dngList = new LinkedList<Property>();
		if (_dngVersion != null) {
			dngList.add(new Property("DNGVersion", PropertyType.INTEGER,
					PropertyArity.ARRAY, _dngVersion));
		}
		if (_dngBackwardVersion != null) {
			dngList.add(new Property("DNGBackwardVersion", PropertyType.INTEGER,
					PropertyArity.ARRAY, _dngBackwardVersion));
		}
		if (_uniqueCameraModel != null) {
			dngList.add(new Property("UniqueCameraModel", PropertyType.STRING,
					_uniqueCameraModel));
		}
		if (_localizedCameraModel != null) {
			dngList.add(new Property("LocalizedCameraModel",
					PropertyType.STRING, _localizedCameraModel));
		}
		if (_cfaPlaneColor != null) {
			dngList.add(new Property("CFAPlaneColor", PropertyType.INTEGER,
					PropertyArity.ARRAY, _cfaPlaneColor));
		}
		if (_cfaLayout != NULL) {
			dngList.add(addIntegerProperty("CFALayout", _cfaLayout, CFALAYOUT_L,
					rawOutput));
		}
		if (_linearizationTable != null) {
			dngList.add(new Property("LinearizationTable", PropertyType.INTEGER,
					PropertyArity.ARRAY, _linearizationTable));
		}
		if (_blackLevelRepeatDim != null) {
			dngList.add(
					new Property("BlackLevelRepeatDim", PropertyType.INTEGER,
							PropertyArity.ARRAY, _blackLevelRepeatDim));
		}
		if (_blackLevel != null) {
			dngList.add(new Property("BlackLevel", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _blackLevel));
		}
		if (_blackLevelDeltaH != null) {
			dngList.add(new Property("BlackLevelDeltaH", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _blackLevelDeltaH));
		}
		if (_blackLevelDeltaV != null) {
			dngList.add(new Property("BlackLevelDeltaV", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _blackLevelDeltaV));
		}
		if (_whiteLevel != null) {
			dngList.add(new Property("WhiteLevel", PropertyType.LONG,
					PropertyArity.ARRAY, _whiteLevel));
		}
		if (_defaultScale != null) {
			dngList.add(new Property("DefaultScale", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _defaultScale));
		}
		if (_bestQualityScale != null) {
			dngList.add(new Property("BestQualityScale", PropertyType.RATIONAL,
					_bestQualityScale));
		}
		if (_defaultCropOrigin != null) {
			dngList.add(new Property("DefaultCropOrigin", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _defaultCropOrigin));
		}
		if (_defaultCropSize != null) {
			dngList.add(new Property("DefaultCropSize", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _defaultCropSize));
		}
		if (_calibrationIlluminant1 != NULL) {
			dngList.add(
					new Property("CalibrationIlluminant1", PropertyType.INTEGER, _calibrationIlluminant1));
		}
		if (_calibrationIlluminant2 != NULL) {
			dngList.add(
					new Property("CalibrationIlluminant2", PropertyType.INTEGER, _calibrationIlluminant2));
		}
		if (_colorMatrix1 != null) {
			dngList.add(new Property("ColorMatrix1", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _colorMatrix1));
		}
		if (_colorMatrix2 != null) {
			dngList.add(new Property("ColorMatrix2", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _colorMatrix2));
		}
		if (_cameraCalibration1 != null) {
			dngList.add(
					new Property("CameraCalibration1", PropertyType.RATIONAL,
							PropertyArity.ARRAY, _cameraCalibration1));
		}
		if (_cameraCalibration2 != null) {
			dngList.add(
					new Property("CameraCalibration2", PropertyType.RATIONAL,
							PropertyArity.ARRAY, _cameraCalibration2));
		}
		if (_reductionMatrix1 != null) {
			dngList.add(new Property("ReductionMatrix1", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _reductionMatrix1));
		}
		if (_reductionMatrix2 != null) {
			dngList.add(new Property("ReductionMatrix2", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _reductionMatrix2));
		}
		if (_analogBalance != null) {
			dngList.add(new Property("AnalogBalance", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _analogBalance));
		}
		if (_asShotNeutral != null) {
			dngList.add(new Property("AsShotNeutral", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _asShotNeutral));
		}
		if (_asShotWhiteXY != null) {
			dngList.add(new Property("AsShotWhiteXY", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _asShotWhiteXY));
		}
		if (_baselineExposure != null) {
			dngList.add(new Property("BaselineExposure", PropertyType.RATIONAL,
					_baselineExposure));
		}
		if (_baselineNoise != null) {
			dngList.add(new Property("BaselineNoise", PropertyType.RATIONAL,
					_baselineNoise));
		}
		if (_baselineNoise != null) {
			dngList.add(new Property("BaselineSharpness", PropertyType.RATIONAL,
					_baselineSharpness));
		}
		if (_bayerGreenSplit != NULL) {
			dngList.add(new Property("BayerGreenSplit", PropertyType.INTEGER, _bayerGreenSplit));
		}
		if (_linearResponseLimit != null) {
			dngList.add(new Property("LinearResponseLimit",
					PropertyType.RATIONAL, _linearResponseLimit));
		}
		if (_cameraSerialNumber != null) {
			dngList.add(new Property("CameraSerialNumber", PropertyType.STRING,
					_cameraSerialNumber));
		}
		if (_lensInfo != null) {
			dngList.add(new Property("LensInfo", PropertyType.RATIONAL,
					PropertyArity.ARRAY, _lensInfo));
		}
		if (_chromaBlurRadius != null) {
			dngList.add(new Property("ChromaBlurRadius", PropertyType.RATIONAL,
					_chromaBlurRadius));
		}
		if (_antiAliasStrength != null) {
			dngList.add(new Property("AntiAliasStrength", PropertyType.RATIONAL,
					_antiAliasStrength));
		}
		if (_dngPrivateData != null) {
			dngList.add(new Property("DNGPrivateData", PropertyType.INTEGER,
					_dngPrivateData));
		}
		if (_makerNoteSafety != NULL) {
			dngList.add(addIntegerProperty("MakerNoteSafety", _makerNoteSafety,
					MAKERNOTESAFETY_L, rawOutput));
		}
		if (dngList.size() > 0) {
			entries.add(new Property("DNGProperties", PropertyType.PROPERTY,
					PropertyArity.LIST, dngList));
		}

	}

	/**
	 * ColorPlanes is an undefined primary in the TIFF spec. It's
	 * actually a non-obvious definition which is something of a
	 * pain to calculate.
	 */
	private int calcColorPlanes() {
		if (_photometricInterpretation == TiffProfileDNG.CFA) {
			// In this case, it's the number of unique colors
			// in the CFA pattern. (You mean you didn't already
			// know that?)
			int nUnique = 0;
			if (_cfaPattern == null) {
				// It's broken; return 1 so we don't try to
				// allocate zero-length objects.
				return 1;
			}
			int[] uniqueColors = new int[_cfaPattern.length];
			for (int i = 0; i < _cfaPattern.length; i++) {
				boolean unique = true;
				int color = _cfaPattern[i];
				for (int j = 0; j < nUnique; j++) {
					if (color == uniqueColors[j]) {
						unique = false;
						break;
					}
				}
				if (unique) {
					uniqueColors[nUnique++] = color;
				}

			}
			return nUnique;
		}
		return _niso.getSamplesPerPixel();
	}

	/**
	 * Set the default values for any DNG tags that haven't been
	 * encountered yet. If _dngVersion is zero, apply the IFD 0
	 * defaults; if PhotometricInterpretation is CFA or RawLinear,
	 * apply the Raw IFD defaults.
	 */
	private void setDNGDefaults() {
		if (_dngVersion != null) {
			// Apply "IFD 0" defaults
			if (_dngBackwardVersion == null) {
				_dngBackwardVersion = new int[4];
				// The default value is _dngVersion with the last two
				// bytes set to zero.
				_dngBackwardVersion[0] = _dngVersion[0];
				_dngBackwardVersion[1] = _dngVersion[1];
				_dngBackwardVersion[2] = 0;
				_dngBackwardVersion[3] = 0;
			}

			if (_uniqueCameraModel != null && _localizedCameraModel == null) {
				_localizedCameraModel = _uniqueCameraModel;
			}

			if (_calibrationIlluminant1 == NULL) {
				_calibrationIlluminant1 = 0;
			}
			if (_baselineExposure == null) {
				_baselineExposure = new Rational(0, 1);
			}
			if (_baselineNoise == null) {
				_baselineNoise = new Rational(1, 1);
			}
			if (_baselineSharpness == null) {
				_baselineSharpness = new Rational(1, 1);
			}
			if (_linearResponseLimit == null) {
				_linearResponseLimit = new Rational(1, 1);
			}
			if (_makerNoteSafety == NULL) {
				_makerNoteSafety = 0;
			}
			// There are some IFD 0 defaults which depend on the value of
			// "ColorPlanes," which is derived from information in the Raw IFD.
			// This would require processing the IFD's out of order, so those
			// defaults (AnalogBalance) go unreported.
		}

		if (_photometricInterpretation == TiffProfileDNG.CFA
				|| _photometricInterpretation == TiffProfileDNG.LINEAR_RAW) {
			// Apply "Raw IFD" defaults. This really isn't sufficient
			// information to establish a file as DNG, but the properties
			// are in their own category, so it's fairly harmless to leave
			// them in even if it's actually, for example, a TIFF-EP file.

			if (_cfaPlaneColor == null) {
				_cfaPlaneColor = new int[] { 0, 1, 2 };
			}

			if (_cfaLayout == NULL) {
				_cfaLayout = 1;
			}

			// The size of the LinearizationTable is -- I quote -- N.
			// This is NOT useful. Skip that default.

			if (_blackLevelRepeatDim == null) {
				_blackLevelRepeatDim = new int[] { 1, 1 };
			}

			Rational zero = new Rational(0, 1);
			if (_blackLevel == null) {
				_blackLevel = new Rational[_blackLevelRepeatDim[0]
						* _blackLevelRepeatDim[1] * _niso.getSamplesPerPixel()];
				for (int i = 0; i < _blackLevel.length; i++) {
					_blackLevel[i] = zero;
				}
			}

			if (_blackLevelDeltaH == null) {
				_blackLevelDeltaH = new Rational[(int) _niso.getImageWidth()];
				for (int i = 0; i < _blackLevelDeltaH.length; i++) {
					_blackLevelDeltaH[i] = zero;
				}
			}

			if (_blackLevelDeltaV == null) {
				_blackLevelDeltaV = new Rational[(int) _niso.getImageLength()];
				for (int i = 0; i < _blackLevelDeltaV.length; i++) {
					_blackLevelDeltaV[i] = zero;
				}
			}

			if (_whiteLevel == null) {
				_whiteLevel = new long[_niso.getSamplesPerPixel()];
				long defWhite = (1L << _niso.getBitsPerSample()[0] - 1);
				for (int i = 0; i < _whiteLevel.length; i++) {
					_whiteLevel[i] = defWhite;
				}
			}

			Rational one = new Rational(1, 1);
			if (_defaultScale == null) {
				_defaultScale = new Rational[] { one, one };
			}

			if (_bestQualityScale == null) {
				_bestQualityScale = one;
			}

			if (_defaultCropOrigin == null) {
				_defaultCropOrigin = new Rational[] { zero, zero };
			}

			if (_defaultCropSize == null) {
				_defaultCropSize = new Rational[2];
				_defaultCropSize[0] = new Rational(_niso.getImageWidth(), 1);
				_defaultCropSize[1] = new Rational(_niso.getImageLength(), 1);
			}
			int colorPlanes = calcColorPlanes();
			if (_cameraCalibration1 == null) {
				// Identity matrix with dimension of ColorPlanes*ColorPlanes
				_cameraCalibration1 = identityMatrix(colorPlanes);
			}
			if (_cameraCalibration2 == null) {
				// Identity matrix with dimension of ColorPlanes*ColorPlanes
				_cameraCalibration2 = identityMatrix(colorPlanes);
			}
			if (_bayerGreenSplit == NULL
					&& _photometricInterpretation == TiffProfileDNG.CFA) {
				_bayerGreenSplit = 0;
			}
			if (_antiAliasStrength == null) {
				_antiAliasStrength = new Rational(1, 1);
			}
		}
	}

	/** Create a Rational identity matrix of the specified size. */
	private Rational[] identityMatrix(int dim) {
		Rational[] val = new Rational[dim * dim];
		// Set them all to zero, then overwrite the diagonal values
		// to one.
		int i;
		for (i = 0; i < dim * dim; i++) {
			val[i] = new Rational(0, 1);
		}
		for (i = 0; i < dim; i++) {
			val[dim * i + i] = new Rational(1, 1);
		}
		return val;
	}

	/** Looks up an IFD tag. */
	public void lookupTag(int tag, int type, long count, long value)
			throws TiffException {
		try {
                    switch (tag) {
                        case APERTUREVALUE:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _aperatureValue = readRational(count, value);
                            break;
                        case ARTIST:
                            checkType(tag, type, ASCII);
                            _niso.setImageProducer(readASCII(count, value));
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case BACKGROUNDCOLORINDICATOR:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 1);
                            _backgroundColorIndicator = readByte(type, count, value);
                            break;
                        case BACKGROUNDCOLORVALUE:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 1);
                            _backgroundColorValue = readByte(type, count, value);
                            break;
                        case BADFAXLINES:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _badFaxLines = readLong(type, count, value);
                            break;
                        case BATTERYLEVEL:
                            checkType(tag, type, RATIONAL, ASCII);
                            if (type == RATIONAL) {
                                Rational r = readRational(count, value);
                                _batteryLevel = Double.toString(r.toDouble());
                            } else {
                                _batteryLevel = readASCII(count, value);
                            }
                            break;
                        case BITSPEREXTENDEDRUNLENGTH:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _bitsPerExtendedRunLength = readShort(type, count, value);
                            break;
                        case BITSPERRUNLENGTH:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _bitsPerRunLength = readShort(type, count, value);
                            break;
                        case BITSPERSAMPLE:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _niso.setBitsPerSample(readShortArray(type, count, value));
                            break;
                        case BRIGHTNESSVALUE:
                            checkType(tag, type, SRATIONAL);
                            if (count == 1) {
                                _niso.setBrightness(
                                        readSignedRational(count, value));
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readSignedRationalArray(count, value);
                                _niso.setBrightness(average(r[0], r[1]));
                            }
                            break;
                        case CELLLENGTH:
                            checkType(tag, type, SHORT);
                            _cellLength = readShort(type, count, value);
                            break;
                        case CELLWIDTH:
                            checkType(tag, type, SHORT);
                            _cellWidth = readShort(type, count, value);
                            break;
                        case CFAPATTERN:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _cfaPattern = readByteArray(type, count, value);
                            break;
                        case CFAREPEATPATTERNDIM:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 2);
                            _cfaRepeatPatternDim = readShortArray(type, count, value);
                            break;
                        case CLEANFAXDATA:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _badFaxLines = readShort(type, count, value);
                            break;
                        case CLIPPATH:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _clipPath = readByteArray(type, count, value);
                            break;
                        case COLORCHARACTERIZATION:
                            checkType(tag, type, ASCII);
                            _colorCharacterization = readASCII(count, value);
                            break;
                        case COLORSEQUENCE:
                            checkType(tag, type, ASCII);
                            _colorSequence = readASCII(count, value);
                            break;
                        case COLORMAP:
                            {
                                checkType(tag, type, SHORT);
                                checkCountArray(tag, count);
                                int[] colorMap = readShortArray(type, count, value);
                                int[] bitCode = new int[colorMap.length];
                                int[] red = new int[colorMap.length];
                                int[] green = new int[colorMap.length];
                                int[] blue = new int[colorMap.length];
                                int len = colorMap.length / 3;
                                int len2 = 2 * len;
                                for (int i = 0; i < len; i++) {
                                    bitCode[i] = i;
                                    red[i] = colorMap[i];
                                    green[i] = colorMap[i + len];
                                    blue[i] = colorMap[i + len2];
                                }
                                _niso.setColormapBitCodeValue(bitCode);
                                _niso.setColormapRedValue(red);
                                _niso.setColormapGreenValue(green);
                                _niso.setColormapBlueValue(blue);
                                if (_version < 5) {
                                    _version = 5;
                                }
                                break;
                            }
                        case COLORTABLE:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _colorTable = readByteArray(type, count, value);
                            break;
                        case COMPRESSEDBITSPERPIXEL:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _compressedBitsPerPixel = readRational(count, value);
                            break;
                        case COMPRESSION:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            int scheme = readShort(type, count, value);
                            _niso.setCompressionScheme(scheme);
                            if (scheme == 5) {
                                // Set default predictor if none has been set
                                if (_predictor == NULL) {
                                    _predictor = 1;
                                }
                                if (_version < 5) {
                                    _version = 5;
                                }
                            }
                            if (scheme == 6 && _version < 6) {
                                _version = 6;
                            }
                            if (scheme == 3 && _t4Options == NULL) {
                                // Set default t4Options only if compression is 3
                                _t4Options = 0;
                            }
                            if (scheme == 4 && _t6Options == NULL) {
                                // Set default t6Options only if compression is 4
                                _t6Options = 0;
                            }
                            if (scheme == 6) {
                                _info.setMessage(new InfoMessage(
                                        MessageConstants.TIFF_HUL_61));
                            }
                            break;
                        case CONSECUTIVEBADFAXLINES:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _consecutiveBadFaxLines = readLong(type, count, value);
                            break;
                        case COPYRIGHT:
                            checkType(tag, type, ASCII);
                            _copyright = readASCII(count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case DATETIME:
                            checkType(tag, type, ASCII);
                            checkCount(tag, count, 20);
                            _dateTime = readASCII(count, value);
                            _niso.setDateTimeCreated(_dateTime);
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case DATETIMEORIGINAL:
                            checkType(tag, type, ASCII);
                            checkCount(tag, count, 20);
                            _niso.setDateTimeCreated(readASCII(count, value));
                            break;
                        case DOCUMENTNAME:
                            checkType(tag, type, ASCII);
                            _documentName = readASCII(count, value);
                            break;
                        case DOTRANGE:
                            checkType(tag, type, BYTE, SHORT);
                            checkCount(tag, count, 2);
                            _dotRange = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case EXIFIFD:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _exifIFD = readLong(type, count, value);
                            break;
                        case EXPOSUREBIASVALUE:
                            checkType(tag, type, SRATIONAL);
                            if (count == 1) {
                                _niso.setExposureBias(
                                        readSignedRational(count, value));
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readSignedRationalArray(count, value);
                                _niso.setExposureBias(average(r[0], r[1]));
                            }
                            break;
                        case EXPOSUREPROGRAM:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _exposureProgram = readShort(type, count, value);
                            break;
                        case EXPOSURETIME:
                            checkType(tag, type, RATIONAL);
                            if (count == 1) {
                                _niso.setExposureTime(
                                        readRational(count, value).toDouble());
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readRationalArray(count, value);
                                _niso.setExposureTime(average(r[0], r[1]).toDouble());
                            }
                            break;
                        case EXTRASAMPLES:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _niso.setExtraSamples(readShortArray(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case FILLORDER:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _fillOrder = readShort(type, count, value);
                            break;
                        case FLASH:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _flash = readShort(type, count, value);
                            _niso.setFlash(_flash);
                            _niso.setFlashReturn(((_flash & 0X6) != 0) ? 1 : 0);
                            break;
                        case FLASHENERGY:
                            checkType(tag, type, RATIONAL);
                            if (count == 1) {
                                _niso.setFlashEnergy(readRational(count, value));
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readRationalArray(count, value);
                                _niso.setFlashEnergy(average(r[0], r[1]));
                            }
                            break;
                        case FNUMBER:
                            checkType(tag, type, RATIONAL);
                            if (count == 1) {
                                _niso.setFNumber(readRational(count, value).toDouble());
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readRationalArray(count, value);
                                _niso.setFNumber(average(r[0], r[1]).toDouble());
                            }
                            break;
                        case FOCALLENGTH:
                            checkType(tag, type, RATIONAL);
                            if (count == 1) {
                                _niso.setFocalLength(readRational(count, value).toDouble());
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r = readRationalArray(count, value);
                                _niso.setFocalLength(average(r[0], r[1]).toDouble());
                            }
                            break;
                        case FOCALPLANERESOLUTIONUNIT:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _focalPlaneResolutionUnit = readShort(type, count, value);
                            break;
                        case FOCALPLANEXRESOLUTION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _focalPlaneXResolution = readRational(count, value);
                            break;
                        case FOCALPLANEYRESOLUTION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _focalPlaneYResolution = readRational(count, value);
                            break;
                        case FREEBYTECOUNTS:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _freeByteCounts = readLongArray(type, count, value);
                            break;
                        case FREEOFFSETS:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _freeOffsets = readLongArray(type, count, value);
                            break;
                        case GEOASCIIPARAMSTAG:
                            checkType(tag, type, ASCII);
                            _geoAsciiParamsTag = readASCII(count, value);
                            break;
                        case GEODOUBLEPARAMSTAG:
                            checkType(tag, type, DOUBLE);
                            checkCountArray(tag, count);
                            _geoDoubleParamsTag = readDoubleArray(count, value);
                            break;
                        case GEOKEYDIRECTORYTAG:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _geoKeyDirectoryTag = readShortArray(type, count, value);
                            int num = _geoKeyDirectoryTag[3];
                            int prevKey = -1;
                            for (int i = 0; i < num; i++) {
                                int j = i * 4 + 4;
                                int key = _geoKeyDirectoryTag[j];
                                if (prevKey > key) {
                                    String mess = MessageFormat.format(MessageConstants.TIFF_HUL_10.getMessage(), key);
                                    JhoveMessage message = JhoveMessages.getMessageInstance(
                                            MessageConstants.TIFF_HUL_10.getId(), mess);
                                    throw new TiffException(message);
                                }
                                prevKey = key;
                            }
                            break;
                        case GLOBALPARAMETERSIFD:
                            checkType(tag, type, LONG, IFD);
                            // RFC 2301 allows only IFD, but the latest working
                            // draft allows LONG. Even though allowing LONG
                            // technically isn't allowed yet, letting it by seems
                            // reasonable, since other IFD tags can be LONG.
                            checkCount(tag, count, 1);
                            _globalParametersIFD = readLong(type, count, value);
                            break;
                        case GPSINFOIFD:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _gpsInfoIFD = readLong(type, count, value);
                            break;
                        case GRAYRESPONSECURVE:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _niso.setGrayResponseCurve(readShortArray(type, count, value));
                            break;
                        case GRAYRESPONSEUNIT:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setGrayResponseUnit(readShort(type, count, value));
                            break;
                        case HALFTONEHINTS:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 2);
                            _halftoneHints = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case HCUSAGE:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _hcUsage = readLong(type, count, value);
                            break;
                        case HOSTCOMPUTER:
                            checkType(tag, type, ASCII);
                            _niso.setHostComputer(readASCII(count, value));
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case IMAGEDESCRIPTION:
                            checkType(tag, type, ASCII);
                            _imageDescription = readASCII(count, value);
                            break;
                        case IMAGEID:
                            checkType(tag, type, ASCII);
                            _niso.setImageIdentifier(readASCII(count, value));
                            break;
                        case IMAGECOLORINDICATOR:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 1);
                            _imageColorIndicator = readByte(type, count, value);
                            break;
                        case IMAGECOLORVALUE:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 1);
                            _imageColorValue = readByte(type, count, value);
                            break;
                        case IMAGEHISTORY:
                            checkType(tag, type, ASCII);
                            _imageHistory = readASCII(count, value);
                            break;
                        case IMAGELAYER:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 2);
                            _imageLayer = readShortArray(type, count, value);
                            break;
                        case IMAGELENGTH:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _niso.setImageLength(readLong(type, count, value));
                            break;
                        case IMAGENUMBER:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _imageNumber = readLong(type, count, value);
                            break;
                        case IMAGESOURCEDATA:
                            checkType(tag, type, UNDEFINED);
                            // _imageSourceData = readByteArray (type, count, value);
                            // GDM 16-Sep-2005:
                            // The ImageSourceData tag sometimes has a gigantic
                            // amount of data, and we don't actually do anything with
                            // it in the current version of JHOVE except determine if
                            // it's there.
                            _imageSourceData = new int[] { 1 };
                            break;
                        case PHOTOSHOPPROPS:
                            // Can't find any info on what type is expected.
                            checkCountArray(tag, count);
                            _photoshopProperties = readByteArray(type, count, value);
                            break;
                        case ANNOTATIONS:
                            // Can't find any info on what type is expected.
                            checkCountArray(tag, count);
                            _annotations = readByteArray(type, count, value);
                            break;
                        case IMAGEWIDTH:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _niso.setImageWidth(readLong(type, count, value));
                            break;
                        case INDEXED:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _indexed = readShort(type, count, value);
                            break;
                        case INKNAMES:
                            checkType(tag, type, ASCII);
                            _inkNames = readASCIIArray(count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case INKSET:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _inkSet = readShort(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case ICC_PROFILE:
                            checkType(tag, type, UNDEFINED);
                            // Read the iccProdfile data as an array of bytes
                            checkCountArray(tag, count);
                            _iccProfile = readTrueByteArray(type, count, value);
                            try {
                                String desc = NisoImageMetadata
                                        .extractIccProfileDescription(_iccProfile);
                                if (desc != null) {
                                    _niso.setProfileName(desc);
                                }
                            } catch (IllegalArgumentException ie) {
                                String mess = MessageFormat.format(MessageConstants.TIFF_HUL_71.getMessage(), tag, ie.getMessage());
                                JhoveMessage message = JhoveMessages.getMessageInstance(MessageConstants.TIFF_HUL_71.getId(), mess);
                                throw new TiffException(message);
                            }
                            break;
                        case INTERLACE:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _interlace = readShort(type, count, value);
                            break;
                        case INTEROPERABILITYIFD:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _interoperabilityIFD = readLong(type, count, value);
                            break;
                        case IPTCNAA:
                            if (type == ASCII) {
                                String s = readASCII(count, value);
                                long[] larray = new long[s.length()];
                                for (int i = 0; i < s.length(); i++) {
                                    larray[i] = (int) s.charAt(i);
                                }
                                _iptc = larray;
                            } else if (type == LONG) {
                                checkCountArray(tag, count);
                                _iptc = readLongArray(type, count, value);
                            } else {
                                checkType(tag, type, BYTE, UNDEFINED);
                                checkCountArray(tag, count);
                                int[] b = readByteArray(type, count, value);
                                long[] larray = new long[b.length];
                                for (int i = 0; i < b.length; i++) {
                                    larray[i] = b[i];
                                    _iptc = larray;
                                }
                            }
                            break;
                        case ISOSPEEDRATINGS:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _isoSpeedRatings = readShortArray(tag, count, value);
                            break;
                        case IT8HEADER:
                            checkType(tag, type, ASCII);
                            _it8Header = readASCII(count, value);
                            break;
                        case JPEGACTABLES:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _jpegACTables = readLongArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGDCTABLES:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _jpegDCTables = readLongArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGINTERCHANGEFORMAT:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _jpegInterchangeFormat = readLong(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGINTERCHANGEFORMATLENGTH:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _jpegInterchangeFormatLength = readLong(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGLOSSLESSPREDICTORS:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _jpegLosslessPredictors = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGPOINTTRANSFORMS:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _jpegPointTransforms = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGPROC:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _jpegProc = readShort(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGQTABLES:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _jpegQTables = readLongArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGRESTARTINTERVAL:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _jpegRestartInterval = readShort(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case JPEGTABLES:
                            checkType(tag, type, UNDEFINED);
                            checkCountArray(tag, count);
                            _jpegTables = readByteArray(type, count, value);
                            break;
                        case LIGHTSOURCE:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setSceneIlluminant(readShort(type, count, value));
                            break;
                        case MAKE:
                            checkType(tag, type, ASCII);
                            _niso.setScannerManufacturer(readASCII(count, value));
                            break;
                        case MAXAPERTUREVALUE:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _maxAperatureValue = readRational(count, value);
                            break;
                        case MAXSAMPLEVALUE:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _maxSampleValue = readShortArray(type, count, value);
                            break;
                        case METERINGMODE:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setMeteringMode(readShort(type, count, value));
                            break;
                        case MINSAMPLEVALUE:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _minSampleValue = readShortArray(type, count, value);
                            break;
                        case MODEL:
                            checkType(tag, type, ASCII);
                            _niso.setScannerModelName(readASCII(count, value));
                            break;
                        case MODELPIXELSCALETAG:
                            checkType(tag, type, DOUBLE);
                            checkCount(tag, count, 3);
                            _modelPixelScaleTag = readDoubleArray(count, value);
                            break;
                        case MODELTIEPOINTTAG:
                            checkType(tag, type, DOUBLE);
                            checkCountArray(tag, count);
                            _modelTiepointTag = readDoubleArray(count, value);
                            break;
                        case MODELTRANSFORMATIONTAG:
                            checkType(tag, type, DOUBLE);
                            checkCount(tag, count, 16);
                            _modelTransformationTag = readDoubleArray(count, value);
                            break;
                        case NEWSUBFILETYPE:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _newSubfileType = readLong(type, count, value);
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case NOISE:
                            checkType(tag, type, UNDEFINED);
                            checkCountArray(tag, count);
                            _noise = readByteArray(type, count, value);
                            break;
                        case NUMBEROFINKS:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _numberOfInks = readShort(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case OECF:
                            checkType(tag, type, UNDEFINED);
                            checkCountArray(tag, count);
                            _oecf = readByteArray(tag, count, value);
                            break;
                        case OPIPROXY:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _opiProxy = readShort(type, count, value);
                            break;
                        case ORIENTATION:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setOrientation(readShort(type, count, value));
                            break;
                        case PAGENAME:
                            checkType(tag, type, ASCII);
                            _pageName = readASCII(count, value);
                            break;
                        case PAGENUMBER:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 2);
                            _pageNumber = readShortArray(type, count, value);
                            break;
                        case PHOTOMETRICINTERPRETATION:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _photometricInterpretation = readShort(type, count, value);
                            _niso.setColorSpace(_photometricInterpretation);
                            // Set default values appropriate to interpretation,
                            // only if no value has been set.
                            if (_photometricInterpretation == 5) {
                                if (_inkSet == NULL) {
                                    _inkSet = 1;
                                }
                                if (_numberOfInks == NULL) {
                                    _numberOfInks = 4;
                                }
                            } else if (_photometricInterpretation == 6) {
                                if (_niso.getYCbCrCoefficients() == null) {
                                    _niso.setYCbCrCoefficients(
                                            new Rational[] { new Rational(299, 1000),
                                                new Rational(587, 1000),
                                                new Rational(114, 1000) });
                                }
                                if (_niso.getYCbCrPositioning() == NULL) {
                                    _niso.setYCbCrPositioning(1);
                                }
                                if (_niso.getYCbCrSubSampling() == null) {
                                    _niso.setYCbCrSubSampling(new int[] { 2, 2 });
                                }
                            }
                            int colorSpace = _niso.getColorSpace();
                            if (colorSpace == 3 || colorSpace == 4) {
                                if (_version < 5) {
                                    _version = 5;
                                }
                            } else if (colorSpace == 5 || colorSpace == 6
                                    || colorSpace == 8) {
                                if (_version < 6) {
                                    _version = 6;
                                }
                            }
                            break;
                        case PIXELINTENSITYRANGE:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _pixelIntensityRange = readShortArray(type, count, value);
                            break;
                        case PLANARCONFIGURATION:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setPlanarConfiguration(readShort(type, count, value));
                            break;
                        case PREDICTOR:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _predictor = readShort(type, count, value);
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case PRIMARYCHROMATICITIES:
                            {
                                checkType(tag, type, RATIONAL);
                                checkCount(tag, count, 6);
                                Rational[] rarray = readRationalArray(count, value);
                                _niso.setPrimaryChromaticitiesRedX(rarray[0]);
                                _niso.setPrimaryChromaticitiesRedY(rarray[1]);
                                _niso.setPrimaryChromaticitiesGreenX(rarray[2]);
                                _niso.setPrimaryChromaticitiesGreenY(rarray[3]);
                                _niso.setPrimaryChromaticitiesBlueX(rarray[4]);
                                _niso.setPrimaryChromaticitiesBlueY(rarray[5]);
                                if (_version < 5) {
                                    _version = 5;
                                }
                                break;
                            }
                        case RASTERPADDING:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _rasterPadding = readShort(type, count, value);
                            break;
                        case REFERENCEBLACKWHITE:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 6);
                            _niso.setReferenceBlackWhite(readRationalArray(count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case RESOLUTIONUNIT:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setSamplingFrequencyUnit(readShort(type, count, value));
                            break;
                        case ROWSPERSTRIP:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _niso.setRowsPerStrip(readLong(type, count, value));
                            break;
                        case SAMPLEFORMAT:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _sampleFormat = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case SAMPLESPERPIXEL:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setSamplesPerPixel(readShort(type, count, value));
                            break;
                        case SECURITYCLASSIFICATION:
                            checkType(tag, type, ASCII);
                            _securityClassification = readASCII(count, value);
                            break;
                        case SELFTIMERMODE:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _selfTimerMode = readShort(type, count, value);
                            break;
                        case SENSINGMETHOD:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setSensor(readShort(type, count, value));
                            break;
                        case SITE:
                            checkType(tag, type, ASCII);
                            _site = readASCII(count, value);
                            break;
                        case STRIPROWCOUNTS:
                            checkType(tag, type, LONG);
                            checkCountArray(tag, count);
                            _stripRowCounts = readLongArray(type, count, value);
                            break;
                        case SUBJECTDISTANCE:
                            checkType(tag, type, RATIONAL, SRATIONAL);
                            double[] darray = new double[2];
                            if (count == 1) {
                                darray[0] = readRational(count, value).toDouble();
                                darray[1] = darray[0];
                            } else {
                                checkCountArray(tag, count);
                                Rational[] r;
                                if (type == RATIONAL) {
                                    r = readRationalArray(count, value);
                                } else {
                                    r = readSignedRationalArray(count, value);
                                }
                                darray[0] = r[0].toDouble();
                                if (r.length > 1) {
                                    darray[1] = r[1].toDouble();
                                } else {
                                    darray[1] = darray[0];
                                }
                            }
                            _niso.setSubjectDistance(darray);
                            break;
                        case SOFTWARE:
                            checkType(tag, type, ASCII);
                            _niso.setScanningSoftware(readASCII(count, value));
                            if (_version < 5) {
                                _version = 5;
                            }
                            break;
                        case SPATIALFREQUENCYRESPONSE:
                            checkType(tag, type, UNDEFINED);
                            checkCountArray(tag, count);
                            _spatialFrequencyResponse = readByteArray(type, count, value);
                            break;
                        case SPECTRALSENSITIVITY:
                            checkType(tag, type, ASCII);
                            _spectralSensitivity = readASCII(count, value);
                            break;
                        case STRIPBYTECOUNTS:
                            checkType(tag, type, SHORT, LONG);
                            checkCountArray(tag, count);
                            _niso.setStripByteCounts(readLongArray(type, count, value));
                            break;
                        case STRIPOFFSETS:
                            checkType(tag, type, SHORT, LONG);
                            checkCountArray(tag, count);
                            _niso.setStripOffsets(readLongArray(type, count, value));
                            break;
                        case SUBFILETYPE:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _subfileType = readShort(type, count, value);
                            break;
                        case SUBIFDS:
                            checkType(tag, type, LONG, IFD);
                            checkCountArray(tag, count);
                            _subIFDs = readLongArray(type, count, value);
                            break;
                        case SUBJECTLOCATION:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _subjectLocation = readShortArray(type, count, value);
                            break;
                        case T4OPTIONS:
                            checkType(tag, type, LONG);
                            _t4Options = readShort(type, count, value);
                            break;
                        case T6OPTIONS:
                            checkType(tag, type, LONG);
                            _t6Options = readShort(type, count, value);
                            break;
                        case TARGETPRINTER:
                            checkType(tag, type, ASCII);
                            _targetPrinter = readASCII(count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case THRESHHOLDING:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _threshholding = readShort(type, count, value);
                            break;
                        case TIFFEPSTANDARDID:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 4);
                            int[] iarray = readShortArray(type, count, value);
                            _tiffEPStandardID = Integer.toString(iarray[0]) + "."
                                    + Integer.toString(iarray[1]) + "."
                                    + Integer.toString(iarray[2]) + "."
                                    + Integer.toString(iarray[3]);
                            break;
                        case TILEBYTECOUNTS:
                            checkType(tag, type, SHORT, LONG);
                            checkCountArray(tag, count);
                            _niso.setTileByteCounts(readLongArray(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case TILELENGTH:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _niso.setTileLength(readLong(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case TILEOFFSETS:
                            checkType(tag, type, SHORT, LONG);
                            checkCountArray(tag, count);
                            _niso.setTileOffsets(readLongArray(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case TILEWIDTH:
                            checkType(tag, type, SHORT, LONG);
                            checkCount(tag, count, 1);
                            _niso.setTileWidth(readLong(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case TIMEZONEOFFSET:
                            checkType(tag, type, SSHORT);
                            checkCountArray(tag, count);
                            _timeZoneOffset = readSShortArray(type, count, value);
                            break;
                        case TRANSFERFUNCTION:
                            /*
                            * Transfer function arrays potentially can have millions
                            * of elements, so we just report presence
                            */
                            checkType(tag, type, SHORT);
                            _transferFunction = true;
                            break;
                        case TRANSFERRANGE:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 6);
                            _transferRange = readShortArray(type, count, value);
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case TRANSPARENCYINDICATOR:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 1);
                            _transparencyIndicator = readByte(type, count, value);
                            break;
                        case WHITEPOINT:
                            {
                                checkType(tag, type, RATIONAL);
                                checkCount(tag, count, 2);
                                Rational[] rarray = readRationalArray(count, value);
                                _niso.setWhitePointXValue(rarray[0]);
                                _niso.setWhitePointYValue(rarray[0]);
                                if (_version < 5) {
                                    _version = 5;
                                }
                                break;
                            }
                        case XCLIPPATHUNITS:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _xClipPathUnits = readLong(type, count, value);
                            break;
                        case XPOSITION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _xPosition = readRational(count, value);
                            break;
                        case XRESOLUTION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _niso.setXSamplingFrequency(readRational(count, value));
                            break;
                        case YCBCRCOEFFICIENTS:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 3);
                            _niso.setYCbCrCoefficients(readRationalArray(count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case YCBCRPOSITIONING:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 1);
                            _niso.setYCbCrPositioning(readShort(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case YCBCRSUBSAMPLING:
                            checkType(tag, type, SHORT);
                            checkCount(tag, count, 2);
                            _niso.setYCbCrSubSampling(readShortArray(type, count, value));
                            if (_version < 6) {
                                _version = 6;
                            }
                            break;
                        case YCLIPPATHUNITS:
                            checkType(tag, type, LONG);
                            checkCount(tag, count, 1);
                            _yClipPathUnits = readLong(type, count, value);
                            break;
                        case YPOSITION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _yPosition = readRational(count, value);
                            break;
                        case YRESOLUTION:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _niso.setYSamplingFrequency(readRational(count, value));
                            break;
                        case XMP:
                            checkType(tag, type, UNDEFINED, BYTE);
                            checkCountArray(tag, count);
                            _xmpProp = readXMP(count, value);
                            break;
                        case DNGVERSION:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 4);
                            _dngVersion = readByteArray(type, count, value);
                            break;
                        case DNGBACKWARDVERSION:
                            checkType(tag, type, BYTE);
                            checkCount(tag, count, 4);
                            _dngBackwardVersion = readByteArray(type, count, value);
                            break;
                        case UNIQUECAMERAMODEL:
                            checkType(tag, type, ASCII);
                            _uniqueCameraModel = readASCII(count, value);
                            break;
                        case LOCALIZEDCAMERAMODEL:
                            {
                                checkType(tag, type, ASCII, BYTE);
                                // This tag is specified as UTF-8
                                byte[] lcm = readTrueByteArray(type, count, value);
                                // Trim off trailing null (s)
                                int len = lcm.length;
                                while (len > 0 && lcm[len - 1] == 0) {
                                    len--;
                                }
                                _localizedCameraModel = new String(lcm, 0, len);
                                break;
                            }
                        case CFAPLANECOLOR:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _cfaPlaneColor = readByteArray(type, count, value);
                            break;
                        case CFALAYOUT:
                            checkType(tag, type, SHORT);
                            _cfaLayout = readShort(type, count, value);
                            break;
                        case LINEARIZATIONTABLE:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _linearizationTable = readShortArray(type, count, value);
                            break;
                        case BLACKLEVELREPEATDIM:
                            checkType(tag, type, SHORT);
                            checkCountArray(tag, count);
                            _blackLevelRepeatDim = readShortArray(type, count, value);
                            break;
                        case BLACKLEVEL:
                            // Just to make things complicated, this can be SHORT, LONG
                            // or RATIONAL. To give these a least common (pardon the
                            // expression) denominator, we convert all to rational.
                            checkCountArray(tag, count);
                            if (type == RATIONAL) {
                                _blackLevel = readRationalArray(count, value);
                            } else {
                                checkType(tag, type, SHORT, LONG);
                                long[] ibl = readLongArray(type, count, value);
                                _blackLevel = new Rational[(int) count];
                                for (int i = 0; i < count; i++) {
                                    _blackLevel[i] = new Rational(ibl[i], 1);
                                }
                            }
                            break;
                        case BLACKLEVELDELTAH:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _blackLevelDeltaH = readSignedRationalArray(count, value);
                            break;
                        case BLACKLEVELDELTAV:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _blackLevelDeltaV = readSignedRationalArray(count, value);
                            break;
                        case WHITELEVEL:
                            checkType(tag, type, SHORT, LONG);
                            checkCountArray(tag, count);
                            _whiteLevel = readLongArray(type, count, value);
                            break;
                        case DEFAULTSCALE:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 2);
                            _defaultScale = readRationalArray(count, value);
                            break;
                        case BESTQUALITYSCALE:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 1);
                            _bestQualityScale = readRational(count, value);
                            break;
                        case DEFAULTCROPORIGIN:
                            checkCount(tag, count, 2);
                            // Just to make things complicated, this can be SHORT, LONG
                            // or RATIONAL. To give these a least common (pardon the
                            // expression) denominator, we convert all to rational.
                            if (type == RATIONAL) {
                                _defaultCropOrigin = readRationalArray(count, value);
                            } else {
                                checkType(tag, type, SHORT, LONG);
                                long[] lco = readLongArray(type, count, value);
                                _defaultCropOrigin = new Rational[(int) count];
                                for (int i = 0; i < count; i++) {
                                    _defaultCropOrigin[i] = new Rational(lco[i], 1);
                                }
                            }
                            break;
                        case DEFAULTCROPSIZE:
                            checkCount(tag, count, 2);
                            if (type == RATIONAL) {
                                _defaultCropSize = readRationalArray(count, value);
                            } else {
                                checkType(tag, type, SHORT, LONG);
                                long[] lcs = readLongArray(type, count, value);
                                _defaultCropSize = new Rational[(int) count];
                                for (int i = 0; i < count; i++) {
                                    _defaultCropSize[i] = new Rational(lcs[i], 1);
                                }
                            }
                            break;
                        case CALIBRATIONILLUMINANT1:
                            checkCount(tag, count, 1);
                            checkType(tag, type, SHORT);
                            _calibrationIlluminant1 = readShort(type, count, value);
                            break;
                        case CALIBRATIONILLUMINANT2:
                            checkCount(tag, count, 1);
                            checkType(tag, type, SHORT);
                            _calibrationIlluminant2 = readShort(type, count, value);
                            break;
                        case COLORMATRIX1:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _colorMatrix1 = readSignedRationalArray(count, value);
                            break;
                        case COLORMATRIX2:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _colorMatrix2 = readSignedRationalArray(count, value);
                            break;
                        case CAMERACALIBRATION1:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _colorMatrix1 = readSignedRationalArray(count, value);
                            break;
                        case CAMERACALIBRATION2:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _colorMatrix2 = readSignedRationalArray(count, value);
                            break;
                        case REDUCTIONMATRIX1:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _reductionMatrix1 = readSignedRationalArray(count, value);
                            break;
                        case REDUCTIONMATRIX2:
                            checkType(tag, type, SRATIONAL);
                            checkCountArray(tag, count);
                            _reductionMatrix2 = readSignedRationalArray(count, value);
                            break;
                        case ANALOGBALANCE:
                            checkType(tag, type, RATIONAL);
                            checkCountArray(tag, count);
                            _analogBalance = readRationalArray(count, value);
                            break;
                        case ASSHOTNEUTRAL:
                            // this can be either SHORT or RATIONAL
                            checkType(tag, type, SHORT, RATIONAL);
                            checkCountArray(tag, count);
                            if (type == SHORT) {
                                int[] asn = readShortArray(type, count, value);
                                _asShotNeutral = new Rational[(int) count];
                                for (int i = 0; i < count; i++) {
                                    _asShotNeutral[i] = new Rational(asn[i], 1);
                                }
                            } else {
                                _asShotNeutral = readRationalArray(count, value);
                            }
                            break;
                        case ASSHOTWHITEXY:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 2);
                            _asShotWhiteXY = readRationalArray(count, value);
                            break;
                        case BASELINEEXPOSURE:
                            checkType(tag, type, SRATIONAL);
                            _baselineExposure = readSignedRational(count, value);
                            break;
                        case BASELINENOISE:
                            checkType(tag, type, RATIONAL);
                            _baselineNoise = readRational(count, value);
                            break;
                        case BASELINESHARPNESS:
                            checkType(tag, type, RATIONAL);
                            _baselineSharpness = readRational(count, value);
                            break;
                        case BAYERGREENSPLIT:
                            checkType(tag, type, LONG);
                            _bayerGreenSplit = (int) readLong(type, count, value);
                            break;
                        case LINEARRESPONSELIMIT:
                            checkType(tag, type, RATIONAL);
                            _linearResponseLimit = readRational(count, value);
                            break;
                        case CAMERASERIALNUMBER:
                            checkType(tag, type, ASCII);
                            _cameraSerialNumber = readASCII(count, value);
                            break;
                        case LENSINFO:
                            checkType(tag, type, RATIONAL);
                            checkCount(tag, count, 4);
                            _lensInfo = readRationalArray(count, value);
                            break;
                        case CHROMABLURRADIUS:
                            checkType(tag, type, RATIONAL);
                            _chromaBlurRadius = readRational(count, value);
                            break;
                        case ANTIALIASSTRENGTH:
                            checkType(tag, type, RATIONAL);
                            _antiAliasStrength = readRational(count, value);
                            break;
                        case SHADOWSCALE:
                            _info.setMessage(new InfoMessage(MessageConstants.TIFF_HUL_11,
                                    MessageConstants.TIFF_HUL_11_SUB.getMessage()));
                            break;
                        case DNGPRIVATEDATA:
                            checkType(tag, type, BYTE);
                            checkCountArray(tag, count);
                            _dngPrivateData = readByteArray(type, count, value);
                            break;
                        case MAKERNOTESAFETY:
                            checkType(tag, type, SHORT);
                            _makerNoteSafety = readShort(type, count, value);
                            break;
                        default:
                            String mess = MessageFormat
                                    .format(MessageConstants.TIFF_HUL_12.getMessage(), tag);
                            JhoveMessage message = JhoveMessages.getMessageInstance(
                                    MessageConstants.TIFF_HUL_12.getId(), mess);
                            _info.setMessage(new InfoMessage(message, value));
                            break;
                    }
		} catch (IOException e) {
			String mess = MessageFormat
					.format(MessageConstants.TIFF_HUL_13.getMessage(), tag);
			JhoveMessage message = JhoveMessages.getMessageInstance(
					MessageConstants.TIFF_HUL_13.getId(), mess);
			throw new TiffException(message, value);
		}
	}

	/**
	 * Perform initializations that have to wait until after the
	 * IFD has been parsed.
	 */
	protected void postParseInitialization() {
		int samplesPerPixel = _niso.getSamplesPerPixel();
		int[] bitsPerSample = _niso.getBitsPerSample();
		if (bitsPerSample == null) {
			bitsPerSample = new int[samplesPerPixel];
			for (int i = 0; i < samplesPerPixel; i++) {
				bitsPerSample[i] = 1;
			}
			_niso.setBitsPerSample(bitsPerSample);
		}
		int bps1 = (1 << bitsPerSample[0]) - 1;
		if (_maxSampleValue == null) {
			_maxSampleValue = new int[samplesPerPixel];
			for (int i = 0; i < samplesPerPixel; i++) {
				_maxSampleValue[i] = bps1;
			}

		}
		if (_minSampleValue == null) {
			_minSampleValue = new int[samplesPerPixel];
			for (int i = 0; i < samplesPerPixel; i++) {
				_minSampleValue[i] = 0;
			}
		}
		if (_sampleFormat == null) {
			_sampleFormat = new int[samplesPerPixel];
			for (int i = 0; i < samplesPerPixel; i++) {
				_sampleFormat[i] = 1;
			}
		}
		/*
		 * The default transfer function is expressed as an array
		 * of 2^BitsPerSample elements. If BitsPerSample is 24,
		 * this would paraylze processing for many minutes, if it
		 * didn't just run out of memory. So it was a nice idea...
		 */
		// if (_photometricInterpretation == 0 ||
		// _photometricInterpretation == 1 ||
		// _photometricInterpretation == 2 ||
		// _photometricInterpretation == 3 ||
		// _photometricInterpretation == 6) {
		// // Set default transfer function only for indicated
		// // photometricInterpretations.
		// if (_transferFunction == null) {
		// int n = 1<<bitsPerSample[0];
		// _transferFunction = new int [n];
		// _transferFunction[0] = 0;
		// for (int i=1; i<n; i++) {
		// _transferFunction[i] =
		// (int) Math.floor (Math.pow (i/(n-1.0), 2.2)*65535 +
		// 0.5);
		// }
		// }
		// }
		if (_photometricInterpretation == 5 && _dotRange == null) {
                    _dotRange = new int[2];
                    _dotRange[0] = 0;
                    _dotRange[1] = bps1;
		}
		if (_photometricInterpretation == 2
				|| _photometricInterpretation == 6) {
			// set defaults for transferRange and referenceBlackWhite only if
			// the photometricInterpretation is RGB or YCbCr.
			if (_transferRange == null) {
				_transferRange = new int[6];
				_transferRange[0] = _transferRange[2] = _transferRange[4] = 0;
				_transferRange[1] = _transferRange[3] = _transferRange[5] = bps1;
			}
			if (_niso.getReferenceBlackWhite() == null) {
				Rational[] reference = new Rational[6];
				reference[0] = reference[2] = reference[4] = new Rational(0, 1);
				reference[1] = reference[3] = reference[5] = new Rational(bps1,
						1);
				_niso.setReferenceBlackWhite(reference);
			}
		}
		if (_pixelIntensityRange == null) {
			_pixelIntensityRange = new int[2];
			_pixelIntensityRange[0] = 0;
			_pixelIntensityRange[1] = bps1;
		}
	}

	public void setTheExifIFD(ExifIFD exif) {
		_theExifIFD = exif;
	}

	public void setTheGPSInfoIFD(GPSInfoIFD gpsInfo) {
		_theGPSInfoIFD = gpsInfo;
	}

	public void setTheInteroperabilityIFD(InteroperabilityIFD interOp) {
		_theInteroperabilityIFD = interOp;
	}

	public void setTheGlobalParametersIFD(GlobalParametersIFD gp) {
		_theGlobalParametersIFD = gp;
	}

	/* Read XMP data from the tag, and return as a string. */
	private Property readXMP(long count, long value) throws TiffException {
		Property xmpProp = null;
		try {
			byte[] buf = readTrueByteArray(BYTE, count, value);
			ByteArrayInputStream strm = new ByteArrayInputStream(buf);
			ByteArrayXMPSource src = new ByteArrayXMPSource(strm);

			// Create an InputSource to feed the parser.
			SAXParserFactory factory = SAXParserFactory.newInstance();
			factory.setNamespaceAware(true);
			XMLReader parser = factory.newSAXParser().getXMLReader();
			// InputStream stream = new XMLWrapperStream
			// (new StreamInputStream (metadata, getFile ()), "dummyroot");
			XMPHandler handler = new XMPHandler();
			parser.setContentHandler(handler);
			// We have to parse twice. The first time, we may get
			// an encoding change as part of an exception thrown. If this
			// happens, we create a new InputSource with the encoding, and
			// continue.
			try {
				parser.parse(src);
				xmpProp = src.makeProperty();
				return xmpProp;
			} catch (SAXException se) {
				String msg = se.getMessage();
				if (msg != null && msg.startsWith("ENC=")) {
					String encoding = msg.substring(5);
					try {
						// Reader rdr = new InputStreamReader (stream,
						// encoding);
						src = new ByteArrayXMPSource(strm, encoding);
						parser.parse(src);
					} catch (UnsupportedEncodingException uee) {
						throw new TiffException(MessageConstants.TIFF_HUL_14);
					}
				}
				xmpProp = src.makeProperty();
				return xmpProp;
			}
		} catch (TiffException e) {
			throw e;
		} catch (Exception f) {
			return null;
		}
	}
}
