From 8e3c27d7116ce0254d7ea81f89ddcd6a4c29a81b Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 13 Feb 2024 00:42:26 +1100 Subject: [PATCH] TIFF extraction makes fewer, larger reads Previously we would read values one-at-a-time through IFD tables of TIFF data. There's a fair amount of indirection here. With this code, we load the entire IFD table into a span (ideally on the stack) and read through it from there. As part of this work, constants were added to indicate the sizes involved, and some variable names were made clearer. Also, the size restriction placed on IFDs was improved, so that they aren't required to have space at the end for the optional follower IFD pointer, fixing some cases in the regression suite. --- MetadataExtractor/Formats/Tiff/TiffReader.cs | 374 +++++++++++-------- 1 file changed, 208 insertions(+), 166 deletions(-) diff --git a/MetadataExtractor/Formats/Tiff/TiffReader.cs b/MetadataExtractor/Formats/Tiff/TiffReader.cs index ce1c776d7..919922801 100644 --- a/MetadataExtractor/Formats/Tiff/TiffReader.cs +++ b/MetadataExtractor/Formats/Tiff/TiffReader.cs @@ -41,56 +41,58 @@ public static void ProcessTiff(IndexedReader reader, ITiffHandler handler) { 0x4d4d => reader.WithByteOrder(isMotorolaByteOrder: true), 0x4949 => reader.WithByteOrder(isMotorolaByteOrder: false), - _ => throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrder), + _ => throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + byteOrder) }; // Check the next two values for correctness. var tiffMarker = reader.GetUInt16(2); - var tiffStandard = handler.ProcessTiffMarker(tiffMarker); + TiffStandard tiffStandard = handler.ProcessTiffMarker(tiffMarker); - bool isBigTiff; - - int firstIfdOffset; - - switch (tiffStandard) + bool? isBigTiff = tiffStandard switch { - case TiffStandard.Tiff: - isBigTiff = false; - firstIfdOffset = checked((int)reader.GetUInt32(4)); + TiffStandard.Tiff => false, + TiffStandard.BigTiff => true, + _ => null + }; - // David Ekholm sent a digital camera image that has this problem - // TODO calling Length should be avoided as it causes IndexedCapturingReader to read to the end of the stream - if (firstIfdOffset >= reader.Length - 1) - { - handler.Warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset"); - // First directory normally starts immediately after the offset bytes, so try that - firstIfdOffset = 2 + 2 + 4; - } + if (isBigTiff is null) + { + handler.Error($"Unsupported TiffStandard {tiffStandard}."); + return; + } - break; + int firstIfdOffset; - case TiffStandard.BigTiff: - isBigTiff = true; - var offsetByteSize = reader.GetInt16(4); + if (!isBigTiff.Value) + { + firstIfdOffset = checked((int)reader.GetUInt32(4)); - if (offsetByteSize != 8) - { - handler.Error($"Unsupported offset byte size: {offsetByteSize}"); - return; - } + // David Ekholm sent a digital camera image that has this problem + // TODO calling Length should be avoided as it causes IndexedCapturingReader to read to the end of the stream -- add reader.TryValidatePosition(int offset) and have implementations return true if they cannot quickly determine the answer, or just buffer until that position + if (firstIfdOffset >= reader.Length - 1) + { + handler.Warn("First IFD offset is beyond the end of the TIFF data segment -- trying default offset"); + // First directory normally starts immediately after the offset bytes, so try that + firstIfdOffset = 2 + 2 + 4; + } + } + else + { + var offsetByteSize = reader.GetInt16(4); - // There are two reserved bytes at offset 6, which are expected to have zero value. - // We skip without validation for now, but may change this in future. + if (offsetByteSize != 8) + { + handler.Error($"Unsupported offset byte size: {offsetByteSize}"); + return; + } - firstIfdOffset = checked((int)reader.GetUInt64(8)); - break; + // There are two reserved bytes at offset 6, which are expected to have zero value. + // We skip without validation for now, but may change this in future. - default: - handler.Error($"Unsupported TiffStandard {tiffStandard}."); - return; + firstIfdOffset = checked((int)reader.GetUInt64(8)); } - var context = new TiffReaderContext(reader, reader.IsMotorolaByteOrder, isBigTiff); + var context = new TiffReaderContext(reader, reader.IsMotorolaByteOrder, isBigTiff.Value); ProcessIfd(handler, context, firstIfdOffset); } @@ -139,82 +141,99 @@ public static void ProcessIfd(ITiffHandler handler, TiffReaderContext context, i return; } - // The number of tags in this directory - var dirTagCount = context.IsBigTiff - ? checked((int)context.Reader.GetUInt64(ifdOffset)) - : context.Reader.GetUInt16(ifdOffset); + // The number of tags in this directory, and various sizes (determined by BigTIFF). +#pragma warning disable format + (int tagCount, int tagCountLength, int entryLength, int followerPointerLength, int entryValueOffset, uint inlineValueLength) = context.IsBigTiff + ? (checked((int)context.Reader.GetUInt64(ifdOffset)), ByteCounts.TagCount_BigTiff, ByteCounts.Entry_BigTiff, ByteCounts.FollowerIfdPointer_BiffTiff, ByteCounts.EntryValueOffset_BigTiff, 8u) + : (context.Reader.GetUInt16(ifdOffset), ByteCounts.TagCount, ByteCounts.Entry, ByteCounts.FollowerIfdPointer, ByteCounts.EntryValueOffset, 4u); +#pragma warning restore format // Some software modifies the byte order of the file, but misses some IFDs (such as makernotes). // The entire test image repository doesn't contain a single IFD with more than 255 entries. // Here we detect switched bytes that suggest this problem, and temporarily swap the byte order. // This was discussed in GitHub issue #136. - if (!context.IsBigTiff && dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) + if (!context.IsBigTiff && tagCount > 0xFF && (tagCount & 0xFF) == 0) { - dirTagCount >>= 8; + tagCount >>= 8; context = context.WithByteOrder(!context.Reader.IsMotorolaByteOrder); } - var dirLength = context.IsBigTiff - ? 8 + 20 * dirTagCount + 8 - : 2 + 12 * dirTagCount + 4; + // The IFD starts after the tag count. + var tagTableOffset = ifdOffset + tagCountLength; - if (dirLength + ifdOffset > checked((int)context.Reader.Length)) + // The IFD table is stored using fixed-size elements. + // When a value is larger than the allotted space, a pointer is stored. + // + // Per tag: 12 bytes / BigTIFF 20 bytes (see above docs for breakdown). + // Finally, a pointer to a "follower" IFD (optional) + var tagTableLength = (tagCount * entryLength); + + if (tagTableOffset + tagTableLength > checked((int)context.Reader.Length)) { handler.Error("Illegally sized IFD"); return; } - var inlineValueSize = context.IsBigTiff ? 8u : 4u; + // TODO better approach here? stack overflow? if backed by array, just want to slice without copy? other stuff? what about longer tables? + Span tagTableBytes = stackalloc byte[tagTableLength]; + context.Reader.GetBytes(tagTableOffset, tagTableBytes); + BufferReader reader = new(tagTableBytes, isBigEndian: context.Reader.IsMotorolaByteOrder); + + // We will track how many invalid formats we see, and stop processing when we meet a threshold. + var invalidTiffFormatCodeCount = 0; + const int InvalidTiffFormatCodeCountThreshold = 5; // // Handle each tag in this directory // - var invalidTiffFormatCodeCount = 0; - for (var tagNumber = 0; tagNumber < dirTagCount; tagNumber++) + for (var tagNumber = 0; tagNumber < tagCount; tagNumber++) { - var tagOffset = CalculateTagOffset(ifdOffset, tagNumber, context.IsBigTiff); + Debug.Assert(reader.Position % entryLength == 0, "Misaligned read of IFD entry"); - int tagId = context.Reader.GetUInt16(tagOffset); + // Tag identifier (2 bytes) + ushort tagId = reader.GetUInt16(); - var formatCode = (TiffDataFormatCode)context.Reader.GetUInt16(tagOffset + 2); + // Format code (2 bytes) + var formatCode = (TiffDataFormatCode)reader.GetUInt16(); - var componentCount = context.IsBigTiff - ? context.Reader.GetUInt64(tagOffset + 4) - : context.Reader.GetUInt32(tagOffset + 4); + // Number of components (4 bytes / BigTIFF 8 bytes) + ulong componentCount = context.IsBigTiff + ? reader.GetUInt64() + : reader.GetUInt32(); var format = TiffDataFormat.FromTiffFormatCode(formatCode, context.IsBigTiff); - ulong byteCount; - if (format is null) + ulong valueLength; + if (format is { ComponentSizeBytes: byte componentSize }) { - if (!handler.TryCustomProcessFormat(tagId, formatCode, componentCount, out byteCount)) - { - // This error suggests that we are processing at an incorrect index and will generate - // rubbish until we go out of bounds (which may be a while). Exit now. - handler.Error($"Invalid TIFF tag format code {(int)formatCode} for tag 0x{tagId:X4}"); - // TODO specify threshold as a parameter, or provide some other external control over this behaviour - if (++invalidTiffFormatCodeCount > 5) - { - handler.Error("Stopping processing as too many errors seen in TIFF IFD"); - return; - } - continue; - } + valueLength = checked(componentCount * componentSize); } - else + else if (!handler.TryCustomProcessFormat(tagId, formatCode, componentCount, out valueLength)) { - byteCount = checked(componentCount * format.ComponentSizeBytes); + // This error suggests that we are processing at an incorrect index and will generate + // rubbish until we go out of bounds (which may be a while). Exit now. + handler.Error($"Invalid TIFF tag format code {(int)formatCode} for tag 0x{tagId:X4}"); + + // TODO specify threshold as a parameter, or provide some other external control over this behaviour + if (++invalidTiffFormatCodeCount > InvalidTiffFormatCodeCountThreshold) + { + handler.Error("Stopping processing as too many errors seen in TIFF IFD"); + return; + } + + reader.Skip(checked((int)inlineValueLength)); + continue; } - uint tagValueOffset; - if (byteCount > inlineValueSize) + uint valueOffset; + if (valueLength > inlineValueLength) { // Value(s) are too big to fit inline. Follow the pointer. - tagValueOffset = context.IsBigTiff - ? checked((uint)context.Reader.GetUInt64(tagOffset + 12)) - : context.Reader.GetUInt32(tagOffset + 8); + valueOffset = context.IsBigTiff + ? checked((uint)reader.GetUInt64()) + : reader.GetUInt32(); - if (tagValueOffset + byteCount > checked((ulong)context.Reader.Length)) + if (valueOffset + valueLength > checked((ulong)context.Reader.Length)) { // Bogus pointer offset and/or byteCount value handler.Error("Illegal TIFF tag pointer offset"); @@ -224,76 +243,90 @@ public static void ProcessIfd(ITiffHandler handler, TiffReaderContext context, i else { // Value(s) can fit inline. - tagValueOffset = context.IsBigTiff - ? checked((uint)tagOffset + 12) - : checked((uint)tagOffset + 8); + int tagOffset = CalculateTagOffset(tagNumber); + valueOffset = checked((uint)(tagOffset + entryValueOffset)); + reader.Skip(checked((int)inlineValueLength)); } - if (tagValueOffset > context.Reader.Length) - { - handler.Error("Illegal TIFF tag pointer offset"); - continue; - } + Debug.Assert(checked(valueOffset + valueLength) <= checked((ulong)context.Reader.Length)); - // Check that this tag isn't going to allocate outside the bounds of the data array. - // This addresses an uncommon OutOfMemoryError. - if (tagValueOffset + byteCount > checked((ulong)context.Reader.Length)) - { - handler.Error("Illegal number of bytes for TIFF tag data: " + byteCount); - continue; - } + bool isIfdPointer = false; + + // TODO is the following IFD8 check correct? IFD8 is 8-bytes in length, and the following assumed 4-bytes. + const ulong IfdPointerLength = 4; // Some tags point to one or more additional IFDs to process - var isIfdPointer = false; - if (byteCount == checked(4L * componentCount) || formatCode == TiffDataFormatCode.Ifd8) + if (valueLength == checked(componentCount * IfdPointerLength) || formatCode == TiffDataFormatCode.Ifd8) { + // There may be multiple IFD pointers, so we try to enter and process an IFD for each. + // They will all have the same TagId, and therefore it's likely they'll be the same + // kind of directory. for (ulong i = 0; i < componentCount; i++) { if (handler.TryEnterSubIfd(tagId)) { isIfdPointer = true; - var subDirOffset = context.Reader.GetUInt32(checked((int)(tagValueOffset + i * 4))); + + int subDirOffsetOffset = checked((int)(valueOffset + (i * IfdPointerLength))); + + uint subDirOffset = context.Reader.GetUInt32(subDirOffsetOffset); + ProcessIfd(handler, context, (int)subDirOffset); } + else + { + // There's no point trying to enter the same tag ID again. + break; + } } } // If it wasn't an IFD pointer, allow custom tag processing to occur - if (!isIfdPointer && !handler.CustomProcessTag(context, tagId, (int)tagValueOffset, (int)byteCount)) + if (!isIfdPointer && !handler.CustomProcessTag(context, tagId, (int)valueOffset, (int)valueLength)) { // If no custom processing occurred, process the tag in the standard fashion - ProcessTag(handler, tagId, (int)tagValueOffset, (int)componentCount, formatCode, context.Reader); + ProcessTag(handler, tagId, (int)valueOffset, (int)componentCount, formatCode, context.Reader); } } // at the end of each IFD is an optional link to the next IFD - var finalTagOffset = CalculateTagOffset(ifdOffset, dirTagCount, context.IsBigTiff); + int finalTagOffset = CalculateTagOffset(tagIndex: tagCount); - var nextIfdOffsetLong = context.IsBigTiff - ? context.Reader.GetUInt64(finalTagOffset) - : context.Reader.GetUInt32(finalTagOffset); - - if (nextIfdOffsetLong != 0 && nextIfdOffsetLong <= int.MaxValue) + if ((long)finalTagOffset + followerPointerLength <= context.Reader.Length) { - var nextIfdOffset = (int)nextIfdOffsetLong; + ulong nextIfdOffsetLong = context.IsBigTiff + ? context.Reader.GetUInt64(finalTagOffset) + : context.Reader.GetUInt32(finalTagOffset); - if (nextIfdOffset >= context.Reader.Length) - { - // Last 4 bytes of IFD reference another IFD with an address that is out of bounds - return; - } - else if (nextIfdOffset < ifdOffset) + if (nextIfdOffsetLong != 0 && nextIfdOffsetLong <= int.MaxValue) { - // TODO is this a valid restriction? - // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory - return; - } + int nextIfdOffset = (int)nextIfdOffsetLong; - if (handler.HasFollowerIfd()) - { - ProcessIfd(handler, context, nextIfdOffset); + if (nextIfdOffset >= context.Reader.Length) + { + // Last 4 bytes of IFD reference another IFD with an address that is out of bounds + return; + } + else if (nextIfdOffset < ifdOffset) + { + // TODO is this a valid restriction? + // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory + return; + } + + if (handler.HasFollowerIfd()) + { + ProcessIfd(handler, context, nextIfdOffset); + } } } + + return; + + int CalculateTagOffset(int tagIndex) + { + return checked(ifdOffset + tagCountLength + (entryLength * tagIndex)); + } } finally { @@ -302,32 +335,32 @@ public static void ProcessIfd(ITiffHandler handler, TiffReaderContext context, i } /// - private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffset, int componentCount, TiffDataFormatCode formatCode, IndexedReader reader) + private static void ProcessTag(ITiffHandler handler, int tagId, in int valueOffset, int componentCount, TiffDataFormatCode formatCode, IndexedReader reader) { switch (formatCode) { case TiffDataFormatCode.Undefined: { // this includes exif user comments - handler.SetByteArray(tagId, reader.GetBytes(tagValueOffset, componentCount)); + handler.SetByteArray(tagId, reader.GetBytes(valueOffset, componentCount)); break; } case TiffDataFormatCode.String: { - handler.SetString(tagId, reader.GetNullTerminatedStringValue(tagValueOffset, componentCount)); + handler.SetString(tagId, reader.GetNullTerminatedStringValue(valueOffset, componentCount)); break; } case TiffDataFormatCode.RationalS: { if (componentCount == 1) { - handler.SetRational(tagId, new Rational(reader.GetInt32(tagValueOffset), reader.GetInt32(tagValueOffset + 4))); + handler.SetRational(tagId, new Rational(reader.GetInt32(valueOffset), reader.GetInt32(valueOffset + 4))); } else if (componentCount > 1) { var array = new Rational[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = new Rational(reader.GetInt32(tagValueOffset + 8 * i), reader.GetInt32(tagValueOffset + 4 + 8 * i)); + for (int i = 0, componentValueOffset = valueOffset; i < componentCount; i++, componentValueOffset += 8) + array[i] = new Rational(reader.GetInt32(componentValueOffset), reader.GetInt32(componentValueOffset + 4)); handler.SetRationalArray(tagId, array); } break; @@ -336,13 +369,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetRational(tagId, new Rational(reader.GetUInt32(tagValueOffset), reader.GetUInt32(tagValueOffset + 4))); + handler.SetRational(tagId, new Rational(reader.GetUInt32(valueOffset), reader.GetUInt32(valueOffset + 4))); } else if (componentCount > 1) { var array = new Rational[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = new Rational(reader.GetUInt32(tagValueOffset + 8 * i), reader.GetUInt32(tagValueOffset + 4 + 8 * i)); + for (int i = 0, componentValueOffset = valueOffset; i < componentCount; i++, componentValueOffset += 8) + array[i] = new Rational(reader.GetUInt32(componentValueOffset), reader.GetUInt32(componentValueOffset + 4)); handler.SetRationalArray(tagId, array); } break; @@ -351,13 +384,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetFloat(tagId, reader.GetFloat32(tagValueOffset)); + handler.SetFloat(tagId, reader.GetFloat32(valueOffset)); } else { var array = new float[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetFloat32(tagValueOffset + i * 4); + for (int i = 0; i < componentCount; i++) + array[i] = reader.GetFloat32(valueOffset + i * 4); handler.SetFloatArray(tagId, array); } break; @@ -366,13 +399,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetDouble(tagId, reader.GetDouble64(tagValueOffset)); + handler.SetDouble(tagId, reader.GetDouble64(valueOffset)); } else { var array = new double[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetDouble64(tagValueOffset + i * 8); + for (int i = 0; i < componentCount; i++) + array[i] = reader.GetDouble64(valueOffset + i * 8); handler.SetDoubleArray(tagId, array); } break; @@ -381,13 +414,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt8S(tagId, reader.GetSByte(tagValueOffset)); + handler.SetInt8S(tagId, reader.GetSByte(valueOffset)); } else { var array = new sbyte[componentCount]; var bytes = MemoryMarshal.Cast(array); - reader.GetBytes(tagValueOffset, bytes); + reader.GetBytes(valueOffset, bytes); handler.SetInt8SArray(tagId, array); } break; @@ -396,12 +429,12 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt8U(tagId, reader.GetByte(tagValueOffset)); + handler.SetInt8U(tagId, reader.GetByte(valueOffset)); } else { var array = new byte[componentCount]; - reader.GetBytes(tagValueOffset, array); + reader.GetBytes(valueOffset, array); handler.SetInt8UArray(tagId, array); } break; @@ -410,13 +443,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt16S(tagId, reader.GetInt16(tagValueOffset)); + handler.SetInt16S(tagId, reader.GetInt16(valueOffset)); } else { var array = new short[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetInt16(tagValueOffset + i * 2); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 2) + array[i] = reader.GetInt16(componentOffset); handler.SetInt16SArray(tagId, array); } break; @@ -425,13 +458,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt16U(tagId, reader.GetUInt16(tagValueOffset)); + handler.SetInt16U(tagId, reader.GetUInt16(valueOffset)); } else { var array = new ushort[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetUInt16(tagValueOffset + i * 2); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 2) + array[i] = reader.GetUInt16(componentOffset); handler.SetInt16UArray(tagId, array); } break; @@ -441,13 +474,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs // NOTE 'long' in this case means 32 bit, not 64 if (componentCount == 1) { - handler.SetInt32S(tagId, reader.GetInt32(tagValueOffset)); + handler.SetInt32S(tagId, reader.GetInt32(valueOffset)); } else { var array = new int[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetInt32(tagValueOffset + i * 4); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 4) + array[i] = reader.GetInt32(componentOffset); handler.SetInt32SArray(tagId, array); } break; @@ -457,13 +490,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs // NOTE 'long' in this case means 32 bit, not 64 if (componentCount == 1) { - handler.SetInt32U(tagId, reader.GetUInt32(tagValueOffset)); + handler.SetInt32U(tagId, reader.GetUInt32(valueOffset)); } else { var array = new uint[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetUInt32(tagValueOffset + i * 4); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 4) + array[i] = reader.GetUInt32(componentOffset); handler.SetInt32UArray(tagId, array); } break; @@ -472,13 +505,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt64S(tagId, reader.GetInt64(tagValueOffset)); + handler.SetInt64S(tagId, reader.GetInt64(valueOffset)); } else { var array = new long[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetInt64(tagValueOffset + i * 8); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 8) + array[i] = reader.GetInt64(componentOffset); handler.SetInt64SArray(tagId, array); } break; @@ -487,13 +520,13 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs { if (componentCount == 1) { - handler.SetInt64U(tagId, reader.GetUInt64(tagValueOffset)); + handler.SetInt64U(tagId, reader.GetUInt64(valueOffset)); } else { var array = new ulong[componentCount]; - for (var i = 0; i < componentCount; i++) - array[i] = reader.GetUInt64(tagValueOffset + i * 8); + for (int i = 0, componentOffset = valueOffset; i < componentCount; i++, componentOffset += 8) + array[i] = reader.GetUInt64(componentOffset); handler.SetInt64UArray(tagId, array); } break; @@ -506,19 +539,28 @@ private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffs } } - /// Determine the offset of a given tag within the specified IFD. - /// - /// Add 2 bytes for the tag count. - /// Each entry is 12 bytes for regular TIFF, or 20 bytes for BigTIFF. - /// - /// The offset at which the IFD starts. - /// The zero-based entry number. - /// Whether we are using BigTIFF encoding. - private static int CalculateTagOffset(int ifdStartOffset, int entryNumber, bool isBigTiff) + private static class ByteCounts { - return !isBigTiff - ? ifdStartOffset + 2 + 12 * entryNumber - : ifdStartOffset + 8 + 20 * entryNumber; + public const int TagCount = 2; + public const int TagCount_BigTiff = 8; + + public const int TagId = 2; + public const int FormatCode = 2; + + public const int ComponentCount = 4; + public const int ComponentCount_BigTiff = 8; + + public const int Value = 4; + public const int Value_BigTiff = 8; + + public const int EntryValueOffset = TagId + FormatCode + ComponentCount; + public const int EntryValueOffset_BigTiff = TagId + FormatCode + ComponentCount_BigTiff; + + public const int Entry = TagId + FormatCode + ComponentCount + Value; + public const int Entry_BigTiff = TagId + FormatCode + ComponentCount_BigTiff + Value_BigTiff; + + public const int FollowerIfdPointer = 4; + public const int FollowerIfdPointer_BiffTiff = 8; } } }