public bool ReadDirectory()
{
const string module = "ReadDirectory";
m_diroff = m_nextdiroff;
if (m_diroff == 0)
{
// no more directories
return false;
}
// Check whether we have the last offset or bad offset (IFD looping).
if (!checkDirOffset(m_nextdiroff))
return false;
// Cleanup any previous compression state.
m_currentCodec.Cleanup();
m_curdir++;
TiffDirEntry[] dir;
short dircount = fetchDirectory(m_nextdiroff, out dir, out m_nextdiroff);
if (dircount == 0)
{
ErrorExt(this, m_clientdata, module, "{0}: Failed to read directory at offset {1}", m_name, m_nextdiroff);
return false;
}
// reset before new dir
m_flags &= ~TiffFlags.BEENWRITING;
// Setup default value and then make a pass over the fields to check type and tag
// information, and to extract info required to size data structures. A second pass is
// made afterwards to read in everthing not taken in the first pass.
// free any old stuff and reinit
FreeDirectory();
setupDefaultDirectory();
// Electronic Arts writes gray-scale TIFF files without a PlanarConfiguration
// directory entry. Thus we setup a default value here, even though the TIFF spec says
// there is no default value.
SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
// Sigh, we must make a separate pass through the directory for the following reason:
//
// We must process the Compression tag in the first pass in order to merge in
// codec-private tag definitions (otherwise we may get complaints about unknown tags).
// However, the Compression tag may be dependent on the SamplesPerPixel tag value
// because older TIFF specs permited Compression to be written as a
// SamplesPerPixel-count tag entry. Thus if we don't first figure out the correct
// SamplesPerPixel tag value then we may end up ignoring the Compression tag value
// because it has an incorrect count value (if the true value of SamplesPerPixel is not 1).
//
// It sure would have been nice if Aldus had really thought this stuff through carefully.
for (int i = 0; i < dircount; i++)
{
TiffDirEntry dp = dir[i];
if ((m_flags & TiffFlags.SWAB) == TiffFlags.SWAB)
{
short temp = (short)dp.tdir_tag;
SwabShort(ref temp);
dp.tdir_tag = (TiffTag)(ushort)temp;
temp = (short)dp.tdir_type;
SwabShort(ref temp);
dp.tdir_type = (TiffType)temp;
SwabLong(ref dp.tdir_count);
SwabUInt(ref dp.tdir_offset);
}
if (dp.tdir_tag == TiffTag.SAMPLESPERPIXEL)
{
if (!fetchNormalTag(dir[i]))
return false;
dp.tdir_tag = TiffTag.IGNORE;
}
}
// First real pass over the directory.
int fix = 0;
bool diroutoforderwarning = false;
bool haveunknowntags = false;
#if FIX_JPEG_IS_OJPEG
bool fixJpegIsOJpeg = false;
#endif
for (int i = 0; i < dircount; i++)
{
if (dir[i].tdir_tag == TiffTag.IGNORE)
continue;
if (fix >= m_nfields)
fix = 0;
// Silicon Beach (at least) writes unordered directory tags (violating the spec).
// Handle it here, but be obnoxious (maybe they'll fix it?).
if (dir[i].tdir_tag < m_fieldinfo[fix].Tag)
{
if (!diroutoforderwarning)
{
WarningExt(this, m_clientdata, module,
"{0}: invalid TIFF directory; tags are not sorted in ascending order", m_name);
diroutoforderwarning = true;
}
fix = 0; // O(n^2)
}
while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag)
fix++;
if (fix >= m_nfields || m_fieldinfo[fix].Tag != dir[i].tdir_tag)
{
// Unknown tag ... we'll deal with it below
haveunknowntags = true;
continue;
}
// null out old tags that we ignore.
if (m_fieldinfo[fix].Bit == FieldBit.Ignore)
{
dir[i].tdir_tag = TiffTag.IGNORE;
continue;
}
// Check data type.
TiffFieldInfo fip = m_fieldinfo[fix];
bool tagIgnored = false;
while (dir[i].tdir_type != fip.Type && fix < m_nfields)
{
if (fip.Type == TiffType.ANY)
{
// wildcard
break;
}
fip = m_fieldinfo[++fix];
if (fix >= m_nfields || fip.Tag != dir[i].tdir_tag)
{
WarningExt(this, m_clientdata, module,
"{0}: wrong data type {1} for \"{2}\"; tag ignored",
m_name, dir[i].tdir_type, m_fieldinfo[fix - 1].Name);
dir[i].tdir_tag = TiffTag.IGNORE;
tagIgnored = true;
break;
}
}
if (tagIgnored)
continue;
// Check count if known in advance.
if (fip.ReadCount != TiffFieldInfo.Variable &&
fip.ReadCount != TiffFieldInfo.Variable2)
{
int expected = fip.ReadCount;
if (fip.ReadCount == TiffFieldInfo.Spp)
expected = m_dir.td_samplesperpixel;
if (!checkDirCount(dir[i], expected))
{
dir[i].tdir_tag = TiffTag.IGNORE;
continue;
}
}
switch (dir[i].tdir_tag)
{
case TiffTag.COMPRESSION:
// The 5.0 spec says the Compression tag has one value,
// while earlier specs say it has one value per sample.
// Because of this, we accept the tag if one value is supplied.
if (dir[i].tdir_count == 1)
{
int v = extractData(dir[i]);
#if FIX_JPEG_IS_OJPEG
fixJpegIsOJpeg = checkJpegIsOJpeg(ref v, dir, dircount);
#endif
if (!SetField(dir[i].tdir_tag, v))
return false;
break;
// XXX: workaround for broken TIFFs
}
else if (dir[i].tdir_type == TiffType.LONG)
{
#if FIX_JPEG_IS_OJPEG
int v;
bool isFetched = fetchPerSampleLongs(dir[i], out v);
if (!isFetched)
return false;
fixJpegIsOJpeg = checkJpegIsOJpeg(ref v, dir, dircount);
if (!SetField(dir[i].tdir_tag, v))
return false;
#else
int v;
if (!fetchPerSampleLongs(dir[i], out v) || !SetField(dir[i].tdir_tag, v))
return false;
#endif
}
else
{
#if FIX_JPEG_IS_OJPEG
short iv;
bool isFetched = fetchPerSampleShorts(dir[i], out iv);
if (!isFetched)
return false;
int v = iv;
fixJpegIsOJpeg = checkJpegIsOJpeg(ref v, dir, dircount);
if (!SetField(dir[i].tdir_tag, (short)v))
return false;
#else
short iv;
if (!fetchPerSampleShorts(dir[i], out iv) || !SetField(dir[i].tdir_tag, iv))
return false;
#endif
}
dir[i].tdir_tag = TiffTag.IGNORE;
break;
case TiffTag.STRIPOFFSETS:
case TiffTag.STRIPBYTECOUNTS:
case TiffTag.TILEOFFSETS:
case TiffTag.TILEBYTECOUNTS:
setFieldBit(fip.Bit);
break;
case TiffTag.IMAGEWIDTH:
case TiffTag.IMAGELENGTH:
case TiffTag.IMAGEDEPTH:
case TiffTag.TILELENGTH:
case TiffTag.TILEWIDTH:
case TiffTag.TILEDEPTH:
case TiffTag.PLANARCONFIG:
case TiffTag.ROWSPERSTRIP:
case TiffTag.EXTRASAMPLES:
if (!fetchNormalTag(dir[i]))
return false;
dir[i].tdir_tag = TiffTag.IGNORE;
break;
}
}
// If we saw any unknown tags, make an extra pass over the directory to deal with
// them. This must be done separately because the tags could have become known when we
// registered a codec after finding the Compression tag. In a correctly-sorted
// directory there's no problem because Compression will come before any codec-private
// tags, but if the sorting is wrong that might not hold.
if (haveunknowntags)
{
fix = 0;
for (int i = 0; i < dircount; i++)
{
if (dir[i].tdir_tag == TiffTag.IGNORE)
continue;
if (fix >= m_nfields || dir[i].tdir_tag < m_fieldinfo[fix].Tag)
{
// O(n^2)
fix = 0;
}
while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag)
fix++;
if (fix >= m_nfields || m_fieldinfo[fix].Tag != dir[i].tdir_tag)
{
Tiff.WarningExt(this, m_clientdata, module,
"{0}: unknown field with tag {1} (0x{2:x}) encountered",
m_name, (ushort)dir[i].tdir_tag, (ushort)dir[i].tdir_tag);
TiffFieldInfo[] arr = new TiffFieldInfo[1];
arr[0] = createAnonFieldInfo(dir[i].tdir_tag, dir[i].tdir_type);
MergeFieldInfo(arr, 1);
fix = 0;
while (fix < m_nfields && m_fieldinfo[fix].Tag < dir[i].tdir_tag)
fix++;
}
// Check data type.
TiffFieldInfo fip = m_fieldinfo[fix];
while (dir[i].tdir_type != fip.Type && fix < m_nfields)
{
if (fip.Type == TiffType.ANY)
{
// wildcard
break;
}
fip = m_fieldinfo[++fix];
if (fix >= m_nfields || fip.Tag != dir[i].tdir_tag)
{
Tiff.WarningExt(this, m_clientdata, module,
"{0}: wrong data type {1} for \"{2}\"; tag ignored",
m_name, dir[i].tdir_type, m_fieldinfo[fix - 1].Name);
dir[i].tdir_tag = TiffTag.IGNORE;
break;
}
}
}
}
// XXX: OJPEG hack.
// If a) compression is OJPEG, b) planarconfig tag says it's separate, c) strip
// offsets/bytecounts tag are both present and d) both contain exactly one value, then
// we consistently find that the buggy implementation of the buggy compression scheme
// matches contig planarconfig best. So we 'fix-up' the tag here
if ((m_dir.td_compression == Compression.OJPEG) && (m_dir.td_planarconfig == PlanarConfig.SEPARATE))
{
int dpIndex = readDirectoryFind(dir, dircount, TiffTag.STRIPOFFSETS);
if (dpIndex != -1 && dir[dpIndex].tdir_count == 1)
{
dpIndex = readDirectoryFind(dir, dircount, TiffTag.STRIPBYTECOUNTS);
if (dpIndex != -1 && dir[dpIndex].tdir_count == 1)
{
m_dir.td_planarconfig = PlanarConfig.CONTIG;
WarningExt(this, m_clientdata, "ReadDirectory",
"Planarconfig tag value assumed incorrect, assuming data is contig instead of chunky");
}
}
}
// Allocate directory structure and setup defaults.
if (!fieldSet(FieldBit.ImageDimensions))
{
missingRequired("ImageLength");
return false;
}
// Setup appropriate structures (by strip or by tile)
if (!fieldSet(FieldBit.TileDimensions))
{
m_dir.td_nstrips = NumberOfStrips();
m_dir.td_tilewidth = m_dir.td_imagewidth;
m_dir.td_tilelength = m_dir.td_rowsperstrip;
m_dir.td_tiledepth = m_dir.td_imagedepth;
m_flags &= ~TiffFlags.ISTILED;
}
else
{
m_dir.td_nstrips = NumberOfTiles();
m_flags |= TiffFlags.ISTILED;
}
if (m_dir.td_nstrips == 0)
{
ErrorExt(this, m_clientdata, module,
"{0}: cannot handle zero number of {1}", m_name, IsTiled() ? "tiles" : "strips");
return false;
}
m_dir.td_stripsperimage = m_dir.td_nstrips;
if (m_dir.td_planarconfig == PlanarConfig.SEPARATE)
m_dir.td_stripsperimage /= m_dir.td_samplesperpixel;
if (!fieldSet(FieldBit.StripOffsets))
{
if ((m_dir.td_compression == Compression.OJPEG) && !IsTiled() && (m_dir.td_nstrips == 1))
{
// XXX: OJPEG hack.
// If a) compression is OJPEG, b) it's not a tiled TIFF, and c) the number of
// strips is 1, then we tolerate the absence of stripoffsets tag, because,
// presumably, all required data is in the JpegInterchangeFormat stream.
setFieldBit(FieldBit.StripOffsets);
}
else
{
missingRequired(IsTiled() ? "TileOffsets" : "StripOffsets");
return false;
}
}
// Second pass: extract other information.
for (int i = 0; i < dircount; i++)
{
if (dir[i].tdir_tag == TiffTag.IGNORE)
continue;
switch (dir[i].tdir_tag)
{
case TiffTag.MINSAMPLEVALUE:
case TiffTag.MAXSAMPLEVALUE:
case TiffTag.BITSPERSAMPLE:
case TiffTag.DATATYPE:
case TiffTag.SAMPLEFORMAT:
// The 5.0 spec says the Compression tag has one value, while earlier
// specs say it has one value per sample. Because of this, we accept the
// tag if one value is supplied.
//
// The MinSampleValue, MaxSampleValue, BitsPerSample DataType and
// SampleFormat tags are supposed to be written as one value/sample, but
// some vendors incorrectly write one value only - so we accept that as
// well (yech). Other vendors write correct value for NumberOfSamples, but
// incorrect one for BitsPerSample and friends, and we will read this too.
if (dir[i].tdir_count == 1)
{
int v = extractData(dir[i]);
if (!SetField(dir[i].tdir_tag, v))
return false;
// XXX: workaround for broken TIFFs
}
else if (dir[i].tdir_tag == TiffTag.BITSPERSAMPLE && dir[i].tdir_type == TiffType.LONG)
{
int v;
if (!fetchPerSampleLongs(dir[i], out v) || !SetField(dir[i].tdir_tag, v))
return false;
}
else
{
short iv;
if (!fetchPerSampleShorts(dir[i], out iv) || !SetField(dir[i].tdir_tag, iv))
return false;
}
break;
case TiffTag.SMINSAMPLEVALUE:
case TiffTag.SMAXSAMPLEVALUE:
double dv;
if (!fetchPerSampleAnys(dir[i], out dv) || !SetField(dir[i].tdir_tag, dv))
return false;
break;
case TiffTag.STRIPOFFSETS:
case TiffTag.TILEOFFSETS:
if (!fetchStripThing(dir[i], m_dir.td_nstrips, ref m_dir.td_stripoffset))
return false;
break;
case TiffTag.STRIPBYTECOUNTS:
case TiffTag.TILEBYTECOUNTS:
if (!fetchStripThing(dir[i], m_dir.td_nstrips, ref m_dir.td_stripbytecount))
return false;
break;
case TiffTag.COLORMAP:
case TiffTag.TRANSFERFUNCTION:
{
// TransferFunction can have either 1x or 3x data values;
// Colormap can have only 3x items.
int v = 1 << m_dir.td_bitspersample;
if (dir[i].tdir_tag == TiffTag.COLORMAP || dir[i].tdir_count != v)
{
if (!checkDirCount(dir[i], 3 * v))
break;
}
byte[] cp = new byte[dir[i].tdir_count * sizeof(short)];
if (fetchData(dir[i], cp) != 0)
{
int c = 1 << m_dir.td_bitspersample;
if (dir[i].tdir_count == c)
{
// This deals with there being only one array to apply to all samples.
short[] u = ByteArrayToShorts(cp, 0, dir[i].tdir_count * sizeof(short));
SetField(dir[i].tdir_tag, u, u, u);
}
else
{
v *= sizeof(short);
short[] u0 = ByteArrayToShorts(cp, 0, v);
short[] u1 = ByteArrayToShorts(cp, v, v);
short[] u2 = ByteArrayToShorts(cp, 2 * v, v);
SetField(dir[i].tdir_tag, u0, u1, u2);
}
}
break;
}
case TiffTag.PAGENUMBER:
case TiffTag.HALFTONEHINTS:
case TiffTag.YCBCRSUBSAMPLING:
case TiffTag.DOTRANGE:
fetchShortPair(dir[i]);
break;
case TiffTag.REFERENCEBLACKWHITE:
fetchRefBlackWhite(dir[i]);
break;
// BEGIN REV 4.0 COMPATIBILITY
case TiffTag.OSUBFILETYPE:
FileType ft = 0;
switch ((OFileType)extractData(dir[i]))
{
case OFileType.REDUCEDIMAGE:
ft = FileType.REDUCEDIMAGE;
break;
case OFileType.PAGE:
ft = FileType.PAGE;
break;
}
if (ft != 0)
SetField(TiffTag.SUBFILETYPE, ft);
break;
// END REV 4.0 COMPATIBILITY
#if FIX_JPEG_IS_OJPEG
case TiffTag.COMPRESSION:
// do not set compression field again (see first pass) to avoid undoing the fix
if (!fixJpegIsOJpeg)
{
fetchNormalTag(dir[i]);
}
break;
#endif
default:
fetchNormalTag(dir[i]);
break;
}
}
// OJPEG hack:
// - If a) compression is OJPEG, and b) photometric tag is missing, then we
// consistently find that photometric should be YCbCr
// - If a) compression is OJPEG, and b) photometric tag says it's RGB, then we
// consistently find that the buggy implementation of the buggy compression scheme
// matches photometric YCbCr instead.
// - If a) compression is OJPEG, and b) bitspersample tag is missing, then we
// consistently find bitspersample should be 8.
// - If a) compression is OJPEG, b) samplesperpixel tag is missing, and c) photometric
// is RGB or YCbCr, then we consistently find samplesperpixel should be 3
// - If a) compression is OJPEG, b) samplesperpixel tag is missing, and c) photometric
// is MINISWHITE or MINISBLACK, then we consistently find samplesperpixel should be 3
if (m_dir.td_compression == Compression.OJPEG)
{
if (!fieldSet(FieldBit.Photometric))
{
WarningExt(this, m_clientdata,
"ReadDirectory", "Photometric tag is missing, assuming data is YCbCr");
if (!SetField(TiffTag.PHOTOMETRIC, Photometric.YCBCR))
return false;
}
else if (m_dir.td_photometric == Photometric.RGB)
{
m_dir.td_photometric = Photometric.YCBCR;
WarningExt(this, m_clientdata, "ReadDirectory",
"Photometric tag value assumed incorrect, assuming data is YCbCr instead of RGB");
}
if (!fieldSet(FieldBit.BitsPerSample))
{
WarningExt(this, m_clientdata, "ReadDirectory",
"BitsPerSample tag is missing, assuming 8 bits per sample");
if (!SetField(TiffTag.BITSPERSAMPLE, 8))
return false;
}
if (!fieldSet(FieldBit.SamplesPerPixel))
{
if ((m_dir.td_photometric == Photometric.RGB) ||
(m_dir.td_photometric == Photometric.YCBCR))
{
WarningExt(this, m_clientdata, "ReadDirectory",
"SamplesPerPixel tag is missing, assuming correct SamplesPerPixel value is 3");
if (!SetField(TiffTag.SAMPLESPERPIXEL, 3))
return false;
}
else if ((m_dir.td_photometric == Photometric.MINISWHITE) ||
(m_dir.td_photometric == Photometric.MINISBLACK))
{
WarningExt(this, m_clientdata, "ReadDirectory",
"SamplesPerPixel tag is missing, assuming correct SamplesPerPixel value is 1");
if (!SetField(TiffTag.SAMPLESPERPIXEL, 1))
return false;
}
}
}
// Verify Palette image has a Colormap.
if (m_dir.td_photometric == Photometric.PALETTE && !fieldSet(FieldBit.ColorMap))
{
missingRequired("Colormap");
return false;
}
// OJPEG hack:
// We do no further messing with strip/tile offsets/bytecounts in OJPEG TIFFs
if (m_dir.td_compression != Compression.OJPEG)
{
// Attempt to deal with a missing StripByteCounts tag.
if (!fieldSet(FieldBit.StripByteCounts))
{
// Some manufacturers violate the spec by not giving the size of the strips.
// In this case, assume there is one uncompressed strip of data.
if ((m_dir.td_planarconfig == PlanarConfig.CONTIG && m_dir.td_nstrips > 1) ||
(m_dir.td_planarconfig == PlanarConfig.SEPARATE && m_dir.td_nstrips != m_dir.td_samplesperpixel))
{
missingRequired("StripByteCounts");
return false;
}
WarningExt(this, m_clientdata, module,
"{0}: TIFF directory is missing required \"{1}\" field, calculating from imagelength",
m_name, FieldWithTag(TiffTag.STRIPBYTECOUNTS).Name);
if (!estimateStripByteCounts(dir, dircount))
return false;
}
else if (m_dir.td_nstrips == 1 && m_dir.td_stripoffset[0] != 0 && byteCountLooksBad(m_dir))
{
// XXX: Plexus (and others) sometimes give a value of zero for a tag when
// they don't know what the correct value is! Try and handle the simple case
// of estimating the size of a one strip image.
WarningExt(this, m_clientdata, module,
"{0}: Bogus \"{1}\" field, ignoring and calculating from imagelength",
m_name, FieldWithTag(TiffTag.STRIPBYTECOUNTS).Name);
if (!estimateStripByteCounts(dir, dircount))
return false;
}
else if (m_dir.td_planarconfig == PlanarConfig.CONTIG && m_dir.td_nstrips > 2 &&
m_dir.td_compression == Compression.NONE &&
m_dir.td_stripbytecount[0] != m_dir.td_stripbytecount[1])
{
// XXX: Some vendors fill StripByteCount array with absolutely wrong values
// (it can be equal to StripOffset array, for example). Catch this case here.
WarningExt(this, m_clientdata, module,
"{0}: Wrong \"{1}\" field, ignoring and calculating from imagelength",
m_name, FieldWithTag(TiffTag.STRIPBYTECOUNTS).Name);
if (!estimateStripByteCounts(dir, dircount))
return false;
}
}
dir = null;
if (!fieldSet(FieldBit.MaxSampleValue))
m_dir.td_maxsamplevalue = (ushort)((1 << m_dir.td_bitspersample) - 1);
// Setup default compression scheme.
// XXX: We can optimize checking for the strip bounds using the sorted bytecounts
// array. See also comments for appendToStrip() function.
if (m_dir.td_nstrips > 1)
{
m_dir.td_stripbytecountsorted = true;
for (int strip = 1; strip < m_dir.td_nstrips; strip++)
{
if (m_dir.td_stripoffset[strip - 1] > m_dir.td_stripoffset[strip])
{
m_dir.td_stripbytecountsorted = false;
break;
}
}
}
if (!fieldSet(FieldBit.Compression))
SetField(TiffTag.COMPRESSION, Compression.NONE);
// Some manufacturers make life difficult by writing large amounts of uncompressed
// data as a single strip. This is contrary to the recommendations of the spec. The
// following makes an attempt at breaking such images into strips closer to the
// recommended 8k bytes. A side effect, however, is that the RowsPerStrip tag value
// may be changed.
if (m_dir.td_nstrips == 1 && m_dir.td_compression == Compression.NONE &&
(m_flags & TiffFlags.STRIPCHOP) == TiffFlags.STRIPCHOP &&
(m_flags & TiffFlags.ISTILED) != TiffFlags.ISTILED)
{
chopUpSingleUncompressedStrip();
}
// Reinitialize i/o since we are starting on a new directory.
m_row = -1;
m_curstrip = -1;
m_col = -1;
m_curtile = -1;
m_tilesize = -1;
m_scanlinesize = ScanlineSize();
if (m_scanlinesize == 0)
{
ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero scanline size", m_name);
return false;
}
if (IsTiled())
{
m_tilesize = TileSize();
if (m_tilesize == 0)
{
ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero tile size", m_name);
return false;
}
}
else
{
if (StripSize() == 0)
{
ErrorExt(this, m_clientdata, module, "{0}: cannot handle zero strip size", m_name);
return false;
}
}
return true;
}