想要獲取圖片的信息,例如快門速度、ISO值等等,咱們能夠經過讀取Exif中存儲的信息。Exif(Exchangeable Image File)是存儲在JPEG格式照片頭部的一段信息,相機和手機拍攝的照片都會攜帶這些信息,可是須要注意,PS的照片的時候採用低質量保存會丟失這些信息。在PS中保存爲10-12等級的時候不會丟失,在美圖秀秀中保存質量爲100%不會丟失。軟件在處理的時候也會將本身的信息寫入Exif,因此也能夠經過這種方式判斷是否爲原圖,或者圖片是否通過處理。html
本文中我介紹兩種方式獲取Exif。一是C#自帶的Image.PropertyItems 屬性(瞭解),二是經過第三方控件metadata-extractor獲取(推薦)。數組
Image.PropertyItems 屬性中有幾個重要屬性,Id:爲int型,不一樣的Id表示不一樣的參數的;Value:表示參數的值,byte[]型;Len:爲int型,表示Value的長度,以字節爲單位;Type:short型,表示Value的取數方法。Type主要有如下幾個類型:工具
type=1 時 Value 爲字節數組。ui
type=2 時 Value 爲空終止 ASCII 字符串。若是將類型數據成員設置爲 ASCII 類型,則應該將 Len 屬性設置爲包括空終止的字符串長度。例如,字符串「Hello」的長度爲 6this
type=3 時 Value 爲無符號的短(16 位)整型數組。spa
type=4 時 Value 爲無符號的長(32 位)整型數組。.net
type=5 時 Value 數據成員爲無符號的長整型對數組。每一對都表示一個分數;第一個整數是分子,第二個整數是分母。插件
type=6 時 Value 爲能夠包含任何數據類型的值的字節數組。3d
type=7 時 Value 爲有符號的長(32 位)整型數組。code
type=10 時 Value 爲有符號的長整型對數組。每一對都表示一個分數;第一個整數是分子,第二個整數是分母。
參考文獻:http://blog.csdn.net/yang073402/article/details/5470127
在使用Image.PropertyItems屬性時須要引用:using System.Drawing
下面是代碼:
#region 經過PropertyItems獲取照片參數 /// <summary> /// 表示參數的結構 /// </summary> public struct Exif { /// <summary> /// 數據的ID /// </summary> public string Id; /// <summary> /// 數據類型 /// </summary> public int Type; /// <summary> /// 數據中值的字節長度 /// </summary> public int Length; /// <summary> /// 根據ID對應的中文名 /// </summary> public string Name; /// <summary> /// 根據原字節解析的參數值 /// </summary> public string Value; } /// <summary>將字節經過ASCII轉換爲字符串 /// </summary> /// <param name="bt">原字節</param> /// <returns></returns> private static string ToStrOfByte(this byte[] bt) { return Encoding.ASCII.GetString(bt); } /// <summary>將字節轉換爲int /// </summary> /// <param name="bt">原字節</param> /// <returns></returns> private static int ToUnInt16(this byte[] bt) { return Convert.ToUInt16(bt[1] << 8 | bt[0]); } /// <summary>將原兩組字節轉換爲uint /// </summary> /// <param name="bt">原字節</param> /// <param name="isFirst">是否轉第一個字節組</param> /// <returns></returns> private static uint ToUnInt32(this byte[] bt,bool isFirst=true) { return isFirst ? Convert.ToUInt32(bt[3] << 24 | bt[2] << 16 | bt[1] << 8 | bt[0]) : Convert.ToUInt32(bt[7] << 24 | bt[6] << 16 | bt[5] << 8 | bt[4]); } /// <summary>獲取曝光模式 /// </summary> /// <param name="value">曝光模式值</param> /// <returns></returns> private static string ExposureMode(int value) { var rt = "Undefined"; switch (value) { case 0: rt = "自動"; break; case 1: rt = "手動控制"; break; case 2: rt = "程序控制"; break; case 3: rt = "光圈優先"; break; case 4: rt = "快門優先"; break; case 5: rt = "夜景模式"; break; case 6: rt = "運動模式"; break; case 7: rt = "肖像模式"; break; case 8: rt = "風景模式"; break; case 9: rt = "其餘模式"; break; } return rt; } /// <summary>獲取測光模式 /// </summary> /// <param name="value">測光模式值</param> /// <returns></returns> private static string MeteringMode(int value) { var rt = "Unknown"; switch (value) { case 0: rt = "Unknown"; break; case 1: rt = "平均測光"; break; case 2: rt = "中央重點平均測光"; break; case 3: rt = "點測光"; break; case 4: rt = "多點測光"; break; case 5: rt = "評價測光"; break; case 6: rt = "局部測光"; break; case 255: rt = "其餘測光"; break; } return rt; } /// <summary>獲取閃光燈模式 /// </summary> /// <param name="value">閃光燈值</param> /// <returns></returns> private static string FlashMode(int value) { var rt = "Unkown"; switch (value) { case 0: rt = "未使用"; break; case 1: rt = "使用閃光燈"; break; } return rt; } /// <summary>獲取白平衡模式 /// </summary> /// <param name="value">白平衡值</param> /// <returns></returns> private static string WhiteBalance(int value) { var rt = "Unkown"; switch (value) { case 0: rt = "自動";//Unkown break; case 1: rt = "日光"; break; case 2: rt = "熒光燈"; break; case 3: rt = "白熾燈"; break; case 17: rt = "標準光源A"; break; case 18: rt = "標準光源B"; break; case 19: rt = "標準光源C"; break; case 255: rt = "其餘"; break; } return rt; } /// <summary>經過Id獲取Exif中關鍵名稱和值 /// </summary> /// <param name="pId">ID</param> /// <param name="pType">類型</param> /// <param name="pBytes">字節值</param> /// <returns></returns> private static Exif InfoOfExif(int pId,int pType,byte[] pBytes) { var rt=new Exif { Id ="0X"+pId.ToString("X"), Length = pBytes.Length, Type = pType }; uint fm; uint fz; switch (pId) { case 0x010F: rt.Name = "相機制造商"; rt.Value = pBytes.ToStrOfByte(); break; case 0x0110: rt.Name = "相機型號"; rt.Value = pBytes.ToStrOfByte(); break; case 0xA433: rt.Name = "鏡頭製造商"; rt.Value = pBytes.ToStrOfByte(); break; case 0xA434: rt.Name = "鏡頭型號"; rt.Value = pBytes.ToStrOfByte(); break; case 0x9003: rt.Name = "拍攝時間"; var temp=pBytes.ToStrOfByte().Split(' '); rt.Value =temp[0].Replace(":","/")+" "+temp[1]; break; case 0x0132: rt.Name = "修改時間"; temp=pBytes.ToStrOfByte().Split(' '); rt.Value =temp[0].Replace(":","/")+" "+temp[1]; break; case 0x0131: rt.Name = "軟件"; rt.Value = pBytes.ToStrOfByte(); break; case 0xA002: rt.Name = "圖像高度"; rt.Value = pBytes.ToUnInt16()+" px"; break; case 0xA003: rt.Name = "圖像寬度"; rt.Value = pBytes.ToUnInt16()+" px"; break; case 0x011A: fm=pBytes.ToUnInt32(false); fz= pBytes.ToUnInt32(); rt.Value = fm == 1 ? fz.ToString() : fz + "/" + fm; rt.Value+=" dpi"; rt.Name = "水平方向分辨率"; break; case 0x011B: fm=pBytes.ToUnInt32(false); fz= pBytes.ToUnInt32(); rt.Value = fm == 1 ? fz.ToString() : fz + "/" + fm; rt.Value += " dpi"; rt.Name = "垂直方向分辨率"; break; case 0x8822: rt.Value = ExposureMode(pBytes.ToUnInt16()); rt.Name = "曝光程序"; break; case 0x9207: rt.Value = MeteringMode(pBytes.ToUnInt16()); rt.Name = "測光模式"; break; case 0x829A: fm=pBytes.ToUnInt32(false); fz= pBytes.ToUnInt32(); //分母大於分子寫爲1/XXX,分母小於分子,寫爲保留一位小數 rt.Value = fm>fz ? "1/"+fm/fz:((double)fz/fm).ToString("0.0"); rt.Value += " 秒"; rt.Name = "曝光時間"; break; case 0x8827: rt.Value = pBytes.ToUnInt16().ToString(); rt.Name = "ISO"; break; case 0x920A: fm=pBytes.ToUnInt32(false); fz= pBytes.ToUnInt32(); rt.Value=fm==1?fz.ToString():((double)fz/fm).ToString("0.00"); rt.Value += " mm"; rt.Name = "焦距"; break; case 0x829D: rt.Value ="f/"+((double)pBytes.ToUnInt32() / pBytes.ToUnInt32(false)); rt.Name = "光圈"; break; case 0x9204: fm = pBytes.ToUnInt32(false); var fz1=Convert.ToInt32(pBytes[3] << 24 | pBytes[2] << 16 | pBytes[1] << 8 | pBytes[0]); //曝光補償要加+ - rt.Value = fz1 > 0 ? "+" : ""; rt.Value +=fz1 == 0 ? "0" : fz1+ "/"+ fm; rt.Name = "曝光補償"; break; case 0x9208: rt.Value = WhiteBalance(pBytes.ToUnInt16()); rt.Name = "白平衡"; break; case 0x9209: rt.Value = FlashMode(pBytes.ToUnInt16()); rt.Name = "閃光燈"; break; default: rt.Name = "其餘"; rt.Value = "Unkown"; break; } return rt; } /// <summary>經過PropertyItems獲取照片參數 /// </summary> /// <param name="imgPath">照片的絕對路徑</param> /// <returns>參數的集合</returns> public static IEnumerable<Exif> GetExifByPi(string imgPath) { var img = Image.FromFile(imgPath); var pItems = img.PropertyItems;//將"其餘"信息過濾掉 return pItems.Select(pi => InfoOfExif(pi.Id, pi.Type, pi.Value)).Where(j=>j.Name!="其餘").ToList(); } #endregion
在調用的時候用 var piList=GetExifByPi("照片路徑");這種方法須要注意如下幾個方面:
注意:
一、Image.PropertyItems的Type中同一個類型有的時候不能用同一個方法獲得,這是因爲參數的表現方式不一樣,因此建議用Id,每個ID用對應的方法將byte[]裝換爲string。
二、不一樣型號的手機和相機Exif中存儲方式不同,這一點很是重要,也就是說這個方法其實沒法準確獲每一個圖片的信息。咱們須要將每種相機和手機分別用不一樣方法獲取,這個工做量太大了,幸虧有第三方插件。
metadata-extractor是目前最簡單易用的EXIF信息處理包,是由Drew Noakes寫的。官網: https://drewnoakes.com/code/exif/ 官網上面的是用的.nupkg的文件,而不是傳統的.dll文件,須要經過nuget引入本地。若是不會安裝和使用nuget的能夠參考文獻:http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html 成功安裝nuget後再vs中點擊:工具->NuGet程序包管理器->程序包管理器控制檯。
而後在"pm>"處輸入:Install-Package MetadataExtractor 能夠參考:https://www.nuget.org/packages/MetadataExtractor/
最後將dll引用到您的項目中:
完整代碼:
#region 經過metadata-extractor獲取照片參數 //參考文獻 //官網: https://drewnoakes.com/code/exif/ //nuget 官網:https://www.nuget.org/ //nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html //nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/ /// <summary>經過MetadataExtractor獲取照片參數 /// </summary> /// <param name="imgPath">照片絕對路徑</param> /// <returns></returns> public static Dictionary<string,string> GetExifByMe(string imgPath) { var rmd = ImageMetadataReader.ReadMetadata(imgPath); var rt=new Dictionary<string,string>(); foreach (var rd in rmd) { foreach(var tag in rd.Tags) { var temp = EngToChs(tag.Name); if (temp == "其餘") { continue; } if (!rt.ContainsKey(temp)) { rt.Add(temp, tag.Description); } } } return rt; } /// <summary>篩選參數並將其名稱轉換爲中文 /// </summary> /// <param name="str">參數名稱</param> /// <returns>參數中文名</returns> private static string EngToChs(string str) { var rt = "其餘"; switch (str) { case "Exif Version": rt = "Exif版本"; break; case "Model": rt = "相機型號"; break; case "Lens Model": rt = "鏡頭類型"; break; case "File Name": rt = "文件名"; break; case "File Size": rt = "文件大小"; break; case "Date/Time": rt = "拍攝時間"; break; case "File Modified Date": rt = "修改時間"; break; case "Image Height": rt = "照片高度"; break; case "Image Width": rt = "照片寬度"; break; case "X Resolution": rt = "水平分辨率"; break; case "Y Resolution": rt = "垂直分辨率"; break; case "Color Space": rt = "色彩空間"; break; case "Shutter Speed Value": rt = "快門速度"; break; case "F-Number": rt = "光圈";//Aperture Value也表示光圈 break; case "ISO Speed Ratings": rt = "ISO"; break; case "Exposure Bias Value": rt = "曝光補償"; break; case "Focal Length": rt = "焦距"; break; case "Exposure Program": rt = "曝光程序"; break; case "Metering Mode": rt = "測光模式"; break; case "Flash Mode": rt = "閃光燈"; break; case "White Balance Mode": rt = "白平衡"; break; case "Exposure Mode": rt = "曝光模式"; break; case "Continuous Drive Mode": rt = "驅動模式"; break; case "Focus Mode": rt = "對焦模式"; break; } return rt; } #endregion
使用的時候:var me=GetExifByMe();
注意:
一、var rmd = ImageMetadataReader.ReadMetadata(imgPath);方法裏能夠是照片路徑和Stream類型。
二、metadata-extractor會將全部信息讀出來,並且仍是英文的,因此要將裏面的數據進行選取,須要的還要轉換爲中文。
參考文獻:官網: https://drewnoakes.com/code/exif/nuget MetadataExtractor: https://www.nuget.org/packages/MetadataExtractor/nuget 使用: http://www.cnblogs.com/chsword/archive/2011/09/14/NuGet_Install_OperatePackage.html