private static void ProcessBinary([NotNull] Directory directory, int tagValueOffset, [NotNull] IndexedReader reader, int byteCount, bool issigned = true, int arrayLength = 1) { // expects signed/unsigned int16 (for now) int byteSize = issigned ? sizeof(short) : sizeof(ushort); // 'directory' is assumed to contain tags that correspond to the byte position unless it's a set of bytes for (var i = 0; i < byteCount; i++) { if (directory.HasTagName(i)) { // only process this tag if the 'next' integral tag exists. Otherwise, it's a set of bytes if (i < byteCount - 1 && directory.HasTagName(i + 1)) { if (issigned) { directory.Set(i, reader.GetInt16(tagValueOffset + (i * byteSize))); } else { directory.Set(i, reader.GetUInt16(tagValueOffset + (i * byteSize))); } } else { // the next arrayLength bytes are a multi-byte value if (issigned) { short[] val = new short[arrayLength]; for (int j = 0; j < val.Length; j++) { val[j] = reader.GetInt16(tagValueOffset + ((i + j) * byteSize)); } directory.Set(i, val); } else { ushort[] val = new ushort[arrayLength]; for (int j = 0; j < val.Length; j++) { val[j] = reader.GetUInt16(tagValueOffset + ((i + j) * byteSize)); } directory.Set(i, val); } i += arrayLength - 1; } } } }
/// <summary>Processes a TIFF data sequence.</summary> /// <param name="reader">the <see cref="IndexedReader"/> from which the data should be read</param> /// <param name="handler">the <see cref="ITiffHandler"/> that will coordinate processing and accept read values</param> /// <exception cref="TiffProcessingException">if an error occurred during the processing of TIFF data that could not be ignored or recovered from</exception> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> /// <exception cref="TiffProcessingException"/> public static void ProcessTiff(IndexedReader reader, ITiffHandler handler) { // Read byte order. var byteOrder = reader.GetInt16(0); reader = byteOrder switch { 0x4d4d => reader.WithByteOrder(isMotorolaByteOrder: true), 0x4949 => reader.WithByteOrder(isMotorolaByteOrder: false), _ => throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + reader.GetInt16(0)), }; // Check the next two values for correctness. int tiffMarker = reader.GetUInt16(2); handler.SetTiffMarker(tiffMarker); var firstIfdOffset = reader.GetInt32(4); // 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; } var processedIfdOffsets = new HashSet <int>(); ProcessIfd(handler, reader, processedIfdOffsets, firstIfdOffset); }
/// <summary>Reads JFIF values and returns them in an <see cref="JfifDirectory"/>.</summary> public JfifDirectory Extract(IndexedReader reader) { var directory = new JfifDirectory(); try { // For JFIF the tag number is the value's byte offset directory.Set(JfifDirectory.TagVersion, reader.GetUInt16(JfifDirectory.TagVersion)); directory.Set(JfifDirectory.TagUnits, reader.GetByte(JfifDirectory.TagUnits)); directory.Set(JfifDirectory.TagResX, reader.GetUInt16(JfifDirectory.TagResX)); directory.Set(JfifDirectory.TagResY, reader.GetUInt16(JfifDirectory.TagResY)); directory.Set(JfifDirectory.TagThumbWidth, reader.GetByte(JfifDirectory.TagThumbWidth)); directory.Set(JfifDirectory.TagThumbHeight, reader.GetByte(JfifDirectory.TagThumbHeight)); } catch (IOException e) { directory.AddError(e.Message); } return(directory); }
/// <summary>Processes a TIFF data sequence.</summary> /// <param name="reader">the <see cref="IndexedReader"/> from which the data should be read</param> /// <param name="handler">the <see cref="ITiffHandler"/> that will coordinate processing and accept read values</param> /// <param name="tiffHeaderOffset">the offset within <c>reader</c> at which the TIFF header starts</param> /// <exception cref="TiffProcessingException">if an error occurred during the processing of TIFF data that could not be ignored or recovered from</exception> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> /// <exception cref="TiffProcessingException"/> public static void ProcessTiff([NotNull] IndexedReader reader, [NotNull] ITiffHandler handler, int tiffHeaderOffset = 0) { // Read byte order. switch (reader.GetInt16(tiffHeaderOffset)) { case 0x4d4d: // MM reader.IsMotorolaByteOrder = true; break; case 0x4949: // II reader.IsMotorolaByteOrder = false; break; default: throw new TiffProcessingException("Unclear distinction between Motorola/Intel byte ordering: " + reader.GetInt16(tiffHeaderOffset)); } // Check the next two values for correctness. int tiffMarker = reader.GetUInt16(2 + tiffHeaderOffset); handler.SetTiffMarker(tiffMarker); var firstIfdOffset = reader.GetInt32(4 + tiffHeaderOffset) + tiffHeaderOffset; // 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 = tiffHeaderOffset + 2 + 2 + 4; } var processedIfdOffsets = new HashSet <int>(); ProcessIfd(handler, reader, processedIfdOffsets, firstIfdOffset, tiffHeaderOffset); handler.Completed(reader, tiffHeaderOffset); }
private static void SetDate([NotNull] IccDirectory directory, int tagType, [NotNull] IndexedReader reader) { var year = reader.GetUInt16(tagType); var month = reader.GetUInt16(tagType + 2); var day = reader.GetUInt16(tagType + 4); var hours = reader.GetUInt16(tagType + 6); var minutes = reader.GetUInt16(tagType + 8); var seconds = reader.GetUInt16(tagType + 10); if (DateUtil.IsValidDate(year, month, day) && DateUtil.IsValidTime(hours, minutes, seconds)) { directory.Set(tagType, new DateTime(year, month, day, hours, minutes, seconds, kind: DateTimeKind.Utc)); } else { directory.AddError($"ICC data describes an invalid date/time: year={year} month={month} day={day} hour={hours} minute={minutes} second={seconds}"); } }
private static void ProcessKodakMakernote([NotNull] KodakMakernoteDirectory directory, int tagValueOffset, [NotNull] IndexedReader reader) { // Kodak's makernote is not in IFD format. It has values at fixed offsets. var dataOffset = tagValueOffset + 8; try { directory.Set(KodakMakernoteDirectory.TagKodakModel, reader.GetString(dataOffset, 8)); directory.Set(KodakMakernoteDirectory.TagQuality, reader.GetByte(dataOffset + 9)); directory.Set(KodakMakernoteDirectory.TagBurstMode, reader.GetByte(dataOffset + 10)); directory.Set(KodakMakernoteDirectory.TagImageWidth, reader.GetUInt16(dataOffset + 12)); directory.Set(KodakMakernoteDirectory.TagImageHeight, reader.GetUInt16(dataOffset + 14)); directory.Set(KodakMakernoteDirectory.TagYearCreated, reader.GetUInt16(dataOffset + 16)); directory.Set(KodakMakernoteDirectory.TagMonthDayCreated, reader.GetBytes(dataOffset + 18, 2)); directory.Set(KodakMakernoteDirectory.TagTimeCreated, reader.GetBytes(dataOffset + 20, 4)); directory.Set(KodakMakernoteDirectory.TagBurstMode2, reader.GetUInt16(dataOffset + 24)); directory.Set(KodakMakernoteDirectory.TagShutterMode, reader.GetByte(dataOffset + 27)); directory.Set(KodakMakernoteDirectory.TagMeteringMode, reader.GetByte(dataOffset + 28)); directory.Set(KodakMakernoteDirectory.TagSequenceNumber, reader.GetByte(dataOffset + 29)); directory.Set(KodakMakernoteDirectory.TagFNumber, reader.GetUInt16(dataOffset + 30)); directory.Set(KodakMakernoteDirectory.TagExposureTime, reader.GetUInt32(dataOffset + 32)); directory.Set(KodakMakernoteDirectory.TagExposureCompensation, reader.GetInt16(dataOffset + 36)); directory.Set(KodakMakernoteDirectory.TagFocusMode, reader.GetByte(dataOffset + 56)); directory.Set(KodakMakernoteDirectory.TagWhiteBalance, reader.GetByte(dataOffset + 64)); directory.Set(KodakMakernoteDirectory.TagFlashMode, reader.GetByte(dataOffset + 92)); directory.Set(KodakMakernoteDirectory.TagFlashFired, reader.GetByte(dataOffset + 93)); directory.Set(KodakMakernoteDirectory.TagIsoSetting, reader.GetUInt16(dataOffset + 94)); directory.Set(KodakMakernoteDirectory.TagIso, reader.GetUInt16(dataOffset + 96)); directory.Set(KodakMakernoteDirectory.TagTotalZoom, reader.GetUInt16(dataOffset + 98)); directory.Set(KodakMakernoteDirectory.TagDateTimeStamp, reader.GetUInt16(dataOffset + 100)); directory.Set(KodakMakernoteDirectory.TagColorMode, reader.GetUInt16(dataOffset + 102)); directory.Set(KodakMakernoteDirectory.TagDigitalZoom, reader.GetUInt16(dataOffset + 104)); directory.Set(KodakMakernoteDirectory.TagSharpness, reader.GetSByte(dataOffset + 107)); } catch (IOException ex) { directory.AddError("Error processing Kodak makernote data: " + ex.Message); } }
/// <summary>Processes a TIFF IFD.</summary> /// <remarks> /// IFD Header: /// <list type="bullet"> /// <item><b>2 bytes</b> number of tags</item> /// </list> /// Tag structure: /// <list type="bullet"> /// <item><b>2 bytes</b> tag type</item> /// <item><b>2 bytes</b> format code (values 1 to 12, inclusive)</item> /// <item><b>4 bytes</b> component count</item> /// <item><b>4 bytes</b> inline value, or offset pointer if too large to fit in four bytes</item> /// </list> /// </remarks> /// <param name="handler">the <see cref="ITiffHandler"/> that will coordinate processing and accept read values</param> /// <param name="reader">the <see cref="IndexedReader"/> from which the data should be read</param> /// <param name="processedGlobalIfdOffsets">the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop</param> /// <param name="ifdOffset">the offset within <c>reader</c> at which the IFD data starts</param> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> public static void ProcessIfd(ITiffHandler handler, IndexedReader reader, ICollection <int> processedGlobalIfdOffsets, int ifdOffset) { try { // Check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist. // Note that we track these offsets in the global frame, not the reader's local frame. var globalIfdOffset = reader.ToUnshiftedOffset(ifdOffset); if (processedGlobalIfdOffsets.Contains(globalIfdOffset)) { return; } // Remember that we've visited this directory so that we don't visit it again later processedGlobalIfdOffsets.Add(globalIfdOffset); // Validate IFD offset if (ifdOffset >= reader.Length || ifdOffset < 0) { handler.Error("Ignored IFD marked to start outside data segment"); return; } // First two bytes in the IFD are the number of tags in this directory int dirTagCount = reader.GetUInt16(ifdOffset); // 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 (dirTagCount > 0xFF && (dirTagCount & 0xFF) == 0) { dirTagCount >>= 8; reader = reader.WithByteOrder(!reader.IsMotorolaByteOrder); } var dirLength = 2 + 12 * dirTagCount + 4; if (dirLength + ifdOffset > reader.Length) { handler.Error("Illegally sized IFD"); return; } // // Handle each tag in this directory // var invalidTiffFormatCodeCount = 0; for (var tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { var tagOffset = CalculateTagOffset(ifdOffset, tagNumber); // 2 bytes for the tag id int tagId = reader.GetUInt16(tagOffset); // 2 bytes for the format code var formatCode = (TiffDataFormatCode)reader.GetUInt16(tagOffset + 2); // 4 bytes dictate the number of components in this tag's data var componentCount = reader.GetUInt32(tagOffset + 4); var format = TiffDataFormat.FromTiffFormatCode(formatCode); long byteCount; if (format == null) { 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 {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; } } else { byteCount = componentCount * format.ComponentSizeBytes; } long tagValueOffset; if (byteCount > 4) { // If it's bigger than 4 bytes, the dir entry contains an offset. tagValueOffset = reader.GetUInt32(tagOffset + 8); if (tagValueOffset + byteCount > reader.Length) { // Bogus pointer offset and / or byteCount value handler.Error("Illegal TIFF tag pointer offset"); continue; } } else { // 4 bytes or less and value is in the dir entry itself. tagValueOffset = tagOffset + 8; } if (tagValueOffset < 0 || tagValueOffset > reader.Length) { handler.Error("Illegal TIFF tag pointer offset"); continue; } // Check that this tag isn't going to allocate outside the bounds of the data array. // This addresses an uncommon OutOfMemoryError. if (byteCount < 0 || tagValueOffset + byteCount > reader.Length) { handler.Error("Illegal number of bytes for TIFF tag data: " + byteCount); continue; } // Some tags point to one or more additional IFDs to process var isIfdPointer = false; if (byteCount == checked (4L * componentCount)) { for (var i = 0; i < componentCount; i++) { if (handler.TryEnterSubIfd(tagId)) { isIfdPointer = true; var subDirOffset = reader.GetUInt32((int)(tagValueOffset + i * 4)); ProcessIfd(handler, reader, processedGlobalIfdOffsets, (int)subDirOffset); } } } // If it wasn't an IFD pointer, allow custom tag processing to occur if (!isIfdPointer && !handler.CustomProcessTag((int)tagValueOffset, processedGlobalIfdOffsets, reader, tagId, (int)byteCount)) { // If no custom processing occurred, process the tag in the standard fashion ProcessTag(handler, tagId, (int)tagValueOffset, (int)componentCount, formatCode, reader); } } // at the end of each IFD is an optional link to the next IFD var finalTagOffset = CalculateTagOffset(ifdOffset, dirTagCount); var nextIfdOffset = reader.GetInt32(finalTagOffset); if (nextIfdOffset != 0) { if (nextIfdOffset >= 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, reader, processedGlobalIfdOffsets, nextIfdOffset); } } } finally { handler.EndingIfd(); } }
/// <exception cref="System.IO.IOException"/> private static void ProcessTag(ITiffHandler handler, int tagId, int tagValueOffset, int componentCount, TiffDataFormatCode formatCode, IndexedReader reader) { switch (formatCode) { case TiffDataFormatCode.Undefined: { // this includes exif user comments handler.SetByteArray(tagId, reader.GetBytes(tagValueOffset, componentCount)); break; } case TiffDataFormatCode.String: { handler.SetString(tagId, reader.GetNullTerminatedStringValue(tagValueOffset, componentCount)); break; } case TiffDataFormatCode.RationalS: { if (componentCount == 1) { handler.SetRational(tagId, new Rational(reader.GetInt32(tagValueOffset), reader.GetInt32(tagValueOffset + 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)); } handler.SetRationalArray(tagId, array); } break; } case TiffDataFormatCode.RationalU: { if (componentCount == 1) { handler.SetRational(tagId, new Rational(reader.GetUInt32(tagValueOffset), reader.GetUInt32(tagValueOffset + 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)); } handler.SetRationalArray(tagId, array); } break; } case TiffDataFormatCode.Single: { if (componentCount == 1) { handler.SetFloat(tagId, reader.GetFloat32(tagValueOffset)); } else { var array = new float[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetFloat32(tagValueOffset + i * 4); } handler.SetFloatArray(tagId, array); } break; } case TiffDataFormatCode.Double: { if (componentCount == 1) { handler.SetDouble(tagId, reader.GetDouble64(tagValueOffset)); } else { var array = new double[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetDouble64(tagValueOffset + i * 4); } handler.SetDoubleArray(tagId, array); } break; } case TiffDataFormatCode.Int8S: { if (componentCount == 1) { handler.SetInt8S(tagId, reader.GetSByte(tagValueOffset)); } else { var array = new sbyte[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetSByte(tagValueOffset + i); } handler.SetInt8SArray(tagId, array); } break; } case TiffDataFormatCode.Int8U: { if (componentCount == 1) { handler.SetInt8U(tagId, reader.GetByte(tagValueOffset)); } else { var array = new byte[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetByte(tagValueOffset + i); } handler.SetInt8UArray(tagId, array); } break; } case TiffDataFormatCode.Int16S: { if (componentCount == 1) { handler.SetInt16S(tagId, reader.GetInt16(tagValueOffset)); } else { var array = new short[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetInt16(tagValueOffset + i * 2); } handler.SetInt16SArray(tagId, array); } break; } case TiffDataFormatCode.Int16U: { if (componentCount == 1) { handler.SetInt16U(tagId, reader.GetUInt16(tagValueOffset)); } else { var array = new ushort[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetUInt16(tagValueOffset + i * 2); } handler.SetInt16UArray(tagId, array); } break; } case TiffDataFormatCode.Int32S: { // NOTE 'long' in this case means 32 bit, not 64 if (componentCount == 1) { handler.SetInt32S(tagId, reader.GetInt32(tagValueOffset)); } else { var array = new int[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetInt32(tagValueOffset + i * 4); } handler.SetInt32SArray(tagId, array); } break; } case TiffDataFormatCode.Int32U: { // NOTE 'long' in this case means 32 bit, not 64 if (componentCount == 1) { handler.SetInt32U(tagId, reader.GetUInt32(tagValueOffset)); } else { var array = new uint[componentCount]; for (var i = 0; i < componentCount; i++) { array[i] = reader.GetUInt32(tagValueOffset + i * 4); } handler.SetInt32UArray(tagId, array); } break; } default: { handler.Error($"Invalid TIFF tag format code {formatCode} for tag 0x{tagId:X4}"); break; } } }
public IEnumerable <Directory> Extract(IndexedReader reader) { var header = reader.GetUInt32(0); if (header != 0x46464600) { var flirHeaderDirectory = new FlirHeaderDirectory(); flirHeaderDirectory.AddError("Unexpected FFF header bytes."); yield return(flirHeaderDirectory); yield break; } var headerDirectory = new FlirHeaderDirectory(); headerDirectory.Set(FlirHeaderDirectory.TagCreatorSoftware, reader.GetNullTerminatedStringValue(4, 16)); yield return(headerDirectory); var baseIndexOffset = reader.GetUInt32(24); var indexCount = reader.GetUInt32(28); var indexOffset = checked ((int)baseIndexOffset); for (int i = 0; i < indexCount; i++) { var mainType = (FlirMainTagType)reader.GetUInt16(indexOffset); var subType = reader.GetUInt16(indexOffset + 2); // 1=BE, 2=LE, 3=PNG; 1 for other record types var version = reader.GetUInt32(indexOffset + 4); var id = reader.GetUInt32(indexOffset + 8); var dataOffset = reader.GetInt32(indexOffset + 12); var dataLength = reader.GetInt32(indexOffset + 16); if (mainType == FlirMainTagType.Pixels) { var reader2 = reader.WithShiftedBaseOffset(dataOffset); // should be 0x0002 if byte order is correct var marker = reader2.GetUInt16(0); if (marker > 0x0100) { reader2 = reader2.WithByteOrder(!reader2.IsMotorolaByteOrder); } var directory = new FlirRawDataDirectory(); directory.Set(FlirRawDataDirectory.TagRawThermalImageWidth, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageWidth)); directory.Set(FlirRawDataDirectory.TagRawThermalImageHeight, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageHeight)); directory.Set(FlirRawDataDirectory.TagRawThermalImageType, reader2.GetUInt16(FlirRawDataDirectory.TagRawThermalImageType)); if (ExtractRawThermalImage) { directory.Set(FlirRawDataDirectory.TagRawThermalImage, reader2.GetBytes(32, dataLength - 32)); } yield return(directory); } else if (mainType == FlirMainTagType.BasicData) { var reader2 = reader.WithShiftedBaseOffset(dataOffset); // should be 0x0002 if byte order is correct var marker = reader2.GetUInt16(0); if (marker > 0x0100) { reader2 = reader2.WithByteOrder(!reader2.IsMotorolaByteOrder); } var directory = new FlirCameraInfoDirectory(); directory.Set(TagEmissivity, reader2.GetFloat32(TagEmissivity)); directory.Set(TagObjectDistance, reader2.GetFloat32(TagObjectDistance)); directory.Set(TagReflectedApparentTemperature, reader2.GetFloat32(TagReflectedApparentTemperature)); directory.Set(TagAtmosphericTemperature, reader2.GetFloat32(TagAtmosphericTemperature)); directory.Set(TagIRWindowTemperature, reader2.GetFloat32(TagIRWindowTemperature)); directory.Set(TagIRWindowTransmission, reader2.GetFloat32(TagIRWindowTransmission)); directory.Set(TagRelativeHumidity, reader2.GetFloat32(TagRelativeHumidity)); directory.Set(TagPlanckR1, reader2.GetFloat32(TagPlanckR1)); directory.Set(TagPlanckB, reader2.GetFloat32(TagPlanckB)); directory.Set(TagPlanckF, reader2.GetFloat32(TagPlanckF)); directory.Set(TagAtmosphericTransAlpha1, reader2.GetFloat32(TagAtmosphericTransAlpha1)); directory.Set(TagAtmosphericTransAlpha2, reader2.GetFloat32(TagAtmosphericTransAlpha2)); directory.Set(TagAtmosphericTransBeta1, reader2.GetFloat32(TagAtmosphericTransBeta1)); directory.Set(TagAtmosphericTransBeta2, reader2.GetFloat32(TagAtmosphericTransBeta2)); directory.Set(TagAtmosphericTransX, reader2.GetFloat32(TagAtmosphericTransX)); directory.Set(TagCameraTemperatureRangeMax, reader2.GetFloat32(TagCameraTemperatureRangeMax)); directory.Set(TagCameraTemperatureRangeMin, reader2.GetFloat32(TagCameraTemperatureRangeMin)); directory.Set(TagCameraTemperatureMaxClip, reader2.GetFloat32(TagCameraTemperatureMaxClip)); directory.Set(TagCameraTemperatureMinClip, reader2.GetFloat32(TagCameraTemperatureMinClip)); directory.Set(TagCameraTemperatureMaxWarn, reader2.GetFloat32(TagCameraTemperatureMaxWarn)); directory.Set(TagCameraTemperatureMinWarn, reader2.GetFloat32(TagCameraTemperatureMinWarn)); directory.Set(TagCameraTemperatureMaxSaturated, reader2.GetFloat32(TagCameraTemperatureMaxSaturated)); directory.Set(TagCameraTemperatureMinSaturated, reader2.GetFloat32(TagCameraTemperatureMinSaturated)); directory.Set(TagCameraModel, reader2.GetNullTerminatedStringValue(TagCameraModel, 32)); directory.Set(TagCameraPartNumber, reader2.GetNullTerminatedStringValue(TagCameraPartNumber, 16)); directory.Set(TagCameraSerialNumber, reader2.GetNullTerminatedStringValue(TagCameraSerialNumber, 16)); directory.Set(TagCameraSoftware, reader2.GetNullTerminatedStringValue(TagCameraSoftware, 16)); directory.Set(TagLensModel, reader2.GetNullTerminatedStringValue(TagLensModel, 32)); directory.Set(TagLensPartNumber, reader2.GetNullTerminatedStringValue(TagLensPartNumber, 16)); directory.Set(TagLensSerialNumber, reader2.GetNullTerminatedStringValue(TagLensSerialNumber, 16)); directory.Set(TagFieldOfView, reader2.GetFloat32(TagFieldOfView)); directory.Set(TagFilterModel, reader2.GetNullTerminatedStringValue(TagFilterModel, 16)); directory.Set(TagFilterPartNumber, reader2.GetNullTerminatedStringValue(TagFilterPartNumber, 32)); directory.Set(TagFilterSerialNumber, reader2.GetNullTerminatedStringValue(TagFilterSerialNumber, 32)); directory.Set(TagPlanckO, reader2.GetInt32(TagPlanckO)); directory.Set(TagPlanckR2, reader2.GetFloat32(TagPlanckR2)); directory.Set(TagRawValueRangeMin, reader2.GetUInt16(TagRawValueRangeMin)); directory.Set(TagRawValueRangeMax, reader2.GetUInt16(TagRawValueRangeMax)); directory.Set(TagRawValueMedian, reader2.GetUInt16(TagRawValueMedian)); directory.Set(TagRawValueRange, reader2.GetUInt16(TagRawValueRange)); var dateTimeBytes = reader2.GetBytes(TagDateTimeOriginal, 10); var dateTimeReader = new SequentialByteArrayReader(dateTimeBytes, isMotorolaByteOrder: false); var tm = dateTimeReader.GetUInt32(); var ss = dateTimeReader.GetUInt32() & 0xffff; var tz = dateTimeReader.GetInt16(); directory.Set(TagDateTimeOriginal, new DateTimeOffset(DateUtil.FromUnixTime(tm - tz * 60).AddSeconds(ss / 1000d), TimeSpan.FromMinutes(tz))); directory.Set(TagFocusStepCount, reader2.GetUInt16(TagFocusStepCount)); directory.Set(TagFocusDistance, reader2.GetFloat32(TagFocusDistance)); directory.Set(TagFrameRate, reader2.GetUInt16(TagFrameRate)); yield return(directory); } indexOffset += 32; } }
private static void ProcessReconyxMakernote([NotNull] ReconyxMakernoteDirectory directory, int makernoteOffset, [NotNull] IndexedReader reader) { directory.Set(ReconyxMakernoteDirectory.TagMakernoteVersion, reader.GetUInt16(makernoteOffset)); // revision and build are reversed from .NET ordering ushort major = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagFirmwareVersion); ushort minor = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagFirmwareVersion + 2); ushort revision = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagFirmwareVersion + 4); string buildYear = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagFirmwareVersion + 6).ToString("x4"); string buildDate = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagFirmwareVersion + 8).ToString("x4"); int build = int.Parse(buildYear + buildDate); directory.Set(ReconyxMakernoteDirectory.TagFirmwareVersion, new Version(major, minor, revision, build)); directory.Set(ReconyxMakernoteDirectory.TagTriggerMode, new string((char)reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagTriggerMode), 1)); directory.Set(ReconyxMakernoteDirectory.TagSequence, new[] { reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagSequence), reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagSequence + 2) }); uint eventNumberHigh = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagEventNumber); uint eventNumberLow = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagEventNumber + 2); directory.Set(ReconyxMakernoteDirectory.TagEventNumber, (eventNumberHigh << 16) + eventNumberLow); ushort seconds = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal); ushort minutes = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal + 2); ushort hour = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal + 4); ushort month = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal + 6); ushort day = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal + 8); ushort year = reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagDateTimeOriginal + 10); directory.Set(ReconyxMakernoteDirectory.TagDateTimeOriginal, new DateTime(year, month, day, hour, minutes, seconds)); directory.Set(ReconyxMakernoteDirectory.TagMoonPhase, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagMoonPhase)); directory.Set(ReconyxMakernoteDirectory.TagAmbientTemperatureFarenheit, reader.GetInt16(makernoteOffset + ReconyxMakernoteDirectory.TagAmbientTemperatureFarenheit)); directory.Set(ReconyxMakernoteDirectory.TagAmbientTemperature, reader.GetInt16(makernoteOffset + ReconyxMakernoteDirectory.TagAmbientTemperature)); directory.Set(ReconyxMakernoteDirectory.TagSerialNumber, reader.GetString(makernoteOffset + ReconyxMakernoteDirectory.TagSerialNumber, 28, Encoding.Unicode)); // two unread bytes: the serial number's terminating null directory.Set(ReconyxMakernoteDirectory.TagContrast, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagContrast)); directory.Set(ReconyxMakernoteDirectory.TagBrightness, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagBrightness)); directory.Set(ReconyxMakernoteDirectory.TagSharpness, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagSharpness)); directory.Set(ReconyxMakernoteDirectory.TagSaturation, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagSaturation)); directory.Set(ReconyxMakernoteDirectory.TagInfraredIlluminator, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagInfraredIlluminator)); directory.Set(ReconyxMakernoteDirectory.TagMotionSensitivity, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagMotionSensitivity)); directory.Set(ReconyxMakernoteDirectory.TagBatteryVoltage, reader.GetUInt16(makernoteOffset + ReconyxMakernoteDirectory.TagBatteryVoltage) / 1000.0); directory.Set(ReconyxMakernoteDirectory.TagUserLabel, reader.GetNullTerminatedString(makernoteOffset + ReconyxMakernoteDirectory.TagUserLabel, 44)); }
/// <exception cref="System.IO.IOException"/> private bool ProcessMakernote(int makernoteOffset, [NotNull] ICollection <int> processedIfdOffsets, [NotNull] IndexedReader reader) { Debug.Assert(makernoteOffset >= 0, "makernoteOffset >= 0"); var cameraMake = Directories.OfType <ExifIfd0Directory>().FirstOrDefault()?.GetString(ExifDirectoryBase.TagMake); var firstTwoChars = reader.GetString(makernoteOffset, 2, Encoding.UTF8); var firstThreeChars = reader.GetString(makernoteOffset, 3, Encoding.UTF8); var firstFourChars = reader.GetString(makernoteOffset, 4, Encoding.UTF8); var firstFiveChars = reader.GetString(makernoteOffset, 5, Encoding.UTF8); var firstSixChars = reader.GetString(makernoteOffset, 6, Encoding.UTF8); var firstSevenChars = reader.GetString(makernoteOffset, 7, Encoding.UTF8); var firstEightChars = reader.GetString(makernoteOffset, 8, Encoding.UTF8); var firstTenChars = reader.GetString(makernoteOffset, 10, Encoding.UTF8); var firstTwelveChars = reader.GetString(makernoteOffset, 12, Encoding.UTF8); if (string.Equals("OLYMP\0", firstSixChars, StringComparison.Ordinal) || string.Equals("EPSON", firstFiveChars, StringComparison.Ordinal) || string.Equals("AGFA", firstFourChars, StringComparison.Ordinal)) { // Olympus Makernote // Epson and Agfa use Olympus makernote standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/ PushDirectory(new OlympusMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); } else if (string.Equals("OLYMPUS\0II", firstTenChars, StringComparison.Ordinal)) { // Olympus Makernote (alternate) // Note that data is relative to the beginning of the makernote // http://exiv2.org/makernote.html PushDirectory(new OlympusMakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 12); } else if (cameraMake != null && cameraMake.StartsWith("MINOLTA", StringComparison.OrdinalIgnoreCase)) { // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote // area that commences immediately. PushDirectory(new OlympusMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if (cameraMake != null && cameraMake.TrimStart().StartsWith("NIKON", StringComparison.OrdinalIgnoreCase)) { if (string.Equals("Nikon", firstFiveChars, StringComparison.Ordinal)) { switch (reader.GetByte(makernoteOffset + 6)) { case 1: { /* There are two scenarios here: * Type 1: ** * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon........... * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................ * Type 3: ** * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*... * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200 */ PushDirectory(new NikonType1MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 8); break; } case 2: { PushDirectory(new NikonType2MakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset + 10), processedIfdOffsets, 8); break; } default: { Error("Unsupported Nikon makernote data ignored."); break; } } } else { // The IFD begins with the first Makernote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models. PushDirectory(new NikonType2MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } } else if (string.Equals("SONY CAM", firstEightChars, StringComparison.Ordinal) || string.Equals("SONY DSC", firstEightChars, StringComparison.Ordinal)) { PushDirectory(new SonyType1MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 12); } // Do this check LAST after most other Sony checks else if (cameraMake != null && cameraMake.StartsWith("SONY", StringComparison.Ordinal) && reader.GetBytes(makernoteOffset, 2) != new byte[] { 0x01, 0x00 }) { // The IFD begins with the first Makernote byte (no ASCII name). Used in SR2 and ARW images PushDirectory(new SonyType1MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if (string.Equals("SEMC MS\u0000\u0000\u0000\u0000\u0000", firstTwelveChars, StringComparison.Ordinal)) { // Force Motorola byte order for this directory // skip 12 byte header + 2 for "MM" + 6 PushDirectory(new SonyType6MakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithByteOrder(isMotorolaByteOrder: true), processedIfdOffsets, makernoteOffset + 20); } else if (string.Equals("SIGMA\u0000\u0000\u0000", firstEightChars, StringComparison.Ordinal) || string.Equals("FOVEON\u0000\u0000", firstEightChars, StringComparison.Ordinal)) { PushDirectory(new SigmaMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 10); } else if (string.Equals("KDK", firstThreeChars, StringComparison.Ordinal)) { var directory = new KodakMakernoteDirectory(); Directories.Add(directory); ProcessKodakMakernote(directory, makernoteOffset, reader.WithByteOrder(isMotorolaByteOrder: firstSevenChars == "KDK INFO")); } else if ("CANON".Equals(cameraMake, StringComparison.OrdinalIgnoreCase)) { PushDirectory(new CanonMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } else if (cameraMake != null && cameraMake.StartsWith("CASIO", StringComparison.OrdinalIgnoreCase)) { if (string.Equals("QVC\u0000\u0000\u0000", firstSixChars, StringComparison.Ordinal)) { PushDirectory(new CasioType2MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 6); } else { PushDirectory(new CasioType1MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } } else if (string.Equals("FUJIFILM", firstEightChars, StringComparison.Ordinal) || string.Equals("FUJIFILM", cameraMake, StringComparison.OrdinalIgnoreCase)) { // Note that this also applies to certain Leica cameras, such as the Digilux-4.3. // The 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote // IFD, though the offset is relative to the start of the makernote, not the TIFF header var makernoteReader = reader.WithShiftedBaseOffset(makernoteOffset).WithByteOrder(isMotorolaByteOrder: false); var ifdStart = makernoteReader.GetInt32(8); PushDirectory(new FujifilmMakernoteDirectory()); TiffReader.ProcessIfd(this, makernoteReader, processedIfdOffsets, ifdStart); } else if (string.Equals("KYOCERA", firstSevenChars, StringComparison.Ordinal)) { // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html PushDirectory(new KyoceraMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 22); } else if (string.Equals("LEICA", firstFiveChars, StringComparison.Ordinal)) { // used by the X1/X2/X VARIO/T // (X1 starts with "LEICA\0\x01\0", Make is "LEICA CAMERA AG") // (X2 starts with "LEICA\0\x05\0", Make is "LEICA CAMERA AG") // (X VARIO starts with "LEICA\0\x04\0", Make is "LEICA CAMERA AG") // (T (Typ 701) starts with "LEICA\0\0x6", Make is "LEICA CAMERA AG") // (X (Typ 113) starts with "LEICA\0\0x7", Make is "LEICA CAMERA AG") if (string.Equals("LEICA\0\x1\0", firstEightChars, StringComparison.Ordinal) || string.Equals("LEICA\0\x4\0", firstEightChars, StringComparison.Ordinal) || string.Equals("LEICA\0\x5\0", firstEightChars, StringComparison.Ordinal) || string.Equals("LEICA\0\x6\0", firstEightChars, StringComparison.Ordinal) || string.Equals("LEICA\0\x7\0", firstEightChars, StringComparison.Ordinal)) { PushDirectory(new LeicaType5MakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } else if (string.Equals("Leica Camera AG", cameraMake, StringComparison.Ordinal)) { PushDirectory(new LeicaMakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithByteOrder(isMotorolaByteOrder: false), processedIfdOffsets, makernoteOffset + 8); } else if (string.Equals("LEICA", cameraMake, StringComparison.Ordinal)) { // Some Leica cameras use Panasonic makernote tags PushDirectory(new PanasonicMakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithByteOrder(isMotorolaByteOrder: false), processedIfdOffsets, makernoteOffset + 8); } else { return(false); } } else if (string.Equals("Panasonic\u0000\u0000\u0000", firstTwelveChars, StringComparison.Ordinal)) { // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html PushDirectory(new PanasonicMakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset + 12); } else if (string.Equals("AOC\u0000", firstFourChars, StringComparison.Ordinal)) { // NON-Standard TIFF IFD Data using Casio Type 2 Tags // IFD has no Next-IFD pointer at end of IFD, and // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - Pentax ist D PushDirectory(new CasioType2MakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 6); } else if (cameraMake != null && (cameraMake.StartsWith("PENTAX", StringComparison.OrdinalIgnoreCase) || cameraMake.StartsWith("ASAHI", StringComparison.OrdinalIgnoreCase))) { // NON-Standard TIFF IFD Data using Pentax Tags // IFD has no Next-IFD pointer at end of IFD, and // Offsets are relative to the start of the current IFD tag, not the TIFF header // Observed for: // - PENTAX Optio 330 // - PENTAX Optio 430 PushDirectory(new PentaxMakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 0); } // else if ("KC" == firstTwoChars || "MINOL" == firstFiveChars || "MLY" == firstThreeChars || "+M+M+M+M" == firstEightChars) // { // // This Konica data is not understood. Header identified in accordance with information at this site: // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html // // TODO add support for minolta/konica cameras // exifDirectory.addError("Unsupported Konica/Minolta data ignored."); // } else if (string.Equals("SANYO\x0\x1\x0", firstEightChars, StringComparison.Ordinal)) { PushDirectory(new SanyoMakernoteDirectory()); TiffReader.ProcessIfd(this, reader.WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } else if (cameraMake != null && cameraMake.StartsWith("RICOH", StringComparison.OrdinalIgnoreCase)) { if (string.Equals(firstTwoChars, "Rv", StringComparison.Ordinal) || string.Equals(firstThreeChars, "Rev", StringComparison.Ordinal)) { // This is a textual format, where the makernote bytes look like: // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2E00:������������������������������ // Rv0103;Rg1C;Bg18;Ll0;Ld0;Aj0000;Bn0473800;Fp2D05:������������������������������ // Rv0207;Sf6C84;Rg76;Bg60;Gg42;Ll0;Ld0;Aj0004;Bn0B02900;Fp10B8;Md6700;Ln116900086D27;Sv263:0000000000000000000000�� // This format is currently unsupported return(false); } if (firstFiveChars.Equals("RICOH", StringComparison.OrdinalIgnoreCase)) { PushDirectory(new RicohMakernoteDirectory()); // Always in Motorola byte order TiffReader.ProcessIfd(this, reader.WithByteOrder(isMotorolaByteOrder: true).WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 8); } } else if (string.Equals(firstTenChars, "Apple iOS\0", StringComparison.Ordinal)) { PushDirectory(new AppleMakernoteDirectory()); // Always in Motorola byte order TiffReader.ProcessIfd(this, reader.WithByteOrder(isMotorolaByteOrder: true).WithShiftedBaseOffset(makernoteOffset), processedIfdOffsets, 14); } else if (string.Equals("RECONYX", cameraMake, StringComparison.OrdinalIgnoreCase) || reader.GetUInt16(makernoteOffset) == ReconyxMakernoteDirectory.HyperFireMakernoteVersion) { var directory = new ReconyxMakernoteDirectory(); Directories.Add(directory); ProcessReconyxMakernote(directory, makernoteOffset, reader); } else if (string.Equals("SAMSUNG", cameraMake, StringComparison.Ordinal)) { // Only handles Type2 notes correctly. Others aren't implemented, and it's complex to determine which ones to use PushDirectory(new SamsungType2MakernoteDirectory()); TiffReader.ProcessIfd(this, reader, processedIfdOffsets, makernoteOffset); } else { // The makernote is not comprehended by this library. // If you are reading this and believe a particular camera's image should be processed, get in touch. return(false); } return(true); }
/// <summary>Processes a TIFF IFD.</summary> /// <remarks> /// IFD Header: /// <list type="bullet"> /// <item><b>2 bytes</b> number of tags</item> /// </list> /// Tag structure: /// <list type="bullet"> /// <item><b>2 bytes</b> tag type</item> /// <item><b>2 bytes</b> format code (values 1 to 12, inclusive)</item> /// <item><b>4 bytes</b> component count</item> /// <item><b>4 bytes</b> inline value, or offset pointer if too large to fit in four bytes</item> /// </list> /// </remarks> /// <param name="handler">the <see cref="ITiffHandler"/> that will coordinate processing and accept read values</param> /// <param name="reader">the <see cref="IndexedReader"/> from which the data should be read</param> /// <param name="processedIfdOffsets">the set of visited IFD offsets, to avoid revisiting the same IFD in an endless loop</param> /// <param name="ifdOffset">the offset within <c>reader</c> at which the IFD data starts</param> /// <param name="tiffHeaderOffset">the offset within <c>reader</c> at which the TIFF header starts</param> /// <exception cref="System.IO.IOException">an error occurred while accessing the required data</exception> public static void ProcessIfd([NotNull] ITiffHandler handler, [NotNull] IndexedReader reader, [NotNull] ICollection <int> processedIfdOffsets, int ifdOffset, int tiffHeaderOffset) { try { // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist if (processedIfdOffsets.Contains(ifdOffset)) { return; } // remember that we've visited this directory so that we don't visit it again later processedIfdOffsets.Add(ifdOffset); if (ifdOffset >= reader.Length || ifdOffset < 0) { handler.Error("Ignored IFD marked to start outside data segment"); return; } // First two bytes in the IFD are the number of tags in this directory int dirTagCount = reader.GetUInt16(ifdOffset); var dirLength = (2 + (12 * dirTagCount) + 4); if (dirLength + ifdOffset > reader.Length) { handler.Error("Illegally sized IFD"); return; } // // Handle each tag in this directory // var invalidTiffFormatCodeCount = 0; for (var tagNumber = 0; tagNumber < dirTagCount; tagNumber++) { var tagOffset = CalculateTagOffset(ifdOffset, tagNumber); // 2 bytes for the tag id int tagId = reader.GetUInt16(tagOffset); // 2 bytes for the format code var formatCode = (TiffDataFormatCode)reader.GetUInt16(tagOffset + 2); var format = TiffDataFormat.FromTiffFormatCode(formatCode); if (format == null) { // 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: " + formatCode); // 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; } // 4 bytes dictate the number of components in this tag's data var componentCount = reader.GetInt32(tagOffset + 4); if (componentCount < 0) { handler.Error("Negative TIFF tag component count"); continue; } var byteCount = componentCount * format.ComponentSizeBytes; int tagValueOffset; if (byteCount > 4) { // If it's bigger than 4 bytes, the dir entry contains an offset. var offsetVal = reader.GetInt32(tagOffset + 8); if (offsetVal + byteCount > reader.Length) { // Bogus pointer offset and / or byteCount value handler.Error("Illegal TIFF tag pointer offset"); continue; } tagValueOffset = tiffHeaderOffset + offsetVal; } else { // 4 bytes or less and value is in the dir entry itself. tagValueOffset = tagOffset + 8; } if (tagValueOffset < 0 || tagValueOffset > reader.Length) { handler.Error("Illegal TIFF tag pointer offset"); continue; } // Check that this tag isn't going to allocate outside the bounds of the data array. // This addresses an uncommon OutOfMemoryError. if (byteCount < 0 || tagValueOffset + byteCount > reader.Length) { handler.Error("Illegal number of bytes for TIFF tag data: " + byteCount); continue; } // // Special handling for tags that point to other IFDs // if (byteCount == 4 && handler.IsTagIfdPointer(tagId)) { var subDirOffset = tiffHeaderOffset + reader.GetInt32(tagValueOffset); ProcessIfd(handler, reader, processedIfdOffsets, subDirOffset, tiffHeaderOffset); } else if (!handler.CustomProcessTag(tagValueOffset, processedIfdOffsets, tiffHeaderOffset, reader, tagId, byteCount)) { ProcessTag(handler, tagId, tagValueOffset, componentCount, formatCode, reader); } } // at the end of each IFD is an optional link to the next IFD var finalTagOffset = CalculateTagOffset(ifdOffset, dirTagCount); var nextIfdOffset = reader.GetInt32(finalTagOffset); if (nextIfdOffset != 0) { nextIfdOffset += tiffHeaderOffset; if (nextIfdOffset >= reader.Length) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds // Note this could have been caused by jhead 1.3 cropping too much 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, reader, processedIfdOffsets, nextIfdOffset, tiffHeaderOffset); } } } finally { handler.EndingIfd(); } }