17.1 图像操作

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。

17.1.1 Image类

Image类为源自 Bitmap 和 Metafile 的类提供功能的抽象基类。

Image的属性大多数是只读的:

  1. FrameDimensionsList:获取GUID的数组,这些GUID表示Image中帧的维数。
  2. Size:获取此图像的宽度和高度(单位:像素)。
  3. Width:获取Image的宽度(单位:像素)。
  4. Height:获取Image的高度(单位:像素)。
  5. HorizontalResolution:获取水平分辨率(单位:像素/英寸)。
  6. VerticalResolution:获取垂直分辨率(单位:像素/英寸)。
  7. PixelFormat:获取Image中每个像素的颜色数据的格式。
  8. PropertyIdList:获取存储于Image中的属性项的ID。
  9. PropertyItems:获取存储于Image中的所有属性项(元数据片)。
  10. RawFormat:获取Image的文件格式。

Image常用方法:

  1. Clone:创建此 Image 的一个精确副本。
  1. FromFile:从指定的文件创建Image。
  2. FromHbitmap:从GDI位图的句柄创建Bitmap。
  3. FromStream:从指定的数据流创建Image。
  4. FrameDimensionsList:获取GUID数组,这些GUID表示此Image中帧的维数。
  5. GetBounds:以指定的单位获取图像的界限。
  6. GetEncoderParameterList:返回有关指定的图像编码器所支持的参数的信息。
  7. GetFrameCount:返回指定维度的帧数。
  8. GetPropertyItem:获取指定的属性项。
  9. GetThumbnailImage:返回缩略图。
  10. IsAlphaPixelFormat:获取像素格式是否包含alpha信息。
  11. RemovePropertyItem:从Image移除指定的属性项。
  12. RotateFlip:旋转、翻转或者同时旋转和翻转Image。
  13. Save:保存到指定的文件或流。
  14. SaveAdd:在上一Save方法调用所指定的文件或流内添加一帧。
  15. SelectActiveFrame:选择由维度和索引指定的帧。
  16. 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枚举,包含以下成员:

  1. RotateNoneFlipNone:指定不进行旋转和翻转。
  2. RotateNoneFlipX:水平翻转。
  3. RotateNoneFlipY:垂直翻转。
  4. RotateNoneFlipXY:水平翻转和垂直翻转。
  5. Rotate90FlipNone: 90度顺时针旋转。
  6. Rotate90FlipX:水平翻转和90度顺时针旋转。
  7. Rotate90FlipY:垂直翻转和90度顺时针旋转。
  8. Rotate90FlipXY:水平翻转、垂直翻转和90度顺时针旋转。
  9. Rotate180FlipNone:180度顺时针旋转。
  10. Rotate180FlipX:水平翻转和180度顺时针旋转。
  11. Rotate180FlipY:垂直翻转和180度顺时针旋转。
  12. Rotate180FlipXY:水平翻转、垂直翻转和180度顺时针旋转。
  13. Rotate270FlipNone:270度顺时针旋转。
  14. Rotate270FlipX:水平翻转和270度顺时针旋转。
  15. Rotate270FlipY:垂直翻转和270度顺时针旋转。
  16. 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特有方法:

  1. FromHicon:从图标的Windows句柄创建 Bitmap。
  2. FromResource:从指定的Windows资源创建 Bitmap。
  3. GetPixel:获取Bitmap中指定坐标位置像素的颜色。
  4. SetPixel:设置Bitmap中指定坐标位置像素的颜色。
  5. LockBits:将Bitmap锁定到系统内存中。
  6. UnlockBits:从系统内存解锁Bitmap。
  7. MakeTransparent:用默认或指定的颜色使Bitmap透明。

创建一个Bitmap实例,常采用以下方法:

1、使用Bitmap的构造函数。

Bitmap提供了12种构造函数,常用的是:

  1. Bitmap(String):从指定的文件初始化Bitmap类的新实例。例如:

Bitmap sourceImg = new Bitmap("c:\\lessons\\3.jpg");

  1. Bitmap(Int32, Int32):用指定的宽度和高度初始化Bitmap类的新实例。例如:

Bitmap sourceImg = new Bitmap(this.Width ,this.Height);

2、使用方法创建:

  1. FromFile(String):使用Image的方法从指定的文件创建Bitmap。例如:

Bitmap sourceImg = (Bitmap)Image.FromFile("c:\\lessons\\3.jpg");

  1. 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个属性:

  1. Id:某个图像信息的ID。
  2. Value:某个图像信息的值。
  3. Len:Value属性的长度(以字节为单位)。
  4. Type:整数,它定义了Value 属性包含的数据类型。

使用PropertyItem需要注意以下事项:

1、不同的Id有着不同的含义,例如Id从0至26都是关于gps信息。还有些常见信息如下(Id使用的是十六进制表示):

  1. 010F:PropertyTagEquipMake,相机生产厂家。
  2. 0110:PropertyTagEquipModel,相机型号。
  1. 829A:PropertyTagExifExposureTim,快门速度。
  2. 829D:PropertyTagExifFNumber,光圈。
  3. 9000:PropertyTagExifVer,Exif版本。
  4. 9207:PropertyTagExifMeteringMod,曝光的测光方法。
  5. 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. 1:纬度参照,指示纬度是北纬还是南纬。值为N表示北纬;值为S表示南纬。
  2. 2:纬度。
  3. 3:经度参照,指示经度是东经还是西经。值为E表示东经;值为W表示西经。
  4. 4:经度。
  5. 5:海拔参照。值为0,表示海平面上;值为1,表示海平面下。
  6. 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#教程 目录

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.Net学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值