zoukankan      html  css  js  c++  java
  • 你所能用到的BMP格式介绍(二)

    一、可能你忽视的基础

            在正式开始之前,我不得不从最基本的地方开始,因为这些地方大多数人会忽视的一干二净,如果不在开始进行说明,那么在后面一定会有很多困惑的地方。

            最开始我们回到数字在计算机里的表示,回到最开始的问题上-2&-255是多少,那么我们首先得回顾一下-2在计算机里面的表示是什么样子的。

            最开始,希望你还记得什么叫无符号整数和有符号整数,实际上,你得庆幸在一般的图像中没有浮点数,不然你需要复习或者重新学习的基础比这个还多。

            先说有符号整数,从计算机组成原理中,有符号整数是以2的补码表示的,有很多办法计算这个2的补码,但是我最推荐的可能不是你最常见的那个方法,我推荐的是权值法,因为我觉得这个方法最符合逻辑也最符合2的补码的数学表达式。

            什么是权值法呢?我们从最简单的开始,比如说二进制101,我们之所以能转换成十进制5是因为我们对每一位都赋予了一个权值,从最低位开始权值是2的0次 方,权值依次向最高位提高一个幂值,所以我们能够计算为2^2*1+2^1*0+2^0*1,得出的值是5,而每一个位都有一个权值。对于2的补码也可以 按照这样的思路,但是第一位的权值略有不同,如果同样是101,如果表示的有符号的整数,我们赋予最高位的权值是2的-2次方,于是这个二进制补码转换成 十进制就是2^-2*1+2^1*0+2^0*1,这样就是-3,你可以用传说中反码加一的方式验证。

           为什么我说权值法最符合逻辑呢?我来举个例子,就说3这个数,无论是作为有符号整数还是无符号整数,其表示方法都是11,我同样可以表示成为 0000000011,前面的1只要你喜欢,无论写多少都不会改变3这个值。那么如果是-3呢?我们前面已经验证了-3的二进制补码表示为101,现在我 们试试看将符号位扩展,也就是前面添加1,得到11101(为了我后面的演算方便,就不添加很多1 了),那么这个值是多少呢?按照权值法,最高位是2的-4次方(这个-4就是最高位的位数),后面权值不变,我们来计算这个值2^- 4*1+2^3*1+2^2*1+2^1*0+2^0*1,计算一下吧,还是等于-3,这就是符号扩展的意义。

            有时候,看似简单的东西往往蕴含了很多的学问。

            下面,就要联系c++里面的基本数据类型来说明上面这个问题了,在c++中表示无符号整数用unsigned关键字,根据图像的像素的取值范围是[0,255],那么最适合表示这个值的c++数据类型是什么?

             在回答这个问题之前,再来看一下更基本的一个问题,一个int值在我们的计算机中(32位)是由几个字节表示的?你可能很快的可以回答出是4个字节,那 int的表示范围-2147483648 ~ +2147483647,这个值对于图像的像素的表示范围太大了,[0,255]这个值需要8位就能表示出来,而在32位系统中char是唯一的1个字节 的数据类型,但是还要注意的是char的范围是-128~+127,也就是有符号的,所以我们表示一个像素值应该用的是unsigned char。

            也许你觉得太罗嗦了,那么这是指多媒体编程的冰山一角,下面我开始以实际的例子演示,每一步你都有可能出错。

    二、我们从显示图像数据开始

            图像文件再怎么特殊,实际上它还是一个文件,所以要读取一个图像,自然要用文件流(如果忘了或者不知道的,那我这里只能介绍最基本的,只能靠你自己了)。     

            我们用文件流读入一个图片。

           

            我很想解释为什么后面要用ios::binary,但是篇幅有限,就先这样认为它是必要的好了。

            第一个演示的目的是为了能够制作一个类似UE效果的功能,程序运行结果如下图:

         

           看起来很无趣的黑色,实现这个功能的核心代码如下:

          

    1.       while(getline(fin,str))  
    2.   {  
    3. int length=str.length();  
    4. for(int i=0;i<length;i++, count++)  
    5. {       if(count>0&&count%16==0)  
    6.         cout<<endl;  
    7.     if((int)((unsigned char)str[i])<0x10)  
    8.     {  
    9.         cout<<"0";  
    10.     }  
    11.     cout<<hex<<(int)((unsigned char)str[i])<<" ";  
    12. }  
    13.   
    14. int num=10;  
    15. total_str+=str+(unsigned char)num;  
    16. }  
    17.    cout<<endl;  


             

           代码虽然很少看上去比程序还混乱,我会一行一行解释的,首先第一行一行一行的读取文件中的数值,有没有思考过,文件流读bmp中怎么样算是一 行?这问题下面再进行说明,但是可以明确告诉你的是,bmp中绝对不是按一个像素行为一行的。

           接下来是取得读入字符串的长度,再接下来进入循环,循环的一开始的一个判断是为了做每行显示16个字符的,可以不用管它,然后下面的这些看似简单的包含了这个程序的核心部分。

           让我们回到字节0-1:42 4d这上面来,按照程序我们读入这个第一个字节,str[0]储存的是’B’这个字符,也是42这个值(至于为什么是42,参见ASCII码表),按照我 们第一部分说的,对于一字节的数我们应该用unsigned char来表示,所以我们进行了将str[i]的值转换成unsigned char,至于再转换成int是为了能够显示出十六进制的数字。

            一个让人疑惑的地方出现了,循环内为什么要有最后两句,这要回到getline这个函数的原理上面了,前面说过getline是读取文件的一行,那么怎样判断文件的一行呢?我们来看一下getline的定义:

          After the function extracts an element that compares equal to delim,in which case the element is neither put back nor appended to the controlledsequence.

         msdn上写的是如果读到一个终止符,那么这个函数结束并且这个终止符不会加到这个字符串中,哪些字符是终止符呢? 一般来说换行(0x0A)和回车(0x0D)都会被选为终止符,括号是他们的ASCII码。

         如此便得到了一个图像文件的全部数据,在我们的程序中是存储在total_str之中的。这里面包括了文件信息头,位图信息头,调色板(当然如果有的 话),位图数据区,下面需要进行的就是依次取出每个信息,然后保存起来,按照前面的字节顺序,注意小端法或者大段法。

         这里我想提到的一个问题就是,为什么在第一部分说了一下看似没有关系进制转换问题呢?如果你在存储各个位图部分的数据的时候,如果发现输出的数据不对,请你返回去仔细阅读一下第一部分以及回忆下基本数据的长度。

    三、把数据存储组织起来

            下面说明怎么将上面取出的数据字符串按照bmp的四部分存储起来,存储的目的一个是为了标示,还有一个作用就是在读取的时候方面取出来。       

            首先,bmp最先的一个部分是文件信息头,我们定义一个结构如下:

           

           在开始下面介绍的内容之前,先要说明一下这些UINT16以及DWORD的数据类型:

          

            在32位计算机中short由两个字节表示的,int是四字节表示的,所以UINT16,WORD表示的是两个字节,DWORD,LONG都表示四字节数。

    结构中各个字段的意思如注释所示,和前面说明的文件信息头的字段是一一对应的,下面来展示怎么样将读出来的字符串(total_str)赋值到某一个字段上。

           根据我们读取的字符串,total_str[0]和total_str[1]分别是字符’b’和’m’,这是两个字节,那么我们需要将BITMAPFILEHEADER里面的bfType赋值为这两个值。采用的赋值方法如下所示:

          

             <<是不常见的左移操作,这样的话方便的就能将两个一字节的数扩展为双字节的数,这样bh.bfType里面存的值就是0x424d,你可以输出来进行验证。    

             这个看起来不起眼的操作似乎很容易扩展成将四个四字节的数扩展为一个四字节的数,但是当你操作的时候就会发现并不是这样:

               

            如果你是这样操作的话,那么你可以做一下输出,你会发现结果出乎你意料的错误,为什么?在第一部分我提过的符号扩展,后面所有的都没有进行过类型转 换,total_str[i]进行移位并且或操作默认的是带符号操作的,举个例子,如果total_str 5-2依次是00 00 00 f3,那么你这样做之后bh.bfSize就会变成ff ff ff f3,虽然bh.bfSize是无符号数,但是右边的是有符号数,所以你得到的一定是错误的结果。

            那正确的做法应该是怎么样的?一种做法如下:

           

            至于为什么这样是对的,我在这里不进行说明了。

            按照上面的方法,依次对文件信息头内的字段赋值,一样的对其他三个部分进行赋值,

            位图信息头结构:

           

             数据区结构:

            

             调色板结构:

           

          

            也许,你会问我,数据区和调色板为什么不用unsigned char, 这里完全可以用unsigned char 我用UINT16 的目的是因为我懒得再重新定义一个数据类型了。

            赋值的时候要注意的我在上面说过了,其他的要注意的我想说的就是不要忘记有数据对齐,如果你忘了,请你回去再看一下前面的说明,当你能够把bmp的每一 部分保存下来之后,那么你就可以对bmp做各种操作了,从另一种角度来说,你可以自己构造自己的bmp位图。

    四、如果你会MFC

          如果你熟悉windows编程,那么你一定知道上面的结构在MFC以及windows编程里都是已经有了的结构,那你需要做的只是将图片加载到内存,在控件上显示。

         如果你想学这一部分的MFC构成,那么搜索的关键词是”MFC GDI”,在进行这个部分的MFC编程时,但是MFC的位图编程这一块所提供的类CBitmap并不好用,包括CPalette,BITMAP结构等等都 不是很好用,所以在实际开发中,有很多人都是选择自己包装一下再进行使用。

         这个部分如果你都可以会MFC的话,那么你就不是初级入门选手了,主要的学习方法应该是查阅MSDN和资料,如果你不会MFC的话,这一点篇幅也是说明不了任何问题的。

        

           


    欢迎关注我的微博(纯属交朋友用的)http://weibo.com/zxyifr

  • 相关阅读:
    玉蓉方面膜加盟费多少 玉蓉方绿豆面膜怎么做代理 怎么加盟玉蓉方
    音频处理软件:GoldWave,太强大了,批量处理音频
    杂记 SY
    ThinkPHP函数详解--D函数:实例化数据模型类
    杂记
    如何选择jQuery版本 1.x? 2.x? 3.x?
    传值涉及到中文字符串时,字符编码的问题
    对数字进行分组处理:每10个为一组
    mac下安装protocol buffer并用python解析
    淘宝返利攻略
  • 原文地址:https://www.cnblogs.com/ZXYloveFR/p/2626054.html
Copyright © 2011-2022 走看看