引言:
EXIF,是英文Exchangeable Image File(可交換圖像檔案)的縮寫,最初由日本電子工業發展協會(JEIDA --Japan Electronic Industry Development Association) 制訂,目前的版本是修改發表于1998年6月的2.1版。國際标準化組織(ISO)正在制訂的相機檔案設計标準(DCF -- Design role for Camera File system)就是以EXIF2.1格式為基礎而設定的。
記住,EXIF是一種圖像檔案格式,隻是檔案的字尾名還是沿用大家熟悉的jpg而已。實際上,EXIF資訊就是由數位相機在拍攝過程中采集一系列的資訊,然後把資訊放置在我們熟知的jpg檔案的頭部,也就是說EXIF資訊是鑲嵌在JPEG圖像檔案格式内的一組拍攝參數,主要包括攝影時的光圈、快門、ISO、日期時間等各種與當時攝影條件相關的訊息,相機品牌型号,色彩編碼,拍攝時錄制的聲音以及全球定位系統(GPS)等資訊。簡單的說,它就好像是傻瓜相機的日期列印功能一樣,隻不過EXIF資訊所記錄的資訊更為詳盡和完備。不過,具有EXIF資訊的JPEG圖像檔案要比普通的JPEG檔案略大一點。就目前市場而言,新一代的數位相機都具有附加EXIF資訊功能,大多數的數位相機廠商也都會随數位相機發售時附贈能夠讀取EXIF資訊的軟體,例如 Nikon 系列的數位相機就附贈 NikonView 軟體,Agfa系列的相機則附贈 Photowize V1.8版,而富士相機附送的EXIF viewer軟體更是這方面的領軍人物(目前已在很多網站提供免費下載下傳。還有一部分的數位相機會自動将EXIF資訊轉存成文檔檔案,例如:NIKON CoolPix 990和SONY FD系列。除了硬體廠商随數位相機附帶的EXIF資訊檢視軟體,很多專業的圖像軟體廠商在這方面也不甘示弱,相繼推出自己公司看圖軟體的最新版來支援這種近乎完美的JPEG-EXIF圖像資訊附加技術,如最近剛推出的ACDSee 4.0版本,就對現在流行的各種數位相機有相當好的支援,在EXIF圖像資訊附加方面較之其3.0版本也有很大的進步。不管是硬體廠商的配套軟體還是專業名門的看圖工具,所有這些軟體都是為了友善數位攝影者能更友善地儲存檢視攝影圖像的重要資訊。
我們将這些讀取EXIF資訊的軟體歸納後分為四類:專業EXIF資訊檢視工具(以富士的EXIF viewer為例) 、具有檢視EXIF資訊的強大通用看圖工具(以ACDSee為例)、支援EXIF資訊檢視的作業系統(微軟的Windows XP)以及可以修改EXIF資訊的另類工具(EXIF Editer),而我們這裡要講的是通過C#在WEB上讀取一個圖檔的EXIF資訊。
二、代碼實作
首先是提取圖檔資訊的相關類,在此我把它整合成一個單獨的類庫中,以便直接拿來使用。
1.EXIFMetaData類
這個是與調用程式的接口,其中包含了EXIF的所有資訊,都是處理過的,可直接使用。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
namespace EXIFInfo
{
/// <summary>
/// EXIFMetaData 的摘要說明。
/// </summary>
public class EXIFMetaData
{
#region 構造函數
/// <summary>
/// 構造函數
/// </summary>
public EXIFMetaData()
{
}
#endregion
#region 資料轉換結構
/// <summary>
/// 轉換資料結構
/// </summary>
public struct MetadataDetail
{
/// <summary>
/// 顯示值串
/// </summary>
public string DisplayValue;//顯示值串
}
#endregion
#region EXIF元素結構
/// <summary>
/// 結構:存儲EXIF元素資訊
/// </summary>
public struct Metadata
{
/// <summary>
/// 相片拍照日期
/// </summary>
public MetadataDetail DTDigitized;
/// <summary>
/// 裝置制造商
/// </summary>
public MetadataDetail EquipmentMake;
/// <summary>
/// 曝光時間(秒)
/// </summary>
public MetadataDetail ExposureTime;
/// <summary>
/// 快門速度(秒)
/// </summary>
public MetadataDetail ShutterSpeed;
/// <summary>
/// 曝光模式
/// </summary>
public MetadataDetail MeteringMode;
/// <summary>
/// 閃光燈(開/關reserved)
/// </summary>
public MetadataDetail Flash;
/// <summary>
/// 水準分辯率(DPI)
/// </summary>
public MetadataDetail XResolution;
/// <summary>
/// 垂直分辯率(DPI)
/// </summary>
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。
/// <summary>
/// 壓縮(焦距)
/// </summary>
public MetadataDetail ThumbnailCompression;
/// <summary>
/// //----
/// </summary>
public MetadataDetail ThumbnailResolutionUnit;
/// <summary>
/// 攝影機型号
/// </summary>
public MetadataDetail EquipModel;
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。
}
#endregion
#region 取得圖檔的EXIF資訊
/// <summary>
/// 取得圖檔的EXIF資訊
/// </summary>
/// <param name="PhotoName">圖檔實體路徑</param>
/// <returns>圖檔類實體</returns>
public Metadata GetEXIFMetaData(string PhotoName)
{
// 建立一個圖檔的執行個體
System.Drawing.Bitmap bmp = new Bitmap(PhotoName);
EXIFextractor er = new EXIFextractor(ref bmp, "/n");
NameValueCollection items = new NameValueCollection();
//擷取照片的相關資訊
foreach (Pair pr in er)
{
items.Add(pr.First,pr.Second);
}
//自定義類屬性付值
Metadata MyMetadata = new Metadata();
try
{
MyMetadata.DTDigitized.DisplayValue = items.Get("DTDigitized");
MyMetadata.EquipmentMake.DisplayValue = items.Get("Equip Make");
MyMetadata.ExposureProg.DisplayValue = items.Get("Exposure Prog");
MyMetadata.Aperture.DisplayValue = items.Get("Aperture");
MyMetadata.SensingMethod.DisplayValue = items.Get("Sensing Method");
MyMetadata.YResolution.DisplayValue = items.Get("Y Resolution");
MyMetadata.ExposureIndex.DisplayValue = items.Get("Exposure Index");
。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。
}
catch
{}
items.Clear();
return MyMetadata;
}
#endregion
}
}
2.EXIFextractor類
此類是用來提前提取處理相關參數的。
using System;
using System.Text;
using System.Collections;
using System.Drawing.Imaging;
using System.Reflection;
using System.IO;
namespace EXIFInfo
{
/// <summary>
/// EXIFextractor 的摘要說明。
/// </summary>
public class EXIFextractor: IEnumerable
{
/// <summary>
/// Get the individual property value by supplying property name
/// These are the valid property names :
///
/// "Exif IFD"
/// "Gps IFD"
/// "New Subfile Type"
/// "Subfile Type"
//。。。。。。。。。等等
/// "Gps DestDist"
/// </summary>
public object this[string index]
{
get
{
return properties[index];
}
}
//
private System.Drawing.Bitmap bmp;
//
private string data;
//
private translation myHash;
//
private Hashtable properties;
//
internal int Count
{
get
{
return this.properties.Count;
}
}
//
string sp;
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <param name="data"></param>
public void setTag(int id, string data)
{
Encoding ascii = Encoding.ASCII;
this.setTag(id, data.Length, 0x2, ascii.GetBytes(data));
}
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <param name="len"></param>
/// <param name="type"></param>
/// <param name="data"></param>
public void setTag(int id, int len, short type, byte[] data)
{
PropertyItem p = CreatePropertyItem(type, id, len, data);
this.bmp.SetPropertyItem(p);
buildDB(this.bmp.PropertyItems);
}
/// <summary>
///
/// </summary>
/// <param name="type"></param>
/// <param name="tag"></param>
/// <param name="len"></param>
/// <param name="value"></param>
/// <returns></returns>
private static PropertyItem CreatePropertyItem(short type, int tag, int len, byte[] value)
{
PropertyItem item;
// Loads a PropertyItem from a Jpeg image stored in the assembly as a resource.
Assembly assembly = Assembly.GetExecutingAssembly();
Stream emptyBitmapStream = assembly.GetManifestResourceStream("EXIFextractor.decoy.jpg");
System.Drawing.Image empty = System.Drawing.Image.FromStream(emptyBitmapStream);
item = empty.PropertyItems[0];
// Copies the data to the property item.
item.Type = type;
item.Len = len;
item.Id = tag;
item.Value = new byte[value.Length];
value.CopyTo(item.Value, 0);
return item;
}
/// <summary>
///
/// </summary>
/// <param name="bmp"></param>
/// <param name="sp"></param>
public EXIFextractor(ref System.Drawing.Bitmap bmp, string sp)
{
properties = new Hashtable();
//
this.bmp = bmp;
this.sp = sp;
//
myHash = new translation();
buildDB(this.bmp.PropertyItems);
}
string msp = "";
/// <summary>
///
/// </summary>
/// <param name="bmp"></param>
/// <param name="sp"></param>
/// <param name="msp"></param>
public EXIFextractor(ref System.Drawing.Bitmap bmp, string sp, string msp)
{
properties = new Hashtable();
this.sp = sp;
this.msp = msp;
this.bmp = bmp;
//
myHash = new translation();
this.buildDB(bmp.PropertyItems);
}
/// <summary>
///
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static PropertyItem[] GetExifProperties(string fileName)
{
FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream,
true,
false);
return image.PropertyItems;
}
/// <summary>
///
/// </summary>
/// <param name="file"></param>
/// <param name="sp"></param>
/// <param name="msp"></param>
public EXIFextractor(string file, string sp, string msp)
{
properties = new Hashtable();
this.sp = sp;
this.msp = msp;
myHash = new translation();
//
this.buildDB(GetExifProperties(file));
}
/// <summary>
///
/// </summary>
private void buildDB(System.Drawing.Imaging.PropertyItem[] parr)
{
properties.Clear();
//
data = "";
//
Encoding ascii = Encoding.ASCII;
//
foreach (System.Drawing.Imaging.PropertyItem p in parr)
{
string v = "";
string name = (string)myHash[p.Id];
// tag not found. skip it
if (name == null) continue;
//
data += name + ": ";
//
//1 = BYTE An 8-bit unsigned integer.,
if (p.Type == 0x1)
{
v = p.Value[0].ToString();
}
//2 = ASCII An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.,
else if (p.Type == 0x2)
{
// string
v = ascii.GetString(p.Value);
}
//3 = SHORT A 16-bit (2 -byte) unsigned integer,
else if (p.Type == 0x3)
{
// orientation // lookup table
switch (p.Id)
{
case 0x8827: // ISO
v = "ISO-" + convertToInt16U(p.Value).ToString();
break;
case 0xA217: // sensing method
{
switch (convertToInt16U(p.Value))
{
case 1: v = "Not defined"; break;
case 2: v = "One-chip color area sensor"; break;
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。 case 8: v = "Color sequential linear sensor"; break;
default: v = " reserved"; break;
}
}
break;
case 0x8822: // aperture
switch (convertToInt16U(p.Value))
{
case 0: v = "Not defined"; break;
case 1: v = "Manual"; break;
case 2: v = "Normal program"; break;
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。 case 8: v = "Landscape mode (for landscape photos with the background in focus)"; break;
default: v = "reserved"; break;
}
break;
case 0x9207: // metering mode
switch (convertToInt16U(p.Value))
{
case 0: v = "unknown"; break;
case 1: v = "Average"; break;
case 2: v = "CenterWeightedAverage"; break;
case 3: v = "Spot"; break;
case 4: v = "MultiSpot"; break;
case 5: v = "Pattern"; break;
case 6: v = "Partial"; break;
case 255: v = "Other"; break;
default: v = "reserved"; break;
}
break;
case 0x9208: // light source
{
switch (convertToInt16U(p.Value))
{
case 0: v = "unknown"; break;
case 1: v = "Daylight"; break;
case 2: v = "Fluorescent"; break;
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。 case 255: v = "other"; break;
default: v = "reserved"; break;
}
}
break;
case 0x9209:
{
switch (convertToInt16U(p.Value))
{
case 0: v = "Flash did not fire"; break;
case 1: v = "Flash fired"; break;
case 5: v = "Strobe return light not detected"; break;
case 7: v = "Strobe return light detected"; break;
default: v = "reserved"; break;
}
}
break;
default:
v = convertToInt16U(p.Value).ToString();
break;
}
}
//4 = LONG A 32-bit (4 -byte) unsigned integer,
else if (p.Type == 0x4)
{
// orientation // lookup table
v = convertToInt32U(p.Value).ToString();
}
//5 = RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the//denominator.,
else if (p.Type == 0x5)
{
// rational
byte[] n = new byte[p.Len / 2];
byte[] d = new byte[p.Len / 2];
Array.Copy(p.Value, 0, n, 0, p.Len / 2);
Array.Copy(p.Value, p.Len / 2, d, 0, p.Len / 2);
uint a = convertToInt32U(n);
uint b = convertToInt32U(d);
Rational r = new Rational(a, b);
//
//convert here
//
switch (p.Id)
{
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。
case 0x829A://ExposureTime
//v = r.ToDouble().ToString(); modify by gjr 2006.3.22 jinru
v = r.ToString("/");
break;
case 0x829D: // F-number
v = "F/" + r.ToDouble().ToString();
break;
default:
v = r.ToString("/");
break;
}
}
//7 = UNDEFINED An 8-bit byte that can take any value depending on the field definition,
else if (p.Type == 0x7)
{
switch (p.Id)
{
case 0xA300:
{
if (p.Value[0] == 3)
{
v = "DSC";
}
else
{
v = "reserved";
}
break;
}
case 0xA301:
if (p.Value[0] == 1)
v = "A directly photographed image";
else
v = "Not a directly photographed image";
break;
default:
v = "-";
break;
}
}
//9 = SLONG A 32-bit (4 -byte) signed integer (2's complement notation),
else if (p.Type == 0x9)
{
v = convertToInt32(p.Value).ToString();
}
//10 = SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the
//denominator.
else if (p.Type == 0xA)
{
// rational
byte[] n = new byte[p.Len / 2];
byte[] d = new byte[p.Len / 2];
Array.Copy(p.Value, 0, n, 0, p.Len / 2);
Array.Copy(p.Value, p.Len / 2, d, 0, p.Len / 2);
int a = convertToInt32(n);
int b = convertToInt32(d);
Rational r = new Rational(a, b);
//
// convert here
//
switch (p.Id)
{
case 0x9201: // shutter speed
v = "1/" + Math.Round(Math.Pow(2, r.ToDouble()), 2).ToString();
break;
case 0x9203:
v = Math.Round(r.ToDouble(), 4).ToString();
break;
default:
v = r.ToString("/");
break;
}
}
// add it to the list
if (properties[name] == null)
properties.Add(name, v);
// cat it too
data += v;
data += this.sp;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return data;
}
/// <summary>
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
int convertToInt32(byte[] arr)
{
if (arr.Length != 4)
return 0;
else
return arr[3] << 24 | arr[2] << 16 | arr[1] << 8 | arr[0];
}
/// <summary>
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
int convertToInt16(byte[] arr)
{
if (arr.Length != 2)
return 0;
else
return arr[1] << 8 | arr[0];
}
/// <summary>
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
uint convertToInt32U(byte[] arr)
{
if (arr.Length != 4)
return 0;
else
return Convert.ToUInt32(arr[3] << 24 | arr[2] << 16 | arr[1] << 8 | arr[0]);
}
/// <summary>
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
uint convertToInt16U(byte[] arr)
{
if (arr.Length != 2)
return 0;
else
return Convert.ToUInt16(arr[1] << 8 | arr[0]);
}
#region IEnumerable Members
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerator GetEnumerator()
{
// TODO: Add EXIFextractor.GetEnumerator implementation
return (new EXIFextractorEnumerator(this.properties));
}
#endregion
}
//
// dont touch this class. its for IEnumerator
//
//
class EXIFextractorEnumerator : IEnumerator
{
Hashtable exifTable;
IDictionaryEnumerator index;
internal EXIFextractorEnumerator(Hashtable exif)
{
this.exifTable = exif;
this.Reset();
index = exif.GetEnumerator();
}
#region IEnumerator Members
public void Reset()
{
this.index = null;
}
public object Current
{
get
{
return (new Pair(this.index.Key, this.index.Value));
}
}
public bool MoveNext()
{
if (index != null && index.MoveNext())
return true;
else
return false;
}
#endregion
}
/// <summary>
///
/// </summary>
public class Pair
{
/// <summary>
/// First
/// </summary>
public string First;
/// <summary>
/// Second
/// </summary>
public string Second;
/// <summary>
/// Pair
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public Pair(object key, object value)
{
this.First = key.ToString();
this.Second = value.ToString();
}
}
}
3.translation類
此類是用來計算相關參數的值。
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
namespace EXIFInfo
{
/// <summary>
/// translation 的摘要說明。
/// </summary>
public class translation: Hashtable
{
/// <summary>
/// translation
/// </summary>
public translation()
{
this.Add(0x8769,"Exif IFD");
this.Add(0x8825,"Gps IFD");
this.Add(0xFE,"New Subfile Type");
this.Add(0xFF,"Subfile Type");
this.Add(0x100,"Image Width");
this.Add(0x101,"Image Height");
this.Add(0x102,"Bits Per Sample");
this.Add(0x103,"Compression");
// 。。。。。。。//在此有很多相關的屬性,不一一列出。有需要者可與我聯系。
}
}
/// <summary>
/// private class
/// </summary>
internal class Rational
{
private int n;
private int d;
public Rational(int n, int d)
{
this.n = n;
this.d = d;
simplify(ref this.n, ref this.d);
}
public Rational(uint n, uint d)
{
this.n = Convert.ToInt32(n);
this.d = Convert.ToInt32(d);
simplify(ref this.n, ref this.d);
}
public Rational()
{
this.n= this.d=0;
}
public string ToString(string sp)
{
if( sp == null ) sp = "/";
return n.ToString() + sp + d.ToString();
}
public double ToDouble()
{
if( d == 0 )
return 0.0;
return Math.Round(Convert.ToDouble(n)/Convert.ToDouble(d),2);
}
private void simplify( ref int a, ref int b )
{
if( a== 0 || b == 0 )
return;
int gcd = euclid(a,b);
a /= gcd;
b /= gcd;
}
private int euclid(int a, int b)
{
if(b==0)
return a;
else
return euclid(b,a%b);
}
}
}
三、具體頁面應用
首先引用以上所生成的DLL:using EXIFInfo;
EXIFMetaData em = new EXIFMetaData();
string filePath=this.imgfile.PostedFile.FileName;//這裡可以動态傳遞圖檔路徑的
this.Image1.ImageUrl = filePath;//預覽照片
EXIFMetaData.Metadata m = em.GetEXIFMetaData(filePath);//這裡就是調用,傳圖檔絕對路徑
string exif = m.Ver.DisplayValue;
string camera = m.EquipmentMake.DisplayValue;
string model = m.EquipModel.DisplayValue;
string aperture = m.Aperture.DisplayValue;
string shutter = m.ShutterSpeed.DisplayValue;
string isospeed = m.ISOSpeed.DisplayValue;
// 。。。。。。。。。 等等........
this.Label1.Text = "EXIF版本:"+exif+" 相機品牌:"+camera.Replace("COMPANY","")+" 相機型号:"+model+" 光圈:"+aperture+" 快門速度:"+
shutter+"s 曝光時間:"+exposuretime+"s ISO感光度:"+isospeed+" 焦距:"+focallength+"mm"; // 。。。。。。。。。 等等........
經過處理可以在網頁中上傳圖檔時一并顯示出圖檔的一些相關資訊。