using System.Collections;
using System.Collections.Generic;
public ImageType ImageType;
public int MakerNoteOriginalOffset { get; private set; }
public const int IfdShift = 16;
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();
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;
SourceFile = File.OpenRead(SourceFileNameWithPath);
DestFile = File.Open(DestFileNameWithPath, FileMode.Create);
Save(SourceFile, DestFile, SaveOptions);
File.Delete(SourceFileNameWithPath);
File.Move(DestFileNameWithPath, SourceFileNameWithPath);
File.Delete(DestFileNameWithPath);
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);
public bool GetTagValue(ExifTag TagSpec, out string Value, StrCoding Coding)
StrCodingFormat StringTagFormat = (StrCodingFormat)((uint)Coding & 0xFFFF0000);
ushort CodePage = (ushort)Coding;
if (StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode)
Success = GetTagValueWithIdCode(TagSpec, out Value, CodePage);
if (GetTagItem(TagSpec, out t))
if ((CodePage == 1200) || (CodePage == 1201))
while ((i >= 2) && (t.ValueData[j + i - 2] == 0) && (t.ValueData[j + i - 1] == 0))
while ((i >= 1) && (t.ValueData[j + i - 1] == 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);
public bool SetTagValue(ExifTag TagSpec, string Value, StrCoding Coding)
int TotalByteCount, StrByteLen, NullTermBytes;
byte[] StringAsByteArray;
StrCodingFormat StringTagFormat = (StrCodingFormat)((uint)Coding & 0xFFFF0000);
ushort CodePage = (ushort)Coding;
if (StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode)
Success = SetTagValueWithIdCode(TagSpec, Value, CodePage);
if (StringTagFormat == StrCodingFormat.TypeUndefined)
TagType = ExifTagType.Undefined;
else if (StringTagFormat == StrCodingFormat.TypeByte)
TagType = ExifTagType.Byte;
TagType = ExifTagType.Ascii;
if (TagType != ExifTagType.Undefined)
if ((CodePage == 1200) || (CodePage == 1201))
StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
StrByteLen = StringAsByteArray.Length;
TotalByteCount = StrByteLen + NullTermBytes;
t = PrepareTagForCompleteWriting(TagSpec, TagType, TotalByteCount);
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;
public bool GetTagValue(ExifTag TagSpec, out int Value, int Index = 0)
if (GetTagItem(TagSpec, out t) && ReadUintElement(t, Index, out TempValue))
Success = ((t.TagType != ExifTagType.ULong) || ((int)TempValue >= 0));
if (!Success) TempValue = 0;
public bool SetTagValue(ExifTag TagSpec, int Value, ExifTagType TagType, int Index = 0)
if ((Value >= 0) && (Value <= 255)) Success = true;
if ((Value >= 0) && (Value <= 65535)) Success = true;
if (Value >= 0) Success = true;
t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success = WriteUintElement(t, Index, (uint)Value);
public bool GetTagValue(ExifTag TagSpec, out uint Value, int Index = 0)
if (GetTagItem(TagSpec, out t) && ReadUintElement(t, Index, out TempValue))
Success = ((t.TagType != ExifTagType.SLong) || ((int)TempValue >= 0));
if (!Success) TempValue = 0;
public bool SetTagValue(ExifTag TagSpec, uint Value, ExifTagType TagType, int Index = 0)
if ((Value >= 0) && (Value <= 255)) Success = true;
if ((Value >= 0) && (Value <= 65535)) Success = true;
if ((int)Value >= 0) Success = true;
t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success = WriteUintElement(t, Index, (uint)Value);
public bool GetTagValue(ExifTag TagSpec, out ExifRational Value, int Index = 0)
if (GetTagItem(TagSpec, out t) && ReadURatElement(t, Index, out Numer, out Denom) == true)
if (t.TagType == ExifTagType.URational)
Value = new ExifRational(Numer, Denom);
Value = new ExifRational((int)Numer, (int)Denom);
Value = new ExifRational(0, 0);
public bool SetTagValue(ExifTag TagSpec, ExifRational Value, ExifTagType TagType, int Index = 0)
case ExifTagType.SRational:
if ((Value.Numer < 0x80000000) && (Value.Denom < 0x80000000))
Value.Numer = (uint)(-(int)Value.Numer);
case ExifTagType.URational:
Success = !Value.IsNegative();
t = PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success = WriteURatElement(t, Index, Value.Numer, Value.Denom);
public bool GetTagValue(ExifTag TagSpec, out DateTime Value, ExifDateFormat Format = ExifDateFormat.DateAndTime)
int i, Year, Month, Day, Hour, Minute, Second;
Value = DateTime.MinValue;
if (GetTagItem(TagSpec, out t) && (t.TagType == ExifTagType.Ascii))
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);
else if ((Format == ExifDateFormat.DateOnly) && (t.ValueCount == 10 + 1) && (t.ValueData[i + 10] == 0))
Value = new DateTime(Year, Month, Day);
public bool SetTagValue(ExifTag TagSpec, DateTime Value, ExifDateFormat Format = ExifDateFormat.DateAndTime)
int i, sByteCount = 0, Year;
if (Format == ExifDateFormat.DateAndTime)
else if (Format == ExifDateFormat.DateOnly)
t = PrepareTagForCompleteWriting(TagSpec, ExifTagType.Ascii, sByteCount);
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);
public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount, out byte[] RawData,
if (GetTagItem(TagSpec, out TagItem t))
ValueCount = t.ValueCount;
RawDataIndex = t.ValueIndex;
public bool GetTagRawData(ExifTag TagSpec, out ExifTagType TagType, out int ValueCount, out byte[] RawData)
if (GetTagItem(TagSpec, out TagItem t))
ValueCount = t.ValueCount;
Array.Copy(t.ValueData, t.ValueIndex, RawData, 0, k);
public bool SetTagRawData(ExifTag TagSpec, ExifTagType TagType, int ValueCount, byte[] RawData, int RawDataIndex = 0)
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.ValueCount = ValueCount;
t.ValueIndex = RawDataIndex;
t.AllocatedByteCount = RawDataByteCount;
t = new TagItem(ExtractTagId(TagSpec), TagType, ValueCount, RawData, RawDataIndex, RawDataByteCount);
IfdTagTable.Add(t.TagId, t);
public bool GetTagValueCount(ExifTag TagSpec, out int ValueCount)
if (GetTagItem(TagSpec, out TagItem t))
ValueCount = t.ValueCount;
public bool SetTagValueCount(ExifTag TagSpec, int ValueCount)
if (GetTagItem(TagSpec, out TagItem t) && ((uint)ValueCount <= MaxTagValueCount))
t.SetTagTypeAndValueCount(t.TagType, ValueCount, true);
public bool SetTagValueCount(ExifTag TagSpec, int ValueCount, ExifTagType TagType)
TagItem t = PrepareTagForArrayItemWriting(TagSpec, TagType, ValueCount - 1);
public bool GetTagType(ExifTag TagSpec, out ExifTagType TagType)
if (GetTagItem(TagSpec, out TagItem t))
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)));
public bool IfdExists(ExifIfd Ifd)
if ((uint)Ifd < ExifIfdCount)
Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];
return (IfdTagTable.Count > 0);
public bool ImageFileBlockExists(ImageFileBlock BlockType)
return (ImageFileBlockInfo[(int)BlockType] == ImageFileBlockState.Existent);
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)));
public bool RemoveAllTagsFromIfd(ExifIfd Ifd)
if (Ifd == ExifIfd.PrimaryData)
RemoveAllTagsFromIfdPrimaryData();
else if ((uint)Ifd < ExifIfdCount)
if (Ifd == ExifIfd.ThumbnailData)
RemoveThumbnailImage(false);
public void RemoveAllTags()
RemoveAllTagsFromIfdPrimaryData();
for (ExifIfd Ifd = ExifIfd.PrivateData; Ifd < (ExifIfd)ExifIfdCount; Ifd++)
RemoveThumbnailImage(false);
ImageFileBlockInfo[(int)ImageFileBlock.Exif] = ImageFileBlockState.Removed;
public void RemoveImageFileBlock(ImageFileBlock BlockType)
if (BlockType == ImageFileBlock.Exif)
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;
public void ReplaceAllTagsBy(ExifData SourceExifData)
bool SwapByteOrder = false;
if (this.ImageType == ImageType.Tiff)
if (this._ByteOrder != SourceExifData._ByteOrder)
else this._ByteOrder = SourceExifData._ByteOrder;
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);
foreach (TagItem SourceTag in SourceIfdTagList.Values)
CopyTag = !IsInternalTiffTag(SourceTag.TagId) && !IsMetaDataTiffTag(SourceTag.TagId);
TagItem DestTag = CopyTagItemDeeply(ExifIfd.PrimaryData, SourceTag, SwapByteOrder);
AddIfNotExists(DestIfdTagList, DestTag);
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);
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);
RemoveThumbnailImage_Internal();
UpdateImageFileBlockInfo();
this.MakerNoteOriginalOffset = SourceExifData.MakerNoteOriginalOffset;
public bool ThumbnailImageExists()
return (ThumbnailImage != null);
public bool GetThumbnailImage(out byte[] ThumbnailData, out int ThumbnailIndex, out int ThumbnailByteCount)
ThumbnailData = ThumbnailImage;
ThumbnailIndex = ThumbnailStartIndex;
ThumbnailByteCount = this.ThumbnailByteCount;
Success = ThumbnailImageExists();
public bool SetThumbnailImage(byte[] ThumbnailData, int ThumbnailIndex = 0, int ThumbnailByteCount = -1)
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);
SetTagValue(ExifTag.JpegInterchangeFormatLength, this.ThumbnailByteCount, ExifTagType.ULong);
public void RemoveThumbnailImage(bool RemoveAlsoThumbnailTags)
RemoveThumbnailImage_Internal();
if (RemoveAlsoThumbnailTags)
ClearIfd_Unchecked(ExifIfd.ThumbnailData);
RemoveTag(ExifTag.JpegInterchangeFormat);
RemoveTag(ExifTag.JpegInterchangeFormatLength);
public ExifByteOrder ByteOrder
public static ExifIfd ExtractIfd(ExifTag TagSpec)
return (ExifIfd)((uint)TagSpec >> IfdShift);
public static ExifTagId ExtractTagId(ExifTag TagSpec)
return (ExifTagId)((ushort)TagSpec);
public static ExifTag ComposeTagSpec(ExifIfd Ifd, ExifTagId TagId)
return (ExifTag)(((uint)Ifd << IfdShift) | (uint)TagId);
public static int GetTagByteCount(ExifTagType TagType, int ValueCount)
if ((uint)TagType < TypeByteCountLen)
return (TypeByteCount[(uint)TagType] * ValueCount);
public ushort ExifReadUInt16(byte[] Data, int StartIndex)
if (_ByteOrder == ExifByteOrder.BigEndian)
v = (ushort)(((uint)Data[StartIndex] << 8) | Data[StartIndex + 1]);
v = (ushort)(((uint)Data[StartIndex + 1] << 8) | Data[StartIndex]);
public void ExifWriteUInt16(byte[] Data, int StartIndex, ushort Value)
if (_ByteOrder == ExifByteOrder.BigEndian)
Data[StartIndex] = (byte)(Value >> 8);
Data[StartIndex + 1] = (byte)Value;
Data[StartIndex + 1] = (byte)(Value >> 8);
Data[StartIndex] = (byte)Value;
public uint ExifReadUInt32(byte[] Data, int StartIndex)
if (_ByteOrder == ExifByteOrder.BigEndian)
v = ((uint)Data[StartIndex] << 24) |
((uint)Data[StartIndex + 1] << 16) |
((uint)Data[StartIndex + 2] << 8) |
v = ((uint)Data[StartIndex + 3] << 24) |
((uint)Data[StartIndex + 2] << 16) |
((uint)Data[StartIndex + 1] << 8) |
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);
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]);
private uint ReadUInt32BE(byte[] Data, int StartIndex)
uint v = ((uint)Data[StartIndex] << 24) |
((uint)Data[StartIndex + 1] << 16) |
((uint)Data[StartIndex + 2] << 8) |
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);
public bool InitTagEnumeration(ExifIfd Ifd)
if ((uint)Ifd < ExifIfdCount)
ExifIfdForTagEnumeration = Ifd;
TagEnumerator = TagTable[(uint)Ifd].Keys.GetEnumerator();
TagEnumerator = new Dictionary<ExifTagId, TagItem>.KeyCollection.Enumerator();
public bool EnumerateNextTag(out ExifTag TagSpec)
if (TagEnumerator.MoveNext())
ExifTagId TagId = TagEnumerator.Current;
TagSpec = ComposeTagSpec(ExifIfdForTagEnumeration, TagId);
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)
ExifRational AltitudeRat;
if (GetTagValue(ExifTag.GpsAltitude, out AltitudeRat) && AltitudeRat.IsValid())
Value = ExifRational.ToDecimal(AltitudeRat);
if (GetTagValue(ExifTag.GpsAltitudeRef, out BelowSeaLevel) && (BelowSeaLevel == 1))
public bool SetGpsAltitude(decimal Value)
ExifRational AltitudeRat = ExifRational.FromDecimal(Value);
if (AltitudeRat.IsNegative())
AltitudeRat.Sign = false;
if (SetTagValue(ExifTag.GpsAltitude, AltitudeRat, ExifTagType.URational) &&
SetTagValue(ExifTag.GpsAltitudeRef, BelowSeaLevel, ExifTagType.Byte))
public void RemoveGpsAltitude()
RemoveTag(ExifTag.GpsAltitude);
RemoveTag(ExifTag.GpsAltitudeRef);
public bool GetGpsDateTimeStamp(out DateTime Value)
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);
else Value = DateTime.MinValue;
else Value = DateTime.MinValue;
public bool SetGpsDateTimeStamp(DateTime Value)
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;
Sec = new ExifRational(ts.Seconds, 1);
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))
public void RemoveGpsDateTimeStamp()
RemoveTag(ExifTag.GpsDateStamp);
RemoveTag(ExifTag.GpsTimeStamp);
private const int MinExifBlockLen = 2 + 4;
private const int MaxTagValueCount = int.MaxValue / 8;
private byte[] SourceExifBlock;
private Stream SourceExifStream;
private ExifByteOrder _ByteOrder;
private string _FileNameWithPath;
private static readonly int[] TypeByteCount = {
private const int TypeByteCountLen = 13;
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;
private int ThumbnailStartIndex, ThumbnailByteCount;
private const int ExifIfdCount = 5;
private ExifIfd ExifIfdForTagEnumeration;
private Dictionary<ExifTagId, TagItem>.KeyCollection.Enumerator TagEnumerator;
private enum ImageFileBlockState { NonExistent = 0, Removed, Existent };
private ImageFileBlockState[] ImageFileBlockInfo;
private ExifErrCode ErrCodeForIllegalExifBlock;
private const int JpegMaxExifBlockLen = 65534 - 2 - 6 - TiffHeaderLen;
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;
private const ushort JpegSoiMarker = 0xFFD8;
private const ushort JpegSosMarker = 0xFFDA;
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 };
private const int TiffHeaderLen = 8;
private const uint TiffHeaderSignatureLE = 0x49492A00;
private const uint TiffHeaderSignatureBE = 0x4D4D002A;
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;
_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))
if (ImageType == ImageType.Jpeg)
else if (ImageType == ImageType.Tiff)
else if (ImageType == ImageType.Png)
UpdateImageFileBlockInfo();
byte[] BlockContent = new byte[65536];
ImageFileBlock BlockType;
bool ExifBlockFound = false;
SourceExifStream.Position = 2;
ReadJpegBlock(SourceExifStream, BlockContent, out int BlockContentSize, out BlockMarker, out BlockType, out MetaDataIndex);
if (BlockType != ImageFileBlock.Unknown)
if ((BlockType == ImageFileBlock.Exif) && (!ExifBlockFound))
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);
byte[] BlockContent = new byte[8];
SourceExifStream.Position = 0;
ErrCodeForIllegalExifBlock = ExifErrCode.InternalImageStructureIsWrong;
int TiffHeaderBytesRead = SourceExifStream.Read(BlockContent, 0, TiffHeaderLen);
EvaluateTiffHeader(BlockContent, TiffHeaderBytesRead, out int IfdPrimaryDataOffset);
EvaluateExifBlock(IfdPrimaryDataOffset);
byte[] TempData = new byte[65536];
byte[] BlockContent = new byte[30];
bool ExifBlockFound = false;
SourceExifStream.Position = 4;
int k = SourceExifStream.Read(TempData, 0, 4);
if ((k < 4) || (ReadUInt32BE(TempData, 0) != PngHeaderPart2))
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
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))
SourceExifBlock = new byte[DataLength];
if (SourceExifStream.Read(SourceExifBlock, 0, DataLength) < DataLength)
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
uint Crc32Calculated = CalculateCrc32(TempData, 4, 4, false);
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);
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);
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
ChunkType = ReadUInt32BE(TempData, 4);
private int DetectPngImageBlock(Stream ImageStream, byte[] SignatureData, uint ChunkType, out ImageFileBlock BlockType)
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;
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)
int EndIndexPlus1 = StartIndex + Length;
for (int i = StartIndex; i < EndIndexPlus1; i++)
result = Crc32ChecksumTable[((byte)result) ^ value] ^ (result >> 8);
if (Finalize) result = ~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;
NewExifBlockState = ImageFileBlockState.Existent;
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;
ImageFileBlockInfo[(int)ImageFileBlock.Exif] = NewExifBlockState;
if (ImageType == ImageType.Tiff)
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)))
else if (BlockMarker == 0xFFFF)
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
ImageStream.Read(TempBuffer, 0, 2);
BlockContentSize = (int)ReadUInt16BE(TempBuffer, 0) - 2;
if (BlockContentSize < 0)
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
private void ReadJpegBlock(Stream ImageStream, byte[] BlockContent, out int BlockContentSize, out ushort BlockMarker,
out ImageFileBlock BlockType, out int MetaDataIndex)
BlockType = ImageFileBlock.Unknown;
ReadJpegBlockMarker(ImageStream, out BlockMarker, out int ContentSize);
int k = ImageStream.Read(BlockContent, 0, 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))
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;
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);
if (StreamToBeChecked.Length > int.MaxValue)
throw new ExifException(ExifErrCode.ImageHasUnsupportedFeatures);
const long CheckOffset = -2;
long i = StreamToBeChecked.Position;
StreamToBeChecked.Position += CheckOffset;
if (StreamToBeChecked.Position != (i + CheckOffset))
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;
byte[] BlockContent = new byte[65536];
byte[] TempBuffer = new byte[4];
SourceStream.Position = FirstBlockStartPosition;
WriteUInt16BE(TempBuffer, 0, JpegSoiMarker);
DestStream.Write(TempBuffer, 0, 2);
ReadJpegBlockMarker(SourceStream, out BlockMarker, out int BlockContentSize);
if (BlockMarker == JpegSosMarker)
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;
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);
SourceStream.Position = FirstBlockStartPosition;
ReadJpegBlock(SourceStream, BlockContent, out int BlockContentSize, out BlockMarker, out ImageFileBlock BlockType, out _);
if (BlockMarker == JpegSosMarker)
WriteUInt16BE(TempBuffer, 0, BlockMarker);
DestStream.Write(TempBuffer, 0, 2);
k = SourceStream.Read(BlockContent, 0, BlockContent.Length);
DestStream.Write(BlockContent, 0, k);
} while (k == BlockContent.Length);
bool CopyBlockFromSourceStream = true;
if (BlockMarker == JpegApp0Marker)
CopyBlockFromSourceStream = false;
else if (BlockType == ImageFileBlock.Exif)
CopyBlockFromSourceStream = false;
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);
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;
CurrentImageExif = CreateSubExifData(SourceStream, ref SourceStreamExifBlockOffset, SourceStreamByteOrder,
out uint[] SegmentOffsetTable, out uint[] SegmentByteCountTable,
out ExifTag SegmentOffsetsTag, out ExifTag SegmentByteCountsTag);
if (!CurrentImageExif.TagExists(ExifTag.ImageWidth) || !CurrentImageExif.TagExists(ExifTag.ImageLength))
throw new ExifException(ExifErrCode.InternalImageStructureIsWrong);
CurrentImageExif.RemoveTag(ExifTag.FreeOffsets);
CurrentImageExif.RemoveTag(ExifTag.FreeByteCounts);
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);
CurrentImageExif.ExifWriteUInt32(TiffHeader, 4, CurrentDestStreamOffset);
DestStream.Write(TiffHeader, 0, TiffHeaderLen);
CurrentImageExif.ExifWriteUInt32(ExifBlockAsBinaryData.Buffer, NextExifBlockPointerIndex, CurrentDestStreamOffset);
DestStream.Write(ExifBlockAsBinaryData.Buffer, 0, ExifBlockAsBinaryData.Length);
for (k = 0; k < ImageSegmentCount; k++)
int RemainingByteCount = (int)SegmentByteCountTable[k];
int BytesToRead = TempBuffer.Length;
SourceStream.Position = SegmentOffsetTable[k];
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);
} 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);
for (int i = 0; i < ValueCount; i++)
v = SubExifBlock.ExifReadUInt16(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex + (i << 1));
else v = SubExifBlock.ExifReadUInt32(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex + (i << 2));
SegmentOffsetTable[i] = v;
v = SubExifBlock.ExifReadUInt16(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex + (i << 1));
else v = SubExifBlock.ExifReadUInt32(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex + (i << 2));
SegmentByteCountTable[i] = v;
private void SavePng(Stream SourceStream, Stream DestStream)
byte[] TempData = new byte[65536];
byte[] BlockContent = new byte[30];
SourceStream.Position = 8;
WriteUInt32BE(TempData, 0, PngHeaderPart1);
WriteUInt32BE(TempData, 4, PngHeaderPart2);
DestStream.Write(TempData, 0, 8);
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;
if (CopyBlockFromSourceStream)
SourceStream.Position = BlockStartStreamPos;
int RemainingByteCount = DataLength + 12;
int BytesToRead = TempData.Length;
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);
SourceStream.Position = BlockStartStreamPos + DataLength + 12;
if (ChunkType == PngIhdrChunk)
CreateExifBlock(out FlexArray NewExifBlock, TiffHeaderLen);
int NewExifBlockLen = NewExifBlock.Length;
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);
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()
SourceExifBlock = new byte[TiffHeaderLen + MinExifBlockLen];
ExifWriteUInt16(SourceExifBlock, TiffHeaderLen, 0);
ExifWriteUInt32(SourceExifBlock, TiffHeaderLen + 2, 0);
EvaluateExifBlock(TiffHeaderLen);
private void WriteTagToFlexArray(TagItem TempTagData, FlexArray WriteData, ref int TagDataIndex, int ExifBlockOffset)
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);
WriteData.Buffer[i + 8] = TempTagData.ValueData[TempTagData.ValueIndex];
if (ByteCount >= 2) v = TempTagData.ValueData[TempTagData.ValueIndex + 1];
WriteData.Buffer[i + 9] = v;
if (ByteCount >= 3) v = TempTagData.ValueData[TempTagData.ValueIndex + 2];
WriteData.Buffer[i + 10] = v;
if (ByteCount >= 4) v = TempTagData.ValueData[TempTagData.ValueIndex + 3];
WriteData.Buffer[i + 11] = v;
int OutsourcedDataIndex = WriteData.Length;
ExifWriteUInt32(WriteData.Buffer, i + 8, (uint)(ExifBlockOffset + OutsourcedDataIndex));
WriteData.Length += ByteCount;
Array.Copy(TempTagData.ValueData, TempTagData.ValueIndex, WriteData.Buffer, OutsourcedDataIndex, ByteCount);
if ((ByteCount & 0x1) != 0)
WriteData.Buffer[WriteData.Length - 1] = 0;
private int CreateExifBlock(out FlexArray NewExifBlock, int CurrentExifBlockOffset)
int TiffNextExifBlockPointerIndex = 0;
FlexArray WriteExifBlock = new FlexArray(JpegMaxExifBlockLen);
NewExifBlock = WriteExifBlock;
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)
TiffNextExifBlockPointerIndex = ThumbnailDataIfdPointerIndex;
ExifWriteUInt32(WriteExifBlock.Buffer, TiffNextExifBlockPointerIndex, 0);
else CreateIfdThumbnailData(WriteExifBlock, ref WriteIndex, CurrentExifBlockOffset, ThumbnailDataIfdPointerIndex);
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);
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);
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);
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;
WriteExifBlock.Length += IfdFixedSize;
ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)PrimaryDataTagCount);
foreach (TagItem t in PrimaryDataIfdTable.Values)
if (t.TagId == ExifTagId.ExifIfdPointer)
t.TagType = ExifTagType.ULong;
PrivateDataIfdPointerIndex = WriteIndex + 8;
else if (t.TagId == ExifTagId.GpsInfoIfdPointer)
t.TagType = ExifTagType.ULong;
GpsInfoDataIfdPointerIndex = WriteIndex + 8;
WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
ThumbnailDataIfdPointerIndex = WriteIndex;
WriteIndex = WriteExifBlock.Length;
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;
ExifWriteUInt32(PrivateDataPointerTag.ValueData, PrivateDataPointerTag.ValueIndex, (uint)(ExifBlockOffset + WriteIndex));
ExifWriteUInt32(WriteExifBlock.Buffer, PrivateDataIfdPointerIndex, (uint)(ExifBlockOffset + WriteIndex));
MakerNoteDataPointerIndex = 0;
OffsetSchemaValueIndex = 0;
int IfdFixedSize = 2 + PrivateDataTagCount * 12 + 4;
WriteExifBlock.Length += IfdFixedSize;
ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex, (ushort)PrivateDataTagCount);
foreach (TagItem t in PrivateDataIfdTable.Values)
if (t.TagId == ExifTagId.InteroperabilityIfdPointer)
t.TagType = ExifTagType.ULong;
InteroperabilityIfdPointerIndex = WriteIndex + 8;
else if (t.TagId == ExifTagId.MakerNote)
MakerNoteDataPointerIndex = WriteIndex + 8;
else if (t.TagId == ExifTagId.OffsetSchema)
t.TagType = ExifTagType.SLong;
OffsetSchemaValueIndex = WriteIndex + 8;
WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0);
WriteIndex = WriteExifBlock.Length;
AddOffsetSchemaTag = CheckIfMakerNoteTagHasMoved(WriteExifBlock.Buffer, MakerNoteDataPointerIndex, OffsetSchemaValueIndex, OffsetSchemaTag);
TagItem t = new TagItem(ExifTag.OffsetSchema, ExifTagType.SLong, 1);
PrivateDataIfdTable.Add(ExifTagId.OffsetSchema, t);
WriteExifBlock.Length = iPrivateDataStart;
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);
foreach (TagItem t in GpsInfoDataIfdTable.Values)
WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0);
WriteIndex = WriteExifBlock.Length;
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);
foreach (TagItem t in InteroperabilityIfdTable.Values)
WriteTagToFlexArray(t, WriteExifBlock, ref WriteIndex, ExifBlockOffset);
ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0);
WriteIndex = WriteExifBlock.Length;
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);
int ThumbnailImagePointerIndex = -1;
TagItem ThumbnailImagePointerTag = null;
bool ThumbnailImageSizeTagExists = false;
foreach (TagItem t in ThumbnailDataIfdTable.Values)
if (t.TagId == ExifTagId.JpegInterchangeFormat)
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)
if ((ThumbnailImagePointerIndex < 0) || (ThumbnailImageSizeTagExists == false))
throw new ExifException(ExifErrCode.InternalError);
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[WriteExifBlock.Length - 1] = 0;
if (ThumbnailImagePointerIndex >= 0)
throw new ExifException(ExifErrCode.InternalError);
ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex, 0);
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))
MakerNoteCurrentOffset = (int)ExifReadUInt32(DataBlock, MakerNoteDataPointerIndex);
if (MakerNoteOriginalOffset != MakerNoteCurrentOffset)
if (OffsetSchemaValueIndex > 0)
int NewOffsetDifference = MakerNoteCurrentOffset - MakerNoteOriginalOffset;
ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, (uint)NewOffsetDifference);
ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, (uint)NewOffsetDifference);
MakerNoteHasMovedAndOffsetSchemaTagRequired = true;
if (OffsetSchemaValueIndex > 0)
ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, 0);
ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, 0);
if (OffsetSchemaValueIndex > 0)
ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex, 0);
ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex, 0);
return (MakerNoteHasMovedAndOffsetSchemaTagRequired);
private TagItem CreateTagWithReferenceToIfdRawData(byte[] IfdRawData, int IfdRawDataIndex)
int ValueIndex, ValueByteCount, AllocatedByteCount, OriginalOffset;
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);
ValueIndex = IfdRawDataIndex + 8;
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);
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);
TagData = new byte[DataLen];
SourceExifStream.Position = DataOffset;
if (SourceExifStream.Read(TagData, 0, DataLen) != DataLen)
throw new ExifException(ErrCodeForIllegalExifBlock);
private void InitIfdPrimaryData(byte[] IfdRawData, int IfdRawDataIndex, out int PrivateDataOffset, out int GpsInfoDataOffset, out int NextImageOffset)
int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
var PrimaryDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.PrimaryData] = PrimaryDataIfdTable;
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);
NextImageOffset = (int)ExifReadUInt32(IfdRawData, IfdRawDataIndex);
private void InitIfdPrivateData(byte[] IfdRawData, int IfdRawDataIndex, out int InteroperabilityOffset)
int MakerNoteOffset, OffsetSchemaValue;
InteroperabilityOffset = 0;
int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
var PrivateDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.PrivateData] = PrivateDataIfdTable;
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);
MakerNoteOriginalOffset = MakerNoteOffset - OffsetSchemaValue;
else MakerNoteOriginalOffset = 0;
private void InitIfdGpsInfoData(byte[] IfdRawData, int IfdRawDataIndex)
int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
var GpsInfoDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.GpsInfoData] = GpsInfoDataIfdTable;
TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
AddIfNotExists(GpsInfoDataIfdTable, TempTagData);
private void InitIfdInteroperability(byte[] IfdRawData, int IfdRawDataIndex)
int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
var InteroperabilityIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.Interoperability] = InteroperabilityIfdTable;
TagItem TempTagData = CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);
AddIfNotExists(InteroperabilityIfdTable, TempTagData);
private void InitIfdThumbnailData(byte[] IfdRawData, int IfdRawDataIndex)
int ThumbnailImageDataOffset = 0;
int TagCount = ExifReadUInt16(IfdRawData, IfdRawDataIndex);
var ThumbnailDataIfdTable = new Dictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.ThumbnailData] = ThumbnailDataIfdTable;
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);
if (ThumbnailImageDataOffset > 0)
GetOutsourcedData(ThumbnailImageDataOffset, ThumbnailByteCount, out ThumbnailImage, out ThumbnailStartIndex);
private void GetNextIfd(int IfdOffset, out byte[] IfdRawData, out int IfdRawDataIndex)
IfdRawData = new byte[2];
else if (SourceExifBlock != null)
IfdRawData = SourceExifBlock;
IfdRawDataIndex = IfdOffset;
SourceExifStream.Position = IfdOffset;
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);
private int EvaluateExifBlock(int PrimaryDataOffset)
int PrivateDataOffset, GpsInfoDataOffset, InteroperabilityOffset, ThumbnailDataOffset, IfdIndex, TiffNextExifBlockOffset = 0;
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)
InitIfdThumbnailData(IfdTable, 0);
TiffNextExifBlockOffset = ThumbnailDataOffset;
GetNextIfd(ThumbnailDataOffset, out IfdTable, out IfdIndex);
InitIfdThumbnailData(IfdTable, IfdIndex);
return (TiffNextExifBlockOffset);
private bool AddIfNotExists(Dictionary<ExifTagId, TagItem> Dict, TagItem TagData)
Dict.Add(TagData.TagId, TagData);
private bool CompareArrays(byte[] Array1, int StartIndex1, byte[] Array2)
if (Array1.Length >= (StartIndex1 + Array2.Length))
foreach (byte b in Array2)
private bool ArrayStartsWith(byte[] Array1, int Array1Length, byte[] Array2)
if (Array1Length >= Array2.Length)
foreach (byte b in Array2)
private bool ReadUintElement(TagItem t, int ElementIndex, out uint Value)
if ((ElementIndex >= 0) && (ElementIndex < t.ValueCount))
Value = t.ValueData[t.ValueIndex + ElementIndex];
Value = ExifReadUInt16(t.ValueData, t.ValueIndex + (ElementIndex << 1));
Value = ExifReadUInt32(t.ValueData, t.ValueIndex + (ElementIndex << 2));
private bool WriteUintElement(TagItem t, int ElementIndex, uint Value)
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);
ExifWriteUInt32(t.ValueData, t.ValueIndex + (ElementIndex << 2), Value);
private bool GetTagValueWithIdCode(ExifTag TagSpec, out string Value, ushort CodePage)
bool Success = false, IsUtf16Coded = false, IsAsciiCoded = false;
if (GetTagItem(TagSpec, out t) && (t.TagType == ExifTagType.Undefined) && (t.ValueCount >= IdCodeLength))
if (CompareArrays(t.ValueData, t.ValueIndex, IdCodeUtf16))
else if (CompareArrays(t.ValueData, t.ValueIndex, IdCodeAscii) || CompareArrays(t.ValueData, t.ValueIndex, IdCodeDefault))
i = t.ValueCount - IdCodeLength;
j = t.ValueIndex + IdCodeLength;
while ((i >= 2) && (t.ValueData[j + i - 2] == 0) && (t.ValueData[j + i - 1] == 0))
if (ByteOrder == ExifByteOrder.BigEndian)
Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
while ((i >= 1) && (t.ValueData[j + i - 1] == 0))
if ((CodePage == 1200) || (CodePage == 1201))
Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
private bool SetTagValueWithIdCode(ExifTag TagSpec, string Value, ushort CodePage)
int TotalByteCount, StrByteLen;
byte[] StringAsByteArray, RequiredIdCode;
if ((CodePage == 1200) && (ByteOrder == ExifByteOrder.BigEndian))
if ((CodePage == 1201) && (ByteOrder == ExifByteOrder.LittleEndian))
StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
StrByteLen = StringAsByteArray.Length;
TotalByteCount = IdCodeLength + StrByteLen;
t = PrepareTagForCompleteWriting(TagSpec, ExifTagType.Undefined, TotalByteCount);
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);
private bool ReadURatElement(TagItem t, int ElementIndex, out uint Numer, out uint Denom)
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);
private bool WriteURatElement(TagItem t, int ElementIndex, uint Numer, uint Denom)
i = t.ValueIndex + (ElementIndex << 3);
ExifWriteUInt32(t.ValueData, i, Numer);
ExifWriteUInt32(t.ValueData, i + 4, Denom);
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));
private TagItem PrepareTagForArrayItemWriting(ExifTag TagSpec, ExifTagType TagType, int ArrayIndex)
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);
int ValueCount = ArrayIndex + 1;
t = new TagItem(TagSpec, TagType, ValueCount);
IfdTagTable.Add(t.TagId, t);
private TagItem PrepareTagForCompleteWriting(ExifTag TagSpec, ExifTagType TagType, int ValueCount)
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);
t = new TagItem(TagSpec, TagType, ValueCount);
IfdTagTable.Add(t.TagId, t);
int k = t.ValueIndex + t.AllocatedByteCount;
for (int i = t.ValueIndex; i < k; i++)
private static int CalculateTwoDigitDecNumber(byte[] ByteArr, int Index)
if ((d1 >= '0') && (d1 <= '9') && (d2 >= '0') && (d2 <= '9'))
Value = (d1 - 0x30) * 10 + (d2 - 0x30);
private static void ConvertTwoDigitNumberToByteArr(byte[] ByteArr, ref int Index, int Value)
ByteArr[Index] = (byte)((Value / 10) + 0x30);
ByteArr[Index] = (byte)((Value % 10) + 0x30);
private void ClearIfd_Unchecked(ExifIfd Ifd)
TagTable[(uint)Ifd].Clear();
private void RemoveAllTagsFromIfdPrimaryData()
if (ImageType == ImageType.Tiff)
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);
ClearIfd_Unchecked(ExifIfd.PrimaryData);
private void RemoveThumbnailImage_Internal()
private bool GetGpsCoordinateHelper(out GeoCoordinate Value, ExifTag ValueTag, ExifTag RefTag, char Cp1, char Cp2)
ExifRational Deg, Min, Sec;
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))
if ((CardinalPoint == Cp1) || (CardinalPoint == Cp2))
Value.Degree = ExifRational.ToDecimal(Deg);
Value.Minute = ExifRational.ToDecimal(Min);
Value.Second = ExifRational.ToDecimal(Sec);
Value.CardinalPoint = CardinalPoint;
else Value = new GeoCoordinate();
else Value = new GeoCoordinate();
private bool SetGpsCoordinateHelper(GeoCoordinate Value, ExifTag ValueTag, ExifTag RefTag, char Cp1, char Cp2)
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))
private bool GetDateAndTimeWithMillisecHelper(out DateTime Value, ExifTag DateAndTimeTag, ExifTag MillisecTag)
if (GetTagValue(DateAndTimeTag, out Value))
if (GetTagValue(MillisecTag, out string SubSec, StrCoding.Utf8))
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);
private bool SetDateAndTimeWithMillisecHelper(DateTime Value, ExifTag DateAndTimeTag, ExifTag MillisecTag)
if (SetTagValue(DateAndTimeTag, Value))
int MilliSec = Value.Millisecond;
if ((MilliSec != 0) || TagExists(MillisecTag))
string s = MilliSec.ToString("000");
Success = Success && SetTagValue(MillisecTag, s, StrCoding.Utf8);
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 = TagIdInt + 1;
InternalTiffTagsBitArray.Length = len;
InternalTiffTagsBitArray.Set(TagIdInt, true);
private static bool IsInternalTiffTag(ExifTagId TagId)
if (InternalTiffTagsBitArray == null)
int TagIdInt = (int)TagId;
if (TagIdInt < InternalTiffTagsBitArray.Length)
return (InternalTiffTagsBitArray.Get(TagIdInt));
private static bool IsMetaDataTiffTag(ExifTagId TagId)
return ((TagId == ExifTagId.XmpMetadata) || (TagId == ExifTagId.IptcMetadata));
private void SwapByteOrderOfTagData(ExifIfd Ifd, TagItem t)
if (Ifd == ExifIfd.PrimaryData)
if (IsMetaDataTiffTag(t.TagId)) return;
else if (Ifd == ExifIfd.PrivateData)
if ((t.TagId == ExifTagId.UserComment) && (t.TagType == ExifTagType.Undefined))
int k = t.ValueIndex + 8;
int Utf16CharCount = (t.ValueCount - 8) / 2;
for (int i = 0; i < Utf16CharCount; i++)
Swap2ByteValue(t.ValueData, k);
for (int i = 0; i < t.ValueCount; i++)
Swap2ByteValue(t.ValueData, k);
for (int i = 0; i < t.ValueCount; i++)
Swap4ByteValue(t.ValueData, k);
case ExifTagType.URational:
case ExifTagType.SRational:
for (int i = 0; i < t.ValueCount; i++)
Swap4ByteValue(t.ValueData, k);
Swap4ByteValue(t.ValueData, k);
for (int i = 0; i < t.ValueCount; i++)
Array.Reverse(t.ValueData, k, 8);
private void Swap2ByteValue(byte[] b, int i)
private void Swap4ByteValue(byte[] b, int i)
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);
SwapByteOrderOfTagData(Ifd, NewTag);
public ExifTagType TagType;
public int AllocatedByteCount;
return (ExifData.GetTagByteCount(TagType, ValueCount));
public int OriginalDataOffset;
public TagItem(ExifTag TagSpec, ExifTagType _TagType, int _ValueCount) :
this(ExtractTagId(TagSpec), _TagType, _ValueCount)
public TagItem(ExifTagId _TagId, ExifTagType _TagType, int _ValueCount)
ValueCount = _ValueCount;
int RequiredByteCount = GetTagByteCount(_TagType, _ValueCount);
ValueData = AllocTagMemory(RequiredByteCount);
AllocatedByteCount = ValueData.Length;
public TagItem(ExifTagId _TagId, ExifTagType _TagType, int _ValueCount, byte[] _ValueArray, int _ValueIndex,
int _AllocatedByteCount, int _OriginalDataOffset = 0)
ValueCount = _ValueCount;
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]);
public void SetTagTypeAndValueCount(ExifTagType _TagType, int _ValueCount, bool KeepExistingData)
int RequiredByteCount = GetTagByteCount(_TagType, _ValueCount);
if (AllocatedByteCount < RequiredByteCount)
uint NewByteCount = (uint)AllocatedByteCount << 1;
if (NewByteCount > int.MaxValue) NewByteCount = int.MaxValue;
else if (NewByteCount < (uint)RequiredByteCount) NewByteCount = (uint)RequiredByteCount;
byte[] NewTagData = AllocTagMemory((int)NewByteCount);
Array.Copy(ValueData, ValueIndex, NewTagData, 0, AllocatedByteCount);
AllocatedByteCount = NewTagData.Length;
ValueCount = _ValueCount;
public FlexArray(int Capacity)
_Buffer = new byte[Capacity];
get { return (_Buffer); }
get { return (_Length); }
if (value > _Buffer.Length)
uint k = (uint)_Buffer.Length;
else if ((uint)value > k)
Array.Resize(ref _Buffer, (int)k);
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,
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,
ImageDescription = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ImageDescription,
Make = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Make,
Model = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Model,
StripOffsets = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.StripOffsets,
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,
ResolutionUnit = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.ResolutionUnit,
PageNumber = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.PageNumber,
TransferFunction = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.TransferFunction,
Software = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.Software,
DateTime = (ExifIfd.PrimaryData << ExifData.IfdShift) | ExifTagId.DateTime,
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,
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,
ExposureTime = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureTime,
FNumber = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FNumber,
ExposureProgram = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureProgram,
SpectralSensitivity = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SpectralSensitivity,
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,
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,
CompressedBitsPerPixel = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.CompressedBitsPerPixel,
ShutterSpeedValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ShutterSpeedValue,
ApertureValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ApertureValue,
BrightnessValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.BrightnessValue,
ExposureBiasValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.ExposureBiasValue,
MaxApertureValue = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MaxApertureValue,
SubjectDistance = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectDistance,
MeteringMode = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MeteringMode,
LightSource = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.LightSource,
Flash = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Flash,
FocalLength = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalLength,
SubjectArea = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectArea,
MakerNote = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.MakerNote,
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,
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,
WhiteBalance = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.WhiteBalance,
DigitalZoomRatio = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DigitalZoomRatio,
FocalLengthIn35mmFilm = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.FocalLengthIn35mmFilm,
SceneCaptureType = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SceneCaptureType,
GainControl = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.GainControl,
Contrast = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Contrast,
Saturation = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Saturation,
Sharpness = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.Sharpness,
DeviceSettingDescription = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.DeviceSettingDescription,
SubjectDistanceRange = (ExifIfd.PrivateData << ExifData.IfdShift) | ExifTagId.SubjectDistanceRange,
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,
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,
GpsDateStamp = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDateStamp,
GpsDifferential = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsDifferential,
GpsHPositioningError = (ExifIfd.GpsInfoData << ExifData.IfdShift) | ExifTagId.GpsHPositioningError,
InteroperabilityIndex = (ExifIfd.Interoperability << ExifData.IfdShift) | ExifTagId.InteroperabilityIndex,
InteroperabilityVersion = (ExifIfd.Interoperability << ExifData.IfdShift) | ExifTagId.InteroperabilityVersion,
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,
PhotometricInterpretation = 0x0106,
ImageDescription = 0x010e,
SamplesPerPixel = 0x0115,
StripByteCounts = 0x0117,
PlanarConfiguration = 0x011c,
GrayResponseUnit = 0x0122,
GrayResponseCurve = 0x0123,
TransferFunction = 0x012d,
PrimaryChromaticities = 0x013f,
SMinSampleValue = 0x0154,
SMaxSampleValue = 0x0155,
YCbCrCoefficients = 0x0211,
YCbCrSubSampling = 0x0212,
YCbCrPositioning = 0x0213,
ReferenceBlackWhite = 0x0214,
GpsInfoIfdPointer = 0x8825,
JpegInterchangeFormat = 0x0201,
JpegInterchangeFormatLength = 0x0202,
ExposureProgram = 0x8822,
SpectralSensitivity = 0x8824,
IsoSpeedRatings = 0x8827,
PhotographicSensitivity = 0x8827,
SensitivityType = 0x8830,
StandardOutputSensitivity = 0x8831,
RecommendedExposureIndex = 0x8832,
IsoSpeedLatitudeyyy = 0x8834,
IsoSpeedLatitudezzz = 0x8835,
DateTimeOriginal = 0x9003,
DateTimeDigitized = 0x9004,
OffsetTimeOriginal = 0x9011,
OffsetTimeDigitized = 0x9012,
ComponentsConfiguration = 0x9101,
CompressedBitsPerPixel = 0x9102,
ShutterSpeedValue = 0x9201,
BrightnessValue = 0x9203,
ExposureBiasValue = 0x9204,
MaxApertureValue = 0x9205,
SubjectDistance = 0x9206,
SubsecTimeOriginal = 0x9291,
SubsecTimeDigitized = 0x9292,
FlashPixVersion = 0xa000,
PixelXDimension = 0xa002,
PixelYDimension = 0xa003,
RelatedSoundFile = 0xa004,
InteroperabilityIfdPointer = 0xa005,
SpatialFrequencyResponse = 0xa20c,
FocalPlaneXResolution = 0xa20e,
FocalPlaneYResolution = 0xa20f,
FocalPlaneResolutionUnit = 0xa210,
SubjectLocation = 0xa214,
DigitalZoomRatio = 0xa404,
FocalLengthIn35mmFilm = 0xa405,
SceneCaptureType = 0xa406,
DeviceSettingDescription = 0xa40b,
SubjectDistanceRange = 0xa40c,
CameraOwnerName = 0xa430,
BodySerialNumber = 0xa431,
LensSpecification = 0xa432,
LensSerialNumber = 0xa435,
GpsLongitudeRef = 0x0003,
GpsImgDirectionRef = 0x0010,
GpsImgDirection = 0x0011,
GpsDestLatitudeRef = 0x0013,
GpsDestLatitude = 0x0014,
GpsDestLongitudeRef = 0x0015,
GpsDestLongitude = 0x0016,
GpsDestBearingRef = 0x0017,
GpsDestDistanceRef = 0x0019,
GpsDestDistance = 0x001a,
GpsProcessingMethod = 0x001b,
GpsAreaInformation = 0x001c,
GpsDifferential = 0x001e,
GpsHPositioningError = 0x001f,
InteroperabilityIndex = 0x0001,
InteroperabilityVersion = 0x0002
public enum StrCodingFormat
TypeUndefined = 0x00010000,
TypeUndefinedWithIdCode = 0x00030000
Utf8 = StrCodingFormat.TypeAscii | 65001,
UsAscii = StrCodingFormat.TypeAscii | 20127,
WestEuropeanWin = StrCodingFormat.TypeAscii | 1252,
UsAscii_Undef = StrCodingFormat.TypeUndefined | 20127,
Utf16Le_Byte = StrCodingFormat.TypeByte | 1200,
IdCode_Utf16 = StrCodingFormat.TypeUndefinedWithIdCode | 1200,
IdCode_UsAscii = StrCodingFormat.TypeUndefinedWithIdCode | 20127,
IdCode_WestEu = StrCodingFormat.TypeUndefinedWithIdCode | 1252
public enum ExifDateFormat
public struct ExifRational
public uint Numer, Denom;
public ExifRational(int _Numer, int _Denom)
public ExifRational(uint _Numer, uint _Denom, bool _Sign = false)
return ((Sign == true) && (Numer != 0));
return ((Sign == false) && (Numer != 0));
public new string ToString()
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;
public static ExifRational FromDecimal(decimal Value)
decimal numer, tempNumer;
numer = Math.Truncate(numer + 0.5m);
if (numer <= uint.MaxValue)
else throw new OverflowException();
while (numer != decimal.Truncate(numer))
if ((denom <= 100000000) && (decimal.Truncate(tempNumer + 0.5m) < 1e9m))
ret.Numer = (uint)decimal.Truncate(numer + 0.5m);
while ((ret.Denom >= 10) && ((ret.Numer % 10) == 0))
public struct GeoCoordinate
public char CardinalPoint;
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;
public static GeoCoordinate FromDecimal(decimal Value, bool IsLatitude)
ret.CardinalPoint = IsLatitude ? 'N' : 'E';
ret.CardinalPoint = IsLatitude ? 'S' : 'W';
ret.Degree = decimal.Truncate(AbsValue);
decimal frac = (AbsValue - ret.Degree) * 60;
ret.Minute = decimal.Truncate(frac);
ret.Second = (frac - ret.Minute) * 60;
public enum ExifByteOrder { LittleEndian, BigEndian };
public enum ExifLoadOptions
CreateEmptyBlock = 0x00000001
public enum ExifSaveOptions
ImageHasUnsupportedFeatures,
InternalImageStructureIsWrong,
ExifBlockHasIllegalContent,
public enum ImageFileBlock
public enum ImageType { Unknown = 0, Jpeg, Tiff, Png };
public class ExifException : Exception
public ExifErrCode ErrorCode;
public ExifException(ExifErrCode _ErrorCode)
public override string Message
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;