2026/08/11 BMP图像
一、BMP图像原理与应用
实际项目开发过程中只在LCD液晶屏显示单一的颜色并不能满足设计需求,为了实现人机交互以及提供用户体验,UI工程师都需要设计符合产品主题的图像素材,然后提交给软件工程师进行人机界面的交互设计。由于Linux系统“一切皆文件”的特性,所以图片在Linux系统中也是以文件的形式存储。
图像格式
另外,图像中的数据必须按照某个已知的、公认的数据存储顺序和结构进行存储,这样才可以使不同的应用程序对该图像文件顺利的进行访问,从而实现数据共享。图像数据在文件中的存储顺序和结构也被称为图像文件格式。

目前广为流传的图像文件格式有许多,常见格式包括BMP、GIF、JPEG、TIFF、MPEG等。在各种图像文件格式中,一部分是由某个软硬件厂商提出并被广泛接受和采用的格式,例如 BMP格式就是由微软公司(MicroSoft)公司发明的数据封装格式。另一部分是由各种国际标准组织提出的格式,例如JPEG格式就是由国际静止图像压缩标准组织提出的格式。
基本概念
BMP格式最大的特征是没有任何的压缩,因此文件尺寸比较大,不适合网络传输,但是优点是这种图像格式中的数据读取出来不需要任何解码器解码就可以直接使用。
BMP格式被称为位图(Bitmap)或设备无关位图(DIB,指的是Device-Independent Bitmap),是Windows操作系统所推荐和支持的图像文件格式,是一种将内存或显示器的图像数据不经过压缩而直接按位存盘的文件格式,所以这种格式的图片也被称为BMP文件。

内部结构
BMP文件的数据按照从文件头开始的先后顺序分为四个部分:分别是位图文件头、位图信息头、调色板(24bit位图是没有的)、位图数据(RGB)。

可以看到,BMP文件的位图文件头和位图信息头一共为54字节大小,其中位图文件头为14字节,位图信息头为40字节。
(1)位图文件头
位图文件头(Bitmap-File Header)包含了图像类型、图像大小、两个保留字以及位图数据存放地址。

(2)位图信息头
位图信息头(Bitmap-Information Header)包含了位图信息头的大小、图像的宽和高、图像的色深、压缩说明、图像数据的大小和其他一些参数。

例:设计程序,利用系统IO读取磁盘上指定BMP图片的宽和高,以及BMP图片的大小,并输出到终端,要求图片名称通过命令行传递。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
int main(int argc, char const *argv[])
{
int bmp_size = 0; //用于存储BMP图片的大小
int bmp_height = 0; //记录BMP图片的高
int bmp_width = 0; //记录BMP图片的宽
//1.BMP图片的路径是通过命令行传递,所以需要检测参数有效性
if (2 != argc)
{
printf("argument is invaild\n");
return -1;
}
//2.利用系统调用open()打开待读取数据的BMP图片
int bmp_fd = open(argv[1],O_RDWR);
if ( -1 == bmp_fd)
{
printf("open %s is error\n",argv[1]);
return -1;
}
//3.从被打开的BMP图片中读取图像信息
lseek(bmp_fd,2,SEEK_SET);
read(bmp_fd,&bmp_size,4); //读取BMP图片大小
lseek(bmp_fd,18,SEEK_SET);
read(bmp_fd,&bmp_width,4); //读取BMP图片的宽
lseek(bmp_fd,22,SEEK_SET);
read(bmp_fd,&bmp_height,4); //读取BMP图片的高
//4.输出BMP信息
printf("bmp name is %s ,width = %d,height = %d,size = %d\n",argv[1],s2.width,s2.height,s1.size);
return 0;
}
(3)调色板
bmp位图按照像素深度分类可以分为:1bit位图(2色)、4bit位图(16色)、8bit位图(256色)、16bit位图(65536色-高彩色)、24bit位图(1670万色-真彩色)、32bit位图(1670万色-增强型真彩色)。
调色板是为了让一些颜色深度比较小(1bit、4bit、8bit)的位图可以表示颜色而设置的。调色板存储颜色,后面的位图数据存储颜色索引,这样调色板+位图数据就可以表示颜色了。
需要注意的是:16bit、24bit、32bit的位图一般没有调色板,因为从16bit开始就直接使用位图数据表示颜色了。

