// 官方 EXIF 规范 V 2.32 https://exiv2.org/tags.html
源地址: https://www.codeproject.com/Articles/5251929/CompactExifLib-Access-to-EXIF-Tags-in-JPEG-TIFF-an
// -------------------------------------------------------------------------------------------------
使用方法:(只写了Set,Get相关数据的方法自行阅读使用. 使用到部分关键字已备注,未备注的关键字自行阅读官方规范进行翻译)(工具类代码过长,方便使用已上传代码包,供下载 )/// <summary>/// 设置图像元数据/// </summary>/// <param name="imagePath">图像路径</param>privatevoidSetImageMetadata(string imagePath){if(imagePath ==""){
Debug.LogError("路径不能为空");return;}ExifData imageExifData =newExifData(imagePath);//--说明//图像描述
imageExifData.SetTagValue(ExifTag.ImageDescription, imagePath, StrCoding.Utf8);//图像用户评论 备注
imageExifData.SetTagValue(ExifTag.UserComment,"备注", StrCoding.Utf8);//--来源作者
imageExifData.SetTagValue(ExifTag.Artist,"HH", StrCoding.Utf8);//拍摄时间DateTime originalTime =newDateTime(DateTime.Now.Ticks);
imageExifData.SetTagValue(ExifTag.DateTimeOriginal, originalTime);//程序名称
imageExifData.SetTagValue(ExifTag.Software,"Unity3D", StrCoding.Utf8);//--图像图像ID
imageExifData.SetTagValue(ExifTag.ImageUniqueId,"123456", StrCoding.Utf8);压缩 https://exiftool.org/TagNames/EXIF.html#Compression
imageExifData.SetTagValue(ExifTag.Compression,2, ExifTagType.UShort);//分辨率单位 1 = 无 2 = 英寸 3 = 厘米
imageExifData.SetTagValue(ExifTag.ResolutionUnit,1, ExifTagType.UShort);//颜色表示 0x1 = sRGB 0x2 = Adobe RGB 0xfffd = Wide Gamut RGB 0xfffe = ICC Profile 0xffff = Uncalibrated
imageExifData.SetTagValue(ExifTag.ColorSpace,0x1, ExifTagType.UShort);//压缩的位/像素ExifRational compressedBitsPerPixel =newExifRational(100,1000);
imageExifData.SetTagValue(ExifTag.CompressedBitsPerPixel, compressedBitsPerPixel, ExifTagType.URational);//--照相机//照相机制造商
imageExifData.SetTagValue(ExifTag.Make,"中国", StrCoding.Utf8);//照相机型号
imageExifData.SetTagValue(ExifTag.Model,"中国相机", StrCoding.Utf8);//光圈值ExifRational fNumber =newExifRational(80,100);
imageExifData.SetTagValue(ExifTag.FNumber, fNumber, ExifTagType.URational);//曝光时间ExifRational exposureTime =newExifRational(1,20);//ExifRational.FromDecimal(1.637m);
imageExifData.SetTagValue(ExifTag.ExposureTime, exposureTime, ExifTagType.URational);//ISO速度
imageExifData.SetTagValue(ExifTag.IsoSpeedRatings,2, ExifTagType.UShort);//曝光补偿ExifRational exposureBiasValue =newExifRational(10,1000);
imageExifData.SetTagValue(ExifTag.ExposureBiasValue, exposureBiasValue, ExifTagType.SRational);//焦距ExifRational focalLength = ExifRational.FromDecimal(10);
imageExifData.SetTagValue(ExifTag.FocalLength, focalLength, ExifTagType.URational);//最大光圈ExifRational mxApertureValue =newExifRational(20,1000);
imageExifData.SetTagValue(ExifTag.MaxApertureValue, mxApertureValue, ExifTagType.URational);//测光模式 0 = 未知 1 = 平均值 2 = 中心加权平均值 3 = 点 4 = 多点 5 = 多段 6 = 部分 255 = 其他
imageExifData.SetTagValue(ExifTag.MeteringMode,0, ExifTagType.UShort);//目标距离ExifRational subjectDistance =newExifRational(10,1000);
imageExifData.SetTagValue(ExifTag.SubjectDistance, subjectDistance, ExifTagType.URational);//闪光灯模式 https://exiftool.org/TagNames/EXIF.html#Flash
imageExifData.SetTagValue(ExifTag.Flash,0, ExifTagType.UShort);闪光灯能量ExifRational flashEnergy =newExifRational(0,1000);
imageExifData.SetTagValue(ExifTag.FlashEnergy, flashEnergy, ExifTagType.URational);//35mm焦距
imageExifData.SetTagValue(ExifTag.FocalLengthIn35mmFilm,2, ExifTagType.UShort);//--高级照片//对比度 0 = 正常 1 = 低 2 = 高
imageExifData.SetTagValue(ExifTag.Contrast,0, ExifTagType.UShort);亮度ExifRational brightnessValue =newExifRational(300,1000);
imageExifData.SetTagValue(ExifTag.BrightnessValue, brightnessValue, ExifTagType.SRational);//光源 https://exiftool.org/TagNames/EXIF.html#LightSource
imageExifData.SetTagValue(ExifTag.LightSource,0, ExifTagType.UShort);//曝光程序 0 = 未定义 1 = 手动 2 = 程序自动曝光 3 = 光圈优先自动曝光 4 = 快门速度优先自动曝光 5 = 创意(慢速) 6 = 动作(高速) 7 = 人像 8 = 风景 9 = B门
imageExifData.SetTagValue(ExifTag.ExposureProgram,0, ExifTagType.UShort);//饱和度 0 = 正常 1 = 低 2 = 高
imageExifData.SetTagValue(ExifTag.Saturation,0, ExifTagType.UShort);//清晰度 0 = 正常 1 = 软 2 = 硬
imageExifData.SetTagValue(ExifTag.Sharpness,0, ExifTagType.UShort);//白平衡 0 = 自动 1 = 手动
imageExifData.SetTagValue(ExifTag.WhiteBalance,0, ExifTagType.UShort);数字缩放(数码变焦倍率)ExifRational digitalZoomRatio =newExifRational(0,1000);
imageExifData.SetTagValue(ExifTag.DigitalZoomRatio, digitalZoomRatio, ExifTagType.URational);//图像Exif版本
imageExifData.SetTagValue(ExifTag.ExifVersion,"2030", StrCoding.Utf8);//--GPS//经纬度GeoCoordinate longitude = GeoCoordinate.FromDecimal((decimal)116.40286,false);
imageExifData.SetGpsLongitude(longitude);GeoCoordinate latitude = GeoCoordinate.FromDecimal((decimal)39.93046,true);
imageExifData.SetGpsLatitude(latitude);//高度
imageExifData.SetGpsAltitude(100);try{//第一个参数DestFileNameWithPath是null或省略,该方法Save将覆盖现有的图像文件。也可以通过在参数中传递文件名来将图像另存为新文件名DestFileNameWithPath。
imageExifData.Save();
Debug.Log($"保存 {imagePath} Image元数据完成");}catch{
Debug.LogError("文件被写保护 | 拒绝访问该文件 | 该文件不再可用,例如,它已被删除或卷已被移除。 | EXIF 数据太大。JPEG 中的最大 EXIF 数据大小为 65526 字节,TIFF 和 PNG 中为 2 GB。");}}// -------------------------------------------------------------------------------------------------
工具类:usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.IO;usingSystem.Text;namespaceCompactExifLib{publicclassExifData{#region Public areapublicImageType ImageType;publicint MakerNoteOriginalOffset {get;privateset;}publicconstint IfdShift =16;// Load EXIF data from a JPEG or TIFF file.// An exception occurs in the following situations:// •The file does not exist.// •The access to the file is denied.// •The file is not a valid JPEG or TIFF file.//// If the file is a valid JPEG file but without an EXIF block, an empty EXIF block is created.publicExifData(string FileNameWithPath,ExifLoadOptions Options =0):this(){
_FileNameWithPath = Path.GetFullPath(FileNameWithPath);using(FileStream ImageFile = File.OpenRead(_FileNameWithPath)){ReadFromStream(ImageFile, Options);}}publicExifData(Stream ImageStream,ExifLoadOptions Options =0):this(){ReadFromStream(ImageStream, Options);}publicstaticExifDataEmpty(){ExifData EmptyBlock =newExifData();
EmptyBlock.SetEmptyExifBlock();return(EmptyBlock);}// Save the EXIF data in a new file or overwrite the current file.//// Parameters:// "DestFileNameWithPath": File name for saving the image and the EXIF data. If this parameter is "null",// the EXIF data is replaced in the current file.publicvoidSave(string DestFileNameWithPath =null,ExifSaveOptions SaveOptions =0){bool DestFileIsTempFile =false;string SourceFileNameWithPath = _FileNameWithPath;if(DestFileNameWithPath !=null){
DestFileNameWithPath = Path.GetFullPath(DestFileNameWithPath);}if((DestFileNameWithPath ==null)||(String.Compare(SourceFileNameWithPath, DestFileNameWithPath, StringComparison.OrdinalIgnoreCase)==0)){int BackslashPosition = SourceFileNameWithPath.LastIndexOf('\\');int DotPosition = SourceFileNameWithPath.LastIndexOf('.');if(DotPosition <= BackslashPosition) DotPosition = SourceFileNameWithPath.Length;
DestFileNameWithPath = SourceFileNameWithPath.Insert(DotPosition,"~");
DestFileIsTempFile =true;}bool DestFileCreated =false;FileStream SourceFile =null, DestFile =null;try{
SourceFile = File.OpenRead(SourceFileNameWithPath);
DestFile = File.Open(DestFileNameWithPath, FileMode.Create);
DestFileCreated =true;Save(SourceFile, DestFile, SaveOptions);
SourceFile.Dispose();
SourceFile =null;
DestFile.Dispose();
DestFile =null;if(DestFileIsTempFile){
File.Delete(SourceFileNameWithPath);// Delete original image file. If the original file cannot be deleted, delete// the temporary file within the "catch" block.
File.Move(DestFileNameWithPath, SourceFileNameWithPath);}}catch{if(SourceFile !=null){
SourceFile.Dispose();}if(DestFile !=null){
DestFile.Dispose();}if(DestFileCreated){
File.Delete(DestFileNameWithPath);}throw;}}publicvoidSave(Stream SourceStream,Stream DestStream,ExifSaveOptions SaveOptions =0){ImageType SourceImageType =CheckStreamTypeAndCompatibility(SourceStream);if(SourceImageType != ImageType){thrownewExifException(ExifErrCode.ImageTypesDoNotMatch);}if(SourceImageType == ImageType.Jpeg){SaveJpeg(SourceStream, DestStream);}elseif(SourceImageType == ImageType.Tiff){SaveTiff(SourceStream, DestStream);}elseif(SourceImageType == ImageType.Png){SavePng(SourceStream, DestStream);}}// Read a string from a tag.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "Value": Return: Tag data as a string.// In case of an error "null" is given back.// "Coding": Tag type and code page in which the string is coded.// Return value: true = The tag was successfully read.// false = The tag was not found or is of wrong type.publicboolGetTagValue(ExifTag TagSpec,outstring Value,StrCoding Coding){TagItem t;bool Success =false;int i, j;StrCodingFormat StringTagFormat =(StrCodingFormat)((uint)Coding &0xFFFF0000);ushort CodePage =(ushort)Coding;if(StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode){
Success =GetTagValueWithIdCode(TagSpec,out Value, CodePage);}else{
Value =null;if(GetTagItem(TagSpec,out t)){
i = t.ValueCount;
j = t.ValueIndex;if((CodePage ==1200)||(CodePage ==1201))// UTF16LE or UTF16BE{// Remove all null terminating characters. Here a null terminating character consists of 2 zero-bytes.while((i >=2)&&(t.ValueData[j + i -2]==0)&&(t.ValueData[j + i -1]==0)){
i -=2;}}else{// Remove all null terminating characters.while((i >=1)&&(t.ValueData[j + i -1]==0)){
i--;}}if(CodePage !=0){if(((StringTagFormat == StrCodingFormat.TypeAscii)&&(t.TagType == ExifTagType.Ascii))||((StringTagFormat == StrCodingFormat.TypeUndefined)&&(t.TagType == ExifTagType.Undefined))||((StringTagFormat == StrCodingFormat.TypeByte)&&(t.TagType == ExifTagType.Byte))){
Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
Success =true;}}}}return(Success);}// Write a string to a tag.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "Value": String to be written.// "Coding": Tag type and code page in which the string should be coded.// Return value: true = The tag was successfully written.// false = Error. Tag ID is not allowed to be written or IFD is invalid.publicboolSetTagValue(ExifTag TagSpec,string Value,StrCoding Coding){int TotalByteCount, StrByteLen, NullTermBytes;TagItem t;bool Success =false;byte[] StringAsByteArray;ExifTagType TagType;StrCodingFormat StringTagFormat =(StrCodingFormat)((uint)Coding &0xFFFF0000);ushort CodePage =(ushort)Coding;if(StringTagFormat == StrCodingFormat.TypeUndefinedWithIdCode){
Success =SetTagValueWithIdCode(TagSpec, Value, CodePage);}else{if(StringTagFormat == StrCodingFormat.TypeUndefined){
TagType = ExifTagType.Undefined;}elseif(StringTagFormat == StrCodingFormat.TypeByte){
TagType = ExifTagType.Byte;}else{
TagType = ExifTagType.Ascii;}
NullTermBytes =0;if(TagType != ExifTagType.Undefined){if((CodePage ==1200)||(CodePage ==1201))// UTF16LE or UTF16BE{
NullTermBytes =2;// Write null terminating character with 2 zero-bytes}else{
NullTermBytes =1;// Write null terminating character with 1 zero-byte}}if(CodePage !=0){
StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
StrByteLen = StringAsByteArray.Length;
TotalByteCount = StrByteLen + NullTermBytes;
t =PrepareTagForCompleteWriting(TagSpec, TagType, TotalByteCount);if(t !=null){
Array.Copy(StringAsByteArray,0, t.ValueData, t.ValueIndex, StrByteLen);if(NullTermBytes >=1) t.ValueData[t.ValueIndex + StrByteLen]=0;if(NullTermBytes >=2) t.ValueData[t.ValueIndex + StrByteLen +1]=0;
Success =true;}}}return(Success);}// Read a 32 bit signed integer number from a tag. The tag type must be one of the following values:// ExifTagType.Byte, ExifTagType.UShort, ExifTagType.ULong, ExifTagType.SLong.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "Value": Return: 32 bit integer number with sign. If the tag type is "ExifTagType.ULong" and// the tag contains a number from 0x80000000 to 0xFFFFFFFF, it is treated as an error.// In case of an error, "Value" is 0.// "Index": Array index of the value to be read.// Return value: true = The tag was successfully read.// false = The tag was not found, is of wrong type or is out of range.publicboolGetTagValue(ExifTag TagSpec,outint Value,int Index =0){TagItem t;bool Success =false;uint TempValue =0;if(GetTagItem(TagSpec,out t)&&ReadUintElement(t, Index,out TempValue)){
Success =((t.TagType != ExifTagType.ULong)||((int)TempValue >=0));if(!Success) TempValue =0;}
Value =(int)TempValue;return(Success);}// Write a 32 bit signed integer number to a tag.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "TagType": Tag type: ExifTagType.Byte, ExifTagType.UShort, ExifTagType.ULong or// ExifTagType.SLong// "Value": 32 bit integer number with sign.// "Index": Array index of the value to be written.// Return value: true = The tag was successfully written.// false = The tag specification is invalid or "Value" is out of range.publicboolSetTagValue(ExifTag TagSpec,int Value,ExifTagType TagType,int Index =0){TagItem t;bool Success =false;switch(TagType){case ExifTagType.Byte:if((Value >=0)&&(Value <=255)) Success =true;break;case ExifTagType.UShort:if((Value >=0)&&(Value <=65535)) Success =true;break;case ExifTagType.ULong:if(Value >=0) Success =true;break;case ExifTagType.SLong:
Success =true;break;}if(Success){
t =PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success =WriteUintElement(t, Index,(uint)Value);}return(Success);}publicboolGetTagValue(ExifTag TagSpec,outuint Value,int Index =0){TagItem t;bool Success =false;uint TempValue =0;if(GetTagItem(TagSpec,out t)&&ReadUintElement(t, Index,out TempValue)){
Success =((t.TagType != ExifTagType.SLong)||((int)TempValue >=0));if(!Success) TempValue =0;}
Value = TempValue;return(Success);}publicboolSetTagValue(ExifTag TagSpec,uint Value,ExifTagType TagType,int Index =0){TagItem t;bool Success =false;switch(TagType){case ExifTagType.Byte:if((Value >=0)&&(Value <=255)) Success =true;break;case ExifTagType.UShort:if((Value >=0)&&(Value <=65535)) Success =true;break;case ExifTagType.ULong:
Success =true;break;case ExifTagType.SLong:if((int)Value >=0) Success =true;break;}if(Success){
t =PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success =WriteUintElement(t, Index,(uint)Value);}return(Success);}publicboolGetTagValue(ExifTag TagSpec,outExifRational Value,int Index =0){TagItem t;bool Success =false;uint Numer, Denom;if(GetTagItem(TagSpec,out t)&&ReadURatElement(t, Index,out Numer,out Denom)==true){if(t.TagType == ExifTagType.URational){
Value =newExifRational(Numer, Denom);}else// ExifTagType.SRational{
Value =newExifRational((int)Numer,(int)Denom);}
Success =true;}else{
Value =newExifRational(0,0);}return(Success);}publicboolSetTagValue(ExifTag TagSpec,ExifRational Value,ExifTagType TagType,int Index =0){TagItem t;bool Success =false;switch(TagType){case ExifTagType.SRational:if((Value.Numer <0x80000000)&&(Value.Denom <0x80000000)){if(Value.Sign){
Value.Numer =(uint)(-(int)Value.Numer);}
Success =true;}else Success =false;break;case ExifTagType.URational:
Success =!Value.IsNegative();break;}if(Success){
t =PrepareTagForArrayItemWriting(TagSpec, TagType, Index);
Success =WriteURatElement(t, Index, Value.Numer, Value.Denom);}return(Success);}// Read time stamp from a tag. The tag type must be "ExifTagType.Ascii" and the tag// must contain a null terminated string with 19 characters in the// format "yyyy:MM:dd HH:mm:ss" or with 10 characters in the format "yyyy:MM:dd".//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "Value": Return: Date time stamp. In case of an error, the value "DateTime.MinValue" is returned.// "Format" Selection between time stamp format with 19 or 10 characters.// Return value: true = The tag was successfully read.// false = The tag was not found, is of wrong type or wrong format.publicboolGetTagValue(ExifTag TagSpec,outDateTime Value,ExifDateFormat Format = ExifDateFormat.DateAndTime){TagItem t;bool Success;int i, Year, Month, Day, Hour, Minute, Second;
Success =false;
Value = DateTime.MinValue;if(GetTagItem(TagSpec,out t)&&(t.TagType == ExifTagType.Ascii)){try{
i = t.ValueIndex;if((t.ValueCount >=10)&&(t.ValueData[i +4]==(byte)':')&&(t.ValueData[i +7]==(byte)':')){
Year =CalculateTwoDigitDecNumber(t.ValueData, i)*100+CalculateTwoDigitDecNumber(t.ValueData, i +2);
Month =CalculateTwoDigitDecNumber(t.ValueData, i +5);
Day =CalculateTwoDigitDecNumber(t.ValueData, i +8);if((Format == ExifDateFormat.DateAndTime)&&(t.ValueCount ==19+1)&&(t.ValueData[i +10]==(byte)' ')&&(t.ValueData[i +13]==(byte)':')&&(t.ValueData[i +16]==(byte)':')&&(t.ValueData[i +19]==0)){
Hour =CalculateTwoDigitDecNumber(t.ValueData, i +11);
Minute =CalculateTwoDigitDecNumber(t.ValueData, i +14);
Second =CalculateTwoDigitDecNumber(t.ValueData, i +17);
Value =newDateTime(Year, Month, Day, Hour, Minute, Second);
Success =true;}elseif((Format == ExifDateFormat.DateOnly)&&(t.ValueCount ==10+1)&&(t.ValueData[i +10]==0)){
Value =newDateTime(Year, Month, Day);
Success =true;}}}catch{// Invalid time stamp}}return(Success);}// Write a time stamp to a tag. The tag type is set to "ExifTagType.Ascii" and the time stammp// is written in the format "yyyy:MM:dd HH:mm:ss" or "yyyy:MM:dd".//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "Value": Date time stamp.// "Format" Selection between time stamp format with 19 or 10 characters.// Return value: true = The tag was successfully written.// false = Error.publicboolSetTagValue(ExifTag TagSpec,DateTime Value,ExifDateFormat Format = ExifDateFormat.DateAndTime){TagItem t;bool Success =false;int i, sByteCount =0, Year;if(Format == ExifDateFormat.DateAndTime){
sByteCount =19+1;// "+ 1" because a null character has to be written}elseif(Format == ExifDateFormat.DateOnly){
sByteCount =10+1;}if(sByteCount !=0){
t =PrepareTagForCompleteWriting(TagSpec, ExifTagType.Ascii, sByteCount);if(t !=null){
i = t.ValueIndex;
Year = Value.Year;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Year /100);ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Year %100);
t.ValueData[i]=(byte)':'; i++;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Value.Month);
t.ValueData[i]=(byte)':'; i++;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Value.Day);if(Format == ExifDateFormat.DateAndTime){
t.ValueData[i]=(byte)' '; i++;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Value.Hour);
t.ValueData[i]=(byte)':'; i++;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Value.Minute);
t.ValueData[i]=(byte)':'; i++;ConvertTwoDigitNumberToByteArr(t.ValueData,ref i, Value.Second);}
t.ValueData[i]=0; i++;
Success =true;}}return(Success);}// Read the tag data as raw data without copying the raw data.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "TagType": Return: Type of the tag.// "ValueCount": Return: Number of values which are stored in the tag.// "RawData" : Return: Array with the tag data, but there may also be other data stored in this array.// There are the following restrictions:// *The data returned in the array "RawData" must not be changed by the caller of this method.// "RawData" should only be used for reading data!// *After new data have been written into the specified tag, the array "RawData" must not be used any// more. Therefore the data returned in "RawData" should be copied as soon as possible.// *If the tag does not exist, "RawData" is null.// "RawDataIndex" : Return: Array index at which the data starts. The first data byte is stored in// "RawData[RawDataIndex]" and the last data byte is stored in// "RawData[RawDataIndex + GetTagByteCount(TagType, ValueCount) - 1]".// Return value: true = The tag was successfully read.// false = The tag was not found.publicboolGetTagRawData(ExifTag TagSpec,outExifTagType TagType,outint ValueCount,outbyte[] RawData,outint RawDataIndex){bool Success =false;if(GetTagItem(TagSpec,outTagItem t)){
TagType = t.TagType;
ValueCount = t.ValueCount;
RawData = t.ValueData;
RawDataIndex = t.ValueIndex;
Success =true;}else{
TagType =0;
ValueCount =0;
RawData =null;
RawDataIndex =0;}return(Success);}// Read the tag data as raw data with copying the raw data.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "TagType": Return: Type of the tag.// "ValueCount": Return: Number of values which are stored in the tag.// "RawData" : Return: Array with the tag data. If the tag does not exist, "RawData" is null.// Return value: true = The tag was successfully read.// false = The tag was not found.publicboolGetTagRawData(ExifTag TagSpec,outExifTagType TagType,outint ValueCount,outbyte[] RawData){bool Success =false;if(GetTagItem(TagSpec,outTagItem t)){
TagType = t.TagType;
ValueCount = t.ValueCount;int k = t.ByteCount;
RawData =newbyte[k];
Array.Copy(t.ValueData, t.ValueIndex, RawData,0, k);
Success =true;}else{
TagType =0;
ValueCount =0;
RawData =null;}return(Success);}// Write the tag data as raw data.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// "TagType": Type of the tag.// "ValueCount": Number of values which are stored in the tag. This is not the number of raw data bytes!// The number of raw data bytes is automatically calculated by the parameters "TagType" and// "ValueCount".// "RawData": Array with the tag data. The tag data is not copied by this method, therefore// the array content must not be changed after the call of this method.// "RawDataIndex": Array index at which the data starts. Set this parameter to 0, if the data starts with// the first element of the array "RawData".// Return value: true = The tag was successfully set.// false = The IFD is invalid.publicboolSetTagRawData(ExifTag TagSpec,ExifTagType TagType,int ValueCount,byte[] RawData,int RawDataIndex =0){TagItem t;bool Success =false;ExifIfd Ifd =ExtractIfd(TagSpec);if((uint)Ifd < ExifIfdCount){int RawDataByteCount =GetTagByteCount(TagType, ValueCount);Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];if(IfdTagTable.TryGetValue(ExtractTagId(TagSpec),out t)){
t.TagType = TagType;
t.ValueCount = ValueCount;
t.ValueData = RawData;
t.ValueIndex = RawDataIndex;
t.AllocatedByteCount = RawDataByteCount;}else{
t =newTagItem(ExtractTagId(TagSpec), TagType, ValueCount, RawData, RawDataIndex, RawDataByteCount);
IfdTagTable.Add(t.TagId, t);}
Success =true;}return(Success);}publicboolGetTagValueCount(ExifTag TagSpec,outint ValueCount){if(GetTagItem(TagSpec,outTagItem t)){
ValueCount = t.ValueCount;return(true);}else{
ValueCount =0;return(false);}}publicboolSetTagValueCount(ExifTag TagSpec,int ValueCount){if(GetTagItem(TagSpec,outTagItem t)&&((uint)ValueCount <= MaxTagValueCount)){
t.SetTagTypeAndValueCount(t.TagType, ValueCount,true);return(true);}elsereturn(false);}publicboolSetTagValueCount(ExifTag TagSpec,int ValueCount,ExifTagType TagType){TagItem t =PrepareTagForArrayItemWriting(TagSpec, TagType, ValueCount -1);return(t !=null);}publicboolGetTagType(ExifTag TagSpec,outExifTagType TagType){if(GetTagItem(TagSpec,outTagItem t)){
TagType = t.TagType;return(true);}else{
TagType =0;return(false);}}publicboolTagExists(ExifTag TagSpec){ExifIfd Ifd =ExtractIfd(TagSpec);if((uint)Ifd < ExifIfdCount){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];return(IfdTagTable.ContainsKey(ExtractTagId(TagSpec)));}elsereturn(false);}publicboolIfdExists(ExifIfd Ifd){if((uint)Ifd < ExifIfdCount){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];return(IfdTagTable.Count >0);}elsereturn(false);}publicboolImageFileBlockExists(ImageFileBlock BlockType){return(ImageFileBlockInfo[(int)BlockType]== ImageFileBlockState.Existent);}// Remove a single tag.//// Parameters:// "TagSpec": Tag specification, consisting of the IFD in which the tag is stored and the tag ID.// Return value: false = Tag was not removed, because it doesn't exist.// true = Tag was removed.publicboolRemoveTag(ExifTag TagSpec){ExifIfd Ifd =ExtractIfd(TagSpec);if((uint)Ifd < ExifIfdCount){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];return(IfdTagTable.Remove(ExtractTagId(TagSpec)));}elsereturn(false);}// Remove all tags from a specific IFD.//// Parameters:// "ExifIfd": IFD from which all tags are to be removed. If the IFD "ThumbnailData" is removed, the thumbnail image// is removed, too.publicboolRemoveAllTagsFromIfd(ExifIfd Ifd){bool Removed =false;if(Ifd == ExifIfd.PrimaryData){RemoveAllTagsFromIfdPrimaryData();
Removed =true;}elseif((uint)Ifd < ExifIfdCount){ClearIfd_Unchecked(Ifd);if(Ifd == ExifIfd.ThumbnailData){RemoveThumbnailImage(false);}
Removed =true;}return(Removed);}// Remove all tags from the EXIF block and remove the thumbnail image.publicvoidRemoveAllTags(){RemoveAllTagsFromIfdPrimaryData();for(ExifIfd Ifd = ExifIfd.PrivateData; Ifd <(ExifIfd)ExifIfdCount; Ifd++){ClearIfd_Unchecked(Ifd);}RemoveThumbnailImage(false);
ImageFileBlockInfo[(int)ImageFileBlock.Exif]= ImageFileBlockState.Removed;}publicvoidRemoveImageFileBlock(ImageFileBlock BlockType){if(BlockType == ImageFileBlock.Exif){RemoveAllTags();}elseif(BlockType != ImageFileBlock.Unknown){if(ImageType == ImageType.Tiff){if(BlockType == ImageFileBlock.Xmp){RemoveTag(ExifTag.XmpMetadata);}elseif(BlockType == ImageFileBlock.Iptc){RemoveTag(ExifTag.IptcMetadata);}}
ImageFileBlockInfo[(int)BlockType]= ImageFileBlockState.Removed;}}// Replace all EXIF tags and the thumbnail image by the EXIF data of another image file.//// Parameters:// "SourceExifData": EXIF data of another image, that will be copied to the EXIF data of the current object.// A deep copy is done.publicvoidReplaceAllTagsBy(ExifData SourceExifData){// Byte order handlingbool SwapByteOrder =false;if(this.ImageType == ImageType.Tiff){if(this._ByteOrder != SourceExifData._ByteOrder){// In TIFF images the EXIF block cannot be completely replaced by another EXIF block because it contains// internal tags which must not be changed. If the byte order of the two EXIF blocks is different, the byte order// of the source EXIF block shall be adapted.
SwapByteOrder =true;}}elsethis._ByteOrder = SourceExifData._ByteOrder;// Complete EXIF block is copied, therefore the byte order is also copied.// Remove all tags. But in TIFF images the TIFF internal tags, the XMP tag and the IPTC tag are not removed.this.RemoveAllTags();// Copy IFD Primary data.Dictionary<ExifTagId, TagItem> SourceIfdTagList = SourceExifData.TagTable[(uint)ExifIfd.PrimaryData];Dictionary<ExifTagId, TagItem> DestIfdTagList =this.TagTable[(uint)ExifIfd.PrimaryData];bool IsTiffImagePresent =(this.ImageType == ImageType.Tiff)||(SourceExifData.ImageType == ImageType.Tiff);bool CopyTag =true;foreach(TagItem SourceTag in SourceIfdTagList.Values){if(IsTiffImagePresent){
CopyTag =!IsInternalTiffTag(SourceTag.TagId)&&!IsMetaDataTiffTag(SourceTag.TagId);}if(CopyTag){TagItem DestTag =CopyTagItemDeeply(ExifIfd.PrimaryData, SourceTag, SwapByteOrder);AddIfNotExists(DestIfdTagList, DestTag);}}// Copy IFDs PrivateData, GpsInfoData and InteroperabilityExifIfd[] TempIfds =newExifIfd[]{ ExifIfd.PrivateData, ExifIfd.GpsInfoData, ExifIfd.Interoperability };foreach(ExifIfd Ifd in TempIfds){
SourceIfdTagList = SourceExifData.TagTable[(uint)Ifd];
DestIfdTagList =this.TagTable[(uint)Ifd];foreach(TagItem SourceTag in SourceIfdTagList.Values){TagItem DestTag =CopyTagItemDeeply(Ifd, SourceTag, SwapByteOrder);AddIfNotExists(DestIfdTagList, DestTag);}}// Copy IFD ThumbnailDataif(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 =newbyte[this.ThumbnailByteCount];
Array.Copy(SourceExifData.ThumbnailImage, SourceExifData.ThumbnailStartIndex,this.ThumbnailImage,0,this.ThumbnailByteCount);}else{RemoveThumbnailImage_Internal();}}// Determine new EXIF block stateUpdateImageFileBlockInfo();// Copy MakerNote indexthis.MakerNoteOriginalOffset = SourceExifData.MakerNoteOriginalOffset;}publicboolThumbnailImageExists(){return(ThumbnailImage !=null);}// Read the thumbnail image data.//// Parameters:// "ThumbnailData": Return: Array with the thumbnail image beginning with the JPEG marker 0xFF 0xD8.// If no thumbnail image is defined, "null" is returned.// "ThumbnailIndex": Return: Index of the first data byte of the thumbnail image.// "ThumbnailByteCount": Return: Number of bytes of the thumbnail image.// Return value: false = Thumbnail image does not exist.// true = Thumbnail image is given back.publicboolGetThumbnailImage(outbyte[] ThumbnailData,outint ThumbnailIndex,outint ThumbnailByteCount){bool Success;
ThumbnailData = ThumbnailImage;
ThumbnailIndex = ThumbnailStartIndex;
ThumbnailByteCount =this.ThumbnailByteCount;
Success =ThumbnailImageExists();return(Success);}// Write the thumbnail image data.//// Parameters:// "ThumbnailData": Array which contains the thumbnail image beginning with the JPEG marker 0xFF 0xD8.// The array is not copied by this method so it should not be changed after calling// this method.publicboolSetThumbnailImage(byte[] ThumbnailData,int ThumbnailIndex =0,int ThumbnailByteCount =-1){bool Success =false;if(ThumbnailData !=null){
ThumbnailImage = ThumbnailData;
ThumbnailStartIndex = ThumbnailIndex;if(ThumbnailByteCount <0){this.ThumbnailByteCount = ThumbnailData.Length - ThumbnailIndex;}elsethis.ThumbnailByteCount = ThumbnailByteCount;SetTagValue(ExifTag.JpegInterchangeFormat,0, ExifTagType.ULong);// "0" is a dummy valueSetTagValue(ExifTag.JpegInterchangeFormatLength,this.ThumbnailByteCount, ExifTagType.ULong);
Success =true;}return(Success);}// Remove the thumbnail image data.//// Parameters:// "RemoveAlsoThumbnailTags": false = Remove the thumbnail image, the thumbnail pointer tag and the thumbnail size tag.// true = Remove also all other tags in the IFD "thumbnail data".publicvoidRemoveThumbnailImage(bool RemoveAlsoThumbnailTags){RemoveThumbnailImage_Internal();if(RemoveAlsoThumbnailTags){ClearIfd_Unchecked(ExifIfd.ThumbnailData);}else{RemoveTag(ExifTag.JpegInterchangeFormat);RemoveTag(ExifTag.JpegInterchangeFormatLength);}}// Get the byte order for the EXIF data.publicExifByteOrder ByteOrder
{get{return(_ByteOrder);}}// Extract the IFD of a tag specification.publicstaticExifIfdExtractIfd(ExifTag TagSpec){return(ExifIfd)((uint)TagSpec >> IfdShift);}// Extract the tag ID of a tag specification.publicstaticExifTagIdExtractTagId(ExifTag TagSpec){return(ExifTagId)((ushort)TagSpec);}// Compose IFD and tag ID to a tag specification.publicstaticExifTagComposeTagSpec(ExifIfd Ifd,ExifTagId TagId){return(ExifTag)(((uint)Ifd << IfdShift)|(uint)TagId);}// Get the number of bytes that a tag with the specified type and value count has.// If the tag type is invalid, the return value is 0.publicstaticintGetTagByteCount(ExifTagType TagType,int ValueCount){if((uint)TagType < TypeByteCountLen){return(TypeByteCount[(uint)TagType]* ValueCount);}elsereturn(0);}publicushortExifReadUInt16(byte[] Data,int StartIndex){ushort v;if(_ByteOrder == ExifByteOrder.BigEndian){
v =(ushort)(((uint)Data[StartIndex]<<8)| Data[StartIndex +1]);}else{
v =(ushort)(((uint)Data[StartIndex +1]<<8)| Data[StartIndex]);}return(v);}publicvoidExifWriteUInt16(byte[] Data,int StartIndex,ushort Value){if(_ByteOrder == ExifByteOrder.BigEndian){
Data[StartIndex]=(byte)(Value >>8);
Data[StartIndex +1]=(byte)Value;}else{
Data[StartIndex +1]=(byte)(Value >>8);
Data[StartIndex]=(byte)Value;}}publicuintExifReadUInt32(byte[] Data,int StartIndex){uint v;if(_ByteOrder == ExifByteOrder.BigEndian){
v =((uint)Data[StartIndex]<<24)|((uint)Data[StartIndex +1]<<16)|((uint)Data[StartIndex +2]<<8)|(Data[StartIndex +3]);}else{
v =((uint)Data[StartIndex +3]<<24)|((uint)Data[StartIndex +2]<<16)|((uint)Data[StartIndex +1]<<8)|(Data[StartIndex]);}return(v);}publicvoidExifWriteUInt32(byte[] Data,int StartIndex,uint Value){if(_ByteOrder == ExifByteOrder.BigEndian){
Data[StartIndex]=(byte)(Value >>24);
Data[StartIndex +1]=(byte)(Value >>16);
Data[StartIndex +2]=(byte)(Value >>8);
Data[StartIndex +3]=(byte)(Value);}else{
Data[StartIndex +3]=(byte)(Value >>24);
Data[StartIndex +2]=(byte)(Value >>16);
Data[StartIndex +1]=(byte)(Value >>8);
Data[StartIndex]=(byte)(Value);}}privateushortReadUInt16BE(byte[] Data,int StartIndex){ushort v =(ushort)(((uint)Data[StartIndex]<<8)| Data[StartIndex +1]);return(v);}privateuintReadUInt32BE(byte[] Data,int StartIndex){uint v =((uint)Data[StartIndex]<<24)|((uint)Data[StartIndex +1]<<16)|((uint)Data[StartIndex +2]<<8)|(Data[StartIndex +3]);return(v);}privatevoidWriteUInt16BE(byte[] Data,int StartIndex,ushort Value){
Data[StartIndex]=(byte)(Value >>8);
Data[StartIndex +1]=(byte)Value;}privatevoidWriteUInt32BE(byte[] Data,int StartIndex,uint Value){
Data[StartIndex]=(byte)(Value >>24);
Data[StartIndex +1]=(byte)(Value >>16);
Data[StartIndex +2]=(byte)(Value >>8);
Data[StartIndex +3]=(byte)(Value);}// Initialize enumeration for all tags of a specific IFD.publicboolInitTagEnumeration(ExifIfd Ifd){if((uint)Ifd < ExifIfdCount){
ExifIfdForTagEnumeration = Ifd;
TagEnumerator = TagTable[(uint)Ifd].Keys.GetEnumerator();return(true);}else{
TagEnumerator =newDictionary<ExifTagId, TagItem>.KeyCollection.Enumerator();return(false);}}// Get the next tag of the IFD, that was defined by a previous call to the method "InitTagEnumeration".//// Return value: true = Next tag was successfully read.// false = There is no further tag available.publicboolEnumerateNextTag(outExifTag TagSpec){bool Success =false;if(TagEnumerator.MoveNext()){ExifTagId TagId = TagEnumerator.Current;
TagSpec =ComposeTagSpec(ExifIfdForTagEnumeration, TagId);
Success =true;}else TagSpec =0;return(Success);}// -----------------------------------------------------------------------------------------------------------------// Methods for accessing EXIF tags on high level// -----------------------------------------------------------------------------------------------------------------publicboolGetDateTaken(outDateTime Value){return(GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTimeOriginal, ExifTag.SubsecTimeOriginal));}publicboolSetDateTaken(DateTime Value){return(SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTimeOriginal, ExifTag.SubsecTimeOriginal));}publicvoidRemoveDateTaken(){RemoveTag(ExifTag.DateTimeOriginal);RemoveTag(ExifTag.SubsecTimeOriginal);}publicboolGetDateDigitized(outDateTime Value){return(GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTimeDigitized, ExifTag.SubsecTimeDigitized));}publicboolSetDateDigitized(DateTime Value){return(SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTimeDigitized, ExifTag.SubsecTimeDigitized));}publicvoidRemoveDateDigitized(){RemoveTag(ExifTag.DateTimeDigitized);RemoveTag(ExifTag.SubsecTimeDigitized);}publicboolGetDateChanged(outDateTime Value){return(GetDateAndTimeWithMillisecHelper(out Value, ExifTag.DateTime, ExifTag.SubsecTime));}publicboolSetDateChanged(DateTime Value){return(SetDateAndTimeWithMillisecHelper(Value, ExifTag.DateTime, ExifTag.SubsecTime));}publicvoidRemoveDateChanged(){RemoveTag(ExifTag.DateTime);RemoveTag(ExifTag.SubsecTime);}publicboolGetGpsLongitude(outGeoCoordinate Value){return(GetGpsCoordinateHelper(out Value, ExifTag.GpsLongitude, ExifTag.GpsLongitudeRef,'W','E'));}publicboolSetGpsLongitude(GeoCoordinate Value){return(SetGpsCoordinateHelper(Value, ExifTag.GpsLongitude, ExifTag.GpsLongitudeRef,'W','E'));}publicvoidRemoveGpsLongitude(){RemoveTag(ExifTag.GpsLongitude);RemoveTag(ExifTag.GpsLongitudeRef);}publicboolGetGpsLatitude(outGeoCoordinate Value){return(GetGpsCoordinateHelper(out Value, ExifTag.GpsLatitude, ExifTag.GpsLatitudeRef,'N','S'));}publicboolSetGpsLatitude(GeoCoordinate Value){return(SetGpsCoordinateHelper(Value, ExifTag.GpsLatitude, ExifTag.GpsLatitudeRef,'N','S'));}publicvoidRemoveGpsLatitude(){RemoveTag(ExifTag.GpsLatitude);RemoveTag(ExifTag.GpsLatitudeRef);}publicboolGetGpsAltitude(outdecimal Value){bool Success =false;ExifRational AltitudeRat;uint BelowSeaLevel;if(GetTagValue(ExifTag.GpsAltitude,out AltitudeRat)&& AltitudeRat.IsValid()){
Value = ExifRational.ToDecimal(AltitudeRat);if(GetTagValue(ExifTag.GpsAltitudeRef,out BelowSeaLevel)&&(BelowSeaLevel ==1)){
Value =-Value;}
Success =true;}else Value =0;return(Success);}/// <summary>/// 设置相对于海平面的高度/// </summary>/// <param name="Value"></param>/// <returns></returns>publicboolSetGpsAltitude(decimal Value){bool Success =false;ExifRational AltitudeRat = ExifRational.FromDecimal(Value);uint BelowSeaLevel =0;if(AltitudeRat.IsNegative()){
BelowSeaLevel =1;
AltitudeRat.Sign =false;// Remove negative sign from "AltitudeRat"}if(SetTagValue(ExifTag.GpsAltitude, AltitudeRat, ExifTagType.URational)&&SetTagValue(ExifTag.GpsAltitudeRef, BelowSeaLevel, ExifTagType.Byte)){
Success =true;}return(Success);}publicvoidRemoveGpsAltitude(){RemoveTag(ExifTag.GpsAltitude);RemoveTag(ExifTag.GpsAltitudeRef);}publicboolGetGpsDateTimeStamp(outDateTime Value){bool Success =false;ExifRational Hour, Min, Sec;if(GetTagValue(ExifTag.GpsDateStamp,out Value, ExifDateFormat.DateOnly)){if(GetTagValue(ExifTag.GpsTimeStamp,out Hour,0)&&!Hour.IsNegative()&& Hour.IsValid()&&GetTagValue(ExifTag.GpsTimeStamp,out Min,1)&&!Min.IsNegative()&& Min.IsValid()&&GetTagValue(ExifTag.GpsTimeStamp,out Sec,2)&&!Sec.IsNegative()&& Sec.IsValid()){
Value = Value.AddHours(((double)Hour.Numer)/ Hour.Denom);
Value = Value.AddMinutes(((double)Min.Numer)/ Min.Denom);double ms = Math.Truncate(((double)Sec.Numer *1000)/ Sec.Denom);
Value = Value.AddMilliseconds(ms);
Success =true;}else Value = DateTime.MinValue;}else Value = DateTime.MinValue;return(Success);}publicboolSetGpsDateTimeStamp(DateTime Value){bool Success =false;ExifRational Sec;if(SetTagValue(ExifTag.GpsDateStamp, Value.Date, ExifDateFormat.DateOnly)){TimeSpan ts = Value.TimeOfDay;ExifRational Hour =newExifRational(ts.Hours,1);ExifRational Min =newExifRational(ts.Minutes,1);int ms = ts.Milliseconds;if(ms ==0){
Sec =newExifRational(ts.Seconds,1);}else{
Sec =newExifRational(ts.Seconds *1000+ ms,1000);}if(SetTagValue(ExifTag.GpsTimeStamp, Hour, ExifTagType.URational,0)&&SetTagValue(ExifTag.GpsTimeStamp, Min, ExifTagType.URational,1)&&SetTagValue(ExifTag.GpsTimeStamp, Sec, ExifTagType.URational,2)){
Success =true;}}return(Success);}publicvoidRemoveGpsDateTimeStamp(){RemoveTag(ExifTag.GpsDateStamp);RemoveTag(ExifTag.GpsTimeStamp);}#endregion#region Private area// -----------------------------------------------------------------------------------------------------------------// --- Private area ------------------------------------------------------------------------------------------------// -----------------------------------------------------------------------------------------------------------------privateconstint MinExifBlockLen =2+4;// Minimum number of bytes for an EXIF block: Value for the number of tags of IFD Primary Data (2 bytes) +// Offset of IFD Thumbnail Data (JPEG) or offset of next EXIF block (TIFF) (4 bytes)privateconstint MaxTagValueCount =int.MaxValue /8;privatebyte[] SourceExifBlock;// Temporary storage for the EXIF block in JPEG and PNG images, starting at index 8.// In index 0 to 7 the TIFF header is stored.privateStream SourceExifStream;// Temporary stream from which the EXIF block is read in TIFF images.privateExifByteOrder _ByteOrder;// Byte order value, extracted from the TIFF headerprivatestring _FileNameWithPath;privatestaticreadonlyint[] TypeByteCount ={0,// Type 0: invalid1,// Type 1: ExifTagType.Byte1,// Type 2: ExifTagType.Ascii, 8 bit character2,// Type 3: ExifTagType.UShort4,// Type 4: ExifTagType.ULong8,// Type 5: ExifTagType.URational1,// Type 6: ExifTagType.SByte1,// Type 7: ExifTagType.Undefined, 8 bit value2,// Type 8: ExifTagType.SShort4,// Type 9: ExifTagType.SLong8,// Type 10: ExifTagType.SRational4,// Type 11: ExifTagType.Float8// Type 12: ExifTagType.Double};privateconstint TypeByteCountLen =13;// Number of elements of the array "TypeByteCount"privateconstint IdCodeLength =8;privatestaticreadonlybyte[] IdCodeUtf16 =newbyte[]{(byte)'U',(byte)'N',(byte)'I',(byte)'C',(byte)'O',(byte)'D',(byte)'E',0};privatestaticreadonlybyte[] IdCodeAscii =newbyte[]{(byte)'A',(byte)'S',(byte)'C',(byte)'I',(byte)'I',0,0,0};privatestaticreadonlybyte[] IdCodeDefault =newbyte[]{0,0,0,0,0,0,0,0};privateDictionary<ExifTagId, TagItem>[] TagTable;privatebyte[] ThumbnailImage;// Array, which contains among other data the thumbnail imageprivateint ThumbnailStartIndex, ThumbnailByteCount;// Range, in which the thumbnail image in the array "ThumbnailData" is storedprivateconstint ExifIfdCount =5;// Number of elements of the enum type "ExifIfd", i. e. the number of IFDsprivateExifIfd ExifIfdForTagEnumeration;privateDictionary<ExifTagId, TagItem>.KeyCollection.Enumerator TagEnumerator;privateenumImageFileBlockState{ NonExistent =0, Removed, Existent };//private const ImageFileBlock LastImageFileBlock = ImageFileBlock.JpegComment;privateImageFileBlockState[] ImageFileBlockInfo;privateExifErrCode ErrCodeForIllegalExifBlock;//private int ExifBlockFilePosition = 0;// JPEG filesprivateconstint JpegMaxExifBlockLen =65534-2-6- TiffHeaderLen;// Maximum number of bytes an EXIF block in a JPEG file may haveprivateconstushort JpegApp0Marker =0xFFE0;privateconstushort JpegApp1Marker =0xFFE1;privateconstushort JpegApp2Marker =0xFFE2;privateconstushort JpegApp13Marker =0xFFED;privateconstushort JpegApp14Marker =0xFFEE;privateconstushort JpegCommentMarker =0xFFFE;// Start of JPEG comment blockprivateconstushort JpegSoiMarker =0xFFD8;// Start of image (SOI) markerprivateconstushort JpegSosMarker =0xFFDA;// Start of scan (SOS) markerprivatestaticreadonlybyte[] JpegExifSignature ={(byte)'E',(byte)'x',(byte)'i',(byte)'f',0,0};privatestaticreadonlybyte[] 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)'/'};privatestaticreadonlybyte[] JpegIptcSignature ={(byte)'P',(byte)'h',(byte)'o',(byte)'t',(byte)'o',(byte)'s',(byte)'h',(byte)'o',(byte)'p',(byte)' ',(byte)'3',(byte)'.',(byte)'0',0};// TIFF filesprivateconstint TiffHeaderLen =8;// Size of TIFF header. The TIFF header is also present in EXIF blocks of JPEG and PNG files.privateconstuint TiffHeaderSignatureLE =0x49492A00;privateconstuint TiffHeaderSignatureBE =0x4D4D002A;// PNG filesprivateconstuint PngHeaderPart1 =0x89504E47;privateconstuint PngHeaderPart2 =0x0D0A1A0A;privateconstuint PngIhdrChunk =((uint)'I'<<24)|((uint)'H'<<16)|((uint)'D'<<8)|(uint)'R';privateconstuint PngExifChunk =((uint)'e'<<24)|((uint)'X'<<16)|((uint)'I'<<8)|(uint)'f';privateconstuint PngItxtChunk =((uint)'i'<<24)|((uint)'T'<<16)|((uint)'X'<<8)|(uint)'t';privateconstuint PngTextChunk =((uint)'t'<<24)|((uint)'E'<<16)|((uint)'X'<<8)|(uint)'t';privateconstuint PngTimeChunk =((uint)'t'<<24)|((uint)'I'<<16)|((uint)'M'<<8)|(uint)'E';privateconstuint PngIendChunk =((uint)'I'<<24)|((uint)'E'<<16)|((uint)'N'<<8)|(uint)'D';privatestaticreadonlybyte[] 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};privatestaticreadonlybyte[] 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};privatestaticreadonlyint ImageFileBlockCount = Enum.GetValues(typeof(ImageFileBlock)).Length;privateExifData(){
_ByteOrder = ExifByteOrder.BigEndian;
ImageFileBlockInfo =newImageFileBlockState[ImageFileBlockCount];}privatevoidReadFromStream(Stream ImageStream,ExifLoadOptions Options){
SourceExifStream = ImageStream;
ErrCodeForIllegalExifBlock = ExifErrCode.ExifBlockHasIllegalContent;
ImageType =CheckStreamTypeAndCompatibility(SourceExifStream);if(Options.HasFlag(ExifLoadOptions.CreateEmptyBlock)){SetEmptyExifBlock();}else{if(ImageType == ImageType.Jpeg){ReadJepg();}elseif(ImageType == ImageType.Tiff){ReadTiff();}elseif(ImageType == ImageType.Png){ReadPng();}UpdateImageFileBlockInfo();}
SourceExifBlock =null;
SourceExifStream =null;}// Read JPEG file from stream "SourceExifStream".privatevoidReadJepg(){byte[] BlockContent =newbyte[65536];ushort BlockMarker;ImageFileBlock BlockType;int MetaDataIndex;bool ExifBlockFound =false;
SourceExifStream.Position =2;do{ReadJpegBlock(SourceExifStream, BlockContent,outint BlockContentSize,out BlockMarker,out BlockType,out MetaDataIndex);if(BlockType != ImageFileBlock.Unknown){if((BlockType == ImageFileBlock.Exif)&&(!ExifBlockFound)){
ExifBlockFound =true;int TiffHeaderAndExifBlockLen = BlockContentSize - MetaDataIndex;
SourceExifBlock =newbyte[TiffHeaderAndExifBlockLen];
Array.Copy(BlockContent, MetaDataIndex, SourceExifBlock,0, TiffHeaderAndExifBlockLen);EvaluateTiffHeader(SourceExifBlock, TiffHeaderAndExifBlockLen,outint IfdPrimaryDataOffset);EvaluateExifBlock(IfdPrimaryDataOffset);}
ImageFileBlockInfo[(int)BlockType]= ImageFileBlockState.Existent;}}while(BlockMarker != JpegSosMarker);if(!ExifBlockFound){SetEmptyExifBlock();}}// Read TIFF file from stream "SourceExifStream".privatevoidReadTiff(){byte[] BlockContent =newbyte[8];
SourceExifStream.Position =0;
SourceExifBlock =null;
ErrCodeForIllegalExifBlock = ExifErrCode.InternalImageStructureIsWrong;int TiffHeaderBytesRead = SourceExifStream.Read(BlockContent,0, TiffHeaderLen);EvaluateTiffHeader(BlockContent, TiffHeaderBytesRead,outint IfdPrimaryDataOffset);EvaluateExifBlock(IfdPrimaryDataOffset);}// Read PNG file from stream "SourceExifStream".privatevoidReadPng(){byte[] TempData =newbyte[65536];byte[] BlockContent =newbyte[30];bool ExifBlockFound =false;uint ChunkType;
SourceExifStream.Position =4;int k = SourceExifStream.Read(TempData,0,4);if((k <4)||(ReadUInt32BE(TempData,0)!= PngHeaderPart2)){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}do{ReadPngBlockHeader(SourceExifStream, TempData,outint DataLength,out ChunkType);int BytesRead =DetectPngImageBlock(SourceExifStream, BlockContent, ChunkType,outImageFileBlock BlockType);if(BlockType != ImageFileBlock.Unknown){if((BlockType == ImageFileBlock.Exif)&&(!ExifBlockFound)){
ExifBlockFound =true;
SourceExifBlock =newbyte[DataLength];if(SourceExifStream.Read(SourceExifBlock,0, DataLength)< DataLength){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}uint Crc32Calculated =CalculateCrc32(TempData,4,4,false);// Calculate CRC32 of chunk type
Crc32Calculated =CalculateCrc32(SourceExifBlock,0, DataLength,true, Crc32Calculated);
k = SourceExifStream.Read(TempData,0,4);uint Crc32Loaded =ReadUInt32BE(TempData,0);if((k <4)||(Crc32Calculated != Crc32Loaded)){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}EvaluateTiffHeader(SourceExifBlock, DataLength,outint IfdPrimaryDataOffset);EvaluateExifBlock(IfdPrimaryDataOffset);
BytesRead = DataLength +4;}
ImageFileBlockInfo[(int)BlockType]= ImageFileBlockState.Existent;}
SourceExifStream.Position += DataLength +4- BytesRead;}while(ChunkType != PngIendChunk);if(!ExifBlockFound){SetEmptyExifBlock();}}// Read block length and chunk type and return them in "TempData" as raw data and in "BlockLength" and// "ChunkType" as 32 bit values.privatevoidReadPngBlockHeader(Stream ImageStream,byte[] TempData,outint BlockLength,outuint ChunkType){if(ImageStream.Read(TempData,0,8)<8){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}
BlockLength =(int)ReadUInt32BE(TempData,0);if(BlockLength <0){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}
ChunkType =ReadUInt32BE(TempData,4);}privateintDetectPngImageBlock(Stream ImageStream,byte[] SignatureData,uint ChunkType,outImageFileBlock BlockType){int BytesRead =0;
BlockType = ImageFileBlock.Unknown;if(ChunkType == PngExifChunk){
BlockType = ImageFileBlock.Exif;}elseif(ChunkType == PngItxtChunk){int BytesToRead = PngIptcSignature.Length;
BytesRead = ImageStream.Read(SignatureData,0, BytesToRead);if(ArrayStartsWith(SignatureData, BytesRead, PngXmpSignature)){
BlockType = ImageFileBlock.Xmp;}elseif(ArrayStartsWith(SignatureData, BytesRead, PngIptcSignature)){
BlockType = ImageFileBlock.Iptc;}}elseif(ChunkType == PngTextChunk){
BlockType = ImageFileBlock.PngMetaData;}elseif(ChunkType == PngTimeChunk){
BlockType = ImageFileBlock.PngDateChanged;}return(BytesRead);}// CRC32 checksum table for polynomial 0xEDB88320privatestaticreadonlyuint[] 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};privatestaticuintCalculateCrc32(byte[] Data,int StartIndex,int Length,bool Finalize =true,uint StartCrc =0xFFFFFFFF){uint result = StartCrc;int EndIndexPlus1 = StartIndex + Length;for(int i = StartIndex; i < EndIndexPlus1; i++){bytevalue= Data[i];
result = Crc32ChecksumTable[((byte)result)^value]^(result >>8);}if(Finalize) result =~result;return(result);}privatevoidUpdateImageFileBlockInfo(){ImageFileBlockState NewExifBlockState = ImageFileBlockState.NonExistent;Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];foreach(ExifTagId tid in PrimaryDataIfdTable.Keys){if(ImageType == ImageType.Tiff){if(!IsInternalTiffTag(tid)&&!IsMetaDataTiffTag(tid)){
NewExifBlockState = ImageFileBlockState.Existent;break;}}else{
NewExifBlockState = ImageFileBlockState.Existent;break;}}if(NewExifBlockState == ImageFileBlockState.NonExistent){ExifIfd[] TempIfds =newExifIfd[]{ ExifIfd.PrivateData, ExifIfd.GpsInfoData, ExifIfd.Interoperability, ExifIfd.ThumbnailData };foreach(ExifIfd Ifd in TempIfds){if(this.TagTable[(uint)Ifd].Count >0){
NewExifBlockState = ImageFileBlockState.Existent;break;}}}
ImageFileBlockInfo[(int)ImageFileBlock.Exif]= NewExifBlockState;if(ImageType == ImageType.Tiff){// In TIFF images the XMP and IPTC block are stored as tags within the EXIF blockif(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;}}privatevoidReadJpegBlockMarker(Stream ImageStream,outushort BlockMarker,outint BlockContentSize){byte[] TempBuffer =newbyte[2];
ImageStream.Read(TempBuffer,0,2);
BlockMarker =ReadUInt16BE(TempBuffer,0);if((BlockMarker ==0xFF01)||((BlockMarker >=0xFFD0)&&(BlockMarker <=0xFFDA))){// Block does not have a size specification
BlockContentSize =0;}elseif(BlockMarker ==0xFFFF){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}else{
ImageStream.Read(TempBuffer,0,2);
BlockContentSize =(int)ReadUInt16BE(TempBuffer,0)-2;if(BlockContentSize <0){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}}}// Read JPEG block. If the JPEG block has a size specification, the block content is read and returned.// If the JPEG block doesn't have a size specification, only the block marker is read and "BlockContentSize" is set to 0.//// Parameters:// "BlockData": in: Allocated array with a size of at least 65535 Bytes.// out: Content of JPEG block.privatevoidReadJpegBlock(Stream ImageStream,byte[] BlockContent,outint BlockContentSize,outushort BlockMarker,outImageFileBlock BlockType,outint MetaDataIndex){
BlockType = ImageFileBlock.Unknown;
MetaDataIndex =0;ReadJpegBlockMarker(ImageStream,out BlockMarker,outint ContentSize);if(ContentSize >0){int k = ImageStream.Read(BlockContent,0, ContentSize);if(k == ContentSize){if(BlockMarker == JpegApp1Marker){if(ArrayStartsWith(BlockContent, ContentSize, JpegExifSignature)){
BlockType = ImageFileBlock.Exif;
MetaDataIndex = JpegExifSignature.Length;}elseif(ArrayStartsWith(BlockContent, ContentSize, JpegXmpInitialSignature)){
BlockType = ImageFileBlock.Xmp;int i = JpegXmpInitialSignature.Length;while((i < ContentSize)&&(BlockContent[i]!=0)){
i++;}
MetaDataIndex = i +1;}}elseif(BlockMarker == JpegApp13Marker){if(ArrayStartsWith(BlockContent, ContentSize, JpegIptcSignature)){
BlockType = ImageFileBlock.Iptc;
MetaDataIndex = JpegIptcSignature.Length;}}elseif(BlockMarker == JpegCommentMarker){
BlockType = ImageFileBlock.JpegComment;}}elsethrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}
BlockContentSize = ContentSize;}// The first 4 bytes are read from the stream "StreamToBeChecked" to identify the image type.privateImageTypeCheckStreamTypeAndCompatibility(Stream StreamToBeChecked){ImageType StreamImageType;byte[] TempBuffer =newbyte[4];
StreamToBeChecked.Read(TempBuffer,0,4);uint ImageSignature =ReadUInt32BE(TempBuffer,0);if((ImageSignature >>16)== JpegSoiMarker){
StreamImageType = ImageType.Jpeg;}elseif((ImageSignature == TiffHeaderSignatureLE)||(ImageSignature == TiffHeaderSignatureBE)){
StreamImageType = ImageType.Tiff;}elseif(ImageSignature == PngHeaderPart1){
StreamImageType = ImageType.Png;}elsethrownewExifException(ExifErrCode.ImageTypeIsNotSupported);// Check stream sizeif(StreamToBeChecked.Length >int.MaxValue){thrownewExifException(ExifErrCode.ImageHasUnsupportedFeatures);}// Check if stream is seekableconstlong CheckOffset =-2;long i = StreamToBeChecked.Position;
StreamToBeChecked.Position += CheckOffset;if(StreamToBeChecked.Position !=(i + CheckOffset)){// The stream for reading the image data must support the property "Position"thrownewExifException(ExifErrCode.ImageHasUnsupportedFeatures);}
StreamToBeChecked.Position = i;return(StreamImageType);}privatevoidEvaluateTiffHeader(byte[] TiffHeader,int TiffHeaderBytesRead,outint IfdPrimaryDataOffset){if(TiffHeaderBytesRead < TiffHeaderLen){thrownewExifException(ErrCodeForIllegalExifBlock);}uint TiffHeaderSignature =ReadUInt32BE(TiffHeader,0);if(TiffHeaderSignature == TiffHeaderSignatureLE){
_ByteOrder = ExifByteOrder.LittleEndian;}elseif(TiffHeaderSignature == TiffHeaderSignatureBE){
_ByteOrder = ExifByteOrder.BigEndian;}elsethrownewExifException(ErrCodeForIllegalExifBlock);
IfdPrimaryDataOffset =(int)ExifReadUInt32(TiffHeader,4);if(IfdPrimaryDataOffset < TiffHeaderLen){thrownewExifException(ErrCodeForIllegalExifBlock);}}privatevoidSaveJpeg(Stream SourceStream,Stream DestStream){constlong FirstBlockStartPosition =2;ushort BlockMarker;byte[] BlockContent =newbyte[65536];byte[] TempBuffer =newbyte[4];
SourceStream.Position = FirstBlockStartPosition;WriteUInt16BE(TempBuffer,0, JpegSoiMarker);
DestStream.Write(TempBuffer,0,2);// Copy all APP0 blocks of the source file. These blocks should be the JFIF blocks.do{ReadJpegBlockMarker(SourceStream,out BlockMarker,outint BlockContentSize);if(BlockMarker == JpegSosMarker){break;// Start of JPEG image matrix reached}long NextBlockStartPosition = SourceStream.Position + BlockContentSize;if(BlockMarker == JpegApp0Marker){WriteUInt16BE(TempBuffer,0, BlockMarker);WriteUInt16BE(TempBuffer,2,(ushort)(BlockContentSize +2));
DestStream.Write(TempBuffer,0,4);if(SourceStream.Read(BlockContent,0, BlockContentSize)== BlockContentSize){
DestStream.Write(BlockContent,0, BlockContentSize);}elsethrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}
SourceStream.Position = NextBlockStartPosition;}while(true);// Write new EXIF block if the new EXIF block is not emptyCreateExifBlock(outFlexArray NewExifBlock, TiffHeaderLen);int NewExifBlockLen = NewExifBlock.Length;if(NewExifBlockLen > JpegMaxExifBlockLen){thrownewExifException(ExifErrCode.ExifDataAreTooLarge);}elseif(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(outbyte[] TiffHeader, TiffHeaderLen);
DestStream.Write(TiffHeader,0, TiffHeader.Length);
DestStream.Write(NewExifBlock.Buffer,0, NewExifBlockLen);}// Return to the first block of the image
SourceStream.Position = FirstBlockStartPosition;do{ReadJpegBlock(SourceStream, BlockContent,outint BlockContentSize,out BlockMarker,outImageFileBlock BlockType,out _);if(BlockMarker == JpegSosMarker){// Start of JPEG image matrix reached. Copy all remaining data from source stream to destination stream// without further interpretation.WriteUInt16BE(TempBuffer,0, BlockMarker);
DestStream.Write(TempBuffer,0,2);int k;do{
k = SourceStream.Read(BlockContent,0, BlockContent.Length);
DestStream.Write(BlockContent,0, k);}while(k == BlockContent.Length);break;// Destination stream was completely written}bool CopyBlockFromSourceStream =true;if(BlockMarker == JpegApp0Marker){
CopyBlockFromSourceStream =false;// All APP0 blocks have already been copied}elseif(BlockType == ImageFileBlock.Exif){
CopyBlockFromSourceStream =false;// EXIF block has already been written}elseif((BlockType == ImageFileBlock.Xmp)&&(ImageFileBlockInfo[(int)BlockType]== ImageFileBlockState.Removed)){
CopyBlockFromSourceStream =false;}elseif((BlockType == ImageFileBlock.Iptc)&&(ImageFileBlockInfo[(int)BlockType]== ImageFileBlockState.Removed)){
CopyBlockFromSourceStream =false;}elseif((BlockType == ImageFileBlock.JpegComment)&&(ImageFileBlockInfo[(int)BlockType]== ImageFileBlockState.Removed)){
CopyBlockFromSourceStream =false;}if(CopyBlockFromSourceStream){WriteUInt16BE(TempBuffer,0, BlockMarker);WriteUInt16BE(TempBuffer,2,(ushort)(BlockContentSize +2));
DestStream.Write(TempBuffer,0,4);
DestStream.Write(BlockContent,0, BlockContentSize);}}while(true);}privatevoidSaveTiff(Stream SourceStream,Stream DestStream){int k, ImageNumber =0, NextExifBlockPointerIndex =0;byte[] TempBuffer =newbyte[65536];ExifData CurrentImageExif;FlexArray ExifBlockAsBinaryData =null;
SourceStream.Position =0;byte[] TiffHeader =newbyte[TiffHeaderLen];if(SourceStream.Read(TiffHeader,0, TiffHeaderLen)!= TiffHeaderLen){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}ExifByteOrder SourceStreamByteOrder = ExifByteOrder.LittleEndian;uint TiffHeaderSignature =ReadUInt32BE(TiffHeader,0);if(TiffHeaderSignature == TiffHeaderSignatureBE){
SourceStreamByteOrder = ExifByteOrder.BigEndian;}if(_ByteOrder != SourceStreamByteOrder){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}int SourceStreamExifBlockOffset =(int)ExifReadUInt32(TiffHeader,4);uint CurrentDestStreamOffset = TiffHeaderLen;// TIFF file structure when saving a multi-page TIFF file://// --------------------------------------------// TIFF Header (8 bytes)// --------------------------------------------// Image 1 matrix// --------------------------------------------// Image 1 EXIF block// --------------------------------------------// Image 2 matrix// --------------------------------------------// Image 2 EXIF block// --------------------------------------------// ...do{
CurrentImageExif =CreateSubExifData(SourceStream,ref SourceStreamExifBlockOffset, SourceStreamByteOrder,outuint[] SegmentOffsetTable,outuint[] SegmentByteCountTable,outExifTag SegmentOffsetsTag,outExifTag SegmentByteCountsTag);if(ImageNumber ==0){
CurrentImageExif =this;// Discard the EXIF block returned by "CreateSubExifData"}if(!CurrentImageExif.TagExists(ExifTag.ImageWidth)||!CurrentImageExif.TagExists(ExifTag.ImageLength)){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}
CurrentImageExif.RemoveTag(ExifTag.FreeOffsets);// Remove tags for areas of unused bytes
CurrentImageExif.RemoveTag(ExifTag.FreeByteCounts);// Assign new segment offsets.int ImageSegmentCount = SegmentOffsetTable.Length;
CurrentImageExif.SetTagValueCount(SegmentOffsetsTag, ImageSegmentCount, ExifTagType.ULong);
CurrentImageExif.SetTagValueCount(SegmentByteCountsTag, ImageSegmentCount, ExifTagType.ULong);for(k =0; k < ImageSegmentCount; k++){
CurrentImageExif.SetTagValue(SegmentOffsetsTag, CurrentDestStreamOffset, ExifTagType.ULong, k);uint SegmentSize = SegmentByteCountTable[k];
CurrentImageExif.SetTagValue(SegmentByteCountsTag, SegmentSize, ExifTagType.ULong, k);
CurrentDestStreamOffset += SegmentSize;}bool HasImageMatrixFillByte =false;if((CurrentDestStreamOffset &0x1)!=0){
CurrentDestStreamOffset++;
HasImageMatrixFillByte =true;}if(CurrentDestStreamOffset >int.MaxValue)thrownewExifException(ExifErrCode.ExifDataAreTooLarge);if(ImageNumber ==0){// Write TIFF header to destination stream
CurrentImageExif.ExifWriteUInt32(TiffHeader,4, CurrentDestStreamOffset);
DestStream.Write(TiffHeader,0, TiffHeaderLen);}else{// Insert the the pointer to the EXIF block of the current image into the binary data of the EXIF block of// the previous image. Then write the previous EXIF block to destination stream.
CurrentImageExif.ExifWriteUInt32(ExifBlockAsBinaryData.Buffer, NextExifBlockPointerIndex, CurrentDestStreamOffset);
DestStream.Write(ExifBlockAsBinaryData.Buffer,0, ExifBlockAsBinaryData.Length);}// Write image matrix: copy image segments from source to destination streamfor(k =0; k < ImageSegmentCount; k++){int RemainingByteCount =(int)SegmentByteCountTable[k];int BytesToRead = TempBuffer.Length;
SourceStream.Position = SegmentOffsetTable[k];do{if(BytesToRead > RemainingByteCount) BytesToRead = RemainingByteCount;if(SourceStream.Read(TempBuffer,0, BytesToRead)== BytesToRead){
DestStream.Write(TempBuffer,0, BytesToRead);
RemainingByteCount -= BytesToRead;}elsethrownewExifException(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)thrownewExifException(ExifErrCode.ExifDataAreTooLarge);
ImageNumber++;}while(SourceStreamExifBlockOffset !=0);
DestStream.Write(ExifBlockAsBinaryData.Buffer,0, ExifBlockAsBinaryData.Length);}privatestaticExifDataCreateSubExifData(Stream TiffStream,refint ExifBlockOffset,ExifByteOrder ByteOrder,outuint[] SegmentOffsetTable,outuint[] SegmentByteCountTable,outExifTag SegmentOffsetsTag,outExifTag SegmentByteCountsTag){ExifData SubExifBlock =newExifData();
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;}elseif(SubExifBlock.GetTagItem(ExifTag.TileOffsets,out SegmentOffsetsTagItem)&&
SubExifBlock.GetTagItem(ExifTag.TileByteCounts,out SegmentByteCountsTagItem)){
SegmentOffsetsTag = ExifTag.TileOffsets;
SegmentByteCountsTag = ExifTag.TileByteCounts;}elsethrownewExifException(ExifErrCode.InternalImageStructureIsWrong);int ValueCount = SegmentOffsetsTagItem.ValueCount;
SegmentOffsetTable =newuint[ValueCount];
SegmentByteCountTable =newuint[ValueCount];bool IsOffsetTable16Bit =(SegmentOffsetsTagItem.TagType == ExifTagType.UShort);bool ByteCountTable16Bit =(SegmentByteCountsTagItem.TagType == ExifTagType.UShort);uint v;for(int i =0; i < ValueCount; i++){if(IsOffsetTable16Bit){
v = SubExifBlock.ExifReadUInt16(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex +(i <<1));}else v = SubExifBlock.ExifReadUInt32(SegmentOffsetsTagItem.ValueData, SegmentOffsetsTagItem.ValueIndex +(i <<2));
SegmentOffsetTable[i]= v;if(ByteCountTable16Bit){
v = SubExifBlock.ExifReadUInt16(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex +(i <<1));}else v = SubExifBlock.ExifReadUInt32(SegmentByteCountsTagItem.ValueData, SegmentByteCountsTagItem.ValueIndex +(i <<2));
SegmentByteCountTable[i]= v;}return(SubExifBlock);}privatevoidSavePng(Stream SourceStream,Stream DestStream){uint ChunkType;byte[] TempData =newbyte[65536];byte[] BlockContent =newbyte[30];// Used for signature data at the beginning of the block
SourceStream.Position =8;WriteUInt32BE(TempData,0, PngHeaderPart1);WriteUInt32BE(TempData,4, PngHeaderPart2);
DestStream.Write(TempData,0,8);do{bool CopyBlockFromSourceStream =true;long BlockStartStreamPos = SourceStream.Position;ReadPngBlockHeader(SourceStream, TempData,outint DataLength,out ChunkType);DetectPngImageBlock(SourceStream, BlockContent, ChunkType,outImageFileBlock BlockType);if(BlockType != ImageFileBlock.Unknown){if((BlockType == ImageFileBlock.Exif)||(ImageFileBlockInfo[(int)BlockType]== ImageFileBlockState.Removed)){
CopyBlockFromSourceStream =false;// EXIF block will always be replaced by a new EXIF block}}if(CopyBlockFromSourceStream){// Copy current block from source to destination stream
SourceStream.Position = BlockStartStreamPos;int RemainingByteCount = DataLength +12;int BytesToRead = TempData.Length;do{if(BytesToRead > RemainingByteCount) BytesToRead = RemainingByteCount;if(SourceStream.Read(TempData,0, BytesToRead)== BytesToRead){
DestStream.Write(TempData,0, BytesToRead);
RemainingByteCount -= BytesToRead;}elsethrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}while(RemainingByteCount >0);}else{
SourceStream.Position = BlockStartStreamPos + DataLength +12;}if(ChunkType == PngIhdrChunk){// Write new EXIF block after the IHDR chunk if the new EXIF block is not emptyCreateExifBlock(outFlexArray NewExifBlock, TiffHeaderLen);int NewExifBlockLen = NewExifBlock.Length;if(NewExifBlockLen >0){CreateTiffHeader(outbyte[] TiffHeader, TiffHeaderLen);uint BlockLength = TiffHeaderLen +(uint)NewExifBlockLen;WriteUInt32BE(TempData,0, BlockLength);WriteUInt32BE(TempData,4, PngExifChunk);
DestStream.Write(TempData,0,8);
DestStream.Write(TiffHeader,0, TiffHeaderLen);
DestStream.Write(NewExifBlock.Buffer,0, NewExifBlockLen);uint Crc32 =CalculateCrc32(TempData,4,4,false);// Checksum over chunk type
Crc32 =CalculateCrc32(TiffHeader,0, TiffHeaderLen,false, Crc32);
Crc32 =CalculateCrc32(NewExifBlock.Buffer,0, NewExifBlockLen,true, Crc32);WriteUInt32BE(TempData,0, Crc32);
DestStream.Write(TempData,0,4);}}}while(ChunkType != PngIendChunk);}privatevoidSetEmptyExifBlock(){// The TIFF header, which is stored in the array elements from 0 to 7, will not be initialized because it is not used any more.
SourceExifBlock =newbyte[TiffHeaderLen + MinExifBlockLen];ExifWriteUInt16(SourceExifBlock, TiffHeaderLen,0);// Number of tags of IFD Primary DataExifWriteUInt32(SourceExifBlock, TiffHeaderLen +2,0);// Offset of IFD Thumbnail DataEvaluateExifBlock(TiffHeaderLen);}privatevoidWriteTagToFlexArray(TagItem TempTagData,FlexArray WriteData,refint TagDataIndex,int ExifBlockOffset){int i, ByteCount;byte v;
i = TagDataIndex;
TagDataIndex +=12;ExifWriteUInt16(WriteData.Buffer, i,(ushort)TempTagData.TagId);ExifWriteUInt16(WriteData.Buffer, i +2,(ushort)TempTagData.TagType);ExifWriteUInt32(WriteData.Buffer, i +4,(uint)TempTagData.ValueCount);
ByteCount =GetTagByteCount(TempTagData.TagType, TempTagData.ValueCount);if(ByteCount <=4){// The tag does not have outsourced data. In this case exactly 4 bytes have to be written.
WriteData.Buffer[i +8]= TempTagData.ValueData[TempTagData.ValueIndex];
v =0;if(ByteCount >=2) v = TempTagData.ValueData[TempTagData.ValueIndex +1];
WriteData.Buffer[i +9]= v;
v =0;if(ByteCount >=3) v = TempTagData.ValueData[TempTagData.ValueIndex +2];
WriteData.Buffer[i +10]= v;
v =0;if(ByteCount >=4) v = TempTagData.ValueData[TempTagData.ValueIndex +3];
WriteData.Buffer[i +11]= v;}else{// The tag has outsourced data. The outsourced data is added at the end of array "WriteData".int OutsourcedDataIndex = WriteData.Length;ExifWriteUInt32(WriteData.Buffer, i +8,(uint)(ExifBlockOffset + OutsourcedDataIndex));
WriteData.Length += ByteCount;// Allocate memory in array "WriteData"
Array.Copy(TempTagData.ValueData, TempTagData.ValueIndex, WriteData.Buffer, OutsourcedDataIndex, ByteCount);if((ByteCount &0x1)!=0){// The EXIF block must have a 16 bit alignment. "ByteCount" is odd, therefore add a fill byte.
WriteData.Length++;// Allocate 1 byte in array "WriteData"
WriteData.Buffer[WriteData.Length -1]=0;}}}privateintCreateExifBlock(outFlexArray NewExifBlock,int CurrentExifBlockOffset){int TiffNextExifBlockPointerIndex =0;FlexArray WriteExifBlock =newFlexArray(JpegMaxExifBlockLen);
NewExifBlock = WriteExifBlock;int WriteIndex =0;UpdateIfdPointerTags(outTagItem PrivateDataPointerTag,outTagItem GpsInfoDataPointerTag,outTagItem InteroperabilityPointerTag);CreateIfdPrimaryData(WriteExifBlock,ref WriteIndex, CurrentExifBlockOffset,outint PrivateDataIfdPointerIndex,outint GpsInfoDataIfdPointerIndex,outint ThumbnailDataIfdPointerIndex);CreateIfdPrivateData(WriteExifBlock,ref WriteIndex, CurrentExifBlockOffset, PrivateDataIfdPointerIndex, PrivateDataPointerTag,outint InteroperabilityIfdPointerIndex);CreateIfdGpsInfoData(WriteExifBlock,ref WriteIndex, CurrentExifBlockOffset, GpsInfoDataIfdPointerIndex, GpsInfoDataPointerTag);CreateIfdInteroperability(WriteExifBlock,ref WriteIndex, CurrentExifBlockOffset, InteroperabilityIfdPointerIndex, InteroperabilityPointerTag);if(ImageType == ImageType.Tiff){// TIFF files don't have a thumbnail image, but they may have multiple images
TiffNextExifBlockPointerIndex = ThumbnailDataIfdPointerIndex;ExifWriteUInt32(WriteExifBlock.Buffer, TiffNextExifBlockPointerIndex,0);// Set pointer to next image provisorily to 0}elseCreateIfdThumbnailData(WriteExifBlock,ref WriteIndex, CurrentExifBlockOffset, ThumbnailDataIfdPointerIndex);// Check if new EXIF block is empty. Empty EXIF blocks should not be written instead the EXIF block should be removed.if(WriteExifBlock.Length <= MinExifBlockLen){
WriteExifBlock.Length =0;}return(TiffNextExifBlockPointerIndex);}privatevoidCreateTiffHeader(outbyte[] TiffHeader,int ExifBlockOffset){
TiffHeader =newbyte[TiffHeaderLen];if(ByteOrder == ExifByteOrder.BigEndian){WriteUInt32BE(TiffHeader,0, TiffHeaderSignatureBE);}elseif(ByteOrder == ExifByteOrder.LittleEndian){WriteUInt32BE(TiffHeader,0, TiffHeaderSignatureLE);}elsethrownewExifException(ExifErrCode.InternalError);ExifWriteUInt32(TiffHeader,4,(uint)ExifBlockOffset);}privatevoidUpdateIfdPointerTags(outTagItem PrivateDataPointerTag,outTagItem GpsInfoDataPointerTag,outTagItem 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 =newTagItem(ExifTag.InteroperabilityIfdPointer, ExifTagType.ULong,1);
PrivateDataIfdTable.Add(ExifTagId.InteroperabilityIfdPointer, InteroperabilityPointerTag);}}else{if(InteroperabilityPointerTag !=null){
PrivateDataIfdTable.Remove(ExifTagId.InteroperabilityIfdPointer);}}Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];Dictionary<ExifTagId, TagItem> GpsInfoDataIfdTable = TagTable[(uint)ExifIfd.GpsInfoData];
PrimaryDataIfdTable.TryGetValue(ExifTagId.GpsInfoIfdPointer,out GpsInfoDataPointerTag);int GpsInfoDataTagCount = GpsInfoDataIfdTable.Count;if(GpsInfoDataTagCount >0){if(GpsInfoDataPointerTag ==null){
GpsInfoDataPointerTag =newTagItem(ExifTag.GpsInfoIfdPointer, ExifTagType.ULong,1);
PrimaryDataIfdTable.Add(ExifTagId.GpsInfoIfdPointer, GpsInfoDataPointerTag);}}else{if(GpsInfoDataPointerTag !=null){
PrimaryDataIfdTable.Remove(ExifTagId.GpsInfoIfdPointer);}}
PrimaryDataIfdTable.TryGetValue(ExifTagId.ExifIfdPointer,out PrivateDataPointerTag);if(PrivateDataIfdTable.Count >0){if(PrivateDataPointerTag ==null){
PrivateDataPointerTag =newTagItem(ExifTag.ExifIfdPointer, ExifTagType.ULong,1);
PrimaryDataIfdTable.Add(ExifTagId.ExifIfdPointer, PrivateDataPointerTag);}}else{if(PrivateDataPointerTag !=null){
PrimaryDataIfdTable.Remove(ExifTagId.ExifIfdPointer);}}}privatevoidCreateIfdPrimaryData(FlexArray WriteExifBlock,refint WriteIndex,int ExifBlockOffset,outint PrivateDataIfdPointerIndex,outint GpsInfoDataIfdPointerIndex,outint ThumbnailDataIfdPointerIndex){Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];int PrimaryDataTagCount = PrimaryDataIfdTable.Count;
PrivateDataIfdPointerIndex =-1;
GpsInfoDataIfdPointerIndex =-1;int IfdFixedSize =2+ PrimaryDataTagCount *12+4;// "+ 4" for thumbnail data pointer
WriteExifBlock.Length += IfdFixedSize;// Allocate memory in array "WriteExifBlock"ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex,(ushort)PrimaryDataTagCount);
WriteIndex +=2;foreach(TagItem t in PrimaryDataIfdTable.Values){if(t.TagId == ExifTagId.ExifIfdPointer){
t.TagType = ExifTagType.ULong;
PrivateDataIfdPointerIndex = WriteIndex +8;// The "Private Data IFD" pointer is unknown at this time and a dummy// value is written for it. The index is stored, at which the actual pointer can be written later.}elseif(t.TagId == ExifTagId.GpsInfoIfdPointer){
t.TagType = ExifTagType.ULong;
GpsInfoDataIfdPointerIndex = WriteIndex +8;// The "GPS Info Data IFD" pointer is unknown at this time and a dummy// value is written for it. The index is stored, at which the actual pointer can be written later.}WriteTagToFlexArray(t, WriteExifBlock,ref WriteIndex, ExifBlockOffset);}// Now the thumbnail data pointer should be written. Because it is unknown at this time the index is stored.
ThumbnailDataIfdPointerIndex = WriteIndex;
WriteIndex = WriteExifBlock.Length;// Write the next IFD behind the outsourced data of the IFD Primary Data}privatevoidCreateIfdPrivateData(FlexArray WriteExifBlock,refint WriteIndex,int ExifBlockOffset,int PrivateDataIfdPointerIndex,TagItem PrivateDataPointerTag,outint InteroperabilityIfdPointerIndex){Dictionary<ExifTagId, TagItem> PrivateDataIfdTable = TagTable[(uint)ExifIfd.PrivateData];int PrivateDataTagCount = PrivateDataIfdTable.Count;
InteroperabilityIfdPointerIndex =-1;if(PrivateDataTagCount >0){int iPrivateDataStart = WriteIndex;int MakerNoteDataPointerIndex, OffsetSchemaValueIndex;TagItem OffsetSchemaTag;bool AddOffsetSchemaTag;ExifWriteUInt32(PrivateDataPointerTag.ValueData, PrivateDataPointerTag.ValueIndex,(uint)(ExifBlockOffset + WriteIndex));ExifWriteUInt32(WriteExifBlock.Buffer, PrivateDataIfdPointerIndex,(uint)(ExifBlockOffset + WriteIndex));do{
MakerNoteDataPointerIndex =0;
OffsetSchemaValueIndex =0;
OffsetSchemaTag =null;int IfdFixedSize =2+ PrivateDataTagCount *12+4;// "+ 4" for null pointer at the end
WriteExifBlock.Length += IfdFixedSize;// Allocate memory in array "WriteExifBlock"ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex,(ushort)PrivateDataTagCount);
WriteIndex +=2;foreach(TagItem t in PrivateDataIfdTable.Values){if(t.TagId == ExifTagId.InteroperabilityIfdPointer){
t.TagType = ExifTagType.ULong;
InteroperabilityIfdPointerIndex = WriteIndex +8;// The "Interoperability IFD" pointer is unknown at this time and a dummy// value is written for it. The index is stored, at which the actual pointer can be written later.}elseif(t.TagId == ExifTagId.MakerNote){
MakerNoteDataPointerIndex = WriteIndex +8;}elseif(t.TagId == ExifTagId.OffsetSchema){
t.TagType = ExifTagType.SLong;
OffsetSchemaValueIndex = WriteIndex +8;
OffsetSchemaTag = t;}WriteTagToFlexArray(t, WriteExifBlock,ref WriteIndex, ExifBlockOffset);}ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex,0);// Write null pointer at the end of the tag list
WriteIndex = WriteExifBlock.Length;// Write the next IFD behind the outsourced data of the IFD Private Data
AddOffsetSchemaTag =CheckIfMakerNoteTagHasMoved(WriteExifBlock.Buffer, MakerNoteDataPointerIndex, OffsetSchemaValueIndex, OffsetSchemaTag);if(AddOffsetSchemaTag){TagItem t =newTagItem(ExifTag.OffsetSchema, ExifTagType.SLong,1);
PrivateDataIfdTable.Add(ExifTagId.OffsetSchema, t);
PrivateDataTagCount++;
WriteExifBlock.Length = iPrivateDataStart;// Free all memory that was allocated for IFD Private Data
WriteIndex = iPrivateDataStart;}}while(AddOffsetSchemaTag);}}privatevoidCreateIfdGpsInfoData(FlexArray WriteExifBlock,refint WriteIndex,int ExifBlockOffset,int GpsInfoDataIfdPointerIndex,TagItem GpsInfoDataPointerTag){Dictionary<ExifTagId, TagItem> GpsInfoDataIfdTable = TagTable[(uint)ExifIfd.GpsInfoData];int GpsInfoDataTagCount = GpsInfoDataIfdTable.Count;if(GpsInfoDataTagCount >0){uint GpsInfoDataOffset =(uint)(ExifBlockOffset + WriteIndex);ExifWriteUInt32(GpsInfoDataPointerTag.ValueData, GpsInfoDataPointerTag.ValueIndex, GpsInfoDataOffset);ExifWriteUInt32(WriteExifBlock.Buffer, GpsInfoDataIfdPointerIndex, GpsInfoDataOffset);int IfdFixedSize =2+ GpsInfoDataTagCount *12+4;
WriteExifBlock.Length += IfdFixedSize;ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex,(ushort)GpsInfoDataTagCount);
WriteIndex +=2;foreach(TagItem t in GpsInfoDataIfdTable.Values){WriteTagToFlexArray(t, WriteExifBlock,ref WriteIndex, ExifBlockOffset);}ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex,0);// Write null pointer at the end of the tag list
WriteIndex = WriteExifBlock.Length;// Write the next IFD behind the outsourced data of the IFD GPS Info Data}}privatevoidCreateIfdInteroperability(FlexArray WriteExifBlock,refint WriteIndex,int ExifBlockOffset,int InteroperabilityIfdPointerIndex,TagItem InteroperabilityPointerTag){Dictionary<ExifTagId, TagItem> InteroperabilityIfdTable = TagTable[(uint)ExifIfd.Interoperability];int InteroperabilityTagCount = InteroperabilityIfdTable.Count;if(InteroperabilityTagCount >0){uint InteroperabilityOffset =(uint)(ExifBlockOffset + WriteIndex);ExifWriteUInt32(InteroperabilityPointerTag.ValueData, InteroperabilityPointerTag.ValueIndex, InteroperabilityOffset);ExifWriteUInt32(WriteExifBlock.Buffer, InteroperabilityIfdPointerIndex, InteroperabilityOffset);int IfdFixedSize =2+ InteroperabilityTagCount *12+4;
WriteExifBlock.Length += IfdFixedSize;ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex,(ushort)InteroperabilityTagCount);
WriteIndex +=2;foreach(TagItem t in InteroperabilityIfdTable.Values){WriteTagToFlexArray(t, WriteExifBlock,ref WriteIndex, ExifBlockOffset);}ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex,0);// Write null pointer at the end of the tag list
WriteIndex = WriteExifBlock.Length;// Write the next IFD behind the outsourced data of the IFD Interoperability}}privatevoidCreateIfdThumbnailData(FlexArray WriteExifBlock,refint WriteIndex,int ExifBlockOffset,int ThumbnailDataIfdPointerIndex){Dictionary<ExifTagId, TagItem> ThumbnailDataIfdTable = TagTable[(uint)ExifIfd.ThumbnailData];int ThumbnailDataTagCount = ThumbnailDataIfdTable.Count;if((ThumbnailDataTagCount >0)||(ThumbnailImageExists())){ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailDataIfdPointerIndex,(uint)(ExifBlockOffset + WriteIndex));int IfdFixedSize =2+ ThumbnailDataTagCount *12+4;
WriteExifBlock.Length += IfdFixedSize;ExifWriteUInt16(WriteExifBlock.Buffer, WriteIndex,(ushort)ThumbnailDataTagCount);
WriteIndex +=2;int ThumbnailImagePointerIndex =-1;TagItem ThumbnailImagePointerTag =null;bool ThumbnailImageSizeTagExists =false;foreach(TagItem t in ThumbnailDataIfdTable.Values){if(t.TagId == ExifTagId.JpegInterchangeFormat){// The tag "TagId_JpegInterchangeFormat" stores a pointer to the thumbnail image.// The pointer is not known at this time, so the index, where the pointer has to be written, is stored.
t.TagType = ExifTagType.ULong;
ThumbnailImagePointerTag = t;
ThumbnailImagePointerIndex = WriteIndex +8;}elseif(t.TagId == ExifTagId.JpegInterchangeFormatLength){
t.TagType = ExifTagType.ULong;ExifWriteUInt32(t.ValueData, t.ValueIndex,(uint)ThumbnailByteCount);
ThumbnailImageSizeTagExists =true;}WriteTagToFlexArray(t, WriteExifBlock,ref WriteIndex, ExifBlockOffset);}if(ThumbnailImage !=null){// A thumbnail image is definedif((ThumbnailImagePointerIndex <0)||(ThumbnailImageSizeTagExists ==false)){thrownewExifException(ExifErrCode.InternalError);}else{int ThumbnailImageIndex = WriteExifBlock.Length;uint ThumbnailImageOffset =(uint)(ExifBlockOffset + ThumbnailImageIndex);ExifWriteUInt32(ThumbnailImagePointerTag.ValueData, ThumbnailImagePointerTag.ValueIndex, ThumbnailImageOffset);ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailImagePointerIndex, ThumbnailImageOffset);
WriteExifBlock.Length += ThumbnailByteCount;
Array.Copy(ThumbnailImage, ThumbnailStartIndex, WriteExifBlock.Buffer, ThumbnailImageIndex, ThumbnailByteCount);if((WriteExifBlock.Length &0x1)!=0){
WriteExifBlock.Length++;
WriteExifBlock[WriteExifBlock.Length -1]=0;// Insert fill byte}}}else{// A thumbnail image is not definedif(ThumbnailImagePointerIndex >=0){thrownewExifException(ExifErrCode.InternalError);}}ExifWriteUInt32(WriteExifBlock.Buffer, WriteIndex,0);// Write null pointer at the end of the tag table}else{ExifWriteUInt32(WriteExifBlock.Buffer, ThumbnailDataIfdPointerIndex,0);}}privateboolCheckIfMakerNoteTagHasMoved(byte[] DataBlock,int MakerNoteDataPointerIndex,int OffsetSchemaValueIndex,TagItem OffsetSchemaTag){bool MakerNoteHasMovedAndOffsetSchemaTagRequired =false;int MakerNoteCurrentOffset;if((MakerNoteOriginalOffset >0)&&(MakerNoteDataPointerIndex >0)){// "MakerNote" has existed and is now existing
MakerNoteCurrentOffset =(int)ExifReadUInt32(DataBlock, MakerNoteDataPointerIndex);if(MakerNoteOriginalOffset != MakerNoteCurrentOffset){// "MakerNote" has movedif(OffsetSchemaValueIndex >0){int NewOffsetDifference = MakerNoteCurrentOffset - MakerNoteOriginalOffset;ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex,(uint)NewOffsetDifference);ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex,(uint)NewOffsetDifference);}else{
MakerNoteHasMovedAndOffsetSchemaTagRequired =true;}}else{// "MakerNote" has not movedif(OffsetSchemaValueIndex >0){ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex,0);// Set move offset to 0ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex,0);}}}else{if(OffsetSchemaValueIndex >0){ExifWriteUInt32(DataBlock, OffsetSchemaValueIndex,0);// Set move offset to 0ExifWriteUInt32(OffsetSchemaTag.ValueData, OffsetSchemaTag.ValueIndex,0);}}return(MakerNoteHasMovedAndOffsetSchemaTagRequired);}// Create a tag item and initialize it with the data, which are stored in "IfdRawData[IfdRawDataIndex]".// The data values of the tag are not copied, instead a reference to the array "IfdRawData" is assigned.privateTagItemCreateTagWithReferenceToIfdRawData(byte[] IfdRawData,int IfdRawDataIndex){int ValueIndex, ValueByteCount, AllocatedByteCount, OriginalOffset;byte[] TagData;ExifTagId TagId =(ExifTagId)ExifReadUInt16(IfdRawData, IfdRawDataIndex);ExifTagType TagType =(ExifTagType)ExifReadUInt16(IfdRawData, IfdRawDataIndex +2);uint ValueCount =ExifReadUInt32(IfdRawData, IfdRawDataIndex +4);if(ValueCount > MaxTagValueCount)thrownewExifException(ErrCodeForIllegalExifBlock);
ValueByteCount =GetTagByteCount(TagType,(int)ValueCount);if(ValueByteCount <=4){// The tag doesn't have outsourced data. In this case a memory space with a size of 4 bytes is available for the data.
ValueIndex = IfdRawDataIndex +8;
TagData = IfdRawData;
AllocatedByteCount =4;
OriginalOffset =0;}else{// The tag has outsourced data.
OriginalOffset =(int)ExifReadUInt32(IfdRawData, IfdRawDataIndex +8);
AllocatedByteCount = ValueByteCount;GetOutsourcedData(OriginalOffset, AllocatedByteCount,out TagData,out ValueIndex);}TagItem TempTagItem =newTagItem(TagId, TagType,(int)ValueCount, TagData, ValueIndex, AllocatedByteCount, OriginalOffset);return(TempTagItem);}privatevoidGetOutsourcedData(int DataOffset,int DataLen,outbyte[] TagData,outint TagDataIndex){if(SourceExifBlock !=null){if((uint)(DataOffset + DataLen)<=(uint)SourceExifBlock.Length){
TagData = SourceExifBlock;
TagDataIndex = DataOffset;}elsethrownewExifException(ErrCodeForIllegalExifBlock);}else{
TagData =newbyte[DataLen];
SourceExifStream.Position = DataOffset;// + ExifBlockFilePosition;if(SourceExifStream.Read(TagData,0, DataLen)!= DataLen){thrownewExifException(ErrCodeForIllegalExifBlock);}
TagDataIndex =0;}}privatevoidInitIfdPrimaryData(byte[] IfdRawData,int IfdRawDataIndex,outint PrivateDataOffset,outint GpsInfoDataOffset,outint NextImageOffset){
PrivateDataOffset =0;
GpsInfoDataOffset =0;int TagCount =ExifReadUInt16(IfdRawData, IfdRawDataIndex);
IfdRawDataIndex +=2;var PrimaryDataIfdTable =newDictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.PrimaryData]= PrimaryDataIfdTable;int j =0;while(j < TagCount){TagItem TempTagData =CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);if(AddIfNotExists(PrimaryDataIfdTable, TempTagData)){if(TempTagData.TagId == ExifTagId.ExifIfdPointer){
PrivateDataOffset =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}elseif(TempTagData.TagId == ExifTagId.GpsInfoIfdPointer){
GpsInfoDataOffset =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}}
IfdRawDataIndex +=12;
j++;}
NextImageOffset =(int)ExifReadUInt32(IfdRawData, IfdRawDataIndex);}privatevoidInitIfdPrivateData(byte[] IfdRawData,int IfdRawDataIndex,outint InteroperabilityOffset){int MakerNoteOffset, OffsetSchemaValue;
InteroperabilityOffset =0;
MakerNoteOffset =0;
OffsetSchemaValue =0;int TagCount =ExifReadUInt16(IfdRawData, IfdRawDataIndex);
IfdRawDataIndex +=2;var PrivateDataIfdTable =newDictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.PrivateData]= PrivateDataIfdTable;int j =0;while(j < TagCount){TagItem TempTagData =CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);if(AddIfNotExists(PrivateDataIfdTable, TempTagData)){if(TempTagData.TagId == ExifTagId.InteroperabilityIfdPointer){
InteroperabilityOffset =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}elseif(TempTagData.TagId == ExifTagId.MakerNote){
MakerNoteOffset = TempTagData.OriginalDataOffset;}elseif(TempTagData.TagId == ExifTagId.OffsetSchema){
OffsetSchemaValue =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}}
IfdRawDataIndex +=12;
j++;}if(MakerNoteOffset >0){
MakerNoteOriginalOffset = MakerNoteOffset - OffsetSchemaValue;}else MakerNoteOriginalOffset =0;}privatevoidInitIfdGpsInfoData(byte[] IfdRawData,int IfdRawDataIndex){int TagCount =ExifReadUInt16(IfdRawData, IfdRawDataIndex);
IfdRawDataIndex +=2;var GpsInfoDataIfdTable =newDictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.GpsInfoData]= GpsInfoDataIfdTable;int j =0;while(j < TagCount){TagItem TempTagData =CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);AddIfNotExists(GpsInfoDataIfdTable, TempTagData);
IfdRawDataIndex +=12;
j++;}}privatevoidInitIfdInteroperability(byte[] IfdRawData,int IfdRawDataIndex){int TagCount =ExifReadUInt16(IfdRawData, IfdRawDataIndex);
IfdRawDataIndex +=2;var InteroperabilityIfdTable =newDictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.Interoperability]= InteroperabilityIfdTable;int j =0;while(j < TagCount){TagItem TempTagData =CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);AddIfNotExists(InteroperabilityIfdTable, TempTagData);
IfdRawDataIndex +=12;
j++;}}privatevoidInitIfdThumbnailData(byte[] IfdRawData,int IfdRawDataIndex){int ThumbnailImageDataOffset =0;
ThumbnailByteCount =0;
ThumbnailImage =null;int TagCount =ExifReadUInt16(IfdRawData, IfdRawDataIndex);
IfdRawDataIndex +=2;var ThumbnailDataIfdTable =newDictionary<ExifTagId, TagItem>(TagCount);
TagTable[(uint)ExifIfd.ThumbnailData]= ThumbnailDataIfdTable;int j =0;while(j < TagCount){TagItem TempTagData =CreateTagWithReferenceToIfdRawData(IfdRawData, IfdRawDataIndex);if(AddIfNotExists(ThumbnailDataIfdTable, TempTagData)){if(TempTagData.TagId == ExifTagId.JpegInterchangeFormat){
ThumbnailImageDataOffset =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}elseif(TempTagData.TagId == ExifTagId.JpegInterchangeFormatLength){
ThumbnailByteCount =(int)ExifReadUInt32(TempTagData.ValueData, TempTagData.ValueIndex);}}
IfdRawDataIndex +=12;
j++;}if(ThumbnailImageDataOffset >0){GetOutsourcedData(ThumbnailImageDataOffset, ThumbnailByteCount,out ThumbnailImage,out ThumbnailStartIndex);}}privatevoidGetNextIfd(int IfdOffset,outbyte[] IfdRawData,outint IfdRawDataIndex){if(IfdOffset ==0){// IFD does not exist. Create a dummy IFD table.
IfdRawData =newbyte[2];
IfdRawDataIndex =0;}elseif(SourceExifBlock !=null){
IfdRawData = SourceExifBlock;
IfdRawDataIndex = IfdOffset;}else{
SourceExifStream.Position = IfdOffset;// + ExifBlockFilePosition;
IfdRawDataIndex =0;byte[] TagCountArray =newbyte[2];if(SourceExifStream.Read(TagCountArray,0,2)==2){int IfdTagCount =ExifReadUInt16(TagCountArray,0);int IfdSize =2+ IfdTagCount *12+4;
IfdRawData =newbyte[IfdSize];
IfdRawData[0]= TagCountArray[0];
IfdRawData[1]= TagCountArray[1];if(SourceExifStream.Read(IfdRawData,2, IfdSize -2)!=(IfdSize -2)){thrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}}elsethrownewExifException(ExifErrCode.InternalImageStructureIsWrong);}}// JPEG, PNG: The complete EXIF block was read and is now stored in the array "SourceExifBlock" from the array index "TifferHeaderLen" (= 8).// TIFF: Stream "SourceStream" is used to read the EXIF data successively. "SourceExifBlock" is "null" in this case.privateintEvaluateExifBlock(int PrimaryDataOffset){int PrivateDataOffset, GpsInfoDataOffset, InteroperabilityOffset, ThumbnailDataOffset, IfdIndex, TiffNextExifBlockOffset =0;byte[] IfdTable;
TagTable =newDictionary<ExifTagId, TagItem>[ExifIfdCount];GetNextIfd(PrimaryDataOffset,out IfdTable,out IfdIndex);InitIfdPrimaryData(IfdTable, IfdIndex,out PrivateDataOffset,out GpsInfoDataOffset,out ThumbnailDataOffset);GetNextIfd(PrivateDataOffset,out IfdTable,out IfdIndex);InitIfdPrivateData(IfdTable, IfdIndex,out InteroperabilityOffset);GetNextIfd(GpsInfoDataOffset,out IfdTable,out IfdIndex);InitIfdGpsInfoData(IfdTable, IfdIndex);GetNextIfd(InteroperabilityOffset,out IfdTable,out IfdIndex);InitIfdInteroperability(IfdTable, IfdIndex);if(ImageType == ImageType.Tiff){// TIFF files don't have a thumbnail image, but they may have multiple images
IfdTable =newbyte[2];InitIfdThumbnailData(IfdTable,0);// Set empty tag table for IFD ThumbnailData
TiffNextExifBlockOffset = ThumbnailDataOffset;}else{// In JPEG files "NextImageOffset" is the offset to the thumbnail imageGetNextIfd(ThumbnailDataOffset,out IfdTable,out IfdIndex);InitIfdThumbnailData(IfdTable, IfdIndex);}return(TiffNextExifBlockOffset);}privateboolAddIfNotExists(Dictionary<ExifTagId, TagItem> Dict,TagItem TagData){try{
Dict.Add(TagData.TagId, TagData);return(true);}catch{// An exception occured because the tag ID already exists. In this case ignore the new tag item.return(false);}}// Compare "Array1" from index "StartIndex1" with "Array2".privateboolCompareArrays(byte[] Array1,int StartIndex1,byte[] Array2){if(Array1.Length >=(StartIndex1 + Array2.Length)){bool IsEqual =true;int i = StartIndex1;foreach(byte b in Array2){if(Array1[i]!= b){
IsEqual =false;break;}
i++;}return(IsEqual);}elsereturn(false);}// Compare "Array1" from index 0 to index "Array2.Length - 1" with "Array2".// If "Array1" is smaller than "Array2", "false" is returned.privateboolArrayStartsWith(byte[] Array1,int Array1Length,byte[] Array2){if(Array1Length >= Array2.Length){bool IsEqual =true;int i =0;foreach(byte b in Array2){if(Array1[i]!= b){
IsEqual =false;break;}
i++;}return(IsEqual);}elsereturn(false);}privateboolReadUintElement(TagItem t,int ElementIndex,outuint Value){bool Success =false;if((ElementIndex >=0)&&(ElementIndex < t.ValueCount)){switch(t.TagType){case ExifTagType.Byte:
Value = t.ValueData[t.ValueIndex + ElementIndex];
Success =true;break;case ExifTagType.UShort:
Value =ExifReadUInt16(t.ValueData, t.ValueIndex +(ElementIndex <<1));
Success =true;break;case ExifTagType.ULong:case ExifTagType.SLong:
Value =ExifReadUInt32(t.ValueData, t.ValueIndex +(ElementIndex <<2));
Success =true;break;default:
Value =0;break;}}else Value =0;return(Success);}privateboolWriteUintElement(TagItem t,int ElementIndex,uint Value){bool Success =false;if(t !=null){if(t.TagType == ExifTagType.Byte){
t.ValueData[t.ValueIndex + ElementIndex]=(byte)Value;}elseif(t.TagType == ExifTagType.UShort){ExifWriteUInt16(t.ValueData, t.ValueIndex +(ElementIndex <<1),(ushort)Value);}else{ExifWriteUInt32(t.ValueData, t.ValueIndex +(ElementIndex <<2), Value);}
Success =true;}return(Success);}privateboolGetTagValueWithIdCode(ExifTag TagSpec,outstring Value,ushort CodePage){TagItem t;bool Success =false, IsUtf16Coded =false, IsAsciiCoded =false;int i, j;
Value =null;if(GetTagItem(TagSpec,out t)&&(t.TagType == ExifTagType.Undefined)&&(t.ValueCount >= IdCodeLength)){if(CompareArrays(t.ValueData, t.ValueIndex, IdCodeUtf16)){
IsUtf16Coded =true;}elseif(CompareArrays(t.ValueData, t.ValueIndex, IdCodeAscii)||CompareArrays(t.ValueData, t.ValueIndex, IdCodeDefault)){
IsAsciiCoded =true;}
i = t.ValueCount - IdCodeLength;
j = t.ValueIndex + IdCodeLength;if(IsUtf16Coded){// The parameter "CodePage" is ignored in this case.// Remove all null terminating characters. Here a null terminating character consists of 2 zero-bytes.while((i >=2)&&(t.ValueData[j + i -2]==0)&&(t.ValueData[j + i -1]==0)){
i -=2;}if(ByteOrder == ExifByteOrder.BigEndian){
CodePage =1201;// UTF16BE}else CodePage =1200;// UTF16LE
Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
Success =true;}elseif(IsAsciiCoded){// Remove all null terminating characters.while((i >=1)&&(t.ValueData[j + i -1]==0)){
i--;}if((CodePage ==1200)||(CodePage ==1201))// UTF16LE or UTF16BE{
CodePage =20127;// Ignore parameter "CodePage" and overwrite it with US ASCII}
Value = Encoding.GetEncoding(CodePage).GetString(t.ValueData, j, i);
Success =true;}}return(Success);}privateboolSetTagValueWithIdCode(ExifTag TagSpec,string Value,ushort CodePage){int TotalByteCount, StrByteLen;TagItem t;bool Success =false;byte[] StringAsByteArray, RequiredIdCode;if((CodePage ==1200)&&(ByteOrder == ExifByteOrder.BigEndian)){
CodePage =1201;// Set code page to UTF16BE}if((CodePage ==1201)&&(ByteOrder == ExifByteOrder.LittleEndian)){
CodePage =1200;// Set code page to UTF16LE}
StringAsByteArray = Encoding.GetEncoding(CodePage).GetBytes(Value);
StrByteLen = StringAsByteArray.Length;
TotalByteCount = IdCodeLength + StrByteLen;// The ID code is a 8 byte header
t =PrepareTagForCompleteWriting(TagSpec, ExifTagType.Undefined, TotalByteCount);if(t !=null){if((CodePage ==1200)||(CodePage ==1201)){
RequiredIdCode = IdCodeUtf16;}else RequiredIdCode = IdCodeAscii;
Array.Copy(RequiredIdCode,0, t.ValueData, t.ValueIndex, IdCodeLength);
Array.Copy(StringAsByteArray,0, t.ValueData, t.ValueIndex + IdCodeLength, StrByteLen);
Success =true;}return(Success);}privateboolReadURatElement(TagItem t,int ElementIndex,outuint Numer,outuint Denom){bool Success =false;int i;if((ElementIndex >=0)&&(ElementIndex < t.ValueCount)){if((t.TagType == ExifTagType.SRational)||(t.TagType == ExifTagType.URational)){
i = t.ValueIndex +(ElementIndex <<3);
Numer =ExifReadUInt32(t.ValueData, i);
Denom =ExifReadUInt32(t.ValueData, i +4);
Success =true;}else{
Numer =0;
Denom =0;}}else{
Numer =0;
Denom =0;}return(Success);}privateboolWriteURatElement(TagItem t,int ElementIndex,uint Numer,uint Denom){int i;bool Success =false;if(t !=null){
i = t.ValueIndex +(ElementIndex <<3);ExifWriteUInt32(t.ValueData, i, Numer);ExifWriteUInt32(t.ValueData, i +4, Denom);
Success =true;}return(Success);}privateboolGetTagItem(ExifTag TagSpec,outTagItem t){ExifIfd Ifd =ExtractIfd(TagSpec);if((uint)Ifd < ExifIfdCount){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];return(IfdTagTable.TryGetValue(ExtractTagId(TagSpec),out t));}else{
t =null;return(false);}}// Prepare tag for writing a single array item. If the tag does not exist it is created.//// Parameters:// "TagType": Tag type.// "ArrayIndex": Zero-based array index of the value to be written. If the array index// is outside the current tag data, the tag data is automatically enlarged.// If it is necessary to reallocate the tag memory, the old content is copied to the new memory.privateTagItemPrepareTagForArrayItemWriting(ExifTag TagSpec,ExifTagType TagType,int ArrayIndex){TagItem t =null;ExifIfd Ifd =ExtractIfd(TagSpec);if(((uint)Ifd < ExifIfdCount)&&((uint)ArrayIndex < MaxTagValueCount)){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];if(IfdTagTable.TryGetValue(ExtractTagId(TagSpec),out t)){int ValueCount = t.ValueCount;if(ArrayIndex >= ValueCount){
ValueCount = ArrayIndex +1;}
t.SetTagTypeAndValueCount(TagType, ValueCount,true);}else{int ValueCount = ArrayIndex +1;
t =newTagItem(TagSpec, TagType, ValueCount);
IfdTagTable.Add(t.TagId, t);}}return(t);}// Prepare tag for a complete writing of the tag content. The tag content is undefined after calling this method.//// Parameters:// "TagType": New tag type. The tag type must be valid, because it is not checked in this method!// "ValueCount": New number of array elements that the tag should contain. Must be equal or greater than 1.// If it is necessary to reallocate the tag memory, the old content is not copied.privateTagItemPrepareTagForCompleteWriting(ExifTag TagSpec,ExifTagType TagType,int ValueCount){TagItem t =null;ExifIfd Ifd =ExtractIfd(TagSpec);if(((uint)Ifd < ExifIfdCount)&&((uint)ValueCount <= MaxTagValueCount)){Dictionary<ExifTagId, TagItem> IfdTagTable = TagTable[(uint)Ifd];if(IfdTagTable.TryGetValue(ExtractTagId(TagSpec),out t)){
t.SetTagTypeAndValueCount(TagType, ValueCount,false);}else{
t =newTagItem(TagSpec, TagType, ValueCount);
IfdTagTable.Add(t.TagId, t);}#if DEBUGint k = t.ValueIndex + t.AllocatedByteCount;for(int i = t.ValueIndex; i < k; i++){
t.ValueData[i]=0xcc;}#endif}return(t);}privatestaticintCalculateTwoDigitDecNumber(byte[] ByteArr,int Index){int d1, d2, Value;
Value =-1;
d1 = ByteArr[Index];
d2 = ByteArr[Index +1];if((d1 >='0')&&(d1 <='9')&&(d2 >='0')&&(d2 <='9')){
Value =(d1 -0x30)*10+(d2 -0x30);}return(Value);}// "Value": Number from 0 to 99.privatestaticvoidConvertTwoDigitNumberToByteArr(byte[] ByteArr,refint Index,int Value){
ByteArr[Index]=(byte)((Value /10)+0x30);
Index++;
ByteArr[Index]=(byte)((Value %10)+0x30);
Index++;}privatevoidClearIfd_Unchecked(ExifIfd Ifd){
TagTable[(uint)Ifd].Clear();}privatevoidRemoveAllTagsFromIfdPrimaryData(){if(ImageType == ImageType.Tiff){// In TIFF images the IFD PrimaryData contains internal tags which must not be removed otherwise// the image will be damaged.// In TIFF images the XMP and IPTC block are stored as tags within the EXIF block. Therefore these// tags are not removed, too.Dictionary<ExifTagId, TagItem> PrimaryDataIfdTable = TagTable[(uint)ExifIfd.PrimaryData];List<ExifTagId> TagIdsToBeRemoved =newList<ExifTagId>(PrimaryDataIfdTable.Count);foreach(ExifTagId tid in PrimaryDataIfdTable.Keys){if(!IsInternalTiffTag(tid)&&!IsMetaDataTiffTag(tid)){
TagIdsToBeRemoved.Add(tid);}}foreach(ExifTagId tid in TagIdsToBeRemoved){
PrimaryDataIfdTable.Remove(tid);}}else{ClearIfd_Unchecked(ExifIfd.PrimaryData);}}privatevoidRemoveThumbnailImage_Internal(){
ThumbnailImage =null;
ThumbnailStartIndex =0;
ThumbnailByteCount =0;}privateboolGetGpsCoordinateHelper(outGeoCoordinate Value,ExifTag ValueTag,ExifTag RefTag,char Cp1,char Cp2){bool Success =false;ExifRational Deg, Min, Sec;string Ref;char CardinalPoint;if(GetTagValue(ValueTag,out Deg,0)&& Deg.IsValid()&&GetTagValue(ValueTag,out Min,1)&& Min.IsValid()&&GetTagValue(ValueTag,out Sec,2)&& Sec.IsValid()&&GetTagValue(RefTag,out Ref, StrCoding.Utf8)&&(Ref.Length ==1)){
CardinalPoint = Ref[0];if((CardinalPoint == Cp1)||(CardinalPoint == Cp2)){
Value.Degree = ExifRational.ToDecimal(Deg);
Value.Minute = ExifRational.ToDecimal(Min);
Value.Second = ExifRational.ToDecimal(Sec);
Value.CardinalPoint = CardinalPoint;
Success =true;}else Value =newGeoCoordinate();}else Value =newGeoCoordinate();return(Success);}privateboolSetGpsCoordinateHelper(GeoCoordinate Value,ExifTag ValueTag,ExifTag RefTag,char Cp1,char Cp2){bool Success =false;ExifRational Deg = ExifRational.FromDecimal(Value.Degree);ExifRational Min = ExifRational.FromDecimal(Value.Minute);ExifRational Sec = ExifRational.FromDecimal(Value.Second);if(SetTagValue(ValueTag, Deg, ExifTagType.URational,0)&&SetTagValue(ValueTag, Min, ExifTagType.URational,1)&&SetTagValue(ValueTag, Sec, ExifTagType.URational,2)&&((Value.CardinalPoint == Cp1)||(Value.CardinalPoint == Cp2))&&SetTagValue(RefTag, Value.CardinalPoint.ToString(), StrCoding.Utf8)){
Success =true;}return(Success);}privateboolGetDateAndTimeWithMillisecHelper(outDateTime Value,ExifTag DateAndTimeTag,ExifTag MillisecTag){bool Success =false;if(GetTagValue(DateAndTimeTag,out Value)){
Success =true;if(GetTagValue(MillisecTag,outstring SubSec, StrCoding.Utf8)){string s = SubSec;int len = s.Length;if(len >3) s = s.Substring(0,3);if(int.TryParse(s,outint MilliSec)&&(MilliSec >=0)){if(len ==1) MilliSec *=100;elseif(len ==2) MilliSec *=10;
Value = Value.AddMilliseconds(MilliSec);}}}return(Success);}privateboolSetDateAndTimeWithMillisecHelper(DateTime Value,ExifTag DateAndTimeTag,ExifTag MillisecTag){bool Success =false;if(SetTagValue(DateAndTimeTag, Value)){
Success =true;int MilliSec = Value.Millisecond;if((MilliSec !=0)||TagExists(MillisecTag)){string s = MilliSec.ToString("000");// Write exactly 3 decimal digits
Success = Success &&SetTagValue(MillisecTag, s, StrCoding.Utf8);}}return(Success);}privatestaticreadonlyExifTagId[] InternalTiffTags =newExifTagId[]{
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};privatestaticBitArray InternalTiffTagsBitArray;privatestaticvoidInitInternalTiffTags(){
InternalTiffTagsBitArray =newBitArray(0x0215,false);int len = InternalTiffTagsBitArray.Length;foreach(ExifTagId TagId in InternalTiffTags){int TagIdInt =(int)TagId;if(TagIdInt >= len){
len = len <<1;if(TagIdInt >= len) len = TagIdInt +1;
InternalTiffTagsBitArray.Length = len;}
InternalTiffTagsBitArray.Set(TagIdInt,true);}}// Only for TIFF imagesprivatestaticboolIsInternalTiffTag(ExifTagId TagId){if(InternalTiffTagsBitArray ==null){InitInternalTiffTags();}int TagIdInt =(int)TagId;if(TagIdInt < InternalTiffTagsBitArray.Length){return(InternalTiffTagsBitArray.Get(TagIdInt));}return(false);}// Only for TIFF imagesprivatestaticboolIsMetaDataTiffTag(ExifTagId TagId){return((TagId == ExifTagId.XmpMetadata)||(TagId == ExifTagId.IptcMetadata));}privatevoidSwapByteOrderOfTagData(ExifIfd Ifd,TagItem t){// Special tag IDsif(Ifd == ExifIfd.PrimaryData){// There are meta data tags where the byte order must not be changed. These tags are only used in TIFF files.if(IsMetaDataTiffTag(t.TagId))return;}elseif(Ifd == ExifIfd.PrivateData){if((t.TagId == ExifTagId.UserComment)&&(t.TagType == ExifTagType.Undefined)){// This tag is coded UTF16LE or UTF16BE depending on the byte order.int k = t.ValueIndex +8;// Skip 8 byte ID code which is not UTF16 codedint Utf16CharCount =(t.ValueCount -8)/2;for(int i =0; i < Utf16CharCount; i++){Swap2ByteValue(t.ValueData, k);
k +=2;}return;}}switch(t.TagType){case ExifTagType.UShort:case ExifTagType.SShort:int k = t.ValueIndex;for(int i =0; i < t.ValueCount; i++){Swap2ByteValue(t.ValueData, k);
k +=2;}break;case ExifTagType.ULong:case ExifTagType.SLong:case ExifTagType.Float:
k = t.ValueIndex;for(int i =0; i < t.ValueCount; i++){Swap4ByteValue(t.ValueData, k);
k +=4;}break;case ExifTagType.URational:case ExifTagType.SRational:
k = t.ValueIndex;for(int i =0; i < t.ValueCount; i++){Swap4ByteValue(t.ValueData, k);// Numerator of the rational number
k +=4;Swap4ByteValue(t.ValueData, k);// Denominator of the rational number
k +=4;}break;case ExifTagType.Double:
k = t.ValueIndex;for(int i =0; i < t.ValueCount; i++){
Array.Reverse(t.ValueData, k,8);
k +=8;}break;}}privatevoidSwap2ByteValue(byte[] b,int i){byte k = b[i];
b[i]= b[i +1];
b[i +1]= k;}privatevoidSwap4ByteValue(byte[] b,int i){byte k = b[i];
b[i]= b[i +3];
b[i +3]= k;
k = b[i +1];
b[i +1]= b[i +2];
b[i +2]= k;}privateTagItemCopyTagItemDeeply(ExifIfd Ifd,TagItem TagItemToBeCopied,bool SwapByteOrder){TagItem NewTag =newTagItem(TagItemToBeCopied.TagId, TagItemToBeCopied.TagType, TagItemToBeCopied.ValueCount);
Array.Copy(TagItemToBeCopied.ValueData, TagItemToBeCopied.ValueIndex, NewTag.ValueData,0, NewTag.ByteCount);if(SwapByteOrder){SwapByteOrderOfTagData(Ifd, NewTag);}return(NewTag);}privateclassTagItem{publicExifTagId TagId;publicExifTagType TagType;publicint ValueCount;// Number of values of the tag. In general this is not the number of bytes of the tag!publicbyte[] ValueData;// Array which contains the tag data. There may be other data stored in this array.publicint ValueIndex;// Array index from which the tag data starts.publicint AllocatedByteCount;// Number of bytes which are allocated for the tag data.// This number can be greater than the required number of bytes.publicint ByteCount // The number of bytes required to store the tag data, i. e. the number of bytes actually used for the tag data.{get{return(ExifData.GetTagByteCount(TagType, ValueCount));}}publicint OriginalDataOffset;// Offset of outsourced data in the image file. 0 = Tag does not have outsourced data or// tag was created after loading.publicTagItem(ExifTag TagSpec,ExifTagType _TagType,int _ValueCount):this(ExtractTagId(TagSpec), _TagType, _ValueCount){}publicTagItem(ExifTagId _TagId,ExifTagType _TagType,int _ValueCount){
TagId = _TagId;
TagType = _TagType;
ValueCount = _ValueCount;int RequiredByteCount =GetTagByteCount(_TagType, _ValueCount);
ValueData =AllocTagMemory(RequiredByteCount);
AllocatedByteCount = ValueData.Length;
ValueIndex =0;}publicTagItem(ExifTagId _TagId,ExifTagType _TagType,int _ValueCount,byte[] _ValueArray,int _ValueIndex,int _AllocatedByteCount,int _OriginalDataOffset =0){
TagId = _TagId;
TagType = _TagType;
ValueCount = _ValueCount;
ValueData = _ValueArray;
ValueIndex = _ValueIndex;
AllocatedByteCount = _AllocatedByteCount;
OriginalDataOffset = _OriginalDataOffset;}publicstaticbyte[]AllocTagMemory(int RequiredByteCount){constint MinByteCount =32;int NewByteCount = RequiredByteCount;if(NewByteCount < MinByteCount){
NewByteCount = MinByteCount;}return(newbyte[NewByteCount]);}// Set tag type and value count and reallocate the tag memory if required.publicvoidSetTagTypeAndValueCount(ExifTagType _TagType,int _ValueCount,bool KeepExistingData){int RequiredByteCount =GetTagByteCount(_TagType, _ValueCount);if(AllocatedByteCount < RequiredByteCount){uint NewByteCount =(uint)AllocatedByteCount <<1;// Double the allocated byte countif(NewByteCount >int.MaxValue) NewByteCount =int.MaxValue;elseif(NewByteCount <(uint)RequiredByteCount) NewByteCount =(uint)RequiredByteCount;byte[] NewTagData =AllocTagMemory((int)NewByteCount);if(KeepExistingData){
Array.Copy(ValueData, ValueIndex, NewTagData,0, AllocatedByteCount);}
AllocatedByteCount = NewTagData.Length;
ValueData = NewTagData;
ValueIndex =0;}
TagType = _TagType;
ValueCount = _ValueCount;}}privateclassFlexArray{publicFlexArray(int Capacity){
_Buffer =newbyte[Capacity];
_Length =0;}publicbytethis[int i]{get{return _Buffer[i];}set{
_Buffer[i]=value;}}privatebyte[] _Buffer;publicbyte[] Buffer
{get{return(_Buffer);}}privateint _Length;publicint Length
{get{return(_Length);}set{if(value> _Buffer.Length){uint k =(uint)_Buffer.Length;
k = k <<1;if(k >int.MaxValue){
k =int.MaxValue;}elseif((uint)value> k){
k =(uint)value;}
Array.Resize(ref _Buffer,(int)k);}
_Length =value;}}}#endregion}// IFD Constants. These constants are used as array indexes for the array "TagTable".//IFD 常量。 这些常量用作数组“TagTable”的数组索引publicenumExifIfd{/// <summary>/// 主要数据/// </summary>
PrimaryData =0,/// <summary>/// 私有数据/// </summary>
PrivateData =1,/// <summary>/// 全球定位系统信息数据/// </summary>
GpsInfoData =2,/// <summary>/// 互操作性/// </summary>
Interoperability =3,/// <summary>/// 缩略图数据/// </summary>
ThumbnailData =4}// Tag specification constants: Composition of IFD and tag ID.//标签规范常量:IFD 和标签 ID 的组成。publicenumExifTag{// IFD Primary Data
NewSubfileType =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.NewSubfileType,
SubfileType =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.SubfileType,
ImageWidth =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ImageWidth,
ImageLength =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ImageLength,
BitsPerSample =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.BitsPerSample,//压缩( ExifTagType.UShort)
Compression =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Compression,
PhotometricInterpretation =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.PhotometricInterpretation,
Threshholding =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Threshholding,
CellWidth =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.CellWidth,
CellLength =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.CellLength,
FillOrder =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.FillOrder,
DocumentName =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.DocumentName,//图像描述(标题&主题)(StrCoding.iUsAscii/Utf8)
ImageDescription =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ImageDescription,//照相机制造商 (StrCoding.iUsAscii/Utf8)
Make =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Make,//照相机型号 ( StrCoding.iUsAscii/Utf8)
Model =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Model,
StripOffsets =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.StripOffsets,//设置图像方向(可以定义 90、180 或 270 度的顺时针旋转和图像矩阵的反射,前提是图像查看器在绘制图像时考虑到此 EXIF 标签)
Orientation =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Orientation,
SamplesPerPixel =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.SamplesPerPixel,
RowsPerStrip =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.RowsPerStrip,
StripByteCounts =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.StripByteCounts,
MinSampleValue =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.MinSampleValue,
MaxSampleValue =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.MaxSampleValue,
XResolution =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XResolution,
YResolution =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.YResolution,
PlanarConfiguration =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.PlanarConfiguration,
PageName =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.PageName,
XPosition =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XPosition,
YPosition =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.YPosition,
FreeOffsets =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.FreeOffsets,
FreeByteCounts =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.FreeByteCounts,
GrayResponseUnit =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.GrayResponseUnit,
GrayResponseCurve =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.GrayResponseCurve,
T4Options =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.T4Options,
T6Options =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.T6Options,//分辨率单位(如果图像分辨率未知,则指定 2(英寸))(ExifTagType.UShort)
ResolutionUnit =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ResolutionUnit,
PageNumber =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.PageNumber,
TransferFunction =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TransferFunction,//程序名称 (StrCoding.iUsAscii/Utf8)
Software =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Software,//上次更改图像的日期(修改日期)。
DateTime =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.DateTime,//作者 (StrCoding.iUsAscii/Utf8)
Artist =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Artist,
HostComputer =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.HostComputer,
Predictor =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Predictor,
WhitePoint =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.WhitePoint,
PrimaryChromaticities =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.PrimaryChromaticities,
ColorMap =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ColorMap,
HalftoneHints =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.HalftoneHints,
TileWidth =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TileWidth,
TileLength =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TileLength,
TileOffsets =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TileOffsets,
TileByteCounts =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TileByteCounts,
InkSet =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.InkSet,
InkNames =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.InkNames,
NumberOfInks =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.NumberOfInks,
DotRange =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.DotRange,
TargetPrinter =0x0151,
ExtraSamples =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ExtraSamples,
SampleFormat =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.SampleFormat,
SMinSampleValue =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.SMinSampleValue,
SMaxSampleValue =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.SMaxSampleValue,
TransferRange =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.TransferRange,
YCbCrCoefficients =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.YCbCrCoefficients,
YCbCrSubSampling =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.YCbCrSubSampling,
YCbCrPositioning =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.YCbCrPositioning,
ReferenceBlackWhite =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ReferenceBlackWhite,
XmpMetadata =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XmpMetadata,
Copyright =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Copyright,
IptcMetadata =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.IptcMetadata,
ExifIfdPointer =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.ExifIfdPointer,
GpsInfoIfdPointer =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.GpsInfoIfdPointer,
XpTitle =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XpTitle,
XpComment =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XpComment,
XpAuthor =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XpAuthor,
XpKeywords =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XpKeywords,
XpSubject =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.XpSubject,
PrimaryDataPadding =(ExifIfd.PrimaryData << ExifData.IfdShift)| ExifTagId.Padding,// IFD Private Data//曝光时间(ExifTagType.URational)
ExposureTime =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExposureTime,//光圈值( ExifTagType.URational)
FNumber =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FNumber,//曝光程序(ExifTagType.UShort)
ExposureProgram =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExposureProgram,
SpectralSensitivity =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SpectralSensitivity,//ISO速度(ExifTagType.UShort)
IsoSpeedRatings =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.IsoSpeedRatings,
PhotographicSensitivity =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.PhotographicSensitivity,
Oecf =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Oecf,
SensitivityType =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SensitivityType,
StandardOutputSensitivity =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.StandardOutputSensitivity,
RecommendedExposureIndex =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.RecommendedExposureIndex,
IsoSpeed =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.IsoSpeed,
IsoSpeedLatitudeyyy =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.IsoSpeedLatitudeyyy,
IsoSpeedLatitudezzz =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.IsoSpeedLatitudezzz,//图像EXIF版本(StrCoding.Utf8)
ExifVersion =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExifVersion,//拍摄日期/修改日期
DateTimeOriginal =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.DateTimeOriginal,//图像数字化的日期和时间。
DateTimeDigitized =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.DateTimeDigitized,
OffsetTime =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.OffsetTime,
OffsetTimeOriginal =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.OffsetTimeOriginal,
OffsetTimeDigitized =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.OffsetTimeDigitized,
ComponentsConfiguration =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ComponentsConfiguration,//压缩的位/像素( ExifTagType.URational)
CompressedBitsPerPixel =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.CompressedBitsPerPixel,
ShutterSpeedValue =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ShutterSpeedValue,
ApertureValue =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ApertureValue,//亮度(ExifTagType.SRational)
BrightnessValue =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.BrightnessValue,//曝光补偿(ExifTagType.SRational)
ExposureBiasValue =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExposureBiasValue,//最大光圈(ExifTagType.URational)
MaxApertureValue =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.MaxApertureValue,//目标距离(ExifTagType.URational)
SubjectDistance =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubjectDistance,//测光模式(ExifTagType.UShort)
MeteringMode =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.MeteringMode,//光源(ExifTagType.UShort)
LightSource =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.LightSource,//闪光灯模式( ExifTagType.UShort)
Flash =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Flash,//焦距(ExifTagType.URational)
FocalLength =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FocalLength,
SubjectArea =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubjectArea,
MakerNote =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.MakerNote,//用户评论(备注) ( StrCoding.Utf8)
UserComment =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.UserComment,
SubsecTime =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubsecTime,
SubsecTimeOriginal =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubsecTimeOriginal,
SubsecTimeDigitized =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubsecTimeDigitized,
FlashPixVersion =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FlashPixVersion,
ColorSpace =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ColorSpace,
PixelXDimension =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.PixelXDimension,
PixelYDimension =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.PixelYDimension,
RelatedSoundFile =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.RelatedSoundFile,
InteroperabilityIfdPointer =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.InteroperabilityIfdPointer,//闪光灯能量( ExifTagType.URational)
FlashEnergy =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FlashEnergy,
SpatialFrequencyResponse =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SpatialFrequencyResponse,
FocalPlaneXResolution =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FocalPlaneXResolution,
FocalPlaneYResolution =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FocalPlaneYResolution,
FocalPlaneResolutionUnit =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FocalPlaneResolutionUnit,
SubjectLocation =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubjectLocation,
ExposureIndex =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExposureIndex,
SensingMethod =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SensingMethod,
FileSource =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FileSource,
SceneType =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SceneType,
CfaPattern =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.CfaPattern,
CustomRendered =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.CustomRendered,
ExposureMode =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ExposureMode,//白平衡(ExifTagType.UShort)
WhiteBalance =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.WhiteBalance,//数字缩放(数码变焦倍率)( ExifTagType.URational)
DigitalZoomRatio =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.DigitalZoomRatio,//35mm焦距( ExifTagType.UShort)
FocalLengthIn35mmFilm =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.FocalLengthIn35mmFilm,
SceneCaptureType =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SceneCaptureType,
GainControl =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.GainControl,//对比度(ExifTagType.UShort)
Contrast =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Contrast,//饱和度( ExifTagType.UShort)
Saturation =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Saturation,//清晰度( ExifTagType.UShort)
Sharpness =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Sharpness,
DeviceSettingDescription =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.DeviceSettingDescription,
SubjectDistanceRange =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.SubjectDistanceRange,//图像ID(StrCoding.iUsAscii/Utf8)
ImageUniqueId =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.ImageUniqueId,
CameraOwnerName =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.CameraOwnerName,
BodySerialNumber =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.BodySerialNumber,
LensSpecification =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.LensSpecification,
LensMake =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.LensMake,
LensModel =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.LensModel,
LensSerialNumber =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.LensSerialNumber,
PrivateDataPadding =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.Padding,
OffsetSchema =(ExifIfd.PrivateData << ExifData.IfdShift)| ExifTagId.OffsetSchema,// IFD GPS Data
GpsVersionId =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsVersionId,
GpsLatitudeRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsLatitudeRef,
GpsLatitude =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsLatitude,
GpsLongitudeRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsLongitudeRef,
GpsLongitude =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsLongitude,
GpsAltitudeRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsAltitudeRef,
GpsAltitude =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsAltitude,
GpsTimeStamp =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsTimestamp,
GpsSatellites =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsSatellites,
GpsStatus =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsStatus,
GpsMeasureMode =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsMeasureMode,
GpsDop =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDop,
GpsSpeedRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsSpeedRef,
GpsSpeed =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsSpeed,
GpsTrackRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsTrackRef,
GpsTrack =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsTrack,
GpsImgDirectionRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsImgDirectionRef,
GpsImgDirection =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsImgDirection,
GpsMapDatum =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsMapDatum,
GpsDestLatitudeRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestLatitudeRef,
GpsDestLatitude =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestLatitude,
GpsDestLongitudeRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestLongitudeRef,
GpsDestLongitude =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestLongitude,
GpsDestBearingRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestBearingRef,
GpsDestBearing =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestBearing,
GpsDestDistanceRef =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestDistanceRef,
GpsDestDistance =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDestDistance,
GpsProcessingMethod =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsProcessingMethod,
GpsAreaInformation =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsAreaInformation,//来自卫星的 GPS 日期。
GpsDateStamp =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDateStamp,
GpsDifferential =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsDifferential,
GpsHPositioningError =(ExifIfd.GpsInfoData << ExifData.IfdShift)| ExifTagId.GpsHPositioningError,// IFD Interoperability
InteroperabilityIndex =(ExifIfd.Interoperability << ExifData.IfdShift)| ExifTagId.InteroperabilityIndex,
InteroperabilityVersion =(ExifIfd.Interoperability << ExifData.IfdShift)| ExifTagId.InteroperabilityVersion,// IFD Thumbnail Data
ThumbnailImageWidth =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.ImageWidth,
ThumbnailImageLength =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.ImageLength,
ThumbnailCompression =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.Compression,
ThumbnailXResolution =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.XResolution,
ThumbnailYResolution =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.YResolution,
ThumbnailResolutionUnit =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.ResolutionUnit,
ThumbnailOrientation =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.Orientation,
JpegInterchangeFormat =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.JpegInterchangeFormat,
JpegInterchangeFormatLength =(ExifIfd.ThumbnailData << ExifData.IfdShift)| ExifTagId.JpegInterchangeFormatLength,}// Tag ID constants.//标记 ID 常量。publicenumExifTagId{// IFD Primary Data, some tags are also used in IFD Thumbnail Data
NewSubfileType =0x00FE,
SubfileType =0x00FF,
ImageWidth =0x0100,
ImageLength =0x0101,
BitsPerSample =0x0102,
Compression =0x0103,
PhotometricInterpretation =0x0106,
Threshholding =0x0107,
CellWidth =0x0108,
CellLength =0x0109,
FillOrder =0x010a,
DocumentName =0x010d,
ImageDescription =0x010e,
Make =0x010f,
Model =0x0110,
StripOffsets =0x0111,
Orientation =0x0112,
SamplesPerPixel =0x0115,
RowsPerStrip =0x0116,
StripByteCounts =0x0117,
MinSampleValue =0x0118,
MaxSampleValue =0x0119,
XResolution =0x011a,
YResolution =0x011b,
PlanarConfiguration =0x011c,
PageName =0x011d,
XPosition =0x011e,
YPosition =0x011f,
FreeOffsets =0x0120,
FreeByteCounts =0x0121,
GrayResponseUnit =0x0122,
GrayResponseCurve =0x0123,
T4Options =0x0124,
T6Options =0x0125,
ResolutionUnit =0x0128,
PageNumber =0x0129,
TransferFunction =0x012d,
Software =0x0131,
DateTime =0x0132,
Artist =0x013b,
HostComputer =0x013c,
Predictor =0x013d,
WhitePoint =0x013e,
PrimaryChromaticities =0x013f,
ColorMap =0x0140,
HalftoneHints =0x0141,
TileWidth =0x0142,
TileLength =0x0143,
TileOffsets =0x0144,
TileByteCounts =0x0145,
InkSet =0x014c,
InkNames =0x014d,
NumberOfInks =0x014e,
DotRange =0x0150,
TargetPrinter =0x0151,
ExtraSamples =0x0152,
SampleFormat =0x0153,
SMinSampleValue =0x0154,
SMaxSampleValue =0x0155,
TransferRange =0x0156,
YCbCrCoefficients =0x0211,
YCbCrSubSampling =0x0212,
YCbCrPositioning =0x0213,
ReferenceBlackWhite =0x0214,
XmpMetadata =0x02bc,// XMP block within a TIFF file
Copyright =0x8298,
IptcMetadata =0x83bb,// IPTC block within a TIFF file
ExifIfdPointer =0x8769,
GpsInfoIfdPointer =0x8825,
XpTitle =0x9c9b,
XpComment =0x9c9c,
XpAuthor =0x9c9d,
XpKeywords =0x9c9e,
XpSubject =0x9c9f,
Padding =0xea1c,// IFD Thumbnail Data
JpegInterchangeFormat =0x0201,
JpegInterchangeFormatLength =0x0202,// IFD Private Data
ExposureTime =0x829a,
FNumber =0x829d,
ExposureProgram =0x8822,
SpectralSensitivity =0x8824,
IsoSpeedRatings =0x8827,
PhotographicSensitivity =0x8827,// Tag was renamed from "IsoSpeedRatings" to this name
Oecf =0x8828,
SensitivityType =0x8830,
StandardOutputSensitivity =0x8831,
RecommendedExposureIndex =0x8832,
IsoSpeed =0x8833,
IsoSpeedLatitudeyyy =0x8834,
IsoSpeedLatitudezzz =0x8835,
ExifVersion =0x9000,
DateTimeOriginal =0x9003,
DateTimeDigitized =0x9004,
OffsetTime =0x9010,
OffsetTimeOriginal =0x9011,
OffsetTimeDigitized =0x9012,
ComponentsConfiguration =0x9101,
CompressedBitsPerPixel =0x9102,
ShutterSpeedValue =0x9201,
ApertureValue =0x9202,
BrightnessValue =0x9203,
ExposureBiasValue =0x9204,
MaxApertureValue =0x9205,
SubjectDistance =0x9206,
MeteringMode =0x9207,
LightSource =0x9208,
Flash =0x9209,
FocalLength =0x920a,
SubjectArea =0x9214,
MakerNote =0x927c,
UserComment =0x9286,
SubsecTime =0x9290,
SubsecTimeOriginal =0x9291,
SubsecTimeDigitized =0x9292,
FlashPixVersion =0xa000,
ColorSpace =0xa001,
PixelXDimension =0xa002,
PixelYDimension =0xa003,
RelatedSoundFile =0xa004,
InteroperabilityIfdPointer =0xa005,
FlashEnergy =0xa20b,
SpatialFrequencyResponse =0xa20c,
FocalPlaneXResolution =0xa20e,
FocalPlaneYResolution =0xa20f,
FocalPlaneResolutionUnit =0xa210,
SubjectLocation =0xa214,
ExposureIndex =0xa215,
SensingMethod =0xa217,
FileSource =0xa300,
SceneType =0xa301,
CfaPattern =0xa302,
CustomRendered =0xa401,
ExposureMode =0xa402,
WhiteBalance =0xa403,
DigitalZoomRatio =0xa404,
FocalLengthIn35mmFilm =0xa405,
SceneCaptureType =0xa406,
GainControl =0xa407,
Contrast =0xa408,
Saturation =0xa409,
Sharpness =0xa40a,
DeviceSettingDescription =0xa40b,
SubjectDistanceRange =0xa40c,
ImageUniqueId =0xa420,
CameraOwnerName =0xa430,
BodySerialNumber =0xa431,
LensSpecification =0xa432,
LensMake =0xa433,
LensModel =0xa434,
LensSerialNumber =0xa435,
OffsetSchema =0xea1d,// IFD GPS Data
GpsVersionId =0x0000,
GpsLatitudeRef =0x0001,
GpsLatitude =0x0002,
GpsLongitudeRef =0x0003,
GpsLongitude =0x0004,
GpsAltitudeRef =0x0005,
GpsAltitude =0x0006,
GpsTimestamp =0x0007,
GpsSatellites =0x0008,
GpsStatus =0x0009,
GpsMeasureMode =0x000a,
GpsDop =0x000b,
GpsSpeedRef =0x000c,
GpsSpeed =0x000d,
GpsTrackRef =0x000e,
GpsTrack =0x000f,
GpsImgDirectionRef =0x0010,
GpsImgDirection =0x0011,
GpsMapDatum =0x0012,
GpsDestLatitudeRef =0x0013,
GpsDestLatitude =0x0014,
GpsDestLongitudeRef =0x0015,
GpsDestLongitude =0x0016,
GpsDestBearingRef =0x0017,
GpsDestBearing =0x0018,
GpsDestDistanceRef =0x0019,
GpsDestDistance =0x001a,
GpsProcessingMethod =0x001b,
GpsAreaInformation =0x001c,
GpsDateStamp =0x001d,
GpsDifferential =0x001e,
GpsHPositioningError =0x001f,// IFD Interoperability
InteroperabilityIndex =0x0001,
InteroperabilityVersion =0x0002}// Tag types. These constants are used as array indexes for the array "TypeByteCount".publicenumExifTagType{
Byte =1,
Ascii =2,
UShort =3,
ULong =4,
URational =5,
SByte =6,// Only for TIFFs
Undefined =7,
SShort =8,// Only for TIFFs
SLong =9,
SRational =10,
Float =11,// Only for TIFFs
Double =12// Only for TIFFs}publicenumStrCodingFormat{
TypeAscii =0x00000000,// Tag type is "ExifTagType.Ascii". A null terminating character is added when writing.
TypeUndefined =0x00010000,// Tag type is "ExifTagType.Undefined". A null terminating character is not present.
TypeByte =0x00020000,// Tag type is "ExifTagType.Byte". A null terminating character is added when writing.
TypeUndefinedWithIdCode =0x00030000// Tag type is "ExifTagType.Undefined" and an additional ID code is present. A null terminating character is not present.};// Strings coding constants. In the lower 16 bits the code page number (1 to 65535) is coded.// In the higher 16 bits the EXIF tag type and additional infos are coded.publicenumStrCoding{
Utf8 = StrCodingFormat.TypeAscii |65001,// Default value for all tags of type "ExifTagType.Ascii".
UsAscii = StrCodingFormat.TypeAscii |20127,
WestEuropeanWin = StrCodingFormat.TypeAscii |1252,
UsAscii_Undef = StrCodingFormat.TypeUndefined |20127,// For the tags "ExifVersion", "FlashPixVersion" and others.
Utf16Le_Byte = StrCodingFormat.TypeByte |1200,// For the Microsoft tags "XpTitle", "XpComment", "XpAuthor", "XpKeywords" and "XpSubject".
IdCode_Utf16 = StrCodingFormat.TypeUndefinedWithIdCode |1200,// Default value for the tag "UserComment".
IdCode_UsAscii = StrCodingFormat.TypeUndefinedWithIdCode |20127,
IdCode_WestEu = StrCodingFormat.TypeUndefinedWithIdCode |1252}publicenumExifDateFormat{
DateAndTime =0,
DateOnly =1}publicstructExifRational{publicuint Numer, Denom;publicbool Sign;// true = Negative number or negative zeropublicExifRational(int _Numer,int _Denom){if(_Numer <0){
Numer =(uint)-_Numer;
Sign =true;}else{
Numer =(uint)_Numer;
Sign =false;}if(_Denom <0){
Denom =(uint)-_Denom;
Sign =!Sign;}else{
Denom =(uint)_Denom;}}publicExifRational(uint _Numer,uint _Denom,bool _Sign =false){
Numer = _Numer;
Denom = _Denom;
Sign = _Sign;}publicboolIsNegative(){return((Sign ==true)&&(Numer !=0));}publicboolIsPositive(){return((Sign ==false)&&(Numer !=0));}publicboolIsZero(){return(Numer ==0);}publicboolIsValid(){return(Denom !=0);}publicnewstringToString(){string Sign ="";if(IsNegative()) Sign ="-";return(Sign + Numer.ToString()+'/'+ Denom.ToString());}publicstaticdecimalToDecimal(ExifRational Value){decimal ret =((decimal)Value.Numer)/ Value.Denom;if(Value.Sign) ret =-ret;return(ret);}publicstaticExifRationalFromDecimal(decimal Value){ExifRational ret;uint denom =1;decimal numer, tempNumer;if(Value >=0){
numer = Value;
ret.Sign =false;}else{
numer =-Value;
ret.Sign =true;}if(numer >=1e9m){
numer = Math.Truncate(numer +0.5m);if(numer <=uint.MaxValue){
ret.Numer =(uint)numer;}elsethrownewOverflowException();}else{while(numer !=decimal.Truncate(numer)){
tempNumer = numer *10;if((denom <=100000000)&&(decimal.Truncate(tempNumer +0.5m)<1e9m)){
numer = tempNumer;// The rounded numerator should be smaller than 10^9 if "Value" has decimals
denom *=10;// The maximum possible power of 10 for the denominator is 10^9}elsebreak;}
ret.Numer =(uint)decimal.Truncate(numer +0.5m);}
ret.Denom = denom;// Reduce fraction by a power of 10 if possiblewhile((ret.Denom >=10)&&((ret.Numer %10)==0)){
ret.Numer /=10;
ret.Denom /=10;}return(ret);}}publicstructGeoCoordinate{publicdecimal Degree;// Integer number: 0 ≤ Degree ≤ 90 (for latitudes) or 180 (for longitudes)publicdecimal Minute;// Integer number: 0 ≤ Minute < 60publicdecimal Second;// Fraction number: 0 ≤ Second < 60publicchar CardinalPoint;// For latitudes: 'N' or 'S'; for longitudes: 'E' or 'W'publicstaticdecimalToDecimal(GeoCoordinate Value){decimal DecimalDegree = Value.Degree + Value.Minute /60+ Value.Second /3600;if((Value.CardinalPoint =='S')||(Value.CardinalPoint =='W')){
DecimalDegree =-DecimalDegree;}return(DecimalDegree);}/// <summary>/// 从经纬度数值转换成度分秒/// </summary>/// <param name="Value">经纬度数据</param>/// <param name="IsLatitude">是否是纬度</param>/// <returns></returns>publicstaticGeoCoordinateFromDecimal(decimal Value,bool IsLatitude){decimal AbsValue;GeoCoordinate ret;if(Value >=0){
ret.CardinalPoint = IsLatitude ?'N':'E';
AbsValue = Value;}else{
ret.CardinalPoint = IsLatitude ?'S':'W';
AbsValue =-Value;}
ret.Degree =decimal.Truncate(AbsValue);decimal frac =(AbsValue - ret.Degree)*60;
ret.Minute =decimal.Truncate(frac);
ret.Second =(frac - ret.Minute)*60;return(ret);}}// Byte order of the EXIF datapublicenumExifByteOrder{ LittleEndian, BigEndian };[Flags]publicenumExifLoadOptions{
CreateEmptyBlock =0x00000001};[Flags]publicenumExifSaveOptions{// There are no save options at the moment};publicenumExifErrCode{
InternalError,
ImageTypeIsNotSupported,
ImageHasUnsupportedFeatures,
InternalImageStructureIsWrong,
ExifBlockHasIllegalContent,// Internal image structure is OK but the EXIF block has an illegal content
ExifDataAreTooLarge,
ImageTypesDoNotMatch
};publicenumImageFileBlock{// The values must be enumerated consecutively
Unknown =0,// Internal value, do not use
Exif =1,
Iptc =2,
Xmp =3,
JpegComment =4,
PngMetaData =5,
PngDateChanged =6};publicenumImageType{ Unknown =0, Jpeg, Tiff, Png };publicclassExifException:Exception{publicExifErrCode ErrorCode;publicExifException(ExifErrCode _ErrorCode){
ErrorCode = _ErrorCode;}publicoverridestring Message
{get{string s;switch(ErrorCode){case ExifErrCode.ImageTypeIsNotSupported: s ="Image type is not supported!";break;case ExifErrCode.ImageHasUnsupportedFeatures: s ="Image has unsupported features!";break;case ExifErrCode.InternalImageStructureIsWrong: s ="Internal image structure is wrong!";break;case ExifErrCode.ExifBlockHasIllegalContent: s ="EXIF block has an illegal content!";break;case ExifErrCode.ExifDataAreTooLarge: s ="EXIF data are too large: 64 kB for JPEG files, 2 GB for TIFF files!";break;case ExifErrCode.ImageTypesDoNotMatch: s ="Image types do not match!";break;default: s ="Internal error!";break;}return(s);}}}}