Unity 读写JPG图像元数据 工具类
  ZcJgkb1g5qMe 2023年11月02日 36 0

源地址: https://www.codeproject.com/Articles/5251929/CompactExifLib-Access-to-EXIF-Tags-in-JPEG-TIFF-an

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CompactExifLib
{
    public class ExifData
    {
        #region Public area

        public ImageType ImageType;
        public int MakerNoteOriginalOffset { get; private set; }
        public const int IfdShift = 16;


        // Load EXIF data from a JPEG or TIFF file.
        // An exception occurs in the following situations:
        //  •The file does not exist.
        //  •The access to the file is denied.
        //  •The file is not a valid JPEG or TIFF file.
        //
        // If the file is a valid JPEG file but without an EXIF block, an empty EXIF block is created.
        public ExifData(string FileNameWithPath, ExifLoadOptions Options = 0) : this()
        {
            _FileNameWithPath = Path.GetFullPath(FileNameWithPath);
            using (FileStream ImageFile = File.OpenRead(_FileNameWithPath))
            {
                ReadFromStream(ImageFile, Options);
            }
        }


        public ExifData(Stream ImageStream, ExifLoadOptions Options = 0) : this()
        {
            ReadFromStream(ImageStream, Options);
        }


        public static ExifData Empty()
        {
            ExifData EmptyBlock = new ExifData();
            EmptyBlock.SetEmptyExifBlock();
            return (EmptyBlock);
        }


        // Save the EXIF data in a new file or overwrite the current file.
        //
        // Parameters:
        //  "DestFileNameWithPath": File name for saving the image and the EXIF data. If this parameter is "null",
        //                          the EXIF data is replaced in the current file.
        public void Save(string DestFileNameWithPath = null, ExifSaveOptions SaveOptions = 0)
        {
            bool DestFileIsTempFile = false;
            string SourceFileNameWithPath = _FileNameWithPath;
            if (DestFileNameWithPath != null)
            {
                DestFileNameWithPath = Path.GetFullPath(DestFileNameWithPath);
            }
            if ((DestFileNameWithPath == null) ||
                (String.Compare(SourceFileNameWithPath, DestFileNameWithPath, StringComparison.OrdinalIgnoreCase) == 0))
            {
                int BackslashPosition = SourceFileNameWithPath.LastIndexOf('\\');
                int DotPosition = SourceFileNameWithPath.LastIndexOf('.');
                if (DotPosition <= BackslashPosition) DotPosition = SourceFileNameWithPath.Length;
                DestFileNameWithPath = SourceFileNameWithPath.Insert(DotPosition, "~");
                DestFileIsTempFile = true;
            }

            bool DestFileCreated = false;
            FileStream SourceFile = null, DestFile = null;
            try
            {
                SourceFile = File.OpenRead(SourceFileNameWithPath);
                DestFile = File.Open(DestFileNameWithPath, FileMode.Create);
                DestFileCreated = true;
                Save(SourceFile, DestFile, SaveOptions);
                SourceFile.Dispose();
                SourceFile = null;
                DestFile.Dispose();
                DestFile = null;
                if (DestFileIsTempFile)
                {
                    File.Delete(SourceFileNameWithPath); // Delete original image file. If the original file cannot be deleted, delete
                                                         // the temporary file within the "catch" block.
                    File.Move(DestFileNameWithPath, SourceFileNameWithPath);
                }
            }
            catch
            {
                if (SourceFile != null)
                {
                    SourceFile.Dispose();
                }
                if (DestFile != null)
                {
                    DestFile.Dispose();
                }
                if (DestFileCreated)
                {
                    File.Delete(DestFileNameWithPath);
                }
                throw;
            }
        }


        public void Save(Stream SourceStream, Stream DestStream, ExifSaveOptions SaveOptions = 0)
        {
            ImageType SourceImageType = CheckStreamTypeAndCompatibility(SourceStream);
            if (SourceImageType != ImageType)
            {
                throw new ExifException(ExifErrCode.ImageTypesDoNotMatch);
            }
            if (SourceImageType == ImageType.Jpeg)
            {
                SaveJpeg(SourceStream, DestStream);
            }
            else if (SourceImageType == ImageType.Tiff)
            {
                SaveTiff(SourceStream, DestStream);
            }
            else if (SourceImageType == ImageType.Png)
            {
                SavePng(SourceStream, DestStream);
            }
        }


        // Read a string from a tag.
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "Value":      Return: Tag data as a string.
        //                In case of an error "null" is given back.
        //  "Coding":     Tag type and code page in which the string is coded.
        //  Return value: true  = The tag was successfully read.
        //                false = The tag was not found or is of wrong type.
        public bool GetTagValue(ExifTag TagSpec, out string Value, StrCoding Coding)
        {
            TagItem t;
            bool Success = false;
            int i, j;

            StrCodingFormat StringTagFormat = (StrCodingFormat)((uint)Coding & 0xFFFF0000);
            ushort CodePage = (ushort)Coding;
            if (StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode)
            {
                Success = GetTagValueWithIdCode(TagSpec, out Value, CodePage);
            }
            else
            {
                Value = null;
                if (GetTagItem(TagSpec, out t))
                {
                    i = t.ValueCount;
                    j = t.ValueIndex;

                    if ((CodePage == 1200) || (CodePage == 1201)) // UTF16LE or UTF16BE
                    {
                        // Remove all null terminating characters. Here a null terminating character consists of 2 zero-bytes.
                        while ((i >= 2) && (t.ValueData[j + i - 2] == 0) && (t.ValueData[j + i - 1] == 0))
                        {
                            i -= 2;
                        }
                    }
                    else
                    {
                        // Remove all null terminating characters.
                        while ((i >= 1) && (t.ValueData[j + i - 1] == 0))
                        {
                            i--;
                        }
                    }
                    if (CodePage != 0)
                    {
                        if (((StringTagFormat == StrCodingFormat.TypeAscii) && (t.TagType == ExifTagType.Ascii)) ||
                            ((StringTagFormat == StrCodingFormat.TypeUndefined) && (t.TagType == ExifTagType.Undefined)) ||
                            ((StringTagFormat == StrCodingFormat.TypeByte) && (t.TagType == ExifTagType.Byte)))
                        {
                            Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
                            Success = true;
                        }
                    }
                }
            }
            return (Success);
        }


        // Write a string to a tag.
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "Value":      String to be written.
        //  "Coding":     Tag type and code page in which the string should be coded.
        //  Return value: true  = The tag was successfully written.
        //                false = Error. Tag ID is not allowed to be written or IFD is invalid.
        public bool SetTagValue(ExifTag TagSpec, string Value, StrCoding Coding)
        {
            int TotalByteCount, StrByteLen, NullTermBytes;
            TagItem t;
            bool Success = false;
            byte[] StringAsByteArray;
            ExifTagType TagType;

            StrCodingFormat StringTagFormat = (StrCodingFormat)((uint)Coding & 0xFFFF0000);
            ushort CodePage = (ushort)Coding;
            if (StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode)
            {
                Success = SetTagValueWithIdCode(TagSpec, Value, CodePage);
            }
            else
            {
                if (StringTagFormat == StrCodingFormat.TypeUndefined)
                {
                    TagType = ExifTagType.Undefined;
                }
                else if (StringTagFormat == StrCodingFormat.TypeByte)
                {
                    TagType = ExifTagType.Byte;
                }
                else
                {
                    TagType = ExifTagType.Ascii;
                }

                NullTermBytes = 0;
                if (TagType != ExifTagType.Undefined)
                {
                    if ((CodePage == 1200) || (CodePage == 1201)) // UTF16LE or UTF16BE
                    {
                        NullTermBytes = 2; // Write null terminating character with 2 zero-bytes
                    }
                    else
                    {
                        NullTermBytes = 1; // Write null terminating character with 1 zero-byte
                    }
                }

                if (CodePage != 0)
                {
                    StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
                    StrByteLen = StringAsByteArray.Length;
                    TotalByteCount = StrByteLen + NullTermBytes;
                    t = PrepareTagForCompleteWriting(TagSpec, TagType, TotalByteCount);
                    if (t != null)
                    {
                        Array.Copy(StringAsByteArray, 0, t.ValueData, t.ValueIndex, StrByteLen);
                        if (NullTermBytes >= 1) t.ValueData[t.ValueIndex + StrByteLen] = 0;
                        if (NullTermBytes >= 2) t.ValueData[t.ValueIndex + StrByteLen + 1] = 0;
                        Success = true;
                    }
                }
            }
            return (Success);
        }


        // Read a 32 bit signed integer number from a tag. The tag type must be one of the following values:
        //  ExifTagType.Byte, ExifTagType.UShort, ExifTagType.ULong, ExifTagType.SLong.
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "Value":      Return: 32 bit integer number with sign. If the tag type is "ExifTagType.ULong" and
        //                the tag contains a number from 0x80000000 to 0xFFFFFFFF, it is treated as an error.
        //                In case of an error, "Value" is 0.
        //  "Index":      Array index of the value to be read.
        //  Return value: true  = The tag was successfully read.
        //                false = The tag was not found, is of wrong type or is out of range.
        public bool GetTagValue(ExifTag TagSpec, out int Value, int Index = 0)
        {
            TagItem t;
            bool Success = false;
            uint TempValue = 0;

            if (GetTagItem(TagSpec, out t) && ReadUintElement(t, Index, out TempValue))
            {
                Success = ((t.TagType != ExifTagType.ULong) || ((int)TempValue >= 0));
                if (!Success) TempValue = 0;
            }
            Value = (int)TempValue;
            return (Success);
        }


        // Write a 32 bit signed integer number to a tag.
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "TagType":    Tag type: ExifTagType.Byte, ExifTagType.UShort, ExifTagType.ULong or
        //                ExifTagType.SLong
        //  "Value":      32 bit integer number with sign.
        //  "Index":      Array index of the value to be written.
        //  Return value: true  = The tag was successfully written.
        //                false = The tag specification is invalid or "Value" is out of range.
        public bool SetTagValue(ExifTag TagSpec, int Value, ExifTagType TagType, int Index = 0)
        {
            TagItem t;
            bool Success = false;

            switch (TagType)
            {
                case ExifTagType.Byte:
                    if ((Value >= 0) && (Value <= 255)) Success = true;
                    break;
                case ExifTagType.UShort:
                    if ((Value >= 0) && (Value <= 65535)) Success = true;
                    break;
                case ExifTagType.ULong:
                    if (Value >= 0) Success = true;
                    break;
                case ExifTagType.SLong:
                    Success = true;
                    break;
            }
            if (Success)
            {
                t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
                Success = WriteUintElement(t, Index, (uint)Value);
            }
            return (Success);
        }


        public bool GetTagValue(ExifTag TagSpec, out uint Value, int Index = 0)
        {
            TagItem t;
            bool Success = false;
            uint TempValue = 0;

            if (GetTagItem(TagSpec, out t) && ReadUintElement(t, Index, out TempValue))
            {
                Success = ((t.TagType != ExifTagType.SLong) || ((int)TempValue >= 0));
                if (!Success) TempValue = 0;
            }
            Value = TempValue;
            return (Success);
        }


        public bool SetTagValue(ExifTag TagSpec, uint Value, ExifTagType TagType, int Index = 0)
        {
            TagItem t;
            bool Success = false;

            switch (TagType)
            {
                case ExifTagType.Byte:
                    if ((Value >= 0) && (Value <= 255)) Success = true;
                    break;
                case ExifTagType.UShort:
                    if ((Value >= 0) && (Value <= 65535)) Success = true;
                    break;
                case ExifTagType.ULong:
                    Success = true;
                    break;
                case ExifTagType.SLong:
                    if ((int)Value >= 0) Success = true;
                    break;
            }
            if (Success)
            {
                t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
                Success = WriteUintElement(t, Index, (uint)Value);
            }
            return (Success);
        }


        public bool GetTagValue(ExifTag TagSpec, out ExifRational Value, int Index = 0)
        {
            TagItem t;
            bool Success = false;
            uint Numer, Denom;

            if (GetTagItem(TagSpec, out t) && ReadURatElement(t, Index, out Numer, out Denom) == true)
            {
                if (t.TagType == ExifTagType.URational)
                {
                    Value = new ExifRational(Numer, Denom);
                }
                else // ExifTagType.SRational
                {
                    Value = new ExifRational((int)Numer, (int)Denom);
                }
                Success = true;
            }
            else
            {
                Value = new ExifRational(0, 0);
            }
            return (Success);
        }


        public bool SetTagValue(ExifTag TagSpec, ExifRational Value, ExifTagType TagType, int Index = 0)
        {
            TagItem t;
            bool Success = false;

            switch (TagType)
            {
                case ExifTagType.SRational:
                    if ((Value.Numer < 0x80000000) && (Value.Denom < 0x80000000))
                    {
                        if (Value.Sign)
                        {
                            Value.Numer = (uint)(-(int)Value.Numer);
                        }
                        Success = true;
                    }
                    else Success = false;
                    break;
                case ExifTagType.URational:
                    Success = !Value.IsNegative();
                    break;
            }
            if (Success)
            {
                t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
                Success = WriteURatElement(t, Index, Value.Numer, Value.Denom);
            }
            return (Success);
        }


        // Read time stamp from a tag. The tag type must be "ExifTagType.Ascii" and the tag
        // must contain a null terminated string with 19 characters in the
        // format "yyyy:MM:dd HH:mm:ss" or with 10 characters in the format "yyyy:MM:dd".
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "Value":      Return: Date time stamp. In case of an error, the value "DateTime.MinValue" is returned.
        //  "Format"      Selection between time stamp format with 19 or 10 characters.
        //  Return value: true  = The tag was successfully read.
        //                false = The tag was not found, is of wrong type or wrong format.
        public bool GetTagValue(ExifTag TagSpec, out DateTime Value, ExifDateFormat Format = ExifDateFormat.DateAndTime)
        {
            TagItem t;
            bool Success;
            int i, Year, Month, Day, Hour, Minute, Second;

            Success = false;
            Value = DateTime.MinValue;
            if (GetTagItem(TagSpec, out t) && (t.TagType == ExifTagType.Ascii))
            {
                try
                {
                    i = t.ValueIndex;
                    if ((t.ValueCount >= 10) && (t.ValueData[i + 4] == (byte)':') && (t.ValueData[i + 7] == (byte)':'))
                    {
                        Year = CalculateTwoDigitDecNumber(t.ValueData, i) * 100 + CalculateTwoDigitDecNumber(t.ValueData, i + 2);
                        Month = CalculateTwoDigitDecNumber(t.ValueData, i + 5);
                        Day = CalculateTwoDigitDecNumber(t.ValueData, i + 8);
                        if ((Format == ExifDateFormat.DateAndTime) && (t.ValueCount == 19 + 1) && (t.ValueData[i + 10] == (byte)' ') &&
                            (t.ValueData[i + 13] == (byte)':') && (t.ValueData[i + 16] == (byte)':') && (t.ValueData[i + 19] == 0))
                        {
                            Hour = CalculateTwoDigitDecNumber(t.ValueData, i + 11);
                            Minute = CalculateTwoDigitDecNumber(t.ValueData, i + 14);
                            Second = CalculateTwoDigitDecNumber(t.ValueData, i + 17);
                            Value = new DateTime(Year, Month, Day, Hour, Minute, Second);
                            Success = true;
                        }
                        else if ((Format == ExifDateFormat.DateOnly) && (t.ValueCount == 10 + 1) && (t.ValueData[i + 10] == 0))
                        {
                            Value = new DateTime(Year, Month, Day);
                            Success = true;
                        }
                    }
                }
                catch
                {
                    // Invalid time stamp
                }
            }
            return (Success);
        }


        // Write a time stamp to a tag. The tag type is set to "ExifTagType.Ascii" and the time stammp
        // is written in the format "yyyy:MM:dd HH:mm:ss" or "yyyy:MM:dd".
        //
        // Parameters:
        //  "TagSpec":    Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "Value":      Date time stamp.
        //  "Format"      Selection between time stamp format with 19 or 10 characters.
        //  Return value: true  = The tag was successfully written.
        //                false = Error.
        public bool SetTagValue(ExifTag TagSpec, DateTime Value, ExifDateFormat Format = ExifDateFormat.DateAndTime)
        {
            TagItem t;
            bool Success = false;
            int i, sByteCount = 0, Year;

            if (Format == ExifDateFormat.DateAndTime)
            {
                sByteCount = 19 + 1; // "+ 1" because a null character has to be written
            }
            else if (Format == ExifDateFormat.DateOnly)
            {
                sByteCount = 10 + 1;
            }
            if (sByteCount != 0)
            {
                t = PrepareTagForCompleteWriting(TagSpec, ExifTagType.Ascii, sByteCount);
                if (t != null)
                {
                    i = t.ValueIndex;
                    Year = Value.Year;
                    ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Year / 100);
                    ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Year % 100);
                    t.ValueData[i] = (byte)':'; i++;
                    ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Value.Month);
                    t.ValueData[i] = (byte)':'; i++;
                    ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Value.Day);
                    if (Format == ExifDateFormat.DateAndTime)
                    {
                        t.ValueData[i] = (byte)' '; i++;
                        ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Value.Hour);
                        t.ValueData[i] = (byte)':'; i++;
                        ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Value.Minute);
                        t.ValueData[i] = (byte)':'; i++;
                        ConvertTwoDigitNumberToByteArr(t.ValueData, ref i, Value.Second);
                    }
                    t.ValueData[i] = 0; i++;
                    Success = true;
                }
            }
            return (Success);
        }


        // Read the tag data as raw data without copying the raw data.
        //
        // Parameters:
        //  "TagSpec":          Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "TagType":          Return: Type of the tag.
        //  "ValueCount":       Return: Number of values which are stored in the tag.
        //  "RawData" :         Return: Array with the tag data, but there may also be other data stored in this array.
        //                      There are the following restrictions:
        //                      *The data returned in the array "RawData" must not be changed by the caller of this method.
        //                       "RawData" should only be used for reading data!
        //                      *After new data have been written into the specified tag, the array "RawData" must not be used any
        //                       more. Therefore the data returned in "RawData" should be copied as soon as possible.
        //                      *If the tag does not exist, "RawData" is null.
        //  "RawDataIndex" :    Return: Array index at which the data starts. The first data byte is stored in
        //                      "RawData[RawDataIndex]" and the last data byte is stored in
        //                      "RawData[RawDataIndex + GetTagByteCount(TagType, ValueCount) - 1]".
        //  Return value:       true  = The tag was successfully read.
        //                      false = The tag was not found.
        public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount, out byte[] RawData,
          out int RawDataIndex)
        {
            bool Success = false;

            if (GetTagItem(TagSpec, out TagItem t))
            {
                TagType = t.TagType;
                ValueCount = t.ValueCount;
                RawData = t.ValueData;
                RawDataIndex = t.ValueIndex;
                Success = true;
            }
            else
            {
                TagType = 0;
                ValueCount = 0;
                RawData = null;
                RawDataIndex = 0;
            }
            return (Success);
        }


        // Read the tag data as raw data with copying the raw data.
        //
        // Parameters:
        //  "TagSpec":          Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "TagType":          Return: Type of the tag.
        //  "ValueCount":       Return: Number of values which are stored in the tag.
        //  "RawData" :         Return: Array with the tag data. If the tag does not exist, "RawData" is null.
        //  Return value:       true  = The tag was successfully read.
        //                      false = The tag was not found.
        public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount, out byte[] RawData)
        {
            bool Success = false;

            if (GetTagItem(TagSpec, out TagItem t))
            {
                TagType = t.TagType;
                ValueCount = t.ValueCount;
                int k = t.ByteCount;
                RawData = new byte[k];
                Array.Copy(t.ValueData, t.ValueIndex, RawData, 0, k);
                Success = true;
            }
            else
            {
                TagType = 0;
                ValueCount = 0;
                RawData = null;
            }
            return (Success);
        }


        // Write the tag data as raw data.
        //
        // Parameters:
        //  "TagSpec":      Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  "TagType":      Type of the tag.
        //  "ValueCount":   Number of values which are stored in the tag. This is not the number of raw data bytes!
        //                  The number of raw data bytes is automatically calculated by the parameters "TagType" and
        //                  "ValueCount".
        //  "RawData":      Array with the tag data. The tag data is not copied by this method, therefore
        //                  the array content must not be changed after the call of this method.
        //  "RawDataIndex": Array index at which the data starts. Set this parameter to 0, if the data starts with
        //                  the first element of the array "RawData".
        //  Return value:   true  = The tag was successfully set.
        //                  false = The IFD is invalid.
        public bool SetTagRawData(ExifTag TagSpec, ExifTagType TagType, int ValueCount, byte[] RawData, int RawDataIndex = 0)
        {
            TagItem t;
            bool Success = false;

            ExifIfd Ifd = ExtractIfd(TagSpec);
            if ((uint)Ifd < ExifIfdCount)
            {
                int RawDataByteCount = GetTagByteCount(TagType, ValueCount);
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                if (IfdTagTable.TryGetValue(ExtractTagId(TagSpec), out t))
                {
                    t.TagType = TagType;
                    t.ValueCount = ValueCount;
                    t.ValueData = RawData;
                    t.ValueIndex = RawDataIndex;
                    t.AllocatedByteCount = RawDataByteCount;
                }
                else
                {
                    t = new TagItem(ExtractTagId(TagSpec), TagType, ValueCount, RawData, RawDataIndex, RawDataByteCount);
                    IfdTagTable.Add(t.TagId, t);
                }
                Success = true;
            }
            return (Success);
        }


        public bool GetTagValueCount(ExifTag TagSpec, out int ValueCount)
        {
            if (GetTagItem(TagSpec, out TagItem t))
            {
                ValueCount = t.ValueCount;
                return (true);
            }
            else
            {
                ValueCount = 0;
                return (false);
            }
        }


        public bool SetTagValueCount(ExifTag TagSpec, int ValueCount)
        {
            if (GetTagItem(TagSpec, out TagItem t) && ((uint)ValueCount <= MaxTagValueCount))
            {
                t.SetTagTypeAndValueCount(t.TagType, ValueCount, true);
                return (true);
            }
            else return (false);
        }


        public bool SetTagValueCount(ExifTag TagSpec, int ValueCount, ExifTagType TagType)
        {
            TagItem t = PrepareTagForArrayItemWriting(TagSpec, TagType, ValueCount - 1);
            return (t != null);
        }


        public bool GetTagType(ExifTag TagSpec, out ExifTagType TagType)
        {
            if (GetTagItem(TagSpec, out TagItem t))
            {
                TagType = t.TagType;
                return (true);
            }
            else
            {
                TagType = 0;
                return (false);
            }
        }


        public bool TagExists(ExifTag TagSpec)
        {
            ExifIfd Ifd = ExtractIfd(TagSpec);
            if ((uint)Ifd < ExifIfdCount)
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                return (IfdTagTable.ContainsKey(ExtractTagId(TagSpec)));
            }
            else return (false);
        }


        public bool IfdExists(ExifIfd Ifd)
        {
            if ((uint)Ifd < ExifIfdCount)
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                return (IfdTagTable.Count > 0);
            }
            else return (false);
        }


        public bool ImageFileBlockExists(ImageFileBlock BlockType)
        {
            return (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Existent);
        }


        // Remove a single tag.
        //
        // Parameters:
        //  "TagSpec":      Tag specification, consisting of the IFD in which the tag is stored and the tag ID.
        //  Return value:   false = Tag was not removed, because it doesn't exist.
        //                  true  = Tag was removed.
        public bool RemoveTag(ExifTag TagSpec)
        {
            ExifIfd Ifd = ExtractIfd(TagSpec);
            if ((uint)Ifd < ExifIfdCount)
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                return (IfdTagTable.Remove(ExtractTagId(TagSpec)));
            }
            else return (false);
        }


        // Remove all tags from a specific IFD.
        //
        // Parameters:
        //  "ExifIfd": IFD from which all tags are to be removed. If the IFD "ThumbnailData" is removed, the thumbnail image
        //             is removed, too.
        public bool RemoveAllTagsFromIfd(ExifIfd Ifd)
        {
            bool Removed = false;

            if (Ifd == ExifIfd.PrimaryData)
            {
                RemoveAllTagsFromIfdPrimaryData();
                Removed = true;
            }
            else if ((uint)Ifd < ExifIfdCount)
            {
                ClearIfd_Unchecked(Ifd);
                if (Ifd == ExifIfd.ThumbnailData)
                {
                    RemoveThumbnailImage(false);
                }
                Removed = true;
            }
            return (Removed);
        }


        // Remove all tags from the EXIF block and remove the thumbnail image.
        public void RemoveAllTags()
        {
            RemoveAllTagsFromIfdPrimaryData();
            for (ExifIfd Ifd = ExifIfd.PrivateData; Ifd < (ExifIfd)ExifIfdCount; Ifd++)
            {
                ClearIfd_Unchecked(Ifd);
            }
            RemoveThumbnailImage(false);
            ImageFileBlockInfo[(int)ImageFileBlock.Exif] = ImageFileBlockState.Removed;
        }


        public void RemoveImageFileBlock(ImageFileBlock BlockType)
        {
            if (BlockType == ImageFileBlock.Exif)
            {
                RemoveAllTags();
            }
            else if (BlockType != ImageFileBlock.Unknown)
            {
                if (ImageType == ImageType.Tiff)
                {
                    if (BlockType == ImageFileBlock.Xmp)
                    {
                        RemoveTag(ExifTag.XmpMetadata);

                    }
                    else if (BlockType == ImageFileBlock.Iptc)
                    {
                        RemoveTag(ExifTag.IptcMetadata);
                    }
                }
                ImageFileBlockInfo[(int)BlockType] = ImageFileBlockState.Removed;
            }
        }


        // Replace all EXIF tags and the thumbnail image by the EXIF data of another image file.
        //
        // Parameters:
        //  "SourceExifData": EXIF data of another image, that will be copied to the EXIF data of the current object.
        //                    A deep copy is done.
        public void ReplaceAllTagsBy(ExifData SourceExifData)
        {
            // Byte order handling
            bool SwapByteOrder = false;
            if (this.ImageType == ImageType.Tiff)
            {
                if (this._ByteOrder != SourceExifData._ByteOrder)
                {
                    // In TIFF images the EXIF block cannot be completely replaced by another EXIF block because it contains
                    // internal tags which must not be changed. If the byte order of the two EXIF blocks is different, the byte order
                    // of the source EXIF block shall be adapted.
                    SwapByteOrder = true;
                }
            }
            else this._ByteOrder = SourceExifData._ByteOrder; // Complete EXIF block is copied, therefore the byte order is also copied.

            // Remove all tags. But in TIFF images the TIFF internal tags, the XMP tag and the IPTC tag are not removed.
            this.RemoveAllTags();

            // Copy IFD Primary data.
            Dictionary<ExifTagId, TagItem> SourceIfdTagList = SourceExifData.TagTable[(uint)ExifIfd.PrimaryData];
            Dictionary<ExifTagId, TagItem> DestIfdTagList = this.TagTable[(uint)ExifIfd.PrimaryData];
            bool IsTiffImagePresent = (this.ImageType == ImageType.Tiff) || (SourceExifData.ImageType == ImageType.Tiff);
            bool CopyTag = true;
            foreach (TagItem SourceTag in SourceIfdTagList.Values)
            {
                if (IsTiffImagePresent)
                {
                    CopyTag = !IsInternalTiffTag(SourceTag.TagId) && !IsMetaDataTiffTag(SourceTag.TagId);
                }
                if (CopyTag)
                {
                    TagItem DestTag = CopyTagItemDeeply(ExifIfd.PrimaryData, SourceTag, SwapByteOrder);
                    AddIfNotExists(DestIfdTagList, DestTag);
                }
            }

            // Copy IFDs PrivateData, GpsInfoData and Interoperability
            ExifIfd[] TempIfds = new ExifIfd[] { ExifIfd.PrivateData, ExifIfd.GpsInfoData, ExifIfd.Interoperability };
            foreach (ExifIfd Ifd in TempIfds)
            {
                SourceIfdTagList = SourceExifData.TagTable[(uint)Ifd];
                DestIfdTagList = this.TagTable[(uint)Ifd];
                foreach (TagItem SourceTag in SourceIfdTagList.Values)
                {
                    TagItem DestTag = CopyTagItemDeeply(Ifd, SourceTag, SwapByteOrder);
                    AddIfNotExists(DestIfdTagList, DestTag);
                }
            }

            // Copy IFD ThumbnailData
            if (this.ImageType == ImageType.Jpeg)
            {
                SourceIfdTagList = SourceExifData.TagTable[(uint)ExifIfd.ThumbnailData];
                DestIfdTagList = this.TagTable[(uint)ExifIfd.ThumbnailData];
                foreach (TagItem SourceTag in SourceIfdTagList.Values)
                {
                    TagItem DestTag = CopyTagItemDeeply(ExifIfd.ThumbnailData, SourceTag, SwapByteOrder);
                    AddIfNotExists(DestIfdTagList, DestTag);
                }

                if (SourceExifData.ThumbnailImageExists())
                {
                    this.ThumbnailStartIndex = 0;
                    this.ThumbnailByteCount = SourceExifData.ThumbnailByteCount;
                    this.ThumbnailImage = new byte[this.ThumbnailByteCount];
                    Array.Copy(SourceExifData.ThumbnailImage, SourceExifData.ThumbnailStartIndex, this.ThumbnailImage, 0, this.ThumbnailByteCount);
                }
                else
                {
                    RemoveThumbnailImage_Internal();
                }
            }

            // Determine new EXIF block state
            UpdateImageFileBlockInfo();

            // Copy MakerNote index
            this.MakerNoteOriginalOffset = SourceExifData.MakerNoteOriginalOffset;
        }


        public bool ThumbnailImageExists()
        {
            return (ThumbnailImage != null);
        }


        // Read the thumbnail image data.
        //
        // Parameters:
        //  "ThumbnailData":      Return: Array with the thumbnail image beginning with the JPEG marker 0xFF 0xD8.
        //                        If no thumbnail image is defined, "null" is returned.
        //  "ThumbnailIndex":     Return: Index of the first data byte of the thumbnail image.
        //  "ThumbnailByteCount": Return: Number of bytes of the thumbnail image.
        //  Return value:         false = Thumbnail image does not exist.
        //                        true  = Thumbnail image is given back.
        public bool GetThumbnailImage(out byte[] ThumbnailData, out int ThumbnailIndex, out int ThumbnailByteCount)
        {
            bool Success;

            ThumbnailData = ThumbnailImage;
            ThumbnailIndex = ThumbnailStartIndex;
            ThumbnailByteCount = this.ThumbnailByteCount;
            Success = ThumbnailImageExists();
            return (Success);
        }


        // Write the thumbnail image data.
        //
        // Parameters:
        //  "ThumbnailData": Array which contains the thumbnail image beginning with the JPEG marker 0xFF 0xD8.
        //                   The array is not copied by this method so it should not be changed after calling
        //                   this method.
        public bool SetThumbnailImage(byte[] ThumbnailData, int ThumbnailIndex = 0, int ThumbnailByteCount = -1)
        {
            bool Success = false;

            if (ThumbnailData != null)
            {
                ThumbnailImage = ThumbnailData;
                ThumbnailStartIndex = ThumbnailIndex;
                if (ThumbnailByteCount < 0)
                {
                    this.ThumbnailByteCount = ThumbnailData.Length - ThumbnailIndex;
                }
                else this.ThumbnailByteCount = ThumbnailByteCount;
                SetTagValue(ExifTag.JpegInterchangeFormat, 0, ExifTagType.ULong); // "0" is a dummy value
                SetTagValue(ExifTag.JpegInterchangeFormatLength, this.ThumbnailByteCount, ExifTagType.ULong);
                Success = true;
            }
            return (Success);
        }


        // Remove the thumbnail image data.
        //
        // Parameters:
        //  "RemoveAlsoThumbnailTags": false = Remove the thumbnail image, the thumbnail pointer tag and the thumbnail size tag.
        //                             true  = Remove also all other tags in the IFD "thumbnail data".
        public void RemoveThumbnailImage(bool RemoveAlsoThumbnailTags)
        {
            RemoveThumbnailImage_Internal();
            if (RemoveAlsoThumbnailTags)
            {
                ClearIfd_Unchecked(ExifIfd.ThumbnailData);
            }
            else
            {
                RemoveTag(ExifTag.JpegInterchangeFormat);
                RemoveTag(ExifTag.JpegInterchangeFormatLength);
            }
        }


        // Get the byte order for the EXIF data.
        public ExifByteOrder ByteOrder
        {
            get
            {
                return (_ByteOrder);
            }
        }


        // Extract the IFD of a tag specification.
        public static ExifIfd ExtractIfd(ExifTag TagSpec)
        {
            return (ExifIfd)((uint)TagSpec >> IfdShift);
        }


        // Extract the tag ID of a tag specification.
        public static ExifTagId ExtractTagId(ExifTag TagSpec)
        {
            return (ExifTagId)((ushort)TagSpec);
        }


        // Compose IFD and tag ID to a tag specification.
        public static ExifTag ComposeTagSpec(ExifIfd Ifd, ExifTagId TagId)
        {
            return (ExifTag)(((uint)Ifd << IfdShift) | (uint)TagId);
        }


        // Get the number of bytes that a tag with the specified type and value count has.
        // If the tag type is invalid, the return value is 0.
        public static int GetTagByteCount(ExifTagType TagType, int ValueCount)
        {
            if ((uint)TagType < TypeByteCountLen)
            {
                return (TypeByteCount[(uint)TagType] * ValueCount);
            }
            else return (0);
        }


        public ushort ExifReadUInt16(byte[] Data, int StartIndex)
        {
            ushort v;

            if (_ByteOrder == ExifByteOrder.BigEndian)
            {
                v = (ushort)(((uint)Data[StartIndex] << 8) | Data[StartIndex + 1]);
            }
            else
            {
                v = (ushort)(((uint)Data[StartIndex + 1] << 8) | Data[StartIndex]);
            }
            return (v);
        }


        public void ExifWriteUInt16(byte[] Data, int StartIndex, ushort Value)
        {
            if (_ByteOrder == ExifByteOrder.BigEndian)
            {
                Data[StartIndex] = (byte)(Value >> 8);
                Data[StartIndex + 1] = (byte)Value;
            }
            else
            {
                Data[StartIndex + 1] = (byte)(Value >> 8);
                Data[StartIndex] = (byte)Value;
            }
        }


        public uint ExifReadUInt32(byte[] Data, int StartIndex)
        {
            uint v;

            if (_ByteOrder == ExifByteOrder.BigEndian)
            {
                v = ((uint)Data[StartIndex] << 24) |
                    ((uint)Data[StartIndex + 1] << 16) |
                    ((uint)Data[StartIndex + 2] << 8) |
                    (Data[StartIndex + 3]);
            }
            else
            {
                v = ((uint)Data[StartIndex + 3] << 24) |
                   ((uint)Data[StartIndex + 2] << 16) |
                   ((uint)Data[StartIndex + 1] << 8) |
                   (Data[StartIndex]);
            }
            return (v);
        }


        public void ExifWriteUInt32(byte[] Data, int StartIndex, uint Value)
        {
            if (_ByteOrder == ExifByteOrder.BigEndian)
            {
                Data[StartIndex] = (byte)(Value >> 24);
                Data[StartIndex + 1] = (byte)(Value >> 16);
                Data[StartIndex + 2] = (byte)(Value >> 8);
                Data[StartIndex + 3] = (byte)(Value);
            }
            else
            {
                Data[StartIndex + 3] = (byte)(Value >> 24);
                Data[StartIndex + 2] = (byte)(Value >> 16);
                Data[StartIndex + 1] = (byte)(Value >> 8);
                Data[StartIndex] = (byte)(Value);
            }
        }


        private ushort ReadUInt16BE(byte[] Data, int StartIndex)
        {
            ushort v = (ushort)(((uint)Data[StartIndex] << 8) | Data[StartIndex + 1]);
            return (v);
        }


        private uint ReadUInt32BE(byte[] Data, int StartIndex)
        {
            uint v = ((uint)Data[StartIndex] << 24) |
                ((uint)Data[StartIndex + 1] << 16) |
                ((uint)Data[StartIndex + 2] << 8) |
                (Data[StartIndex + 3]);
            return (v);
        }


        private void WriteUInt16BE(byte[] Data, int StartIndex, ushort Value)
        {
            Data[StartIndex] = (byte)(Value >> 8);
            Data[StartIndex + 1] = (byte)Value;
        }


        private void WriteUInt32BE(byte[] Data, int StartIndex, uint Value)
        {
            Data[StartIndex] = (byte)(Value >> 24);
            Data[StartIndex + 1] = (byte)(Value >> 16);
            Data[StartIndex + 2] = (byte)(Value >> 8);
            Data[StartIndex + 3] = (byte)(Value);
        }


        // Initialize enumeration for all tags of a specific IFD.
        public bool InitTagEnumeration(ExifIfd Ifd)
        {
            if ((uint)Ifd < ExifIfdCount)
            {
                ExifIfdForTagEnumeration = Ifd;
                TagEnumerator = TagTable[(uint)Ifd].Keys.GetEnumerator();
                return (true);
            }
            else
            {
                TagEnumerator = new Dictionary<ExifTagId, TagItem>.KeyCollection.Enumerator();
                return (false);
            }
        }


        // Get the next tag of the IFD, that was defined by a previous call to the method "InitTagEnumeration".
        //
        // Return value: true  = Next tag was successfully read.
        //               false = There is no further tag available.
        public bool EnumerateNextTag(out ExifTag TagSpec)
        {
            bool Success = false;

            if (TagEnumerator.MoveNext())
            {
                ExifTagId TagId = TagEnumerator.Current;
                TagSpec = ComposeTagSpec(ExifIfdForTagEnumeration, TagId);
                Success = true;
            }
            else TagSpec = 0;
            return (Success);
        }

        // -----------------------------------------------------------------------------------------------------------------
        // Methods for accessing EXIF tags on high level
        // -----------------------------------------------------------------------------------------------------------------

        public bool GetDateTaken(out DateTime Value)
        {
            return (GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTimeOriginal, ExifTag.SubsecTimeOriginal));
        }


        public bool SetDateTaken(DateTime Value)
        {
            return (SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTimeOriginal, ExifTag.SubsecTimeOriginal));
        }


        public void RemoveDateTaken()
        {
            RemoveTag(ExifTag.DateTimeOriginal);
            RemoveTag(ExifTag.SubsecTimeOriginal);
        }


        public bool GetDateDigitized(out DateTime Value)
        {
            return (GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTimeDigitized, ExifTag.SubsecTimeDigitized));
        }


        public bool SetDateDigitized(DateTime Value)
        {
            return (SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTimeDigitized, ExifTag.SubsecTimeDigitized));
        }


        public void RemoveDateDigitized()
        {
            RemoveTag(ExifTag.DateTimeDigitized);
            RemoveTag(ExifTag.SubsecTimeDigitized);
        }


        public bool GetDateChanged(out DateTime Value)
        {
            return (GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTime, ExifTag.SubsecTime));
        }


        public bool SetDateChanged(DateTime Value)
        {
            return (SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTime, ExifTag.SubsecTime));
        }


        public void RemoveDateChanged()
        {
            RemoveTag(ExifTag.DateTime);
            RemoveTag(ExifTag.SubsecTime);
        }


        public bool GetGpsLongitude(out GeoCoordinate Value)
        {
            return (GetGpsCoordinateHelper(out Value, ExifTag.GpsLongitude, ExifTag.GpsLongitudeRef, 'W', 'E'));
        }


        public bool SetGpsLongitude(GeoCoordinate Value)
        {
            return (SetGpsCoordinateHelper(Value, ExifTag.GpsLongitude, ExifTag.GpsLongitudeRef, 'W', 'E'));
        }


        public void RemoveGpsLongitude()
        {
            RemoveTag(ExifTag.GpsLongitude);
            RemoveTag(ExifTag.GpsLongitudeRef);
        }


        public bool GetGpsLatitude(out GeoCoordinate Value)
        {
            return (GetGpsCoordinateHelper(out Value, ExifTag.GpsLatitude, ExifTag.GpsLatitudeRef, 'N', 'S'));
        }


        public bool SetGpsLatitude(GeoCoordinate Value)
        {
            return (SetGpsCoordinateHelper(Value, ExifTag.GpsLatitude, ExifTag.GpsLatitudeRef, 'N', 'S'));
        }


        public void RemoveGpsLatitude()
        {
            RemoveTag(ExifTag.GpsLatitude);
            RemoveTag(ExifTag.GpsLatitudeRef);
        }


        public bool GetGpsAltitude(out decimal Value)
        {
            bool Success = false;
            ExifRational AltitudeRat;
            uint BelowSeaLevel;

            if (GetTagValue(ExifTag.GpsAltitude, out AltitudeRat) && AltitudeRat.IsValid())
            {
                Value = ExifRational.ToDecimal(AltitudeRat);
                if (GetTagValue(ExifTag.GpsAltitudeRef, out BelowSeaLevel) && (BelowSeaLevel == 1))
                {
                    Value = -Value;
                }
                Success = true;
            }
            else Value = 0;
            return (Success);
        }

        /// <summary>
        /// 设置相对于海平面的高度
        /// </summary>
        /// <param name="Value"></param>
        /// <returns></returns>
        public bool SetGpsAltitude(decimal Value)
        {
            bool Success = false;
            ExifRational AltitudeRat = ExifRational.FromDecimal(Value);
            uint BelowSeaLevel = 0;
            if (AltitudeRat.IsNegative())
            {
                BelowSeaLevel = 1;
                AltitudeRat.Sign = false; // Remove negative sign from "AltitudeRat"
            }
            if (SetTagValue(ExifTag.GpsAltitude, AltitudeRat, ExifTagType.URational) &&
                SetTagValue(ExifTag.GpsAltitudeRef, BelowSeaLevel, ExifTagType.Byte))
            {
                Success = true;
            }
            return (Success);
        }


        public void RemoveGpsAltitude()
        {
            RemoveTag(ExifTag.GpsAltitude);
            RemoveTag(ExifTag.GpsAltitudeRef);
        }


        public bool GetGpsDateTimeStamp(out DateTime Value)
        {
            bool Success = false;
            ExifRational Hour, Min, Sec;

            if (GetTagValue(ExifTag.GpsDateStamp, out Value, ExifDateFormat.DateOnly))
            {
                if (GetTagValue(ExifTag.GpsTimeStamp, out Hour, 0) && !Hour.IsNegative() && Hour.IsValid() &&
                    GetTagValue(ExifTag.GpsTimeStamp, out Min, 1) && !Min.IsNegative() && Min.IsValid() &&
                    GetTagValue(ExifTag.GpsTimeStamp, out Sec, 2) && !Sec.IsNegative() && Sec.IsValid())
                {
                    Value = Value.AddHours(((double)Hour.Numer) / Hour.Denom);
                    Value = Value.AddMinutes(((double)Min.Numer) / Min.Denom);
                    double ms = Math.Truncate(((double)Sec.Numer * 1000) / Sec.Denom);
                    Value = Value.AddMilliseconds(ms);
                    Success = true;
                }
                else Value = DateTime.MinValue;
            }
            else Value = DateTime.MinValue;
            return (Success);
        }


        public bool SetGpsDateTimeStamp(DateTime Value)
        {
            bool Success = false;
            ExifRational Sec;

            if (SetTagValue(ExifTag.GpsDateStamp, Value.Date, ExifDateFormat.DateOnly))
            {
                TimeSpan ts = Value.TimeOfDay;
                ExifRational Hour = new ExifRational(ts.Hours, 1);
                ExifRational Min = new ExifRational(ts.Minutes, 1);
                int ms = ts.Milliseconds;
                if (ms == 0)
                {
                    Sec = new ExifRational(ts.Seconds, 1);
                }
                else
                {
                    Sec = new ExifRational(ts.Seconds * 1000 + ms, 1000);
                }
                if (SetTagValue(ExifTag.GpsTimeStamp, Hour, ExifTagType.URational, 0) &&
                    SetTagValue(ExifTag.GpsTimeStamp, Min, ExifTagType.URational, 1) &&
                    SetTagValue(ExifTag.GpsTimeStamp, Sec, ExifTagType.URational, 2))
                {
                    Success = true;
                }
            }
            return (Success);
        }


        public void RemoveGpsDateTimeStamp()
        {
            RemoveTag(ExifTag.GpsDateStamp);
            RemoveTag(ExifTag.GpsTimeStamp);
        }

        #endregion

        #region Private area
        // -----------------------------------------------------------------------------------------------------------------
        // --- Private area ------------------------------------------------------------------------------------------------
        // -----------------------------------------------------------------------------------------------------------------
        private const int MinExifBlockLen = 2 + 4; // Minimum number of bytes for an EXIF block: Value for the number of tags of IFD Primary Data (2 bytes) +
                                                   // Offset of IFD Thumbnail Data (JPEG) or offset of next EXIF block (TIFF) (4 bytes)
        private const int MaxTagValueCount = int.MaxValue / 8;
        private byte[] SourceExifBlock; // Temporary storage for the EXIF block in JPEG and PNG images, starting at index 8.
                                        // In index 0 to 7 the TIFF header is stored.
        private Stream SourceExifStream; // Temporary stream from which the EXIF block is read in TIFF images.
        private ExifByteOrder _ByteOrder; // Byte order value, extracted from the TIFF header
        private string _FileNameWithPath;
        private static readonly int[] TypeByteCount = {
      0, // Type 0: invalid
      1, // Type 1: ExifTagType.Byte
      1, // Type 2: ExifTagType.Ascii, 8 bit character
      2, // Type 3: ExifTagType.UShort
      4, // Type 4: ExifTagType.ULong
      8, // Type 5: ExifTagType.URational
      1, // Type 6: ExifTagType.SByte
      1, // Type 7: ExifTagType.Undefined, 8 bit value
      2, // Type 8: ExifTagType.SShort
      4, // Type 9: ExifTagType.SLong
      8, // Type 10: ExifTagType.SRational
      4, // Type 11: ExifTagType.Float
      8  // Type 12: ExifTagType.Double
    };
        private const int TypeByteCountLen = 13; // Number of elements of the array "TypeByteCount"
        private const int IdCodeLength = 8;
        private static readonly byte[] IdCodeUtf16 = new byte[] { (byte)'U', (byte)'N', (byte)'I', (byte)'C', (byte)'O', (byte)'D', (byte)'E', 0 };
        private static readonly byte[] IdCodeAscii = new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I', 0, 0, 0 };
        private static readonly byte[] IdCodeDefault = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };

        private Dictionary<ExifTagId, TagItem>[] TagTable;
        private byte[] ThumbnailImage; // Array, which contains among other data the thumbnail image
        private int ThumbnailStartIndex, ThumbnailByteCount; // Range, in which the thumbnail image in the array "ThumbnailData" is stored

        private const int ExifIfdCount = 5; // Number of elements of the enum type "ExifIfd", i. e. the number of IFDs
        private ExifIfd ExifIfdForTagEnumeration;
        private Dictionary<ExifTagId, TagItem>.KeyCollection.Enumerator TagEnumerator;
        private enum ImageFileBlockState { NonExistent = 0, Removed, Existent };
        //private const ImageFileBlock LastImageFileBlock = ImageFileBlock.JpegComment;
        private ImageFileBlockState[] ImageFileBlockInfo;
        private ExifErrCode ErrCodeForIllegalExifBlock;

        //private int ExifBlockFilePosition = 0;

        // JPEG files
        private const int JpegMaxExifBlockLen = 65534 - 2 - 6 - TiffHeaderLen; // Maximum number of bytes an EXIF block in a JPEG file may have
        private const ushort JpegApp0Marker = 0xFFE0;
        private const ushort JpegApp1Marker = 0xFFE1;
        private const ushort JpegApp2Marker = 0xFFE2;
        private const ushort JpegApp13Marker = 0xFFED;
        private const ushort JpegApp14Marker = 0xFFEE;
        private const ushort JpegCommentMarker = 0xFFFE; // Start of JPEG comment block
        private const ushort JpegSoiMarker = 0xFFD8; // Start of image (SOI) marker
        private const ushort JpegSosMarker = 0xFFDA; // Start of scan (SOS) marker
        private static readonly byte[] JpegExifSignature = { (byte)'E', (byte)'x', (byte)'i', (byte)'f', 0, 0 };
        private static readonly byte[] JpegXmpInitialSignature = { (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)':',
      (byte)'/', (byte)'/', (byte)'n', (byte)'s', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b',
      (byte)'e', (byte)'.', (byte)'c', (byte)'o', (byte)'m', (byte)'/' };
        private static readonly byte[] JpegIptcSignature = { (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o',
      (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', 0 };

        // TIFF files
        private const int TiffHeaderLen = 8; // Size of TIFF header. The TIFF header is also present in EXIF blocks of JPEG and PNG files.
        private const uint TiffHeaderSignatureLE = 0x49492A00;
        private const uint TiffHeaderSignatureBE = 0x4D4D002A;

        // PNG files
        private const uint PngHeaderPart1 = 0x89504E47;
        private const uint PngHeaderPart2 = 0x0D0A1A0A;
        private const uint PngIhdrChunk = ((uint)'I' << 24) | ((uint)'H' << 16) | ((uint)'D' << 8) | (uint)'R';
        private const uint PngExifChunk = ((uint)'e' << 24) | ((uint)'X' << 16) | ((uint)'I' << 8) | (uint)'f';
        private const uint PngItxtChunk = ((uint)'i' << 24) | ((uint)'T' << 16) | ((uint)'X' << 8) | (uint)'t';
        private const uint PngTextChunk = ((uint)'t' << 24) | ((uint)'E' << 16) | ((uint)'X' << 8) | (uint)'t';
        private const uint PngTimeChunk = ((uint)'t' << 24) | ((uint)'I' << 16) | ((uint)'M' << 8) | (uint)'E';
        private const uint PngIendChunk = ((uint)'I' << 24) | ((uint)'E' << 16) | ((uint)'N' << 8) | (uint)'D';
        private static readonly byte[] PngXmpSignature = { (byte)'X', (byte)'M', (byte)'L', (byte)':', (byte)'c',
      (byte)'o', (byte)'m', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b', (byte)'e', (byte)'.',
      (byte)'x', (byte)'m', (byte)'p', 0 };
        private static readonly byte[] PngIptcSignature = { (byte)'R', (byte)'a', (byte)'w', (byte)' ', (byte)'p',
      (byte)'r', (byte)'o', (byte)'f', (byte)'i', (byte)'l', (byte)'e', (byte)' ', (byte)'t', (byte)'y',
      (byte)'p', (byte)'e', (byte)' ', (byte)'i', (byte)'p', (byte)'t', (byte)'c', 0 };
        private static readonly int ImageFileBlockCount = Enum.GetValues(typeof(ImageFileBlock)).Length;


        private ExifData()
        {
            _ByteOrder = ExifByteOrder.BigEndian;
            ImageFileBlockInfo = new ImageFileBlockState[ImageFileBlockCount];
        }


        private void ReadFromStream(Stream ImageStream, ExifLoadOptions Options)
        {
            SourceExifStream = ImageStream;
            ErrCodeForIllegalExifBlock = ExifErrCode.ExifBlockHasIllegalContent;
            ImageType = CheckStreamTypeAndCompatibility(SourceExifStream);
            if (Options.HasFlag(ExifLoadOptions.CreateEmptyBlock))
            {
                SetEmptyExifBlock();
            }
            else
            {
                if (ImageType == ImageType.Jpeg)
                {
                    ReadJepg();
                }
                else if (ImageType == ImageType.Tiff)
                {
                    ReadTiff();
                }
                else if (ImageType == ImageType.Png)
                {
                    ReadPng();
                }
                UpdateImageFileBlockInfo();
            }

            SourceExifBlock = null;
            SourceExifStream = null;
        }


        // Read JPEG file from stream "SourceExifStream".
        private void ReadJepg()
        {
            byte[] BlockContent = new byte[65536];
            ushort BlockMarker;
            ImageFileBlock BlockType;
            int MetaDataIndex;
            bool ExifBlockFound = false;

            SourceExifStream.Position = 2;
            do
            {
                ReadJpegBlock(SourceExifStream, BlockContent, out int BlockContentSize, out BlockMarker, out BlockType, out MetaDataIndex);
                if (BlockType != ImageFileBlock.Unknown)
                {
                    if ((BlockType == ImageFileBlock.Exif) && (!ExifBlockFound))
                    {
                        ExifBlockFound = true;
                        int TiffHeaderAndExifBlockLen = BlockContentSize - MetaDataIndex;
                        SourceExifBlock = new byte[TiffHeaderAndExifBlockLen];
                        Array.Copy(BlockContent, MetaDataIndex, SourceExifBlock, 0, TiffHeaderAndExifBlockLen);
                        EvaluateTiffHeader(SourceExifBlock, TiffHeaderAndExifBlockLen, out int IfdPrimaryDataOffset);
                        EvaluateExifBlock(IfdPrimaryDataOffset);
                    }
                    ImageFileBlockInfo[(int)BlockType] = ImageFileBlockState.Existent;
                }
            } while (BlockMarker != JpegSosMarker);
            if (!ExifBlockFound)
            {
                SetEmptyExifBlock();
            }
        }


        // Read TIFF file from stream "SourceExifStream".
        private void ReadTiff()
        {
            byte[] BlockContent = new byte[8];

            SourceExifStream.Position = 0;
            SourceExifBlock = null;
            ErrCodeForIllegalExifBlock = ExifErrCode.InternalImageStructureIsWrong;
            int TiffHeaderBytesRead = SourceExifStream.Read(BlockContent, 0, TiffHeaderLen);
            EvaluateTiffHeader(BlockContent, TiffHeaderBytesRead, out int IfdPrimaryDataOffset);
            EvaluateExifBlock(IfdPrimaryDataOffset);
        }


        // Read PNG file from stream "SourceExifStream".
        private void ReadPng()
        {
            byte[] TempData = new byte[65536];
            byte[] BlockContent = new byte[30];
            bool ExifBlockFound = false;
            uint ChunkType;

            SourceExifStream.Position = 4;
            int k = SourceExifStream.Read(TempData, 0, 4);
            if ((k < 4) || (ReadUInt32BE(TempData, 0) != PngHeaderPart2))
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            do
            {
                ReadPngBlockHeader(SourceExifStream, TempData, out int DataLength, out ChunkType);
                int BytesRead = DetectPngImageBlock(SourceExifStream, BlockContent, ChunkType, out ImageFileBlock BlockType);
                if (BlockType != ImageFileBlock.Unknown)
                {
                    if ((BlockType == ImageFileBlock.Exif) && (!ExifBlockFound))
                    {
                        ExifBlockFound = true;
                        SourceExifBlock = new byte[DataLength];
                        if (SourceExifStream.Read(SourceExifBlock, 0, DataLength) < DataLength)
                        {
                            throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                        }
                        uint Crc32Calculated = CalculateCrc32(TempData, 4, 4, false); // Calculate CRC32 of chunk type
                        Crc32Calculated = CalculateCrc32(SourceExifBlock, 0, DataLength, true, Crc32Calculated);
                        k = SourceExifStream.Read(TempData, 0, 4);
                        uint Crc32Loaded = ReadUInt32BE(TempData, 0);
                        if ((k < 4) || (Crc32Calculated != Crc32Loaded))
                        {
                            throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                        }
                        EvaluateTiffHeader(SourceExifBlock, DataLength, out int IfdPrimaryDataOffset);
                        EvaluateExifBlock(IfdPrimaryDataOffset);
                        BytesRead = DataLength + 4;
                    }
                    ImageFileBlockInfo[(int)BlockType] = ImageFileBlockState.Existent;
                }
                SourceExifStream.Position += DataLength + 4 - BytesRead;
            } while (ChunkType != PngIendChunk);
            if (!ExifBlockFound)
            {
                SetEmptyExifBlock();
            }
        }


        // Read block length and chunk type and return them in "TempData" as raw data and in "BlockLength" and
        // "ChunkType" as 32 bit values.
        private void ReadPngBlockHeader(Stream ImageStream, byte[] TempData, out int BlockLength, out uint ChunkType)
        {
            if (ImageStream.Read(TempData, 0, 8) < 8)
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            BlockLength = (int)ReadUInt32BE(TempData, 0);
            if (BlockLength < 0)
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            ChunkType = ReadUInt32BE(TempData, 4);
        }


        private int DetectPngImageBlock(Stream ImageStream, byte[] SignatureData, uint ChunkType, out ImageFileBlock BlockType)
        {
            int BytesRead = 0;
            BlockType = ImageFileBlock.Unknown;
            if (ChunkType == PngExifChunk)
            {
                BlockType = ImageFileBlock.Exif;
            }
            else if (ChunkType == PngItxtChunk)
            {
                int BytesToRead = PngIptcSignature.Length;
                BytesRead = ImageStream.Read(SignatureData, 0, BytesToRead);
                if (ArrayStartsWith(SignatureData, BytesRead, PngXmpSignature))
                {
                    BlockType = ImageFileBlock.Xmp;
                }
                else if (ArrayStartsWith(SignatureData, BytesRead, PngIptcSignature))
                {
                    BlockType = ImageFileBlock.Iptc;
                }
            }
            else if (ChunkType == PngTextChunk)
            {
                BlockType = ImageFileBlock.PngMetaData;
            }
            else if (ChunkType == PngTimeChunk)
            {
                BlockType = ImageFileBlock.PngDateChanged;
            }
            return (BytesRead);
        }


        // CRC32 checksum table for polynomial 0xEDB88320
        private static readonly uint[] Crc32ChecksumTable = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F,
      0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
      0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0,
      0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
      0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75,
      0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
      0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106,
      0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
      0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B,
      0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
      0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC,
      0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
      0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
      0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
      0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2,
      0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
      0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767,
      0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
      0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28,
      0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
      0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D,
      0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
      0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE,
      0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
      0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693,
      0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D };


        private static uint CalculateCrc32(byte[] Data, int StartIndex, int Length, bool Finalize = true, uint StartCrc = 0xFFFFFFFF)
        {
            uint result = StartCrc;
            int EndIndexPlus1 = StartIndex + Length;
            for (int i = StartIndex; i < EndIndexPlus1; i++)
            {
                byte value = Data[i];
                result = Crc32ChecksumTable[((byte)result) ^ value] ^ (result >> 8);
            }
            if (Finalize) result = ~result;
            return (result);
        }


        private void UpdateImageFileBlockInfo()
        {
            ImageFileBlockState NewExifBlockState = ImageFileBlockState.NonExistent;
            Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];
            foreach (ExifTagId tid in PrimaryDataIfdTable.Keys)
            {
                if (ImageType == ImageType.Tiff)
                {
                    if (!IsInternalTiffTag(tid) && !IsMetaDataTiffTag(tid))
                    {
                        NewExifBlockState = ImageFileBlockState.Existent;
                        break;
                    }
                }
                else
                {
                    NewExifBlockState = ImageFileBlockState.Existent;
                    break;
                }
            }

            if (NewExifBlockState == ImageFileBlockState.NonExistent)
            {
                ExifIfd[] TempIfds = new ExifIfd[] { ExifIfd.PrivateData, ExifIfd.GpsInfoData, ExifIfd.Interoperability, ExifIfd.ThumbnailData };
                foreach (ExifIfd Ifd in TempIfds)
                {
                    if (this.TagTable[(uint)Ifd].Count > 0)
                    {
                        NewExifBlockState = ImageFileBlockState.Existent;
                        break;
                    }
                }
            }
            ImageFileBlockInfo[(int)ImageFileBlock.Exif] = NewExifBlockState;

            if (ImageType == ImageType.Tiff)
            {
                // In TIFF images the XMP and IPTC block are stored as tags within the EXIF block
                if (PrimaryDataIfdTable.ContainsKey(ExifTagId.XmpMetadata))
                {
                    ImageFileBlockInfo[(int)ImageFileBlock.Xmp] = ImageFileBlockState.Existent;
                }
                else ImageFileBlockInfo[(int)ImageFileBlock.Xmp] = ImageFileBlockState.NonExistent;
                if (PrimaryDataIfdTable.ContainsKey(ExifTagId.IptcMetadata))
                {
                    ImageFileBlockInfo[(int)ImageFileBlock.Iptc] = ImageFileBlockState.Existent;
                }
                else ImageFileBlockInfo[(int)ImageFileBlock.Iptc] = ImageFileBlockState.NonExistent;
            }
        }


        private void ReadJpegBlockMarker(Stream ImageStream, out ushort BlockMarker, out int BlockContentSize)
        {
            byte[] TempBuffer = new byte[2];

            ImageStream.Read(TempBuffer, 0, 2);
            BlockMarker = ReadUInt16BE(TempBuffer, 0);
            if ((BlockMarker == 0xFF01) || ((BlockMarker >= 0xFFD0) && (BlockMarker <= 0xFFDA)))
            {
                // Block does not have a size specification
                BlockContentSize = 0;
            }
            else if (BlockMarker == 0xFFFF)
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            else
            {
                ImageStream.Read(TempBuffer, 0, 2);
                BlockContentSize = (int)ReadUInt16BE(TempBuffer, 0) - 2;
                if (BlockContentSize < 0)
                {
                    throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                }
            }
        }


        // Read JPEG block. If the JPEG block has a size specification, the block content is read and returned.
        // If the JPEG block doesn't have a size specification, only the block marker is read and "BlockContentSize" is set to 0.
        //
        // Parameters:
        //  "BlockData": in:  Allocated array with a size of at least 65535 Bytes.
        //               out: Content of JPEG block.
        private void ReadJpegBlock(Stream ImageStream, byte[] BlockContent, out int BlockContentSize, out ushort BlockMarker,
          out ImageFileBlock BlockType, out int MetaDataIndex)
        {
            BlockType = ImageFileBlock.Unknown;
            MetaDataIndex = 0;
            ReadJpegBlockMarker(ImageStream, out BlockMarker, out int ContentSize);
            if (ContentSize > 0)
            {
                int k = ImageStream.Read(BlockContent, 0, ContentSize);
                if (k == ContentSize)
                {
                    if (BlockMarker == JpegApp1Marker)
                    {
                        if (ArrayStartsWith(BlockContent, ContentSize, JpegExifSignature))
                        {
                            BlockType = ImageFileBlock.Exif;
                            MetaDataIndex = JpegExifSignature.Length;
                        }
                        else if (ArrayStartsWith(BlockContent, ContentSize, JpegXmpInitialSignature))
                        {
                            BlockType = ImageFileBlock.Xmp;
                            int i = JpegXmpInitialSignature.Length;
                            while ((i < ContentSize) && (BlockContent[i] != 0))
                            {
                                i++;
                            }
                            MetaDataIndex = i + 1;
                        }
                    }
                    else if (BlockMarker == JpegApp13Marker)
                    {
                        if (ArrayStartsWith(BlockContent, ContentSize, JpegIptcSignature))
                        {
                            BlockType = ImageFileBlock.Iptc;
                            MetaDataIndex = JpegIptcSignature.Length;
                        }
                    }
                    else if (BlockMarker == JpegCommentMarker)
                    {
                        BlockType = ImageFileBlock.JpegComment;
                    }
                }
                else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            BlockContentSize = ContentSize;
        }


        // The first 4 bytes are read from the stream "StreamToBeChecked" to identify the image type.
        private ImageType CheckStreamTypeAndCompatibility(Stream StreamToBeChecked)
        {
            ImageType StreamImageType;
            byte[] TempBuffer = new byte[4];

            StreamToBeChecked.Read(TempBuffer, 0, 4);
            uint ImageSignature = ReadUInt32BE(TempBuffer, 0);
            if ((ImageSignature >> 16) == JpegSoiMarker)
            {
                StreamImageType = ImageType.Jpeg;
            }
            else if ((ImageSignature == TiffHeaderSignatureLE) || (ImageSignature == TiffHeaderSignatureBE))
            {
                StreamImageType = ImageType.Tiff;
            }
            else if (ImageSignature == PngHeaderPart1)
            {
                StreamImageType = ImageType.Png;
            }
            else throw new ExifException(ExifErrCode.ImageTypeIsNotSupported);

            // Check stream size
            if (StreamToBeChecked.Length > int.MaxValue)
            {
                throw new ExifException(ExifErrCode.ImageHasUnsupportedFeatures);
            }

            // Check if stream is seekable
            const long CheckOffset = -2;
            long i = StreamToBeChecked.Position;
            StreamToBeChecked.Position += CheckOffset;
            if (StreamToBeChecked.Position != (i + CheckOffset))
            {
                // The stream for reading the image data must support the property "Position"
                throw new ExifException(ExifErrCode.ImageHasUnsupportedFeatures);
            }
            StreamToBeChecked.Position = i;
            return (StreamImageType);
        }


        private void EvaluateTiffHeader(byte[] TiffHeader, int TiffHeaderBytesRead, out int IfdPrimaryDataOffset)
        {
            if (TiffHeaderBytesRead < TiffHeaderLen)
            {
                throw new ExifException(ErrCodeForIllegalExifBlock);
            }

            uint TiffHeaderSignature = ReadUInt32BE(TiffHeader, 0);
            if (TiffHeaderSignature == TiffHeaderSignatureLE)
            {
                _ByteOrder = ExifByteOrder.LittleEndian;
            }
            else if (TiffHeaderSignature == TiffHeaderSignatureBE)
            {
                _ByteOrder = ExifByteOrder.BigEndian;
            }
            else throw new ExifException(ErrCodeForIllegalExifBlock);

            IfdPrimaryDataOffset = (int)ExifReadUInt32(TiffHeader, 4);
            if (IfdPrimaryDataOffset < TiffHeaderLen)
            {
                throw new ExifException(ErrCodeForIllegalExifBlock);
            }
        }


        private void SaveJpeg(Stream SourceStream, Stream DestStream)
        {
            const long FirstBlockStartPosition = 2;
            ushort BlockMarker;
            byte[] BlockContent = new byte[65536];
            byte[] TempBuffer = new byte[4];

            SourceStream.Position = FirstBlockStartPosition;
            WriteUInt16BE(TempBuffer, 0, JpegSoiMarker);
            DestStream.Write(TempBuffer, 0, 2);

            // Copy all APP0 blocks of the source file. These blocks should be the JFIF blocks.
            do
            {
                ReadJpegBlockMarker(SourceStream, out BlockMarker, out int BlockContentSize);
                if (BlockMarker == JpegSosMarker)
                {
                    break; // Start of JPEG image matrix reached
                }
                long NextBlockStartPosition = SourceStream.Position + BlockContentSize;
                if (BlockMarker == JpegApp0Marker)
                {
                    WriteUInt16BE(TempBuffer, 0, BlockMarker);
                    WriteUInt16BE(TempBuffer, 2, (ushort)(BlockContentSize + 2));
                    DestStream.Write(TempBuffer, 0, 4);
                    if (SourceStream.Read(BlockContent, 0, BlockContentSize) == BlockContentSize)
                    {
                        DestStream.Write(BlockContent, 0, BlockContentSize);
                    }
                    else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                }
                SourceStream.Position = NextBlockStartPosition;
            } while (true);

            // Write new EXIF block if the new EXIF block is not empty
            CreateExifBlock(out FlexArray NewExifBlock, TiffHeaderLen);
            int NewExifBlockLen = NewExifBlock.Length;
            if (NewExifBlockLen > JpegMaxExifBlockLen)
            {
                throw new ExifException(ExifErrCode.ExifDataAreTooLarge);
            }
            else if (NewExifBlockLen > 0)
            {
                WriteUInt16BE(TempBuffer, 0, JpegApp1Marker);
                WriteUInt16BE(TempBuffer, 2, (ushort)(2 + 6 + TiffHeaderLen + NewExifBlockLen));
                DestStream.Write(TempBuffer, 0, 4);
                DestStream.Write(JpegExifSignature, 0, JpegExifSignature.Length);
                CreateTiffHeader(out byte[] TiffHeader, TiffHeaderLen);
                DestStream.Write(TiffHeader, 0, TiffHeader.Length);
                DestStream.Write(NewExifBlock.Buffer, 0, NewExifBlockLen);
            }

            // Return to the first block of the image
            SourceStream.Position = FirstBlockStartPosition;
            do
            {
                ReadJpegBlock(SourceStream, BlockContent, out int BlockContentSize, out BlockMarker, out ImageFileBlock BlockType, out _);
                if (BlockMarker == JpegSosMarker)
                {
                    // Start of JPEG image matrix reached. Copy all remaining data from source stream to destination stream
                    // without further interpretation.
                    WriteUInt16BE(TempBuffer, 0, BlockMarker);
                    DestStream.Write(TempBuffer, 0, 2);
                    int k;
                    do
                    {
                        k = SourceStream.Read(BlockContent, 0, BlockContent.Length);
                        DestStream.Write(BlockContent, 0, k);
                    } while (k == BlockContent.Length);
                    break; // Destination stream was completely written
                }

                bool CopyBlockFromSourceStream = true;
                if (BlockMarker == JpegApp0Marker)
                {
                    CopyBlockFromSourceStream = false; // All APP0 blocks have already been copied
                }
                else if (BlockType == ImageFileBlock.Exif)
                {
                    CopyBlockFromSourceStream = false; // EXIF block has already been written
                }
                else if ((BlockType == ImageFileBlock.Xmp) && (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Removed))
                {
                    CopyBlockFromSourceStream = false;
                }
                else if ((BlockType == ImageFileBlock.Iptc) && (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Removed))
                {
                    CopyBlockFromSourceStream = false;
                }
                else if ((BlockType == ImageFileBlock.JpegComment) && (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Removed))
                {
                    CopyBlockFromSourceStream = false;
                }

                if (CopyBlockFromSourceStream)
                {
                    WriteUInt16BE(TempBuffer, 0, BlockMarker);
                    WriteUInt16BE(TempBuffer, 2, (ushort)(BlockContentSize + 2));
                    DestStream.Write(TempBuffer, 0, 4);
                    DestStream.Write(BlockContent, 0, BlockContentSize);
                }
            } while (true);
        }


        private void SaveTiff(Stream SourceStream, Stream DestStream)
        {
            int k, ImageNumber = 0, NextExifBlockPointerIndex = 0;
            byte[] TempBuffer = new byte[65536];
            ExifData CurrentImageExif;
            FlexArray ExifBlockAsBinaryData = null;

            SourceStream.Position = 0;
            byte[] TiffHeader = new byte[TiffHeaderLen];
            if (SourceStream.Read(TiffHeader, 0, TiffHeaderLen) != TiffHeaderLen)
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            ExifByteOrder SourceStreamByteOrder = ExifByteOrder.LittleEndian;
            uint TiffHeaderSignature = ReadUInt32BE(TiffHeader, 0);
            if (TiffHeaderSignature == TiffHeaderSignatureBE)
            {
                SourceStreamByteOrder = ExifByteOrder.BigEndian;
            }
            if (_ByteOrder != SourceStreamByteOrder)
            {
                throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
            int SourceStreamExifBlockOffset = (int)ExifReadUInt32(TiffHeader, 4);
            uint CurrentDestStreamOffset = TiffHeaderLen;

            // TIFF file structure when saving a multi-page TIFF file:
            //
            // --------------------------------------------
            // TIFF Header (8 bytes)
            // --------------------------------------------
            // Image 1 matrix
            // --------------------------------------------
            // Image 1 EXIF block
            // --------------------------------------------
            // Image 2 matrix
            // --------------------------------------------
            // Image 2 EXIF block
            // --------------------------------------------
            // ...

            do
            {
                CurrentImageExif = CreateSubExifData(SourceStream, ref SourceStreamExifBlockOffset, SourceStreamByteOrder,
                  out uint[] SegmentOffsetTable, out uint[] SegmentByteCountTable,
                  out ExifTag SegmentOffsetsTag, out ExifTag SegmentByteCountsTag);

                if (ImageNumber == 0)
                {
                    CurrentImageExif = this; // Discard the EXIF block returned by "CreateSubExifData"
                }

                if (!CurrentImageExif.TagExists(ExifTag.ImageWidth) || !CurrentImageExif.TagExists(ExifTag.ImageLength))
                {
                    throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                }
                CurrentImageExif.RemoveTag(ExifTag.FreeOffsets); // Remove tags for areas of unused bytes
                CurrentImageExif.RemoveTag(ExifTag.FreeByteCounts);

                // Assign new segment offsets.
                int ImageSegmentCount = SegmentOffsetTable.Length;
                CurrentImageExif.SetTagValueCount(SegmentOffsetsTag, ImageSegmentCount, ExifTagType.ULong);
                CurrentImageExif.SetTagValueCount(SegmentByteCountsTag, ImageSegmentCount, ExifTagType.ULong);
                for (k = 0; k < ImageSegmentCount; k++)
                {
                    CurrentImageExif.SetTagValue(SegmentOffsetsTag, CurrentDestStreamOffset, ExifTagType.ULong, k);
                    uint SegmentSize = SegmentByteCountTable[k];
                    CurrentImageExif.SetTagValue(SegmentByteCountsTag, SegmentSize, ExifTagType.ULong, k);
                    CurrentDestStreamOffset += SegmentSize;
                }
                bool HasImageMatrixFillByte = false;
                if ((CurrentDestStreamOffset & 0x1) != 0)
                {
                    CurrentDestStreamOffset++;
                    HasImageMatrixFillByte = true;
                }
                if (CurrentDestStreamOffset > int.MaxValue) throw new ExifException(ExifErrCode.ExifDataAreTooLarge);

                if (ImageNumber == 0)
                {
                    // Write TIFF header to destination stream
                    CurrentImageExif.ExifWriteUInt32(TiffHeader, 4, CurrentDestStreamOffset);
                    DestStream.Write(TiffHeader, 0, TiffHeaderLen);
                }
                else
                {
                    // Insert the the pointer to the EXIF block of the current image into the binary data of the EXIF block of
                    // the previous image. Then write the previous EXIF block to destination stream.
                    CurrentImageExif.ExifWriteUInt32(ExifBlockAsBinaryData.Buffer, NextExifBlockPointerIndex, CurrentDestStreamOffset);
                    DestStream.Write(ExifBlockAsBinaryData.Buffer, 0, ExifBlockAsBinaryData.Length);
                }

                // Write image matrix: copy image segments from source to destination stream
                for (k = 0; k < ImageSegmentCount; k++)
                {
                    int RemainingByteCount = (int)SegmentByteCountTable[k];
                    int BytesToRead = TempBuffer.Length;
                    SourceStream.Position = SegmentOffsetTable[k];
                    do
                    {
                        if (BytesToRead > RemainingByteCount) BytesToRead = RemainingByteCount;
                        if (SourceStream.Read(TempBuffer, 0, BytesToRead) == BytesToRead)
                        {
                            DestStream.Write(TempBuffer, 0, BytesToRead);
                            RemainingByteCount -= BytesToRead;
                        }
                        else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                    } while (RemainingByteCount > 0);
                }
                if (HasImageMatrixFillByte) DestStream.WriteByte(0);

                NextExifBlockPointerIndex = CurrentImageExif.CreateExifBlock(out ExifBlockAsBinaryData, (int)CurrentDestStreamOffset);
                CurrentDestStreamOffset += (uint)ExifBlockAsBinaryData.Length;
                if (CurrentDestStreamOffset > int.MaxValue) throw new ExifException(ExifErrCode.ExifDataAreTooLarge);

                ImageNumber++;
            } while (SourceStreamExifBlockOffset != 0);

            DestStream.Write(ExifBlockAsBinaryData.Buffer, 0, ExifBlockAsBinaryData.Length);
        }


        private static ExifData CreateSubExifData(Stream TiffStream, ref int ExifBlockOffset, ExifByteOrder ByteOrder, out uint[] SegmentOffsetTable,
          out uint[] SegmentByteCountTable, out ExifTag SegmentOffsetsTag, out ExifTag SegmentByteCountsTag)
        {
            ExifData SubExifBlock = new ExifData();
            SubExifBlock._ByteOrder = ByteOrder;
            SubExifBlock.ImageType = ImageType.Tiff;
            SubExifBlock.SourceExifBlock = null;
            SubExifBlock.SourceExifStream = TiffStream;

            ExifBlockOffset = SubExifBlock.EvaluateExifBlock(ExifBlockOffset);
            TagItem SegmentOffsetsTagItem, SegmentByteCountsTagItem;
            if (SubExifBlock.GetTagItem(ExifTag.StripOffsets, out SegmentOffsetsTagItem) &&
                SubExifBlock.GetTagItem(ExifTag.StripByteCounts, out SegmentByteCountsTagItem))
            {
                SegmentOffsetsTag = ExifTag.StripOffsets;
                SegmentByteCountsTag = ExifTag.StripByteCounts;
            }
            else if (SubExifBlock.GetTagItem(ExifTag.TileOffsets, out SegmentOffsetsTagItem) &&
                     SubExifBlock.GetTagItem(ExifTag.TileByteCounts, out SegmentByteCountsTagItem))
            {
                SegmentOffsetsTag = ExifTag.TileOffsets;
                SegmentByteCountsTag = ExifTag.TileByteCounts;
            }
            else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);

            int ValueCount = SegmentOffsetsTagItem.ValueCount;
            SegmentOffsetTable = new uint[ValueCount];
            SegmentByteCountTable = new uint[ValueCount];
            bool IsOffsetTable16Bit = (SegmentOffsetsTagItem.TagType == ExifTagType.UShort);
            bool ByteCountTable16Bit = (SegmentByteCountsTagItem.TagType == ExifTagType.UShort);
            uint v;
            for (int i = 0; i < ValueCount; i++)
            {
                if (IsOffsetTable16Bit)
                {
                    v = SubExifBlock.ExifReadUInt16(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex + (i << 1));
                }
                else v = SubExifBlock.ExifReadUInt32(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex + (i << 2));
                SegmentOffsetTable[i] = v;
                if (ByteCountTable16Bit)
                {
                    v = SubExifBlock.ExifReadUInt16(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex + (i << 1));
                }
                else v = SubExifBlock.ExifReadUInt32(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex + (i << 2));
                SegmentByteCountTable[i] = v;
            }
            return (SubExifBlock);
        }


        private void SavePng(Stream SourceStream, Stream DestStream)
        {
            uint ChunkType;
            byte[] TempData = new byte[65536];
            byte[] BlockContent = new byte[30]; // Used for signature data at the beginning of the block

            SourceStream.Position = 8;
            WriteUInt32BE(TempData, 0, PngHeaderPart1);
            WriteUInt32BE(TempData, 4, PngHeaderPart2);
            DestStream.Write(TempData, 0, 8);
            do
            {
                bool CopyBlockFromSourceStream = true;
                long BlockStartStreamPos = SourceStream.Position;
                ReadPngBlockHeader(SourceStream, TempData, out int DataLength, out ChunkType);
                DetectPngImageBlock(SourceStream, BlockContent, ChunkType, out ImageFileBlock BlockType);
                if (BlockType != ImageFileBlock.Unknown)
                {
                    if ((BlockType == ImageFileBlock.Exif) || (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Removed))
                    {
                        CopyBlockFromSourceStream = false; // EXIF block will always be replaced by a new EXIF block
                    }
                }

                if (CopyBlockFromSourceStream)
                {
                    // Copy current block from source to destination stream
                    SourceStream.Position = BlockStartStreamPos;
                    int RemainingByteCount = DataLength + 12;
                    int BytesToRead = TempData.Length;
                    do
                    {
                        if (BytesToRead > RemainingByteCount) BytesToRead = RemainingByteCount;
                        if (SourceStream.Read(TempData, 0, BytesToRead) == BytesToRead)
                        {
                            DestStream.Write(TempData, 0, BytesToRead);
                            RemainingByteCount -= BytesToRead;
                        }
                        else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                    } while (RemainingByteCount > 0);
                }
                else
                {
                    SourceStream.Position = BlockStartStreamPos + DataLength + 12;
                }

                if (ChunkType == PngIhdrChunk)
                {
                    // Write new EXIF block after the IHDR chunk if the new EXIF block is not empty
                    CreateExifBlock(out FlexArray NewExifBlock, TiffHeaderLen);
                    int NewExifBlockLen = NewExifBlock.Length;
                    if (NewExifBlockLen > 0)
                    {
                        CreateTiffHeader(out byte[] TiffHeader, TiffHeaderLen);
                        uint BlockLength = TiffHeaderLen + (uint)NewExifBlockLen;
                        WriteUInt32BE(TempData, 0, BlockLength);
                        WriteUInt32BE(TempData, 4, PngExifChunk);
                        DestStream.Write(TempData, 0, 8);
                        DestStream.Write(TiffHeader, 0, TiffHeaderLen);
                        DestStream.Write(NewExifBlock.Buffer, 0, NewExifBlockLen);

                        uint Crc32 = CalculateCrc32(TempData, 4, 4, false); // Checksum over chunk type
                        Crc32 = CalculateCrc32(TiffHeader, 0, TiffHeaderLen, false, Crc32);
                        Crc32 = CalculateCrc32(NewExifBlock.Buffer, 0, NewExifBlockLen, true, Crc32);
                        WriteUInt32BE(TempData, 0, Crc32);
                        DestStream.Write(TempData, 0, 4);
                    }
                }

            } while (ChunkType != PngIendChunk);
        }


        private void SetEmptyExifBlock()
        {
            // The TIFF header, which is stored in the array elements from 0 to 7, will not be initialized because it is not used any more.
            SourceExifBlock = new byte[TiffHeaderLen + MinExifBlockLen];
            ExifWriteUInt16(SourceExifBlock, TiffHeaderLen, 0); // Number of tags of IFD Primary Data
            ExifWriteUInt32(SourceExifBlock, TiffHeaderLen + 2, 0); // Offset of IFD Thumbnail Data
            EvaluateExifBlock(TiffHeaderLen);
        }


        private void WriteTagToFlexArray(TagItem TempTagData, FlexArray WriteData, ref int TagDataIndex, int ExifBlockOffset)
        {
            int i, ByteCount;
            byte v;

            i = TagDataIndex;
            TagDataIndex += 12;
            ExifWriteUInt16(WriteData.Buffer, i, (ushort)TempTagData.TagId);
            ExifWriteUInt16(WriteData.Buffer, i + 2, (ushort)TempTagData.TagType);
            ExifWriteUInt32(WriteData.Buffer, i + 4, (uint)TempTagData.ValueCount);
            ByteCount = GetTagByteCount(TempTagData.TagType, TempTagData.ValueCount);
            if (ByteCount <= 4)
            {
                // The tag does not have outsourced data. In this case exactly 4 bytes have to be written.
                WriteData.Buffer[i + 8] = TempTagData.ValueData[TempTagData.ValueIndex];
                v = 0;
                if (ByteCount >= 2) v = TempTagData.ValueData[TempTagData.ValueIndex + 1];
                WriteData.Buffer[i + 9] = v;
                v = 0;
                if (ByteCount >= 3) v = TempTagData.ValueData[TempTagData.ValueIndex + 2];
                WriteData.Buffer[i + 10] = v;
                v = 0;
                if (ByteCount >= 4) v = TempTagData.ValueData[TempTagData.ValueIndex + 3];
                WriteData.Buffer[i + 11] = v;
            }
            else
            {
                // The tag has outsourced data. The outsourced data is added at the end of array "WriteData".
                int OutsourcedDataIndex = WriteData.Length;
                ExifWriteUInt32(WriteData.Buffer, i + 8, (uint)(ExifBlockOffset + OutsourcedDataIndex));
                WriteData.Length += ByteCount; // Allocate memory in array "WriteData"
                Array.Copy(TempTagData.ValueData, TempTagData.ValueIndex, WriteData.Buffer, OutsourcedDataIndex, ByteCount);
                if ((ByteCount & 0x1) != 0)
                {
                    // The EXIF block must have a 16 bit alignment. "ByteCount" is odd, therefore add a fill byte.
                    WriteData.Length++; // Allocate 1 byte in array "WriteData"
                    WriteData.Buffer[WriteData.Length - 1] = 0;
                }
            }
        }


        private int CreateExifBlock(out FlexArray NewExifBlock, int CurrentExifBlockOffset)
        {
            int TiffNextExifBlockPointerIndex = 0;

            FlexArray WriteExifBlock = new FlexArray(JpegMaxExifBlockLen);
            NewExifBlock = WriteExifBlock;
            int WriteIndex = 0;
            UpdateIfdPointerTags(out TagItem PrivateDataPointerTag, out TagItem GpsInfoDataPointerTag, out TagItem InteroperabilityPointerTag);
            CreateIfdPrimaryData(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, out int PrivateDataIfdPointerIndex,
              out int GpsInfoDataIfdPointerIndex, out int ThumbnailDataIfdPointerIndex);
            CreateIfdPrivateData(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, PrivateDataIfdPointerIndex, PrivateDataPointerTag,
              out int InteroperabilityIfdPointerIndex);
            CreateIfdGpsInfoData(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, GpsInfoDataIfdPointerIndex, GpsInfoDataPointerTag);
            CreateIfdInteroperability(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, InteroperabilityIfdPointerIndex, InteroperabilityPointerTag);
            if (ImageType == ImageType.Tiff)
            {
                // TIFF files don't have a thumbnail image, but they may have multiple images
                TiffNextExifBlockPointerIndex = ThumbnailDataIfdPointerIndex;
                ExifWriteUInt32(WriteExifBlock.Buffer, TiffNextExifBlockPointerIndex, 0); // Set pointer to next image provisorily to 0
            }
            else CreateIfdThumbnailData(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, ThumbnailDataIfdPointerIndex);

            // Check if new EXIF block is empty. Empty EXIF blocks should not be written instead the EXIF block should be removed.
            if (WriteExifBlock.Length <= MinExifBlockLen)
            {
                WriteExifBlock.Length = 0;
            }

            return (TiffNextExifBlockPointerIndex);
        }


        private void CreateTiffHeader(out byte[] TiffHeader, int ExifBlockOffset)
        {
            TiffHeader = new byte[TiffHeaderLen];
            if (ByteOrder == ExifByteOrder.BigEndian)
            {
                WriteUInt32BE(TiffHeader, 0, TiffHeaderSignatureBE);
            }
            else if (ByteOrder == ExifByteOrder.LittleEndian)
            {
                WriteUInt32BE(TiffHeader, 0, TiffHeaderSignatureLE);
            }
            else throw new ExifException(ExifErrCode.InternalError);
            ExifWriteUInt32(TiffHeader, 4, (uint)ExifBlockOffset);
        }


        private void UpdateIfdPointerTags(out TagItem PrivateDataPointerTag, out TagItem GpsInfoDataPointerTag, out TagItem InteroperabilityPointerTag)
        {
            Dictionary<ExifTagId, TagItem> PrivateDataIfdTable = TagTable[(uint)ExifIfd.PrivateData];
            Dictionary<ExifTagId, TagItem> InteroperabilityIfdTable = TagTable[(uint)ExifIfd.Interoperability];
            PrivateDataIfdTable.TryGetValue(ExifTagId.InteroperabilityIfdPointer, out InteroperabilityPointerTag);
            int InteroperabilityTagCount = InteroperabilityIfdTable.Count;
            if (InteroperabilityTagCount > 0)
            {
                if (InteroperabilityPointerTag == null)
                {
                    InteroperabilityPointerTag = new TagItem(ExifTag.InteroperabilityIfdPointer, ExifTagType.ULong, 1);
                    PrivateDataIfdTable.Add(ExifTagId.InteroperabilityIfdPointer, InteroperabilityPointerTag);
                }
            }
            else
            {
                if (InteroperabilityPointerTag != null)
                {
                    PrivateDataIfdTable.Remove(ExifTagId.InteroperabilityIfdPointer);
                }
            }

            Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];
            Dictionary<ExifTagId, TagItem> GpsInfoDataIfdTable = TagTable[(uint)ExifIfd.GpsInfoData];
            PrimaryDataIfdTable.TryGetValue(ExifTagId.GpsInfoIfdPointer, out GpsInfoDataPointerTag);
            int GpsInfoDataTagCount = GpsInfoDataIfdTable.Count;
            if (GpsInfoDataTagCount > 0)
            {
                if (GpsInfoDataPointerTag == null)
                {
                    GpsInfoDataPointerTag = new TagItem(ExifTag.GpsInfoIfdPointer, ExifTagType.ULong, 1);
                    PrimaryDataIfdTable.Add(ExifTagId.GpsInfoIfdPointer, GpsInfoDataPointerTag);
                }
            }
            else
            {
                if (GpsInfoDataPointerTag != null)
                {
                    PrimaryDataIfdTable.Remove(ExifTagId.GpsInfoIfdPointer);
                }
            }

            PrimaryDataIfdTable.TryGetValue(ExifTagId.ExifIfdPointer, out PrivateDataPointerTag);
            if (PrivateDataIfdTable.Count > 0)
            {
                if (PrivateDataPointerTag == null)
                {
                    PrivateDataPointerTag = new TagItem(ExifTag.ExifIfdPointer, ExifTagType.ULong, 1);
                    PrimaryDataIfdTable.Add(ExifTagId.ExifIfdPointer, PrivateDataPointerTag);
                }
            }
            else
            {
                if (PrivateDataPointerTag != null)
                {
                    PrimaryDataIfdTable.Remove(ExifTagId.ExifIfdPointer);
                }
            }
        }


        private void CreateIfdPrimaryData(FlexArray WriteExifBlock, ref int WriteIndex, int ExifBlockOffset, out int PrivateDataIfdPointerIndex,
          out int GpsInfoDataIfdPointerIndex, out int ThumbnailDataIfdPointerIndex)
        {
            Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];
            int PrimaryDataTagCount = PrimaryDataIfdTable.Count;
            PrivateDataIfdPointerIndex = -1;
            GpsInfoDataIfdPointerIndex = -1;
            int IfdFixedSize = 2 + PrimaryDataTagCount * 12 + 4; // "+ 4" for thumbnail data pointer
            WriteExifBlock.Length += IfdFixedSize; // Allocate memory in array "WriteExifBlock"
            ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)PrimaryDataTagCount);
            WriteIndex += 2;
            foreach (TagItem t in PrimaryDataIfdTable.Values)
            {
                if (t.TagId == ExifTagId.ExifIfdPointer)
                {
                    t.TagType = ExifTagType.ULong;
                    PrivateDataIfdPointerIndex = WriteIndex + 8;  // The "Private Data IFD" pointer is unknown at this time and a dummy
                                                                  // value is written for it. The index is stored, at which the actual pointer can be written later.
                }
                else if (t.TagId == ExifTagId.GpsInfoIfdPointer)
                {
                    t.TagType = ExifTagType.ULong;
                    GpsInfoDataIfdPointerIndex = WriteIndex + 8; // The "GPS Info Data IFD" pointer is unknown at this time and a dummy
                                                                 // value is written for it. The index is stored, at which the actual pointer can be written later.
                }
                WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
            }

            // Now the thumbnail data pointer should be written. Because it is unknown at this time the index is stored.
            ThumbnailDataIfdPointerIndex = WriteIndex;
            WriteIndex = WriteExifBlock.Length; // Write the next IFD behind the outsourced data of the IFD Primary Data
        }


        private void CreateIfdPrivateData(FlexArray WriteExifBlock, ref int WriteIndex, int ExifBlockOffset, int PrivateDataIfdPointerIndex,
          TagItem PrivateDataPointerTag, out int InteroperabilityIfdPointerIndex)
        {
            Dictionary<ExifTagId, TagItem> PrivateDataIfdTable = TagTable[(uint)ExifIfd.PrivateData];
            int PrivateDataTagCount = PrivateDataIfdTable.Count;
            InteroperabilityIfdPointerIndex = -1;
            if (PrivateDataTagCount > 0)
            {
                int iPrivateDataStart = WriteIndex;
                int MakerNoteDataPointerIndex, OffsetSchemaValueIndex;
                TagItem OffsetSchemaTag;
                bool AddOffsetSchemaTag;
                ExifWriteUInt32(PrivateDataPointerTag.ValueData, PrivateDataPointerTag.ValueIndex, (uint)(ExifBlockOffset + WriteIndex));
                ExifWriteUInt32(WriteExifBlock.Buffer, PrivateDataIfdPointerIndex, (uint)(ExifBlockOffset + WriteIndex));
                do
                {
                    MakerNoteDataPointerIndex = 0;
                    OffsetSchemaValueIndex = 0;
                    OffsetSchemaTag = null;
                    int IfdFixedSize = 2 + PrivateDataTagCount * 12 + 4; // "+ 4" for null pointer at the end
                    WriteExifBlock.Length += IfdFixedSize; // Allocate memory in array "WriteExifBlock"
                    ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)PrivateDataTagCount);
                    WriteIndex += 2;
                    foreach (TagItem t in PrivateDataIfdTable.Values)
                    {
                        if (t.TagId == ExifTagId.InteroperabilityIfdPointer)
                        {
                            t.TagType = ExifTagType.ULong;
                            InteroperabilityIfdPointerIndex = WriteIndex + 8; // The "Interoperability IFD" pointer is unknown at this time and a dummy
                                                                              // value is written for it. The index is stored, at which the actual pointer can be written later.
                        }
                        else if (t.TagId == ExifTagId.MakerNote)
                        {
                            MakerNoteDataPointerIndex = WriteIndex + 8;
                        }
                        else if (t.TagId == ExifTagId.OffsetSchema)
                        {
                            t.TagType = ExifTagType.SLong;
                            OffsetSchemaValueIndex = WriteIndex + 8;
                            OffsetSchemaTag = t;
                        }
                        WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
                    }
                    ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0); // Write null pointer at the end of the tag list
                    WriteIndex = WriteExifBlock.Length; // Write the next IFD behind the outsourced data of the IFD Private Data
                    AddOffsetSchemaTag = CheckIfMakerNoteTagHasMoved(WriteExifBlock.Buffer, MakerNoteDataPointerIndex, OffsetSchemaValueIndex, OffsetSchemaTag);
                    if (AddOffsetSchemaTag)
                    {
                        TagItem t = new TagItem(ExifTag.OffsetSchema, ExifTagType.SLong, 1);
                        PrivateDataIfdTable.Add(ExifTagId.OffsetSchema, t);
                        PrivateDataTagCount++;
                        WriteExifBlock.Length = iPrivateDataStart; // Free all memory that was allocated for IFD Private Data
                        WriteIndex = iPrivateDataStart;
                    }
                } while (AddOffsetSchemaTag);
            }
        }


        private void CreateIfdGpsInfoData(FlexArray WriteExifBlock, ref int WriteIndex, int ExifBlockOffset, int GpsInfoDataIfdPointerIndex, TagItem GpsInfoDataPointerTag)
        {
            Dictionary<ExifTagId, TagItem> GpsInfoDataIfdTable = TagTable[(uint)ExifIfd.GpsInfoData];
            int GpsInfoDataTagCount = GpsInfoDataIfdTable.Count;
            if (GpsInfoDataTagCount > 0)
            {
                uint GpsInfoDataOffset = (uint)(ExifBlockOffset + WriteIndex);
                ExifWriteUInt32(GpsInfoDataPointerTag.ValueData, GpsInfoDataPointerTag.ValueIndex, GpsInfoDataOffset);
                ExifWriteUInt32(WriteExifBlock.Buffer, GpsInfoDataIfdPointerIndex, GpsInfoDataOffset);
                int IfdFixedSize = 2 + GpsInfoDataTagCount * 12 + 4;
                WriteExifBlock.Length += IfdFixedSize;
                ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)GpsInfoDataTagCount);
                WriteIndex += 2;
                foreach (TagItem t in GpsInfoDataIfdTable.Values)
                {
                    WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
                }
                ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0); // Write null pointer at the end of the tag list
                WriteIndex = WriteExifBlock.Length; // Write the next IFD behind the outsourced data of the IFD GPS Info Data
            }
        }


        private void CreateIfdInteroperability(FlexArray WriteExifBlock, ref int WriteIndex, int ExifBlockOffset, int InteroperabilityIfdPointerIndex,
          TagItem InteroperabilityPointerTag)
        {
            Dictionary<ExifTagId, TagItem> InteroperabilityIfdTable = TagTable[(uint)ExifIfd.Interoperability];
            int InteroperabilityTagCount = InteroperabilityIfdTable.Count;
            if (InteroperabilityTagCount > 0)
            {
                uint InteroperabilityOffset = (uint)(ExifBlockOffset + WriteIndex);
                ExifWriteUInt32(InteroperabilityPointerTag.ValueData, InteroperabilityPointerTag.ValueIndex, InteroperabilityOffset);
                ExifWriteUInt32(WriteExifBlock.Buffer, InteroperabilityIfdPointerIndex, InteroperabilityOffset);
                int IfdFixedSize = 2 + InteroperabilityTagCount * 12 + 4;
                WriteExifBlock.Length += IfdFixedSize;
                ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)InteroperabilityTagCount);
                WriteIndex += 2;
                foreach (TagItem t in InteroperabilityIfdTable.Values)
                {
                    WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
                }
                ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0); // Write null pointer at the end of the tag list
                WriteIndex = WriteExifBlock.Length; // Write the next IFD behind the outsourced data of the IFD Interoperability
            }
        }


        private void CreateIfdThumbnailData(FlexArray WriteExifBlock, ref int WriteIndex, int ExifBlockOffset, int ThumbnailDataIfdPointerIndex)
        {
            Dictionary<ExifTagId, TagItem> ThumbnailDataIfdTable = TagTable[(uint)ExifIfd.ThumbnailData];
            int ThumbnailDataTagCount = ThumbnailDataIfdTable.Count;
            if ((ThumbnailDataTagCount > 0) || (ThumbnailImageExists()))
            {
                ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailDataIfdPointerIndex, (uint)(ExifBlockOffset + WriteIndex));
                int IfdFixedSize = 2 + ThumbnailDataTagCount * 12 + 4;
                WriteExifBlock.Length += IfdFixedSize;
                ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)ThumbnailDataTagCount);
                WriteIndex += 2;
                int ThumbnailImagePointerIndex = -1;
                TagItem ThumbnailImagePointerTag = null;
                bool ThumbnailImageSizeTagExists = false;
                foreach (TagItem t in ThumbnailDataIfdTable.Values)
                {
                    if (t.TagId == ExifTagId.JpegInterchangeFormat)
                    {
                        // The tag "TagId_JpegInterchangeFormat" stores a pointer to the thumbnail image.
                        // The pointer is not known at this time, so the index, where the pointer has to be written, is stored.
                        t.TagType = ExifTagType.ULong;
                        ThumbnailImagePointerTag = t;
                        ThumbnailImagePointerIndex = WriteIndex + 8;
                    }
                    else if (t.TagId == ExifTagId.JpegInterchangeFormatLength)
                    {
                        t.TagType = ExifTagType.ULong;
                        ExifWriteUInt32(t.ValueData, t.ValueIndex, (uint)ThumbnailByteCount);
                        ThumbnailImageSizeTagExists = true;
                    }
                    WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
                }

                if (ThumbnailImage != null)
                {
                    // A thumbnail image is defined
                    if ((ThumbnailImagePointerIndex < 0) || (ThumbnailImageSizeTagExists == false))
                    {
                        throw new ExifException(ExifErrCode.InternalError);
                    }
                    else
                    {
                        int ThumbnailImageIndex = WriteExifBlock.Length;
                        uint ThumbnailImageOffset = (uint)(ExifBlockOffset + ThumbnailImageIndex);
                        ExifWriteUInt32(ThumbnailImagePointerTag.ValueData, ThumbnailImagePointerTag.ValueIndex, ThumbnailImageOffset);
                        ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailImagePointerIndex, ThumbnailImageOffset);
                        WriteExifBlock.Length += ThumbnailByteCount;
                        Array.Copy(ThumbnailImage, ThumbnailStartIndex, WriteExifBlock.Buffer, ThumbnailImageIndex, ThumbnailByteCount);
                        if ((WriteExifBlock.Length & 0x1) != 0)
                        {
                            WriteExifBlock.Length++;
                            WriteExifBlock[WriteExifBlock.Length - 1] = 0; // Insert fill byte
                        }
                    }
                }
                else
                {
                    // A thumbnail image is not defined
                    if (ThumbnailImagePointerIndex >= 0)
                    {
                        throw new ExifException(ExifErrCode.InternalError);
                    }
                }
                ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0); // Write null pointer at the end of the tag table
            }
            else
            {
                ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailDataIfdPointerIndex, 0);
            }
        }


        private bool CheckIfMakerNoteTagHasMoved(byte[] DataBlock, int MakerNoteDataPointerIndex, int OffsetSchemaValueIndex, TagItem OffsetSchemaTag)
        {
            bool MakerNoteHasMovedAndOffsetSchemaTagRequired = false;
            int MakerNoteCurrentOffset;

            if ((MakerNoteOriginalOffset > 0) && (MakerNoteDataPointerIndex > 0))
            {
                // "MakerNote" has existed and is now existing
                MakerNoteCurrentOffset = (int)ExifReadUInt32(DataBlock, MakerNoteDataPointerIndex);
                if (MakerNoteOriginalOffset != MakerNoteCurrentOffset)
                {
                    // "MakerNote" has moved
                    if (OffsetSchemaValueIndex > 0)
                    {
                        int NewOffsetDifference = MakerNoteCurrentOffset - MakerNoteOriginalOffset;
                        ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, (uint)NewOffsetDifference);
                        ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, (uint)NewOffsetDifference);
                    }
                    else
                    {
                        MakerNoteHasMovedAndOffsetSchemaTagRequired = true;
                    }
                }
                else
                {
                    // "MakerNote" has not moved
                    if (OffsetSchemaValueIndex > 0)
                    {
                        ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, 0); // Set move offset to 0
                        ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, 0);
                    }
                }
            }
            else
            {
                if (OffsetSchemaValueIndex > 0)
                {
                    ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, 0); // Set move offset to 0
                    ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, 0);
                }
            }
            return (MakerNoteHasMovedAndOffsetSchemaTagRequired);
        }


        // Create a tag item and initialize it with the data, which are stored in "IfdRawData[IfdRawDataIndex]".
        // The data values of the tag are not copied, instead a reference to the array "IfdRawData" is assigned.
        private TagItem CreateTagWithReferenceToIfdRawData(byte[] IfdRawData, int IfdRawDataIndex)
        {
            int ValueIndex, ValueByteCount, AllocatedByteCount, OriginalOffset;
            byte[] TagData;

            ExifTagId TagId = (ExifTagId)ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            ExifTagType TagType = (ExifTagType)ExifReadUInt16(IfdRawData, IfdRawDataIndex + 2);
            uint ValueCount = ExifReadUInt32(IfdRawData, IfdRawDataIndex + 4);

            if (ValueCount > MaxTagValueCount) throw new ExifException(ErrCodeForIllegalExifBlock);
            ValueByteCount = GetTagByteCount(TagType, (int)ValueCount);
            if (ValueByteCount <= 4)
            {
                // The tag doesn't have outsourced data. In this case a memory space with a size of 4 bytes is available for the data.
                ValueIndex = IfdRawDataIndex + 8;
                TagData = IfdRawData;
                AllocatedByteCount = 4;
                OriginalOffset = 0;
            }
            else
            {
                // The tag has outsourced data.
                OriginalOffset = (int)ExifReadUInt32(IfdRawData, IfdRawDataIndex + 8);
                AllocatedByteCount = ValueByteCount;
                GetOutsourcedData(OriginalOffset, AllocatedByteCount, out TagData, out ValueIndex);
            }
            TagItem TempTagItem = new TagItem(TagId, TagType, (int)ValueCount, TagData, ValueIndex, AllocatedByteCount, OriginalOffset);
            return (TempTagItem);
        }


        private void GetOutsourcedData(int DataOffset, int DataLen, out byte[] TagData, out int TagDataIndex)
        {
            if (SourceExifBlock != null)
            {
                if ((uint)(DataOffset + DataLen) <= (uint)SourceExifBlock.Length)
                {
                    TagData = SourceExifBlock;
                    TagDataIndex = DataOffset;
                }
                else throw new ExifException(ErrCodeForIllegalExifBlock);
            }
            else
            {
                TagData = new byte[DataLen];
                SourceExifStream.Position = DataOffset; // + ExifBlockFilePosition;
                if (SourceExifStream.Read(TagData, 0, DataLen) != DataLen)
                {
                    throw new ExifException(ErrCodeForIllegalExifBlock);
                }
                TagDataIndex = 0;
            }
        }


        private void InitIfdPrimaryData(byte[] IfdRawData, int IfdRawDataIndex, out int PrivateDataOffset, out int GpsInfoDataOffset, out int NextImageOffset)
        {
            PrivateDataOffset = 0;
            GpsInfoDataOffset = 0;
            int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            IfdRawDataIndex += 2;
            var PrimaryDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
            TagTable[(uint)ExifIfd.PrimaryData] = PrimaryDataIfdTable;
            int j = 0;
            while (j < TagCount)
            {
                TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
                if (AddIfNotExists(PrimaryDataIfdTable, TempTagData))
                {
                    if (TempTagData.TagId == ExifTagId.ExifIfdPointer)
                    {
                        PrivateDataOffset = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                    else if (TempTagData.TagId == ExifTagId.GpsInfoIfdPointer)
                    {
                        GpsInfoDataOffset = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                }
                IfdRawDataIndex += 12;
                j++;
            }
            NextImageOffset = (int)ExifReadUInt32(IfdRawData, IfdRawDataIndex);
        }


        private void InitIfdPrivateData(byte[] IfdRawData, int IfdRawDataIndex, out int InteroperabilityOffset)
        {
            int MakerNoteOffset, OffsetSchemaValue;

            InteroperabilityOffset = 0;
            MakerNoteOffset = 0;
            OffsetSchemaValue = 0;
            int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            IfdRawDataIndex += 2;
            var PrivateDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
            TagTable[(uint)ExifIfd.PrivateData] = PrivateDataIfdTable;
            int j = 0;
            while (j < TagCount)
            {
                TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
                if (AddIfNotExists(PrivateDataIfdTable, TempTagData))
                {
                    if (TempTagData.TagId == ExifTagId.InteroperabilityIfdPointer)
                    {
                        InteroperabilityOffset = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                    else if (TempTagData.TagId == ExifTagId.MakerNote)
                    {
                        MakerNoteOffset = TempTagData.OriginalDataOffset;
                    }
                    else if (TempTagData.TagId == ExifTagId.OffsetSchema)
                    {
                        OffsetSchemaValue = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                }
                IfdRawDataIndex += 12;
                j++;
            }

            if (MakerNoteOffset > 0)
            {
                MakerNoteOriginalOffset = MakerNoteOffset - OffsetSchemaValue;
            }
            else MakerNoteOriginalOffset = 0;
        }


        private void InitIfdGpsInfoData(byte[] IfdRawData, int IfdRawDataIndex)
        {
            int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            IfdRawDataIndex += 2;
            var GpsInfoDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
            TagTable[(uint)ExifIfd.GpsInfoData] = GpsInfoDataIfdTable;
            int j = 0;
            while (j < TagCount)
            {
                TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
                AddIfNotExists(GpsInfoDataIfdTable, TempTagData);
                IfdRawDataIndex += 12;
                j++;
            }
        }


        private void InitIfdInteroperability(byte[] IfdRawData, int IfdRawDataIndex)
        {
            int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            IfdRawDataIndex += 2;
            var InteroperabilityIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
            TagTable[(uint)ExifIfd.Interoperability] = InteroperabilityIfdTable;
            int j = 0;
            while (j < TagCount)
            {
                TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
                AddIfNotExists(InteroperabilityIfdTable, TempTagData);
                IfdRawDataIndex += 12;
                j++;
            }
        }


        private void InitIfdThumbnailData(byte[] IfdRawData, int IfdRawDataIndex)
        {
            int ThumbnailImageDataOffset = 0;
            ThumbnailByteCount = 0;
            ThumbnailImage = null;
            int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
            IfdRawDataIndex += 2;
            var ThumbnailDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
            TagTable[(uint)ExifIfd.ThumbnailData] = ThumbnailDataIfdTable;
            int j = 0;
            while (j < TagCount)
            {
                TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
                if (AddIfNotExists(ThumbnailDataIfdTable, TempTagData))
                {
                    if (TempTagData.TagId == ExifTagId.JpegInterchangeFormat)
                    {
                        ThumbnailImageDataOffset = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                    else if (TempTagData.TagId == ExifTagId.JpegInterchangeFormatLength)
                    {
                        ThumbnailByteCount = (int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);
                    }
                }
                IfdRawDataIndex += 12;
                j++;
            }

            if (ThumbnailImageDataOffset > 0)
            {
                GetOutsourcedData(ThumbnailImageDataOffset, ThumbnailByteCount, out ThumbnailImage, out ThumbnailStartIndex);
            }
        }


        private void GetNextIfd(int IfdOffset, out byte[] IfdRawData, out int IfdRawDataIndex)
        {
            if (IfdOffset == 0)
            {
                // IFD does not exist. Create a dummy IFD table.
                IfdRawData = new byte[2];
                IfdRawDataIndex = 0;
            }
            else if (SourceExifBlock != null)
            {
                IfdRawData = SourceExifBlock;
                IfdRawDataIndex = IfdOffset;
            }
            else
            {
                SourceExifStream.Position = IfdOffset; // + ExifBlockFilePosition;
                IfdRawDataIndex = 0;
                byte[] TagCountArray = new byte[2];
                if (SourceExifStream.Read(TagCountArray, 0, 2) == 2)
                {
                    int IfdTagCount = ExifReadUInt16(TagCountArray, 0);
                    int IfdSize = 2 + IfdTagCount * 12 + 4;
                    IfdRawData = new byte[IfdSize];
                    IfdRawData[0] = TagCountArray[0];
                    IfdRawData[1] = TagCountArray[1];
                    if (SourceExifStream.Read(IfdRawData, 2, IfdSize - 2) != (IfdSize - 2))
                    {
                        throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
                    }
                }
                else throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
            }
        }


        // JPEG, PNG: The complete EXIF block was read and is now stored in the array "SourceExifBlock" from the array index "TifferHeaderLen" (= 8).
        // TIFF: Stream "SourceStream" is used to read the EXIF data successively. "SourceExifBlock" is "null" in this case.
        private int EvaluateExifBlock(int PrimaryDataOffset)
        {
            int PrivateDataOffset, GpsInfoDataOffset, InteroperabilityOffset, ThumbnailDataOffset, IfdIndex, TiffNextExifBlockOffset = 0;
            byte[] IfdTable;

            TagTable = new Dictionary<ExifTagId, TagItem>[ExifIfdCount];
            GetNextIfd(PrimaryDataOffset, out IfdTable, out IfdIndex);
            InitIfdPrimaryData(IfdTable, IfdIndex, out PrivateDataOffset, out GpsInfoDataOffset, out ThumbnailDataOffset);
            GetNextIfd(PrivateDataOffset, out IfdTable, out IfdIndex);
            InitIfdPrivateData(IfdTable, IfdIndex, out InteroperabilityOffset);
            GetNextIfd(GpsInfoDataOffset, out IfdTable, out IfdIndex);
            InitIfdGpsInfoData(IfdTable, IfdIndex);
            GetNextIfd(InteroperabilityOffset, out IfdTable, out IfdIndex);
            InitIfdInteroperability(IfdTable, IfdIndex);
            if (ImageType == ImageType.Tiff)
            {
                // TIFF files don't have a thumbnail image, but they may have multiple images
                IfdTable = new byte[2];
                InitIfdThumbnailData(IfdTable, 0); // Set empty tag table for IFD ThumbnailData
                TiffNextExifBlockOffset = ThumbnailDataOffset;
            }
            else
            {
                // In JPEG files "NextImageOffset" is the offset to the thumbnail image
                GetNextIfd(ThumbnailDataOffset, out IfdTable, out IfdIndex);
                InitIfdThumbnailData(IfdTable, IfdIndex);
            }
            return (TiffNextExifBlockOffset);
        }


        private bool AddIfNotExists(Dictionary<ExifTagId, TagItem> Dict, TagItem TagData)
        {
            try
            {
                Dict.Add(TagData.TagId, TagData);
                return (true);
            }
            catch
            {
                // An exception occured because the tag ID already exists. In this case ignore the new tag item.
                return (false);
            }
        }


        // Compare "Array1" from index "StartIndex1" with "Array2".
        private bool CompareArrays(byte[] Array1, int StartIndex1, byte[] Array2)
        {
            if (Array1.Length >= (StartIndex1 + Array2.Length))
            {
                bool IsEqual = true;
                int i = StartIndex1;
                foreach (byte b in Array2)
                {
                    if (Array1[i] != b)
                    {
                        IsEqual = false;
                        break;
                    }
                    i++;
                }
                return (IsEqual);
            }
            else return (false);
        }


        // Compare "Array1" from index 0 to index "Array2.Length - 1" with "Array2".
        // If "Array1" is smaller than "Array2", "false" is returned.
        private bool ArrayStartsWith(byte[] Array1, int Array1Length, byte[] Array2)
        {
            if (Array1Length >= Array2.Length)
            {
                bool IsEqual = true;
                int i = 0;
                foreach (byte b in Array2)
                {
                    if (Array1[i] != b)
                    {
                        IsEqual = false;
                        break;
                    }
                    i++;
                }
                return (IsEqual);
            }
            else return (false);
        }


        private bool ReadUintElement(TagItem t, int ElementIndex, out uint Value)
        {
            bool Success = false;

            if ((ElementIndex >= 0) && (ElementIndex < t.ValueCount))
            {
                switch (t.TagType)
                {
                    case ExifTagType.Byte:
                        Value = t.ValueData[t.ValueIndex + ElementIndex];
                        Success = true;
                        break;
                    case ExifTagType.UShort:
                        Value = ExifReadUInt16(t.ValueData, t.ValueIndex + (ElementIndex << 1));
                        Success = true;
                        break;
                    case ExifTagType.ULong:
                    case ExifTagType.SLong:
                        Value = ExifReadUInt32(t.ValueData, t.ValueIndex + (ElementIndex << 2));
                        Success = true;
                        break;
                    default:
                        Value = 0;
                        break;
                }
            }
            else Value = 0;
            return (Success);
        }


        private bool WriteUintElement(TagItem t, int ElementIndex, uint Value)
        {
            bool Success = false;

            if (t != null)
            {
                if (t.TagType == ExifTagType.Byte)
                {
                    t.ValueData[t.ValueIndex + ElementIndex] = (byte)Value;
                }
                else if (t.TagType == ExifTagType.UShort)
                {
                    ExifWriteUInt16(t.ValueData, t.ValueIndex + (ElementIndex << 1), (ushort)Value);
                }
                else
                {
                    ExifWriteUInt32(t.ValueData, t.ValueIndex + (ElementIndex << 2), Value);
                }
                Success = true;
            }
            return (Success);
        }


        private bool GetTagValueWithIdCode(ExifTag TagSpec, out string Value, ushort CodePage)
        {
            TagItem t;
            bool Success = false, IsUtf16Coded = false, IsAsciiCoded = false;
            int i, j;

            Value = null;
            if (GetTagItem(TagSpec, out t) && (t.TagType == ExifTagType.Undefined) && (t.ValueCount >= IdCodeLength))
            {
                if (CompareArrays(t.ValueData, t.ValueIndex, IdCodeUtf16))
                {
                    IsUtf16Coded = true;
                }
                else if (CompareArrays(t.ValueData, t.ValueIndex, IdCodeAscii) || CompareArrays(t.ValueData, t.ValueIndex, IdCodeDefault))
                {
                    IsAsciiCoded = true;
                }

                i = t.ValueCount - IdCodeLength;
                j = t.ValueIndex + IdCodeLength;
                if (IsUtf16Coded)
                {
                    // The parameter "CodePage" is ignored in this case.
                    // Remove all null terminating characters. Here a null terminating character consists of 2 zero-bytes.
                    while ((i >= 2) && (t.ValueData[j + i - 2] == 0) && (t.ValueData[j + i - 1] == 0))
                    {
                        i -= 2;
                    }
                    if (ByteOrder == ExifByteOrder.BigEndian)
                    {
                        CodePage = 1201; // UTF16BE
                    }
                    else CodePage = 1200; // UTF16LE
                    Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
                    Success = true;
                }
                else if (IsAsciiCoded)
                {
                    // Remove all null terminating characters.
                    while ((i >= 1) && (t.ValueData[j + i - 1] == 0))
                    {
                        i--;
                    }
                    if ((CodePage == 1200) || (CodePage == 1201)) // UTF16LE or UTF16BE
                    {
                        CodePage = 20127; // Ignore parameter "CodePage" and overwrite it with US ASCII
                    }
                    Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
                    Success = true;
                }
            }

            return (Success);
        }


        private bool SetTagValueWithIdCode(ExifTag TagSpec, string Value, ushort CodePage)
        {
            int TotalByteCount, StrByteLen;
            TagItem t;
            bool Success = false;
            byte[] StringAsByteArray, RequiredIdCode;

            if ((CodePage == 1200) && (ByteOrder == ExifByteOrder.BigEndian))
            {
                CodePage = 1201; // Set code page to UTF16BE
            }
            if ((CodePage == 1201) && (ByteOrder == ExifByteOrder.LittleEndian))
            {
                CodePage = 1200; // Set code page to UTF16LE
            }
            StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
            StrByteLen = StringAsByteArray.Length;
            TotalByteCount = IdCodeLength + StrByteLen; // The ID code is a 8 byte header
            t = PrepareTagForCompleteWriting(TagSpec, ExifTagType.Undefined, TotalByteCount);
            if (t != null)
            {
                if ((CodePage == 1200) || (CodePage == 1201))
                {
                    RequiredIdCode = IdCodeUtf16;
                }
                else RequiredIdCode = IdCodeAscii;
                Array.Copy(RequiredIdCode, 0, t.ValueData, t.ValueIndex, IdCodeLength);
                Array.Copy(StringAsByteArray, 0, t.ValueData, t.ValueIndex + IdCodeLength, StrByteLen);
                Success = true;
            }
            return (Success);
        }


        private bool ReadURatElement(TagItem t, int ElementIndex, out uint Numer, out uint Denom)
        {
            bool Success = false;
            int i;

            if ((ElementIndex >= 0) && (ElementIndex < t.ValueCount))
            {
                if ((t.TagType == ExifTagType.SRational) || (t.TagType == ExifTagType.URational))
                {
                    i = t.ValueIndex + (ElementIndex << 3);
                    Numer = ExifReadUInt32(t.ValueData, i);
                    Denom = ExifReadUInt32(t.ValueData, i + 4);
                    Success = true;
                }
                else
                {
                    Numer = 0;
                    Denom = 0;
                }
            }
            else
            {
                Numer = 0;
                Denom = 0;
            }
            return (Success);
        }


        private bool WriteURatElement(TagItem t, int ElementIndex, uint Numer, uint Denom)
        {
            int i;
            bool Success = false;

            if (t != null)
            {
                i = t.ValueIndex + (ElementIndex << 3);
                ExifWriteUInt32(t.ValueData, i, Numer);
                ExifWriteUInt32(t.ValueData, i + 4, Denom);
                Success = true;
            }
            return (Success);
        }


        private bool GetTagItem(ExifTag TagSpec, out TagItem t)
        {
            ExifIfd Ifd = ExtractIfd(TagSpec);
            if ((uint)Ifd < ExifIfdCount)
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                return (IfdTagTable.TryGetValue(ExtractTagId(TagSpec), out t));
            }
            else
            {
                t = null;
                return (false);
            }
        }


        // Prepare tag for writing a single array item. If the tag does not exist it is created.
        //
        // Parameters:
        //  "TagType":    Tag type.
        //  "ArrayIndex": Zero-based array index of the value to be written. If the array index
        //                is outside the current tag data, the tag data is automatically enlarged.
        //                If it is necessary to reallocate the tag memory, the old content is copied to the new memory.
        private TagItem PrepareTagForArrayItemWriting(ExifTag TagSpec, ExifTagType TagType, int ArrayIndex)
        {
            TagItem t = null;
            ExifIfd Ifd = ExtractIfd(TagSpec);
            if (((uint)Ifd < ExifIfdCount) && ((uint)ArrayIndex < MaxTagValueCount))
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                if (IfdTagTable.TryGetValue(ExtractTagId(TagSpec), out t))
                {
                    int ValueCount = t.ValueCount;
                    if (ArrayIndex >= ValueCount)
                    {
                        ValueCount = ArrayIndex + 1;
                    }
                    t.SetTagTypeAndValueCount(TagType, ValueCount, true);
                }
                else
                {
                    int ValueCount = ArrayIndex + 1;
                    t = new TagItem(TagSpec, TagType, ValueCount);
                    IfdTagTable.Add(t.TagId, t);
                }
            }
            return (t);
        }


        // Prepare tag for a complete writing of the tag content. The tag content is undefined after calling this method.
        //
        // Parameters:
        //  "TagType":    New tag type. The tag type must be valid, because it is not checked in this method!
        //  "ValueCount": New number of array elements that the tag should contain. Must be equal or greater than 1.
        //                If it is necessary to reallocate the tag memory, the old content is not copied.
        private TagItem PrepareTagForCompleteWriting(ExifTag TagSpec, ExifTagType TagType, int ValueCount)
        {
            TagItem t = null;

            ExifIfd Ifd = ExtractIfd(TagSpec);
            if (((uint)Ifd < ExifIfdCount) && ((uint)ValueCount <= MaxTagValueCount))
            {
                Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
                if (IfdTagTable.TryGetValue(ExtractTagId(TagSpec), out t))
                {
                    t.SetTagTypeAndValueCount(TagType, ValueCount, false);
                }
                else
                {
                    t = new TagItem(TagSpec, TagType, ValueCount);
                    IfdTagTable.Add(t.TagId, t);
                }
#if DEBUG
                int k = t.ValueIndex + t.AllocatedByteCount;
                for (int i = t.ValueIndex; i < k; i++)
                {
                    t.ValueData[i] = 0xcc;
                }
#endif
            }
            return (t);
        }


        private static int CalculateTwoDigitDecNumber(byte[] ByteArr, int Index)
        {
            int d1, d2, Value;

            Value = -1;
            d1 = ByteArr[Index];
            d2 = ByteArr[Index + 1];
            if ((d1 >= '0') && (d1 <= '9') && (d2 >= '0') && (d2 <= '9'))
            {
                Value = (d1 - 0x30) * 10 + (d2 - 0x30);
            }
            return (Value);
        }


        // "Value": Number from 0 to 99.
        private static void ConvertTwoDigitNumberToByteArr(byte[] ByteArr, ref int Index, int Value)
        {
            ByteArr[Index] = (byte)((Value / 10) + 0x30);
            Index++;
            ByteArr[Index] = (byte)((Value % 10) + 0x30);
            Index++;
        }


        private void ClearIfd_Unchecked(ExifIfd Ifd)
        {
            TagTable[(uint)Ifd].Clear();
        }


        private void RemoveAllTagsFromIfdPrimaryData()
        {
            if (ImageType == ImageType.Tiff)
            {
                // In TIFF images the IFD PrimaryData contains internal tags which must not be removed otherwise
                // the image will be damaged.
                // In TIFF images the XMP and IPTC block are stored as tags within the EXIF block. Therefore these
                // tags are not removed, too.
                Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];
                List<ExifTagId> TagIdsToBeRemoved = new List<ExifTagId>(PrimaryDataIfdTable.Count);
                foreach (ExifTagId tid in PrimaryDataIfdTable.Keys)
                {
                    if (!IsInternalTiffTag(tid) && !IsMetaDataTiffTag(tid))
                    {
                        TagIdsToBeRemoved.Add(tid);
                    }
                }
                foreach (ExifTagId tid in TagIdsToBeRemoved)
                {
                    PrimaryDataIfdTable.Remove(tid);
                }
            }
            else
            {
                ClearIfd_Unchecked(ExifIfd.PrimaryData);
            }
        }


        private void RemoveThumbnailImage_Internal()
        {
            ThumbnailImage = null;
            ThumbnailStartIndex = 0;
            ThumbnailByteCount = 0;
        }


        private bool GetGpsCoordinateHelper(out GeoCoordinate Value, ExifTag ValueTag, ExifTag RefTag, char Cp1, char Cp2)
        {
            bool Success = false;
            ExifRational Deg, Min, Sec;
            string Ref;
            char CardinalPoint;

            if (GetTagValue(ValueTag, out Deg, 0) && Deg.IsValid() &&
                GetTagValue(ValueTag, out Min, 1) && Min.IsValid() &&
                GetTagValue(ValueTag, out Sec, 2) && Sec.IsValid() &&
                GetTagValue(RefTag, out Ref, StrCoding.Utf8) && (Ref.Length == 1))
            {
                CardinalPoint = Ref[0];
                if ((CardinalPoint == Cp1) || (CardinalPoint == Cp2))
                {
                    Value.Degree = ExifRational.ToDecimal(Deg);
                    Value.Minute = ExifRational.ToDecimal(Min);
                    Value.Second = ExifRational.ToDecimal(Sec);
                    Value.CardinalPoint = CardinalPoint;
                    Success = true;
                }
                else Value = new GeoCoordinate();
            }
            else Value = new GeoCoordinate();
            return (Success);
        }


        private bool SetGpsCoordinateHelper(GeoCoordinate Value, ExifTag ValueTag, ExifTag RefTag, char Cp1, char Cp2)
        {
            bool Success = false;

            ExifRational Deg = ExifRational.FromDecimal(Value.Degree);
            ExifRational Min = ExifRational.FromDecimal(Value.Minute);
            ExifRational Sec = ExifRational.FromDecimal(Value.Second);
            if (SetTagValue(ValueTag, Deg, ExifTagType.URational, 0) &&
                SetTagValue(ValueTag, Min, ExifTagType.URational, 1) &&
                SetTagValue(ValueTag, Sec, ExifTagType.URational, 2) &&
                ((Value.CardinalPoint == Cp1) || (Value.CardinalPoint == Cp2)) &&
                SetTagValue(RefTag, Value.CardinalPoint.ToString(), StrCoding.Utf8))
            {
                Success = true;
            }
            return (Success);
        }


        private bool GetDateAndTimeWithMillisecHelper(out DateTime Value, ExifTag DateAndTimeTag, ExifTag MillisecTag)
        {
            bool Success = false;
            if (GetTagValue(DateAndTimeTag, out Value))
            {
                Success = true;
                if (GetTagValue(MillisecTag, out string SubSec, StrCoding.Utf8))
                {
                    string s = SubSec;
                    int len = s.Length;
                    if (len > 3) s = s.Substring(0, 3);
                    if (int.TryParse(s, out int MilliSec) && (MilliSec >= 0))
                    {
                        if (len == 1) MilliSec *= 100;
                        else if (len == 2) MilliSec *= 10;
                        Value = Value.AddMilliseconds(MilliSec);
                    }
                }
            }
            return (Success);
        }


        private bool SetDateAndTimeWithMillisecHelper(DateTime Value, ExifTag DateAndTimeTag, ExifTag MillisecTag)
        {
            bool Success = false;
            if (SetTagValue(DateAndTimeTag, Value))
            {
                Success = true;
                int MilliSec = Value.Millisecond;
                if ((MilliSec != 0) || TagExists(MillisecTag))
                {
                    string s = MilliSec.ToString("000"); // Write exactly 3 decimal digits
                    Success = Success && SetTagValue(MillisecTag, s, StrCoding.Utf8);
                }
            }
            return (Success);
        }


        private static readonly ExifTagId[] InternalTiffTags = new ExifTagId[] {
      ExifTagId.ImageWidth, ExifTagId.ImageLength, ExifTagId.BitsPerSample,
      ExifTagId.Compression, ExifTagId.PhotometricInterpretation, ExifTagId.Threshholding, ExifTagId.CellWidth, ExifTagId.CellLength,
      ExifTagId.FillOrder, ExifTagId.StripOffsets, ExifTagId.SamplesPerPixel, ExifTagId.RowsPerStrip, ExifTagId.StripByteCounts,
      ExifTagId.MinSampleValue, ExifTagId.MaxSampleValue, ExifTagId.PlanarConfiguration, ExifTagId.FreeOffsets, ExifTagId.FreeByteCounts,
      ExifTagId.GrayResponseUnit, ExifTagId.GrayResponseCurve, ExifTagId.T4Options, ExifTagId.T6Options, ExifTagId.TransferFunction,
      ExifTagId.Predictor, ExifTagId.WhitePoint, ExifTagId.PrimaryChromaticities, ExifTagId.ColorMap, ExifTagId.HalftoneHints,
      ExifTagId.TileWidth, ExifTagId.TileLength, ExifTagId.TileOffsets, ExifTagId.TileByteCounts, ExifTagId.ExtraSamples,
      ExifTagId.SampleFormat, ExifTagId.SMinSampleValue, ExifTagId.SMaxSampleValue, ExifTagId.TransferRange,
      ExifTagId.YCbCrCoefficients, ExifTagId.YCbCrSubSampling, ExifTagId.YCbCrPositioning, ExifTagId.ReferenceBlackWhite,
      (ExifTagId)0x0200, (ExifTagId)0x0201, (ExifTagId)0x0202, (ExifTagId)0x0203, (ExifTagId)0x0205, (ExifTagId)0x0206,
      (ExifTagId)0x0207, (ExifTagId)0x0208, (ExifTagId)0x0209
      };
        private static BitArray InternalTiffTagsBitArray;


        private static void InitInternalTiffTags()
        {
            InternalTiffTagsBitArray = new BitArray(0x0215, false);
            int len = InternalTiffTagsBitArray.Length;
            foreach (ExifTagId TagId in InternalTiffTags)
            {
                int TagIdInt = (int)TagId;
                if (TagIdInt >= len)
                {
                    len = len << 1;
                    if (TagIdInt >= len) len = TagIdInt + 1;
                    InternalTiffTagsBitArray.Length = len;
                }
                InternalTiffTagsBitArray.Set(TagIdInt, true);
            }
        }


        // Only for TIFF images
        private static bool IsInternalTiffTag(ExifTagId TagId)
        {
            if (InternalTiffTagsBitArray == null)
            {
                InitInternalTiffTags();
            }
            int TagIdInt = (int)TagId;
            if (TagIdInt < InternalTiffTagsBitArray.Length)
            {
                return (InternalTiffTagsBitArray.Get(TagIdInt));
            }
            return (false);
        }


        // Only for TIFF images
        private static bool IsMetaDataTiffTag(ExifTagId TagId)
        {
            return ((TagId == ExifTagId.XmpMetadata) || (TagId == ExifTagId.IptcMetadata));
        }


        private void SwapByteOrderOfTagData(ExifIfd Ifd, TagItem t)
        {
            // Special tag IDs
            if (Ifd == ExifIfd.PrimaryData)
            {
                // There are meta data tags where the byte order must not be changed. These tags are only used in TIFF files.
                if (IsMetaDataTiffTag(t.TagId)) return;
            }
            else if (Ifd == ExifIfd.PrivateData)
            {
                if ((t.TagId == ExifTagId.UserComment) && (t.TagType == ExifTagType.Undefined))
                {
                    // This tag is coded UTF16LE or UTF16BE depending on the byte order.
                    int k = t.ValueIndex + 8; // Skip 8 byte ID code which is not UTF16 coded
                    int Utf16CharCount = (t.ValueCount - 8) / 2;
                    for (int i = 0; i < Utf16CharCount; i++)
                    {
                        Swap2ByteValue(t.ValueData, k);
                        k += 2;
                    }
                    return;
                }
            }

            switch (t.TagType)
            {
                case ExifTagType.UShort:
                case ExifTagType.SShort:
                    int k = t.ValueIndex;
                    for (int i = 0; i < t.ValueCount; i++)
                    {
                        Swap2ByteValue(t.ValueData, k);
                        k += 2;
                    }
                    break;

                case ExifTagType.ULong:
                case ExifTagType.SLong:
                case ExifTagType.Float:
                    k = t.ValueIndex;
                    for (int i = 0; i < t.ValueCount; i++)
                    {
                        Swap4ByteValue(t.ValueData, k);
                        k += 4;
                    }
                    break;

                case ExifTagType.URational:
                case ExifTagType.SRational:
                    k = t.ValueIndex;
                    for (int i = 0; i < t.ValueCount; i++)
                    {
                        Swap4ByteValue(t.ValueData, k); // Numerator of the rational number
                        k += 4;
                        Swap4ByteValue(t.ValueData, k); // Denominator of the rational number
                        k += 4;
                    }
                    break;

                case ExifTagType.Double:
                    k = t.ValueIndex;
                    for (int i = 0; i < t.ValueCount; i++)
                    {
                        Array.Reverse(t.ValueData, k, 8);
                        k += 8;
                    }
                    break;
            }
        }


        private void Swap2ByteValue(byte[] b, int i)
        {
            byte k = b[i];
            b[i] = b[i + 1];
            b[i + 1] = k;
        }


        private void Swap4ByteValue(byte[] b, int i)
        {
            byte k = b[i];
            b[i] = b[i + 3];
            b[i + 3] = k;
            k = b[i + 1];
            b[i + 1] = b[i + 2];
            b[i + 2] = k;
        }


        private TagItem CopyTagItemDeeply(ExifIfd Ifd, TagItem TagItemToBeCopied, bool SwapByteOrder)
        {
            TagItem NewTag = new TagItem(TagItemToBeCopied.TagId, TagItemToBeCopied.TagType, TagItemToBeCopied.ValueCount);
            Array.Copy(TagItemToBeCopied.ValueData, TagItemToBeCopied.ValueIndex, NewTag.ValueData, 0, NewTag.ByteCount);
            if (SwapByteOrder)
            {
                SwapByteOrderOfTagData(Ifd, NewTag);
            }
            return (NewTag);
        }


        private class TagItem
        {
            public ExifTagId TagId;
            public ExifTagType TagType;
            public int ValueCount; // Number of values of the tag. In general this is not the number of bytes of the tag!
            public byte[] ValueData; // Array which contains the tag data. There may be other data stored in this array.
            public int ValueIndex; // Array index from which the tag data starts.
            public int AllocatedByteCount; // Number of bytes which are allocated for the tag data.
                                           // This number can be greater than the required number of bytes.
            public int ByteCount // The number of bytes required to store the tag data, i. e. the number of bytes actually used for the tag data.
            {
                get
                {
                    return (ExifData.GetTagByteCount(TagType, ValueCount));
                }
            }
            public int OriginalDataOffset; // Offset of outsourced data in the image file. 0 = Tag does not have outsourced data or
                                           // tag was created after loading.


            public TagItem(ExifTag TagSpec, ExifTagType _TagType, int _ValueCount) :
              this(ExtractTagId(TagSpec), _TagType, _ValueCount)
            {
            }


            public TagItem(ExifTagId _TagId, ExifTagType _TagType, int _ValueCount)
            {
                TagId = _TagId;
                TagType = _TagType;
                ValueCount = _ValueCount;
                int RequiredByteCount = GetTagByteCount(_TagType, _ValueCount);
                ValueData = AllocTagMemory(RequiredByteCount);
                AllocatedByteCount = ValueData.Length;
                ValueIndex = 0;
            }


            public TagItem(ExifTagId _TagId, ExifTagType _TagType, int _ValueCount, byte[] _ValueArray, int _ValueIndex,
              int _AllocatedByteCount, int _OriginalDataOffset = 0)
            {
                TagId = _TagId;
                TagType = _TagType;
                ValueCount = _ValueCount;
                ValueData = _ValueArray;
                ValueIndex = _ValueIndex;
                AllocatedByteCount = _AllocatedByteCount;
                OriginalDataOffset = _OriginalDataOffset;
            }


            public static byte[] AllocTagMemory(int RequiredByteCount)
            {
                const int MinByteCount = 32;
                int NewByteCount = RequiredByteCount;
                if (NewByteCount < MinByteCount)
                {
                    NewByteCount = MinByteCount;
                }
                return (new byte[NewByteCount]);
            }


            // Set tag type and value count and reallocate the tag memory if required.
            public void SetTagTypeAndValueCount(ExifTagType _TagType, int _ValueCount, bool KeepExistingData)
            {
                int RequiredByteCount = GetTagByteCount(_TagType, _ValueCount);
                if (AllocatedByteCount < RequiredByteCount)
                {
                    uint NewByteCount = (uint)AllocatedByteCount << 1; // Double the allocated byte count
                    if (NewByteCount > int.MaxValue) NewByteCount = int.MaxValue;
                    else if (NewByteCount < (uint)RequiredByteCount) NewByteCount = (uint)RequiredByteCount;
                    byte[] NewTagData = AllocTagMemory((int)NewByteCount);
                    if (KeepExistingData)
                    {
                        Array.Copy(ValueData, ValueIndex, NewTagData, 0, AllocatedByteCount);
                    }
                    AllocatedByteCount = NewTagData.Length;
                    ValueData = NewTagData;
                    ValueIndex = 0;
                }
                TagType = _TagType;
                ValueCount = _ValueCount;
            }
        }


        private class FlexArray
        {
            public FlexArray(int Capacity)
            {
                _Buffer = new byte[Capacity];
                _Length = 0;
            }


            public byte this[int i]
            {
                get
                {
                    return _Buffer[i];
                }
                set
                {
                    _Buffer[i] = value;
                }
            }


            private byte[] _Buffer;
            public byte[] Buffer
            {
                get { return (_Buffer); }
            }


            private int _Length;
            public int Length
            {
                get { return (_Length); }
                set
                {
                    if (value > _Buffer.Length)
                    {
                        uint k = (uint)_Buffer.Length;
                        k = k << 1;
                        if (k > int.MaxValue)
                        {
                            k = int.MaxValue;
                        }
                        else if ((uint)value > k)
                        {
                            k = (uint)value;
                        }
                        Array.Resize(ref _Buffer, (int)k);
                    }
                    _Length = value;
                }
            }
        }
        #endregion
    }


    // IFD Constants. These constants are used as array indexes for the array "TagTable".
    //IFD 常量。 这些常量用作数组“TagTable”的数组索引
    public enum ExifIfd
    {
        /// <summary>
        /// 主要数据
        /// </summary>
        PrimaryData = 0,
        /// <summary>
        /// 私有数据
        /// </summary>
        PrivateData = 1,
        /// <summary>
        /// 全球定位系统信息数据
        /// </summary>
        GpsInfoData = 2,
        /// <summary>
        /// 互操作性
        /// </summary>
        Interoperability = 3,
        /// <summary>
        /// 缩略图数据
        /// </summary>
        ThumbnailData = 4
    }


    // Tag specification constants: Composition of IFD and tag ID.
    //标签规范常量:IFD 和标签 ID 的组成。
    public enum ExifTag
    {
        // IFD Primary Data
        NewSubfileType = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.NewSubfileType,
        SubfileType = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.SubfileType,
        ImageWidth = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ImageWidth,
        ImageLength = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ImageLength,
        BitsPerSample = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.BitsPerSample,
        //压缩( ExifTagType.UShort)
        Compression = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Compression,
        PhotometricInterpretation = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PhotometricInterpretation,
        Threshholding = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Threshholding,
        CellWidth = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.CellWidth,
        CellLength = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.CellLength,
        FillOrder = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.FillOrder,
        DocumentName = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.DocumentName,
        //图像描述(标题&主题)(StrCoding.iUsAscii/Utf8)
        ImageDescription = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ImageDescription,
        //照相机制造商    (StrCoding.iUsAscii/Utf8)
        Make = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Make,
        //照相机型号 ( StrCoding.iUsAscii/Utf8)
        Model = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Model,
        StripOffsets = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.StripOffsets,
        //设置图像方向(可以定义 90、180 或 270 度的顺时针旋转和图像矩阵的反射,前提是图像查看器在绘制图像时考虑到此 EXIF 标签)
        Orientation = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Orientation,
        SamplesPerPixel = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.SamplesPerPixel,
        RowsPerStrip = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.RowsPerStrip,
        StripByteCounts = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.StripByteCounts,
        MinSampleValue = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.MinSampleValue,
        MaxSampleValue = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.MaxSampleValue,
        XResolution = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XResolution,
        YResolution = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.YResolution,
        PlanarConfiguration = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PlanarConfiguration,
        PageName = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PageName,
        XPosition = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XPosition,
        YPosition = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.YPosition,
        FreeOffsets = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.FreeOffsets,
        FreeByteCounts = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.FreeByteCounts,
        GrayResponseUnit = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.GrayResponseUnit,
        GrayResponseCurve = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.GrayResponseCurve,
        T4Options = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.T4Options,
        T6Options = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.T6Options,
        //分辨率单位(如果图像分辨率未知,则指定 2(英寸))(ExifTagType.UShort)
        ResolutionUnit = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ResolutionUnit,
        PageNumber = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PageNumber,
        TransferFunction = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TransferFunction,
        //程序名称  (StrCoding.iUsAscii/Utf8)
        Software = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Software,
        //上次更改图像的日期(修改日期)。
        DateTime = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.DateTime,
        //作者 (StrCoding.iUsAscii/Utf8)
        Artist = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Artist,
        HostComputer = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.HostComputer,
        Predictor = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Predictor,
        WhitePoint = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.WhitePoint,
        PrimaryChromaticities = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PrimaryChromaticities,
        ColorMap = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ColorMap,
        HalftoneHints = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.HalftoneHints,
        TileWidth = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TileWidth,
        TileLength = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TileLength,
        TileOffsets = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TileOffsets,
        TileByteCounts = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TileByteCounts,
        InkSet = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.InkSet,
        InkNames = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.InkNames,
        NumberOfInks = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.NumberOfInks,
        DotRange = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.DotRange,
        TargetPrinter = 0x0151,
        ExtraSamples = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ExtraSamples,
        SampleFormat = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.SampleFormat,
        SMinSampleValue = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.SMinSampleValue,
        SMaxSampleValue = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.SMaxSampleValue,
        TransferRange = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TransferRange,
        YCbCrCoefficients = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.YCbCrCoefficients,
        YCbCrSubSampling = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.YCbCrSubSampling,
        YCbCrPositioning = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.YCbCrPositioning,
        ReferenceBlackWhite = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ReferenceBlackWhite,
        XmpMetadata = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XmpMetadata,
        Copyright = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Copyright,
        IptcMetadata = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.IptcMetadata,
        ExifIfdPointer = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ExifIfdPointer,
        GpsInfoIfdPointer = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.GpsInfoIfdPointer,
        XpTitle = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XpTitle,
        XpComment = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XpComment,
        XpAuthor = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XpAuthor,
        XpKeywords = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XpKeywords,
        XpSubject = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.XpSubject,
        PrimaryDataPadding = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Padding,

        // IFD Private Data
        //曝光时间(ExifTagType.URational)
        ExposureTime = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureTime,
        //光圈值( ExifTagType.URational)
        FNumber = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FNumber,
        //曝光程序(ExifTagType.UShort)
        ExposureProgram = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureProgram,
        SpectralSensitivity = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SpectralSensitivity,
        //ISO速度(ExifTagType.UShort)
        IsoSpeedRatings = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.IsoSpeedRatings,
        PhotographicSensitivity = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.PhotographicSensitivity,
        Oecf = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Oecf,
        SensitivityType = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SensitivityType,
        StandardOutputSensitivity = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.StandardOutputSensitivity,
        RecommendedExposureIndex = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.RecommendedExposureIndex,
        IsoSpeed = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.IsoSpeed,
        IsoSpeedLatitudeyyy = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.IsoSpeedLatitudeyyy,
        IsoSpeedLatitudezzz = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.IsoSpeedLatitudezzz,
        //图像EXIF版本(StrCoding.Utf8)
        ExifVersion = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExifVersion,
        //拍摄日期/修改日期
        DateTimeOriginal = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DateTimeOriginal,
        //图像数字化的日期和时间。
        DateTimeDigitized = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DateTimeDigitized,
        OffsetTime = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.OffsetTime,
        OffsetTimeOriginal = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.OffsetTimeOriginal,
        OffsetTimeDigitized = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.OffsetTimeDigitized,
        ComponentsConfiguration = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ComponentsConfiguration,
        //压缩的位/像素( ExifTagType.URational)
        CompressedBitsPerPixel = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.CompressedBitsPerPixel,
        ShutterSpeedValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ShutterSpeedValue,
        ApertureValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ApertureValue,
        //亮度(ExifTagType.SRational)
        BrightnessValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.BrightnessValue,
        //曝光补偿(ExifTagType.SRational)
        ExposureBiasValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureBiasValue,
        //最大光圈(ExifTagType.URational)
        MaxApertureValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MaxApertureValue,
        //目标距离(ExifTagType.URational)
        SubjectDistance = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectDistance,
        //测光模式(ExifTagType.UShort)
        MeteringMode = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MeteringMode,
        //光源(ExifTagType.UShort)
        LightSource = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LightSource,
        //闪光灯模式( ExifTagType.UShort)
        Flash = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Flash,
        //焦距(ExifTagType.URational)
        FocalLength = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalLength,
        SubjectArea = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectArea,
        MakerNote = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MakerNote,
        //用户评论(备注)  ( StrCoding.Utf8)
        UserComment = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.UserComment,
        SubsecTime = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubsecTime,
        SubsecTimeOriginal = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubsecTimeOriginal,
        SubsecTimeDigitized = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubsecTimeDigitized,
        FlashPixVersion = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FlashPixVersion,
        ColorSpace = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ColorSpace,
        PixelXDimension = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.PixelXDimension,
        PixelYDimension = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.PixelYDimension,
        RelatedSoundFile = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.RelatedSoundFile,
        InteroperabilityIfdPointer = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.InteroperabilityIfdPointer,
        //闪光灯能量( ExifTagType.URational)
        FlashEnergy = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FlashEnergy,
        SpatialFrequencyResponse = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SpatialFrequencyResponse,
        FocalPlaneXResolution = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalPlaneXResolution,
        FocalPlaneYResolution = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalPlaneYResolution,
        FocalPlaneResolutionUnit = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalPlaneResolutionUnit,
        SubjectLocation = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectLocation,
        ExposureIndex = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureIndex,
        SensingMethod = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SensingMethod,
        FileSource = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FileSource,
        SceneType = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SceneType,
        CfaPattern = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.CfaPattern,
        CustomRendered = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.CustomRendered,
        ExposureMode = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureMode,
        //白平衡(ExifTagType.UShort)
        WhiteBalance = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.WhiteBalance,
        //数字缩放(数码变焦倍率)( ExifTagType.URational)
        DigitalZoomRatio = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DigitalZoomRatio,
        //35mm焦距( ExifTagType.UShort)
        FocalLengthIn35mmFilm = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalLengthIn35mmFilm,
        SceneCaptureType = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SceneCaptureType,
        GainControl = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.GainControl,
        //对比度(ExifTagType.UShort)
        Contrast = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Contrast,
        //饱和度( ExifTagType.UShort)
        Saturation = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Saturation,
        //清晰度( ExifTagType.UShort)
        Sharpness = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Sharpness,
        DeviceSettingDescription = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DeviceSettingDescription,
        SubjectDistanceRange = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectDistanceRange,
        //图像ID(StrCoding.iUsAscii/Utf8)
        ImageUniqueId = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ImageUniqueId,
        CameraOwnerName = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.CameraOwnerName,
        BodySerialNumber = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.BodySerialNumber,
        LensSpecification = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LensSpecification,
        LensMake = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LensMake,
        LensModel = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LensModel,
        LensSerialNumber = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LensSerialNumber,
        PrivateDataPadding = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Padding,
        OffsetSchema = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.OffsetSchema,

        // IFD GPS Data
        GpsVersionId = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsVersionId,
        GpsLatitudeRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsLatitudeRef,
        GpsLatitude = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsLatitude,
        GpsLongitudeRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsLongitudeRef,
        GpsLongitude = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsLongitude,
        GpsAltitudeRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsAltitudeRef,
        GpsAltitude = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsAltitude,
        GpsTimeStamp = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsTimestamp,
        GpsSatellites = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsSatellites,
        GpsStatus = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsStatus,
        GpsMeasureMode = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsMeasureMode,
        GpsDop = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDop,
        GpsSpeedRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsSpeedRef,
        GpsSpeed = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsSpeed,
        GpsTrackRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsTrackRef,
        GpsTrack = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsTrack,
        GpsImgDirectionRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsImgDirectionRef,
        GpsImgDirection = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsImgDirection,
        GpsMapDatum = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsMapDatum,
        GpsDestLatitudeRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestLatitudeRef,
        GpsDestLatitude = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestLatitude,
        GpsDestLongitudeRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestLongitudeRef,
        GpsDestLongitude = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestLongitude,
        GpsDestBearingRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestBearingRef,
        GpsDestBearing = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestBearing,
        GpsDestDistanceRef = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestDistanceRef,
        GpsDestDistance = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDestDistance,
        GpsProcessingMethod = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsProcessingMethod,
        GpsAreaInformation = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsAreaInformation,
        //来自 satellite 的 GPS 日期。
        GpsDateStamp = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDateStamp,
        GpsDifferential = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDifferential,
        GpsHPositioningError = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsHPositioningError,

        // IFD Interoperability
        InteroperabilityIndex = (ExifIfd.Interoperability << ExifData.IfdShift) | ExifTagId.InteroperabilityIndex,
        InteroperabilityVersion = (ExifIfd.Interoperability << ExifData.IfdShift) | ExifTagId.InteroperabilityVersion,

        // IFD Thumbnail Data
        ThumbnailImageWidth = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.ImageWidth,
        ThumbnailImageLength = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.ImageLength,
        ThumbnailCompression = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.Compression,
        ThumbnailXResolution = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.XResolution,
        ThumbnailYResolution = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.YResolution,
        ThumbnailResolutionUnit = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.ResolutionUnit,
        ThumbnailOrientation = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.Orientation,
        JpegInterchangeFormat = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.JpegInterchangeFormat,
        JpegInterchangeFormatLength = (ExifIfd.ThumbnailData << ExifData.IfdShift) | ExifTagId.JpegInterchangeFormatLength,
    }


    // Tag ID constants.
    //标记 ID 常量。
    public enum ExifTagId
    {
        // IFD Primary Data, some tags are also used in IFD Thumbnail Data
        NewSubfileType = 0x00FE,
        SubfileType = 0x00FF,
        ImageWidth = 0x0100,
        ImageLength = 0x0101,
        BitsPerSample = 0x0102,
        Compression = 0x0103,
        PhotometricInterpretation = 0x0106,
        Threshholding = 0x0107,
        CellWidth = 0x0108,
        CellLength = 0x0109,
        FillOrder = 0x010a,
        DocumentName = 0x010d,
        ImageDescription = 0x010e,
        Make = 0x010f,
        Model = 0x0110,
        StripOffsets = 0x0111,
        Orientation = 0x0112,
        SamplesPerPixel = 0x0115,
        RowsPerStrip = 0x0116,
        StripByteCounts = 0x0117,
        MinSampleValue = 0x0118,
        MaxSampleValue = 0x0119,
        XResolution = 0x011a,
        YResolution = 0x011b,
        PlanarConfiguration = 0x011c,
        PageName = 0x011d,
        XPosition = 0x011e,
        YPosition = 0x011f,
        FreeOffsets = 0x0120,
        FreeByteCounts = 0x0121,
        GrayResponseUnit = 0x0122,
        GrayResponseCurve = 0x0123,
        T4Options = 0x0124,
        T6Options = 0x0125,
        ResolutionUnit = 0x0128,
        PageNumber = 0x0129,
        TransferFunction = 0x012d,
        Software = 0x0131,
        DateTime = 0x0132,
        Artist = 0x013b,
        HostComputer = 0x013c,
        Predictor = 0x013d,
        WhitePoint = 0x013e,
        PrimaryChromaticities = 0x013f,
        ColorMap = 0x0140,
        HalftoneHints = 0x0141,
        TileWidth = 0x0142,
        TileLength = 0x0143,
        TileOffsets = 0x0144,
        TileByteCounts = 0x0145,
        InkSet = 0x014c,
        InkNames = 0x014d,
        NumberOfInks = 0x014e,
        DotRange = 0x0150,
        TargetPrinter = 0x0151,
        ExtraSamples = 0x0152,
        SampleFormat = 0x0153,
        SMinSampleValue = 0x0154,
        SMaxSampleValue = 0x0155,
        TransferRange = 0x0156,
        YCbCrCoefficients = 0x0211,
        YCbCrSubSampling = 0x0212,
        YCbCrPositioning = 0x0213,
        ReferenceBlackWhite = 0x0214,
        XmpMetadata = 0x02bc, // XMP block within a TIFF file
        Copyright = 0x8298,
        IptcMetadata = 0x83bb, // IPTC block within a TIFF file
        ExifIfdPointer = 0x8769,
        GpsInfoIfdPointer = 0x8825,
        XpTitle = 0x9c9b,
        XpComment = 0x9c9c,
        XpAuthor = 0x9c9d,
        XpKeywords = 0x9c9e,
        XpSubject = 0x9c9f,
        Padding = 0xea1c,

        // IFD Thumbnail Data
        JpegInterchangeFormat = 0x0201,
        JpegInterchangeFormatLength = 0x0202,

        // IFD Private Data
        ExposureTime = 0x829a,
        FNumber = 0x829d,
        ExposureProgram = 0x8822,
        SpectralSensitivity = 0x8824,
        IsoSpeedRatings = 0x8827,
        PhotographicSensitivity = 0x8827, // Tag was renamed from "IsoSpeedRatings" to this name
        Oecf = 0x8828,
        SensitivityType = 0x8830,
        StandardOutputSensitivity = 0x8831,
        RecommendedExposureIndex = 0x8832,
        IsoSpeed = 0x8833,
        IsoSpeedLatitudeyyy = 0x8834,
        IsoSpeedLatitudezzz = 0x8835,
        ExifVersion = 0x9000,
        DateTimeOriginal = 0x9003,
        DateTimeDigitized = 0x9004,
        OffsetTime = 0x9010,
        OffsetTimeOriginal = 0x9011,
        OffsetTimeDigitized = 0x9012,
        ComponentsConfiguration = 0x9101,
        CompressedBitsPerPixel = 0x9102,
        ShutterSpeedValue = 0x9201,
        ApertureValue = 0x9202,
        BrightnessValue = 0x9203,
        ExposureBiasValue = 0x9204,
        MaxApertureValue = 0x9205,
        SubjectDistance = 0x9206,
        MeteringMode = 0x9207,
        LightSource = 0x9208,
        Flash = 0x9209,
        FocalLength = 0x920a,
        SubjectArea = 0x9214,
        MakerNote = 0x927c,
        UserComment = 0x9286,
        SubsecTime = 0x9290,
        SubsecTimeOriginal = 0x9291,
        SubsecTimeDigitized = 0x9292,
        FlashPixVersion = 0xa000,
        ColorSpace = 0xa001,
        PixelXDimension = 0xa002,
        PixelYDimension = 0xa003,
        RelatedSoundFile = 0xa004,
        InteroperabilityIfdPointer = 0xa005,
        FlashEnergy = 0xa20b,
        SpatialFrequencyResponse = 0xa20c,
        FocalPlaneXResolution = 0xa20e,
        FocalPlaneYResolution = 0xa20f,
        FocalPlaneResolutionUnit = 0xa210,
        SubjectLocation = 0xa214,
        ExposureIndex = 0xa215,
        SensingMethod = 0xa217,
        FileSource = 0xa300,
        SceneType = 0xa301,
        CfaPattern = 0xa302,
        CustomRendered = 0xa401,
        ExposureMode = 0xa402,
        WhiteBalance = 0xa403,
        DigitalZoomRatio = 0xa404,
        FocalLengthIn35mmFilm = 0xa405,
        SceneCaptureType = 0xa406,
        GainControl = 0xa407,
        Contrast = 0xa408,
        Saturation = 0xa409,
        Sharpness = 0xa40a,
        DeviceSettingDescription = 0xa40b,
        SubjectDistanceRange = 0xa40c,
        ImageUniqueId = 0xa420,
        CameraOwnerName = 0xa430,
        BodySerialNumber = 0xa431,
        LensSpecification = 0xa432,
        LensMake = 0xa433,
        LensModel = 0xa434,
        LensSerialNumber = 0xa435,
        OffsetSchema = 0xea1d,

        // IFD GPS Data
        GpsVersionId = 0x0000,
        GpsLatitudeRef = 0x0001,
        GpsLatitude = 0x0002,
        GpsLongitudeRef = 0x0003,
        GpsLongitude = 0x0004,
        GpsAltitudeRef = 0x0005,
        GpsAltitude = 0x0006,
        GpsTimestamp = 0x0007,
        GpsSatellites = 0x0008,
        GpsStatus = 0x0009,
        GpsMeasureMode = 0x000a,
        GpsDop = 0x000b,
        GpsSpeedRef = 0x000c,
        GpsSpeed = 0x000d,
        GpsTrackRef = 0x000e,
        GpsTrack = 0x000f,
        GpsImgDirectionRef = 0x0010,
        GpsImgDirection = 0x0011,
        GpsMapDatum = 0x0012,
        GpsDestLatitudeRef = 0x0013,
        GpsDestLatitude = 0x0014,
        GpsDestLongitudeRef = 0x0015,
        GpsDestLongitude = 0x0016,
        GpsDestBearingRef = 0x0017,
        GpsDestBearing = 0x0018,
        GpsDestDistanceRef = 0x0019,
        GpsDestDistance = 0x001a,
        GpsProcessingMethod = 0x001b,
        GpsAreaInformation = 0x001c,
        GpsDateStamp = 0x001d,
        GpsDifferential = 0x001e,
        GpsHPositioningError = 0x001f,

        // IFD Interoperability
        InteroperabilityIndex = 0x0001,
        InteroperabilityVersion = 0x0002
    }


    // Tag types. These constants are used as array indexes for the array "TypeByteCount".
    public enum ExifTagType
    {
        Byte = 1,
        Ascii = 2,
        UShort = 3,
        ULong = 4,
        URational = 5,
        SByte = 6, // Only for TIFFs
        Undefined = 7,
        SShort = 8, // Only for TIFFs
        SLong = 9,
        SRational = 10,
        Float = 11, // Only for TIFFs
        Double = 12 // Only for TIFFs
    }


    public enum StrCodingFormat
    {
        TypeAscii = 0x00000000, // Tag type is "ExifTagType.Ascii". A null terminating character is added when writing.
        TypeUndefined = 0x00010000, // Tag type is "ExifTagType.Undefined". A null terminating character is not present.
        TypeByte = 0x00020000, // Tag type is "ExifTagType.Byte". A null terminating character is added when writing.
        TypeUndefinedWithIdCode = 0x00030000 // Tag type is "ExifTagType.Undefined" and an additional ID code is present. A null terminating character is not present.
    };


    // Strings coding constants. In the lower 16 bits the code page number (1 to 65535) is coded.
    // In the higher 16 bits the EXIF tag type and additional infos are coded.
    public enum StrCoding
    {
        Utf8 = StrCodingFormat.TypeAscii | 65001, // Default value for all tags of type "ExifTagType.Ascii".
        UsAscii = StrCodingFormat.TypeAscii | 20127,
        WestEuropeanWin = StrCodingFormat.TypeAscii | 1252,
        UsAscii_Undef = StrCodingFormat.TypeUndefined | 20127, // For the tags "ExifVersion", "FlashPixVersion" and others.               
        Utf16Le_Byte = StrCodingFormat.TypeByte | 1200, // For the Microsoft tags "XpTitle", "XpComment", "XpAuthor", "XpKeywords" and "XpSubject".
        IdCode_Utf16 = StrCodingFormat.TypeUndefinedWithIdCode | 1200, // Default value for the tag "UserComment".
        IdCode_UsAscii = StrCodingFormat.TypeUndefinedWithIdCode | 20127,
        IdCode_WestEu = StrCodingFormat.TypeUndefinedWithIdCode | 1252
    }


    public enum ExifDateFormat
    {
        DateAndTime = 0,
        DateOnly = 1
    }


    public struct ExifRational
    {
        public uint Numer, Denom;
        public bool Sign; // true = Negative number or negative zero

        public ExifRational(int _Numer, int _Denom)
        {
            if (_Numer < 0)
            {
                Numer = (uint)-_Numer;
                Sign = true;
            }
            else
            {
                Numer = (uint)_Numer;
                Sign = false;
            }
            if (_Denom < 0)
            {
                Denom = (uint)-_Denom;
                Sign = !Sign;
            }
            else
            {
                Denom = (uint)_Denom;
            }
        }


        public ExifRational(uint _Numer, uint _Denom, bool _Sign = false)
        {
            Numer = _Numer;
            Denom = _Denom;
            Sign = _Sign;
        }


        public bool IsNegative()
        {
            return ((Sign == true) && (Numer != 0));
        }


        public bool IsPositive()
        {
            return ((Sign == false) && (Numer != 0));
        }


        public bool IsZero()
        {
            return (Numer == 0);
        }


        public bool IsValid()
        {
            return (Denom != 0);
        }


        public new string ToString()
        {
            string Sign = "";
            if (IsNegative()) Sign = "-";
            return (Sign + Numer.ToString() + '/' + Denom.ToString());
        }


        public static decimal ToDecimal(ExifRational Value)
        {
            decimal ret = ((decimal)Value.Numer) / Value.Denom;
            if (Value.Sign) ret = -ret;
            return (ret);
        }


        public static ExifRational FromDecimal(decimal Value)
        {
            ExifRational ret;
            uint denom = 1;
            decimal numer, tempNumer;

            if (Value >= 0)
            {
                numer = Value;
                ret.Sign = false;
            }
            else
            {
                numer = -Value;
                ret.Sign = true;
            }

            if (numer >= 1e9m)
            {
                numer = Math.Truncate(numer + 0.5m);
                if (numer <= uint.MaxValue)
                {
                    ret.Numer = (uint)numer;
                }
                else throw new OverflowException();
            }
            else
            {
                while (numer != decimal.Truncate(numer))
                {
                    tempNumer = numer * 10;
                    if ((denom <= 100000000) && (decimal.Truncate(tempNumer + 0.5m) < 1e9m))
                    {
                        numer = tempNumer; // The rounded numerator should be smaller than 10^9 if "Value" has decimals
                        denom *= 10; // The maximum possible power of 10 for the denominator is 10^9
                    }
                    else break;
                }
                ret.Numer = (uint)decimal.Truncate(numer + 0.5m);
            }
            ret.Denom = denom;

            // Reduce fraction by a power of 10 if possible
            while ((ret.Denom >= 10) && ((ret.Numer % 10) == 0))
            {
                ret.Numer /= 10;
                ret.Denom /= 10;
            }
            return (ret);
        }
    }


    public struct GeoCoordinate
    {
        public decimal Degree; // Integer number: 0 ≤ Degree ≤ 90 (for latitudes) or 180 (for longitudes)
        public decimal Minute; // Integer number: 0 ≤ Minute < 60
        public decimal Second; // Fraction number: 0 ≤ Second < 60
        public char CardinalPoint; // For latitudes: 'N' or 'S'; for longitudes: 'E' or 'W'


        public static decimal ToDecimal(GeoCoordinate Value)
        {
            decimal DecimalDegree = Value.Degree + Value.Minute / 60 + Value.Second / 3600;
            if ((Value.CardinalPoint == 'S') || (Value.CardinalPoint == 'W'))
            {
                DecimalDegree = -DecimalDegree;
            }
            return (DecimalDegree);
        }

        /// <summary>
        /// 从经纬度数值转换成度分秒
        /// </summary>
        /// <param name="Value">经纬度数据</param>
        /// <param name="IsLatitude">是否是纬度</param>
        /// <returns></returns>
        public static GeoCoordinate FromDecimal(decimal Value, bool IsLatitude)
        {
            decimal AbsValue;
            GeoCoordinate ret;

            if (Value >= 0)
            {
                ret.CardinalPoint = IsLatitude ? 'N' : 'E';
                AbsValue = Value;
            }
            else
            {
                ret.CardinalPoint = IsLatitude ? 'S' : 'W';
                AbsValue = -Value;
            }
            ret.Degree = decimal.Truncate(AbsValue);
            decimal frac = (AbsValue - ret.Degree) * 60;
            ret.Minute = decimal.Truncate(frac);
            ret.Second = (frac - ret.Minute) * 60;
            return (ret);
        }
    }


    // Byte order of the EXIF data
    public enum ExifByteOrder { LittleEndian, BigEndian };


    [Flags]
    public enum ExifLoadOptions
    {
        CreateEmptyBlock = 0x00000001
    };


    [Flags]
    public enum ExifSaveOptions
    {
        // There are no save options at the moment
    };


    public enum ExifErrCode
    {
        InternalError,
        ImageTypeIsNotSupported,
        ImageHasUnsupportedFeatures,
        InternalImageStructureIsWrong,
        ExifBlockHasIllegalContent, // Internal image structure is OK but the EXIF block has an illegal content
        ExifDataAreTooLarge,
        ImageTypesDoNotMatch
    };


    public enum ImageFileBlock
    {
        // The values must be enumerated consecutively
        Unknown = 0, // Internal value, do not use
        Exif = 1,
        Iptc = 2,
        Xmp = 3,
        JpegComment = 4,
        PngMetaData = 5,
        PngDateChanged = 6
    };

    public enum ImageType { Unknown = 0, Jpeg, Tiff, Png };

    public class ExifException : Exception
    {
        public ExifErrCode ErrorCode;

        public ExifException(ExifErrCode _ErrorCode)
        {
            ErrorCode = _ErrorCode;
        }

        public override string Message
        {
            get
            {
                string s;
                switch (ErrorCode)
                {
                    case ExifErrCode.ImageTypeIsNotSupported: s = "Image type is not supported!"; break;
                    case ExifErrCode.ImageHasUnsupportedFeatures: s = "Image has unsupported features!"; break;
                    case ExifErrCode.InternalImageStructureIsWrong: s = "Internal image structure is wrong!"; break;
                    case ExifErrCode.ExifBlockHasIllegalContent: s = "EXIF block has an illegal content!"; break;
                    case ExifErrCode.ExifDataAreTooLarge: s = "EXIF data are too large: 64 kB for JPEG files, 2 GB for TIFF files!"; break;
                    case ExifErrCode.ImageTypesDoNotMatch: s = "Image types do not match!"; break;
                    default: s = "Internal error!"; break;
                }
                return (s);
            }
        }
    }
}
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

ZcJgkb1g5qMe