(4)位图数据
位图数据分两种情况,如果带调色板,则位图数据存放的是调色板的颜色索引,如果不带调色板,则位图数据存放实际的argb值。由于24位bmp图片不带调色板,所以文件开头的54字节为图片信息,从第55个字节开始就为bmp图片的颜色数据。
对齐说明
计算机的CPU为了提高工作效率,所以读取数据的时候会进行字节对齐,一般图像刷新都是行为单位,所以CPU每次都是读取一行的大小,所以32bit系统下如果一行字节大小不是4的倍数,就会进行字节补齐,目的是提高效率,是典型的用空间来换时间的案例!
如果分辨率是10*10,每行10个像素点,每个像素点3字节,所以一行是30个字节,不够4的倍数,所以需要在每行的末尾补2个字节。
由于每行必须是4的倍数,所以需要补齐的字节数有4种情况:0、1、2、3,所以需要设计算法来计算每行需要自动补齐的字节数。 公式: byte = ( 4 - (width*3 % 4) ) % 4
存储顺序
如果打算了解BMP图片内部数据的存储顺序(大端 or 小端),应该去查看BMP文件内部数据结构,需要使用专业工具(HEX十六进制查看器)。

通过分析,可以知道BMP图片内部数据是采用小端方式进行存储。
像素结构

