在 Linux 下读取位图遇到的问题, 很好地体现了 linux 与 Windows 操作系统的不同. 按理说位图格式与操作系统无关, 读取也应该无关, 实际上在位图读到内存中时已经不同. 下面主要介绍自己在 Linux 下操作位图遇到的问题.
(一), 位图结构
位图一开始是两个结构体, 包括位图的详细信息, 是读取后面数据的关键. 所以读取位图首先要正确读取这两个结构体: BITMAPFILEHEADER 和 BITMAPINFOHEADER. 其具体定义为:
- typedef struct tagBITMAPFILEHEADER
- { // bmfh
- WORD bfType;
- DWORD bfSize;
- WORD bfReserved1;
- WORD bfReserved2;
- DWORD bfOffBits;
- }__attribute__ ((packed))BITMAPFILEHEADER;
- typedef struct tagBITMAPINFOHEADER
- { // bmih
- DWORD biSize;
- LONG biWidth;
- LONG biHeight;
- WORD biPlanes;
- WORD biBitCount;
16 DWORD biCompression;
17 DWORD biSizeImage;
- LONG biXPelsPerMeter;
- LONG biYPelsPerMeter;
20 DWORD biClrUsed;
21 DWORD biClrImportant;
22 }__attribute__ ((packed))BITMAPINFOHEADER;
上面两个结构是 Windows 下可以正常使用的. 但是 Linux 下没有 WORD,DWORD 之类的变量类型, 所以我们需要将这些变量映射到 Linux 下的常用变量类型:
- typedef unsigned short WORD;
- typedef unsigned int DWORD;
- typedef int LONG;//use int not long here!!!
- typedef unsigned char BYTE;
上述映射要特别注意每个类型的字节数. 不同的操作系统变量的长度不同, 我们在定义时首先需要用 sizeof 获得本机器的变量类型长度, 然后再根据位图每个属性长度去选择合适的变量类型. 在此第三个变量 LONG 在 windows 下是四个字节, 但是在 Linux 下是八个字节, 所以我们需要用 int 来代替 LONG.
(二), 对齐
在位图结构的定义中, 我们在结构体名称前面添加了语句__attribute__ ((packed)).__attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐, 按照实际占用字节数进行对齐, 是 GCC 特有的语法. 在 windows 下, 读取操作不会优化, 按照结构体实际的大小去读取, 但是在 Linux 下, 为了加快访存速度, 会启用访存的对齐操作. 这时读到内存中的结构体大小就大于最初的定义, 此时如果按照之前的大小去访问位图属性, 将读到错误的数值. 为了使访问方便, 我们需要禁止对齐优化.
(三), 位图数据
对于 24 位真彩色位图, 位图不包括调色板, 位图数据就是 RGB 颜色的值. 所以很多人认为数据的大小就是 3*height*width, 读取数据的时候直接利用这个大小, 但这是错误的. 24 位真彩色位图每一行还需要满足一个条件: 数据长度能被 4 整除, 否则需要用 0 补齐到能被 4 整除. 所以读取的过程需要一行一行完成, 而且在每一行的末尾, 我们都需要跳过一定数量的 0, 这个计算公式如下:
1 skip=(4-(3*width)%4)%4;
C 语言下, 读取过程如下:
- for(int i=0;i<height;i++)
- {
- fread(p,sizeof(unsigned char)*width*3,1,fp);
- p+=sizeof(unsigned char)*width)*3;
- fseek(fp,skip*sizeof(unsigned char),SEEK_CUR);
- }
(四),RGB 顺序
如前所述, 24 位真彩色位图不包括调色板, 位图数据就是 RGB 颜色的值, 每个颜色占据一个字节. 此时很多人认为颜色的顺序是 R,G,B, 但这也是错误的, 实际的顺序应该是 B,G,R. 这一点也需要特别注意.
来源: http://www.bubuko.com/infodetail-2570621.html