引言:
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,
/* useEmbeddedColorManagement = */ true,
/* validateImageData = */ 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"; // 。。。。。。。。。 等等........
经过处理可以在网页中上传图片时一并显示出图片的一些相关信息。