例:设计程序,实现在LCD上显示一张分辨率为800*480大小的24bit的bmp图片,要求图像不失真可以在开发板的LCD上显示。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#pragma pack(1) //取消字节对齐
// 定义BMP文件头部结构
typedef struct{
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
}BITMAPFILEHEADER;
// 定义BMP信息头部结构
typedef struct{
unsigned int biSize;
int biWidth; //宽
int biHeight; //高
unsigned short biPlanes;
unsigned short biBitCount; //色深
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
}BITMAPINFOHEADER;
#pragma pack() //恢复字节对齐
int main(int argc, char const *argv[])
{
//1.打开待显示的BMP图像 fopen
FILE * bmp_fp = fopen("demo.bmp","rb");
if (NULL == bmp_fp)
{
return -1;
}
//2.读取BMP文件的图像信息,获取BMP的宽和高
BITMAPINFOHEADER headerinfo;
fseek(bmp_fp,14,SEEK_SET);
fread(&headerinfo,1,40,bmp_fp); //读取40字节
printf("bmp width = %d,height = %d\n",headerinfo.biWidth,headerinfo.biHeight);
//3.读取BMP图*片的颜色分量 800*480*3
char bmp_buf[800*480*3] = {0};
fread(bmp_buf,1,800*480*3,bmp_fp);
//4.关闭BMP
fclose(bmp_fp);
//5.打开LCD open
int lcd_fd = open("/dev/fb0",O_RDWR);
//6.对LCD进行内存映射 mmap
int * lcd_mp = (int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
//7.循环的把BMP图像的颜色分量依次写入到LCD的像素点中
int i = 0;
int data = 0;
for (int y = 480-1; y >= 0; y--)
{
for (int x = 0; x < 800 ; ++x)
{
//把BMP图片的一个像素点的颜色分量转换为LCD屏幕的一个像素点的颜色分量格式 ARGB <--- BGR
data |= bmp_buf[i]; //B
data |= bmp_buf[i+1]<<8; //G
data |= bmp_buf[i+2]<<16; //R
lcd_mp[800*y + x] = data; //BGR BGR BGR ....
i+=3;
data = 0;
}
}
//8.关闭LCD
close(lcd_fd);
munmap(lcd_mp,800*480*4);
return 0;
}
验证结果:

例:设计程序,实现在LCD上任意位置显示一张任意大小的色深为24bit的bmp图片,要求图像不失真可以在开发板的LCD上显示。
```c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#pragma pack(1) //取消字节对齐
// 定义BMP文件头部结构
typedef struct{
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
}BITMAPFILEHEADER;
// 定义BMP信息头部结构
typedef struct{
unsigned int biSize;
int biWidth; //宽
int biHeight; //高
unsigned short biPlanes;
unsigned short biBitCount; //色深
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
}BITMAPINFOHEADER;
#pragma pack() //恢复字节对齐
void show_bmp(const char *bmp_name, int start_x, int start_y)
{
//1.打开待显示的BMP图像 fopen
FILE * bmp_fp = fopen(bmp_name,"rb");
if (NULL == bmp_fp)
{
printf("open file error");
return ;
}
//2.读取BMP文件的图像信息,获取BMP的宽和高
BITMAPINFOHEADER headerinfo;
fseek(bmp_fp,14,SEEK_SET);
fread(&headerinfo,1,40,bmp_fp); //读取40字节
printf("bmp width = %d,height = %d\n",headerinfo.biWidth,headerinfo.biHeight);
//3.判断图片大小是否合规
if(headerinfo.biWidth+start_x > 800 || headerinfo.biHeight+start_y > 480)
{
printf("File size overflow");
return ;
}
//4.读取BMP图*片的颜色分量
char bmp_buf[800*480*3] = {0};
fread(bmp_buf,1,headerinfo.biWidth*headerinfo.biHeight*3,bmp_fp);
//5.关闭BMP
fclose(bmp_fp);
//6.打开LCD open
int lcd_fd = open("/dev/fb0",O_RDWR);
//7.对LCD进行内存映射 mmap
int * lcd_mp = (int *)mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
//8.循环的把BMP图像的颜色分量依次写入到LCD的像素点中
int i = 0;
int data = 0;
for (int y = start_y+headerinfo.biHeight-1; y >= start_y; y--)
{
for (int x = start_x; x < start_x + headerinfo.biWidth; x++)
{
//把BMP图片的一个像素点的颜色分量转换为LCD屏幕的一个像素点的颜色分量格式 ARGB <--- BGR
data |= bmp_buf[i]; //B
data |= bmp_buf[i+1]<<8; //G
data |= bmp_buf[i+2]<<16; //R
lcd_mp[800*y + x] = data; //BGR BGR BGR ....
i+=3;
data = 0;
}
}
//9.关闭LCD
close(lcd_fd);
munmap(lcd_mp,800*480*4);
}
int main(int argc, char const *argv[])
{
char bmp_name[128];
int x, y;
printf("请输入BMP图片文件名:");
scanf("%127s", bmp_name);
printf("请输入图片显示的起始X坐标:");
scanf("%d", &x);
printf("请输入图片显示的起始Y坐标:");
scanf("%d", &y);
printf("\n开始显示:\n");
printf("文件:%s\n", bmp_name);
printf("位置:( %d, %d )\n", x, y);
show_bmp(bmp_name, x, y);
return 0;
}
结果验证:


例:设计算法,要求把一张任意尺寸的BMP图片等比例且不失真的缩小为原来的1/2,并生成一张新的BMP图片,要求BMP图片的路径都需要通过命令行进行传递
int bmp_half_scale(const char *src_file,const char *dst_file)
{
FILE *src_fp;
FILE *dst_fp;
BITMAPFILEHEADER file_header;
BITMAPINFOHEADER info_header;
/*
* 1.打开原BMP
*/
src_fp=fopen(src_file,"rb");
if(src_fp==NULL)
{
printf("open source bmp error\n");
return -1;
}
/*
* 2.读取BMP文件头
*/
fread(&file_header,sizeof(file_header),1,src_fp);
fread(&info_header,sizeof(info_header),1,src_fp);
/*
* 3.判断格式
*/
if(info_header.biBitCount != 24)
{
printf("only support 24bit bmp\n");
fclose(src_fp);
return -1;
}
int width = info_header.biWidth;
int height = info_header.biHeight;
/*
* BMP行字节对齐
*/
int src_row_size =(width*3+3)&~3;
/*
* 4.读取原图像素
*/
unsigned char *src_buf;
src_buf =malloc(src_row_size*height);
if(src_buf==NULL)
{
fclose(src_fp);
return -1;
}
/*
* 跳到像素数据
*/
fseek(src_fp,file_header.bfOffBits,SEEK_SET);
fread(src_buf,src_row_size,height,src_fp);
fclose(src_fp);
/*
* 5.计算新图片尺寸
*/
int new_width =width/2;
int new_height =height/2;
int dst_row_size =(new_width*3+3)&~3;
unsigned char *dst_buf;
dst_buf =calloc(dst_row_size,new_height);
if(dst_buf==NULL)
{
free(src_buf);
return -1;
}
/*
* 6.最近邻缩放
*/
for(int y=0;y<new_height;y++)
{
for(int x=0;x<new_width;x++)
{
int src_x=x*2;
int src_y=y*2;
unsigned char *src_pixel;
src_pixel =
src_buf+
src_y*src_row_size+
src_x*3;
unsigned char *dst_pixel;
dst_pixel =
dst_buf+
y*dst_row_size+
x*3;
dst_pixel[0]=src_pixel[0];
dst_pixel[1]=src_pixel[1];
dst_pixel[2]=src_pixel[2];
}
}
/*
* 7.生成新的BMP头
*/
info_header.biWidth =new_width;
info_header.biHeight =new_height;
info_header.biSizeImage =dst_row_size*new_height;
file_header.bfSize =file_header.bfOffBits+info_header.biSizeImage;
/*
* 8.创建新BMP
*/
dst_fp=fopen(dst_file,"wb");
if(dst_fp==NULL)
{
printf("create bmp error\n");
free(src_buf);
free(dst_buf);
return -1;
}
/*
* 写文件头
*/
fwrite(&file_header,sizeof(file_header),1,dst_fp);
/*
* 写信息头
*/
fwrite(&info_header,
sizeof(info_header),
1,
dst_fp);
/*
* 写像素数据
*/
fwrite(dst_buf,
dst_row_size,
new_height,
dst_fp);
fclose(dst_fp);
free(src_buf);
free(dst_buf);
printf("bmp scale success!\n");
printf("%dx%d --> %dx%d\n",
width,
height,
new_width,
new_height);
return 0;
}
结果验证:

作者:bbzhu04@gmail.com ,多有不妥之处,望批评指正
本文原创,欢迎转载;转载请在文章显著位置标明原文链接: href="https://www.cnblogs.com/zhubb/p/22407592"
保留所有权限,未经许可禁止篡改、商用发布。

浙公网安备 33010602011771号