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;
                    }
                }
            }
        }
Esempio n. 2
0
        /// <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);
        }
Esempio n. 3
0
        /// <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);
        }
Esempio n. 4
0
        /// <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}");
            }
        }
Esempio n. 6
0
        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);
            }
        }
Esempio n. 7
0
        /// <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();
            }
        }
Esempio n. 8
0
        /// <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);
        }
Esempio n. 12
0
        /// <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();
            }
        }