版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。
17.1.1 Image类
Image类为源自 Bitmap 和 Metafile 的类提供功能的抽象基类。
Image的属性大多数是只读的:
- FrameDimensionsList:获取GUID的数组,这些GUID表示Image中帧的维数。
- Size:获取此图像的宽度和高度(单位:像素)。
- Width:获取Image的宽度(单位:像素)。
- Height:获取Image的高度(单位:像素)。
- HorizontalResolution:获取水平分辨率(单位:像素/英寸)。
- VerticalResolution:获取垂直分辨率(单位:像素/英寸)。
- PixelFormat:获取Image中每个像素的颜色数据的格式。
- PropertyIdList:获取存储于Image中的属性项的ID。
- PropertyItems:获取存储于Image中的所有属性项(元数据片)。
- RawFormat:获取Image的文件格式。
Image常用方法:
- Clone:创建此 Image 的一个精确副本。
- FromFile:从指定的文件创建Image。
- FromHbitmap:从GDI位图的句柄创建Bitmap。
- FromStream:从指定的数据流创建Image。
- FrameDimensionsList:获取GUID数组,这些GUID表示此Image中帧的维数。
- GetBounds:以指定的单位获取图像的界限。
- GetEncoderParameterList:返回有关指定的图像编码器所支持的参数的信息。
- GetFrameCount:返回指定维度的帧数。
- GetPropertyItem:获取指定的属性项。
- GetThumbnailImage:返回缩略图。
- IsAlphaPixelFormat:获取像素格式是否包含alpha信息。
- RemovePropertyItem:从Image移除指定的属性项。
- RotateFlip:旋转、翻转或者同时旋转和翻转Image。
- Save:保存到指定的文件或流。
- SaveAdd:在上一Save方法调用所指定的文件或流内添加一帧。
- SelectActiveFrame:选择由维度和索引指定的帧。
- SetPropertyItem:存储一个属性项。
由于Image类没有提供构造函数,只能通过FromFile、FromStream或者FromHbitmap方法来创建Image对象实例。
【例 17.1】【项目:code17-001】获取图片信息。
private void button1_Click(object sender, EventArgs e)
{
string imgfile;
OpenFileDialog ofd =new OpenFileDialog();
ofd.Filter = "图片文件|*.jpg;*.bmp;*.png;*.gif";
if (ofd.ShowDialog() != DialogResult.OK)
return;
imgfile = ofd.FileName;
//从图片文件创建Image实例
Image img = Image.FromFile(imgfile);
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = img;
textBox1.Text = "图片高度:" + img.Height + "\r\n";
textBox1.Text += "图片宽度:" + img.Width + "\r\n";
textBox1.Text += "图片水平分辨率:" + img.HorizontalResolution + "\r\n";
textBox1.Text += "图片垂直分辨率:" + img.VerticalResolution + "\r\n";
textBox1.Text += "文件格式:" + img.RawFormat.ToString() + "\r\n";
textBox1.Text += "像素格式:" + img.PixelFormat.ToString() + "\r\n";
}
运行结果如下图所示:
图17-1 获取图片信息
【例 17.2】【项目:code17-002】翻转和旋转图片。
请在窗体上放置两个Button控件、一个ComboBox控件、一个PictureBox控件。
本例主要使用Image类的RotateFlip方法,它的参数是一个RotateFlipType枚举,包含以下成员:
- RotateNoneFlipNone:指定不进行旋转和翻转。
- RotateNoneFlipX:水平翻转。
- RotateNoneFlipY:垂直翻转。
- RotateNoneFlipXY:水平翻转和垂直翻转。
- Rotate90FlipNone: 90度顺时针旋转。
- Rotate90FlipX:水平翻转和90度顺时针旋转。
- Rotate90FlipY:垂直翻转和90度顺时针旋转。
- Rotate90FlipXY:水平翻转、垂直翻转和90度顺时针旋转。
- Rotate180FlipNone:180度顺时针旋转。
- Rotate180FlipX:水平翻转和180度顺时针旋转。
- Rotate180FlipY:垂直翻转和180度顺时针旋转。
- Rotate180FlipXY:水平翻转、垂直翻转和180度顺时针旋转。
- Rotate270FlipNone:270度顺时针旋转。
- Rotate270FlipX:水平翻转和270度顺时针旋转。
- Rotate270FlipY:垂直翻转和270度顺时针旋转。
- Rotate270FlipXY:水平翻转、垂直翻转和270度顺时针旋转。
请在ComboBox的Items属性中加入以上成员的名称。同时,为了观察图片变化,请将PictureBox控件的SizeMode 属性设置为AutoSize。具体代码如下:
//窗体级变量,保存打开的图片
Image sourceImg;
//打开图片并显示在图片框内
private void Button1_Click(object sender, EventArgs e)
{
string imgfile;
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "图片文件|*.jpg";
if (ofd.ShowDialog() != DialogResult.OK)
return;
imgfile = ofd.FileName;
//从图片文件创建Image实例
sourceImg = Image.FromFile(imgfile);
pictureBox1.Image = sourceImg;
}
private void ComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
if (pictureBox1.Image == null)
return;
Image destImg;
//克隆图片
destImg = (Image)sourceImg.Clone();
//根据组合框文本,使用RotateFlip方法旋转图片
switch( comboBox1.Text)
{
case "Rotate180FlipX":
destImg.RotateFlip(RotateFlipType.Rotate180FlipX);
break;
case "Rotate180FlipNone":
destImg.RotateFlip(RotateFlipType.Rotate180FlipNone);
break;
case "Rotate180FlipXY":
destImg.RotateFlip(RotateFlipType.Rotate180FlipXY);
break;
case "Rotate180FlipY":
destImg.RotateFlip(RotateFlipType.Rotate180FlipY);
break;
case "Rotate270FlipNone":
destImg.RotateFlip(RotateFlipType.Rotate270FlipNone);
break;
case "Rotate270FlipX":
destImg.RotateFlip(RotateFlipType.Rotate270FlipX);
break;
case "Rotate270FlipXY":
destImg.RotateFlip(RotateFlipType.Rotate270FlipXY);
break;
case "Rotate90FlipNone":
destImg.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
case "Rotate90FlipX":
destImg.RotateFlip(RotateFlipType.Rotate90FlipX);
break;
case "Rotate90FlipXY":
destImg.RotateFlip(RotateFlipType.Rotate90FlipXY);
break;
case "Rotate90FlipY":
destImg.RotateFlip(RotateFlipType.Rotate90FlipY);
break;
case "RotateNoneFlipNone":
destImg.RotateFlip(RotateFlipType.RotateNoneFlipNone);
break;
case "RotateNoneFlipX":
destImg.RotateFlip(RotateFlipType.RotateNoneFlipX);
break;
case "RotateNoneFlipXY":
destImg.RotateFlip(RotateFlipType.RotateNoneFlipXY);
break;
case "RotateNoneFlipY":
destImg.RotateFlip(RotateFlipType.RotateNoneFlipY);
break;
}
pictureBox1.Image = destImg;
}
//保存旋转后的图片
private void Button2_Click(object sender, EventArgs e)
{
string imgfile;
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "图片文件|*.jpg";
if (sfd.ShowDialog() != DialogResult.OK)
return;
imgfile = sfd.FileName;
//Save方法保存图片
pictureBox1.Image.Save(imgfile, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
运行结果如下图所示:
图17-2 图片的翻转和旋转
17.1.2 Bitmap类
Bitmap类常用于封装 GDI+ 位图、处理由像素数据定义的图像的对象。
由于Bitmap类继承于Image类,Image类的属性和方法都可以使用。
Bitmap特有方法:
- FromHicon:从图标的Windows句柄创建 Bitmap。
- FromResource:从指定的Windows资源创建 Bitmap。
- GetPixel:获取Bitmap中指定坐标位置像素的颜色。
- SetPixel:设置Bitmap中指定坐标位置像素的颜色。
- LockBits:将Bitmap锁定到系统内存中。
- UnlockBits:从系统内存解锁Bitmap。
- MakeTransparent:用默认或指定的颜色使Bitmap透明。
创建一个Bitmap实例,常采用以下方法:
1、使用Bitmap的构造函数。
Bitmap提供了12种构造函数,常用的是:
- Bitmap(String):从指定的文件初始化Bitmap类的新实例。例如:
Bitmap sourceImg = new Bitmap("c:\\lessons\\3.jpg");
- Bitmap(Int32, Int32):用指定的宽度和高度初始化Bitmap类的新实例。例如:
Bitmap sourceImg = new Bitmap(this.Width ,this.Height);
2、使用方法创建:
- FromFile(String):使用Image的方法从指定的文件创建Bitmap。例如:
Bitmap sourceImg = (Bitmap)Image.FromFile("c:\\lessons\\3.jpg");
- Clone:创建此 Bitmap的一个精确副本
Bitmap destImg = (Bitmap)sourceImg.Clone();
Bitmap类的操作和Image类类似,这里不再累述。
17.1.3 获得图片Exif信息
有些照片保存了拍摄时使用的的相机品牌、型号以及GPS、光圈、快门、白平衡、ISO、焦距、日期时间等信息,通常称为Exif(Exchangeable image file format)信息,保存在Jpg文件头部。
使用Image类或Bitmap类的PropertyItems属性很容易获得图片的Exif信息。
PropertyItems是一个PropertyItem类数组,这些对象分别对应于此图像中存储的每个属性项。
注意:PropertyItem类位于System.Drawing.Imaging命名空间。
PropertyItem有4个属性:
- Id:某个图像信息的ID。
- Value:某个图像信息的值。
- Len:Value属性的长度(以字节为单位)。
- Type:整数,它定义了Value 属性包含的数据类型。
使用PropertyItem需要注意以下事项:
1、不同的Id有着不同的含义,例如Id从0至26都是关于gps信息。还有些常见信息如下(Id使用的是十六进制表示):
- 010F:PropertyTagEquipMake,相机生产厂家。
- 0110:PropertyTagEquipModel,相机型号。
- 829A:PropertyTagExifExposureTim,快门速度。
- 829D:PropertyTagExifFNumber,光圈。
- 9000:PropertyTagExifVer,Exif版本。
- 9207:PropertyTagExifMeteringMod,曝光的测光方法。
- 920A:PropertyTagExifFocalLength,焦距。
更多Id及其相关说明可以参看:Property item descriptions - Win32 apps | Microsoft Learn
2、不同的Type对应不同的数据类型,但是还是需要根据具体的Id来处理数据。表17-1列出了Type值对应的主要的数据类型:
表17-1 Type说明
值 | 表示的类型 |
1 | 字节数组。 |
2 | ASCII字符串,末尾以字符0结束。使用此类型时,对应的Len属性应设置为包括字符0的字符串长度。例如,字符串“Hello”的长度为6。 |
3 | 无符号的短(16位)整型数组。 |
4 | 无符号的长(32位)整型数组。 |
5 | 无符号的长整型对数组。 |
6 | 可以包含任何数据类型的值的字节数组。 |
7 | 有符号的长(32位)整型数组。 |
10 | 有符号的长整型对数组。 |
从表17-1可以看出,类型5和10是长整型对数组,每个长整型对的长度为8字节,前面用来表示分数,前4个字节是分子,后4个字节是分母。可以使用BitConverter 类将基础数据类型与字节数组相互转换,具体代码请参看【例 17.3】。
分数值具体用带“/”的分数表示还是小数值表示,应该参考日常摄影术语。例如
829A(快门速度)获得的值为1/60,表示为1/60;
829D(光圈)获得的值为45/10,表示为4.5;
920A(焦距)获得的值为33/5,表示为6.6。
3、即使Type值相同,处理数据的方式也可能不同。
例如Id为9000和Id为9101这两个图像信息,Type都是7,但是处理方式不相同:
9000(PropertyTagExifVer),获得的字节数组为:48,50,50,48,对应ASCII字符为:0220,也就是图片数据Exif版本为0220。
9101(PropertyTagExifCompConfig),获得的字节数组为:1,2,3,0。对应的数据就是1230,像素数据的顺序. 大多数情况下RGB格式使用4,5,6,0,而YCbCr 格式使用1,2,3,0。
【例 17.3】【项目:code17-003】获取图片的Exif信息。
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPG文件|*.jpg";
ofd.FileName = "";
if (ofd.ShowDialog() != DialogResult.OK)
return;
string filename;
filename = ofd.FileName;
getPicInfo(filename);
}
//获得图片中的exif信息
private void getPicInfo(string picFullPath)
{
listBox1.Items.Clear();
Bitmap bmp =new Bitmap(picFullPath);
string strPro = "";
//不断枚举PropertyItems里面的内容
foreach(System.Drawing.Imaging.PropertyItem pro in bmp.PropertyItems)
{
string strTmp;
//根据类型进行大致处理,实际处理的时候应该根据具体的Id号进行处理
switch( pro.Type)
{
case 1:
strTmp = "length:" + pro.Len;
break;
case 2:
strTmp = System.Text.Encoding.ASCII.GetString(pro.Value).Replace("\0", "");
break;
case 3:
strTmp = BitConverter.ToInt16(pro.Value, 0).ToString();
break;
case 4:
strTmp = BitConverter.ToInt32(pro.Value, 0).ToString();
break;
case 5:
strTmp = getvalue(pro.Id, pro.Value).ToString();
break;
case 6:
strTmp = "type:" + pro.Type + " " + pro.Value.ToString();
break;
case 7:
strTmp = getascvalue(pro.Id, pro.Value);
break;
case 10:
strTmp = getvalue(pro.Id, pro.Value).ToString();
break;
default:
strTmp = "type:" + pro.Type + " " + pro.Value.ToString();
break;
}
//按照 Id-数据长度-数据类型-数据 显示
listBox1.Items.Add(pro.Id.ToString("x").ToUpper() + "--" + pro.Len + "--" + pro.Type + "--" + strTmp);
}
}
//处理整型对
private string getvalue(int id, byte[] bteValue)
{
int intFirst;
int intSecond;
if (bteValue.Length == 8)
{
//将字节数组前4位转为Int32
intFirst = BitConverter.ToInt32(bteValue, 0);
//将字节数组后4为转为Int32
intSecond = BitConverter.ToInt32(bteValue, 4);
//如果是快门速度,那么返回分数,否则返回小数
if (id == 33434)
return intFirst.ToString() + "/" + intSecond.ToString();
return (intFirst / intSecond).ToString();
}
else
//更多的数据不再处理
return "0";
}
private string getascvalue(int id, byte[] bteValue)
{
if (bteValue.Length > 8)
return ("length:" + bteValue.Length);
if (id == 36864)
return (Encoding.ASCII.GetString(bteValue));
string returnString="";
for (int i = 0;i< bteValue.Length; i++)
returnString += bteValue[i].ToString() + " ";
return returnString;
}
运行结果如下图所示:
图17-3 图片的Exif信息
【例 17.4】【项目:code17-004】获取图片的经纬度信息。
通常情况下,通过Gps信息的经纬度和海拔高度就可以对坐标位置定位。图片Exif信息中就包含相关的经纬度信息(根据具体拍照设备设置),对应的PropertyItem.Id是从0至26。比较关键的几个Id是:
- 1:纬度参照,指示纬度是北纬还是南纬。值为N表示北纬;值为S表示南纬。
- 2:纬度。
- 3:经度参照,指示经度是东经还是西经。值为E表示东经;值为W表示西经。
- 4:经度。
- 5:海拔参照。值为0,表示海平面上;值为1,表示海平面下。
- 6:海拔。
其中,纬度和经度的Type是5,长度为24的字节数组,其中前8个字节表示度,中间8个字节表示分,末尾8个字节表示秒。而每8个字节都是一个整数对。里面的前4字节表示分子,后4字节表示分母。
具体代码如下:
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "JPG文件|*.jpg";
ofd.FileName = "";
if (ofd.ShowDialog() != DialogResult.OK)
return;
string filename;
filename = ofd.FileName;
getPicGpsInfo(filename);
}
//获得图片的Gps信息
private void getPicGpsInfo(string picFullPath)
{
listBox1.Items.Clear();
Bitmap bmp =new Bitmap(picFullPath);
string strPro = "";
//纬度参照
string PropertyTagGpsLatitudeRef = "";
//纬度
string PropertyTagGpsLatitude = "";
//经度参照
string PropertyTagGpsLongitudeRef = "";
//经度
string PropertyTagGpsLongitude = "";
//海拔参照
string PropertyTagGpsAltitudeRef = "";
//海拔
string PropertyTagGpsAltitude = "";
foreach(System.Drawing.Imaging.PropertyItem pro in bmp.PropertyItems)
{
switch(pro.Id)
{
case 1:
PropertyTagGpsLatitudeRef = System.Text.Encoding.ASCII.GetString(pro.Value).Replace("\0", "");
break;
case 2:
PropertyTagGpsLatitude = getGpsLL(pro.Value);
break;
case 3:
PropertyTagGpsLongitudeRef = System.Text.Encoding.ASCII.GetString(pro.Value).Replace("\0", "");
break;
case 4:
PropertyTagGpsLongitude = getGpsLL(pro.Value);
break;
case 5:
PropertyTagGpsAltitudeRef = pro.Value[0].ToString();
break;
case 6:
PropertyTagGpsAltitude = getGpsAltitude(pro.Value);
break;
default:
//其它值不处理
break;
}
}
if (PropertyTagGpsLatitude != "")
{
switch (PropertyTagGpsLatitudeRef)
{
case "":
case "N":
listBox1.Items.Add("纬度:北纬 " + PropertyTagGpsLatitude);
break;
default:
listBox1.Items.Add("纬度:南纬 " + PropertyTagGpsLatitude);
break;
}
}
else
listBox1.Items.Add("纬度:没有纬度位置");
if (PropertyTagGpsLongitude != "")
{
switch (PropertyTagGpsLongitudeRef)
{
case "":
case "E":
listBox1.Items.Add("经度:东经 " + PropertyTagGpsLongitude);
break;
default:
listBox1.Items.Add("经度:西经 " + PropertyTagGpsLongitude);
break;
}
}
else
listBox1.Items.Add("经度:没有经度位置");
if (PropertyTagGpsAltitude != "")
{
switch (PropertyTagGpsAltitudeRef)
{
case "":
case "0":
listBox1.Items.Add("海拔:" + PropertyTagGpsAltitude);
break;
default:
listBox1.Items.Add("海拔:-" + PropertyTagGpsAltitude);
break;
}
}
else
listBox1.Items.Add("海拔:没有海拔高度");
}
//获取纬度和经度的度分秒
private string getGpsLL(byte[] bteValue)
{
if (bteValue.Length != 24)
return "";
string strTmp = "";
int intFirst;
int intSecond;
//前8个字节是度
intFirst = BitConverter.ToInt32(bteValue, 0);
intSecond = BitConverter.ToInt32(bteValue, 4);
strTmp += (intFirst / intSecond).ToString() + "°";
//中间8个字节是分
intFirst = BitConverter.ToInt32(bteValue, 8);
intSecond = BitConverter.ToInt32(bteValue, 12);
strTmp += (intFirst / intSecond).ToString() + "'";
//末尾8个字节是秒
intFirst = BitConverter.ToInt32(bteValue, 16);
intSecond = BitConverter.ToInt32(bteValue, 20);
strTmp += (intFirst / intSecond).ToString() + "\"";
return strTmp;
}
//获取海拔
private string getGpsAltitude(byte[] bteValue)
{
if (bteValue.Length != 8)
return "";
int intFirst;
int intSecond;
intFirst = BitConverter.ToInt32(bteValue, 0);
intSecond = BitConverter.ToInt32(bteValue, 4);
return (intFirst / intSecond).ToString() + "米";
}
运行结果如下图所示:
图17-4 图片的Gps信息
学习更多vb.net知识,请参看vb.net 教程 目录
学习更多C#知识,请参看C#教程 目录