继上一篇”利用C#实现任务栏通知窗口”后继续我们的C#实战演练。现在人们具备一台数码相机已经不是什么新鲜事了,更何况500万像素以上的数码相机更是逐渐成了主流。相比较以前以胶卷为感光介质的普通相机,数码相机可以将所照图像即刻转换成计算机可识别的图像文件格式以便浏览、共享和打印。虽然数码相机在技术和方便性上都远远高于普通相机,但是笔者发现所有已经生成的图像文件以及打印的数码照片上都没有拍照日期,若想在日后拍照时加上该选项也不是容易事,翻遍了数码相机的说明书竟然没有关于怎样在照片上显示拍照日期的帮助。而且,随着数码图像文件的不断增加,面对成本成本的相册要想回顾一下其到底是在何时拍照的将会非常困难,尤其像笔者这样不辞辛苦的记录小儿生长历程的朋友更感觉如此。虽然在桌面电脑上利用我的电脑浏览所拍照的图像文件时,在窗口底部任务栏上或者图像文件属性窗口的摘要页都可以清楚地显示出拍照日期,但是要想将拍照日期绘制到图像上且能够打印到数码照片上却没有工具可以做到。其实,拍照日期已经保存在了图像文件里,我们需要自己动手编程获取拍照日期并在图像的右下角将其绘制出来,然后保存新生成的图像文件并拷贝到数码照片打印店进行打印,我们就可以获得具备拍照日期的数码照片了,如下图:
一、简介
目前大部分数码相机都将所拍照的图像保存成JPG格式,而像拍照日期这样的信息统称为EXIF信息。EXIF是英文ExchangeableImageFile(可交换图像文件)的缩写,最初由日本电子工业发展协会(JEIDA--JapanElectronicIndustryDevelopmentAssociation)制订,当时JEITA决定为数码相机厂商专门制定一套标准,随着数码相机的发展,其普及趋势越来越明显,于是JEITA对Exif标准进行了升级,目前最新版本为2.2。其实EXIF也是一种图像文件格式,EXIF信息就是由数码相机在拍摄过程中采集一系列的信息,然后把信息放置在JPG文件或者TIFF文件的头部,也就是说EXIF信息是镶嵌在图像文件格式内的一组拍摄参数,主要包括摄影时的光圈、快门、ISO、拍照日期时间等各种与当时摄影条件相关的信息、相机品牌型号、色彩编码,甚至还包括拍摄时录制的声音以及全球定位系统(GPS)等信息。简单的说,它就好像是傻瓜相机的日期打印功能一样,只不过EXIF信息所记录的资讯更为详尽和完备。需要注意的是,用图像处理软件编辑过的数码图像文件有可能会丢失其EXIF信息。
所以,要想在图像上绘制拍照日期首先必须读取EXIF信息,然后将读取出来的拍照日期绘制在图像表面,我们将以500万像素分辨率为2592x1944的JPG图像文件为对象,使用Visual Studio .Net 2005 的C#来编写一工具程序来实现上述功能。
二、
技术背景
十六进制值 |
说明 |
0x0320 0x 0x0110 0x0132 0x 0x5090 .... |
图像标题 设备制造商 设备型号 拍照时间 Exif 曝光时间 亮度表 .... |
数值 |
说明 |
1 |
一个 Byte |
2 |
ASCII 编码的 Byte 对象的数组 |
3 |
16 位整数 |
4 |
32 位整数 |
5 |
包含两个表示有理数的 Byte 对象的数组 |
6 |
未使用 |
7 |
未定义 |
8 |
未使用 |
9 |
SLong |
10 |
SRational |
我们所感兴趣的ID值就是0x0132即图片拍照时间,对应的标记为PropertyTagDateTime,而在联机的MSDN中我们发现了更详细的关于EXIF属性的GDI+的描述,PropertyTagDateTime值的类型为PropertyTagTypeASCII,它以ASCII编码的形式保存数据,我们在获取数据后就按照ASCII进行解码,将一些列字节转换为日期/时间的字符串。
我们发现里面的日期格式为:2006:06:07 16:33:41,这个格式既不是标准的日期/时间格式也不是当前系统设置的格式,所以还需要对日期/时间格式进行格式化。
获得了拍照日期/时间后,从指定的图片文件来创建Graphics对象,在该Graphics对象上绘制先前我们获取的拍照日期/时间。
三、程序实现
我们自定义一个函数GetExifProperties用于返回图片文件的Exif信息,代码如下:
public static PropertyItem[] GetExifProperties(string fileName)
{
FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
//通过指定的数据流来创建Image
System.Drawing.Image image = System.Drawing.Image.FromStream(stream,true,false);
return image.PropertyItems;
}
private string GetTakePicDateTime(System.Drawing.Imaging.PropertyItem[] parr)
{
Encoding ascii = Encoding.ASCII ;
//遍历图像文件元数据,检索所有属性
foreach (System.Drawing.Imaging.PropertyItem p in parr)
{
//如果是PropertyTagDateTime,则返回该属性所对应的值
if (p.Id==0x0132)
{
return ascii.GetString(p.Value);
}
}
//若没有相关的EXIF信息则返回N/A
return "N/A";
}
{
pi = GetExifProperties(listBox1.Items[i].ToString() );
//获取元数据中的拍照日期时间,以字符串形式保存
TakePicDateTime = GetTakePicDateTime(pi);
//分析字符串分别保存拍照日期和时间的标准格式
SpaceLocation = TakePicDateTime.IndexOf(" ");
dt = TakePicDateTime.Substring(0, SpaceLocation);
dt=dt.Replace(":", "-");
tm = TakePicDateTime.Substring(SpaceLocation+1, TakePicDateTime.Length - SpaceLocation-2);
TakePicDateTime = dt + " " + tm;
//由列表中的文件创建内存位图对象
Pic = new Bitmap(listBox1.Items[i].ToString());
//由位图对象创建Graphics对象的实例
g = Graphics.FromImage(Pic);
//在Graphics表面绘制数码照片的日期/时间戳
g.DrawString(TakePicDateTime, normalContentFont, new SolidBrush(normalContentColor),
Pic.Width - 700, Pic.Height - 200);
//将添加日期/时间戳后的图像进行保存
Pic.Save(textBox1.Text + Path.GetFileName(listBox1.Items[i].ToString()));
//释放内存位图对象
Pic.Dispose();
}
四、总结
该程序在Visual Studio .Net