纯C++实现24位bmp格式图片的读取和修饰

本文介绍了如何使用C++读取和处理24位bmp格式的图片,包括灰度化、水平翻转、模糊和茶色滤镜效果。通过解析bmp文件结构,创建相应的类和结构体,实现文件读写和图像修饰功能。主要内容包括bmp文件头、信息头的读取,以及图像颜色信息的处理,特别讨论了内存对齐和文件空隙问题。
摘要由CSDN通过智能技术生成

问题:现有一张bmp图片,要求将它读取到程序中并进行灰度化、水平翻转、模糊、茶色滤镜四种效果的一种,并输出新图片,如下所示:

命令行输入:

 其中:

参数1:-b/g/s/r,先后表示blur(模糊),grey(灰度化),sepia(褐色),row reverse(水平翻转)

参数2:源文件名

参数3:新文件名

    当我第一次接触到这个问题时,是无从下手的。但在查阅了不少资料之后,整整一天,我成功地只用C++实现了打开、修饰、保存bmp文件的功能!

目录

1.bmp文件的基本信息

(1).bmp文件的种类

(2).bmp文件结构(重点)

1>文件头

2>信息头

3>调色板(不作讨论)

4>图像颜色信息

2.实现思路 

3.定义相关类、结构体

文件头BmpFileHeader

内存对齐和#pragma pack(n)

信息头BmpFileInfoHeader

颜色结构体RGBTriple

图片类bmp

4.读文件

(1).基础知识

(2).读文件的准备工作

(3).读文件头和数据头

(4).读取图像颜色信息

5.写文件

6.修饰图片

(1).灰度化

(2).棕色滤镜效果

(3).水平翻转

(4).模糊

7.main函数

命令行传参

具体实现


1.bmp文件的基本信息

(1).bmp文件的种类

 打开Windows自带的画图软件,发现bmp的存储格式有好几种。

  • 单色位图:只有黑白两种颜色,每个像素占1位(1/8字节)
  • 16色位图:每个像素占4位(1/2字节)
  • 256色位图:每个像素占8位(1字节)
  • 24位位图(真彩色):每个像素占24位(3字节),每个字节存储R/G/B三种中的一种颜色数值(0~255)

每个像素占的位数被称为位深度(biBitCount,在后面会用到),可以在图片的属性->详细信息中查看。

 

(2).bmp文件结构(重点)

bmp文件数据由4部分组成:

  1. 文件头
  2. 文件信息头
  3. 调色板(24位位图无)
  4. 图像颜色信息

在此只讨论24位位图即真彩色的问题,至于其他的bmp文件种类不做讨论。

先放出图片:

1>文件头

bfType 如果是bmp文件,值为“BM”,对应十进制为19778
bfSize 文件总大小
bfReserved1 保留字1,一般为0
bfReserved2 保留字2,一般为0
bfOffBits 文件起始位置距真正的图像信息的距离

2>信息头

biSize 信息头大小,24位图中为40
biWidth 图像宽度(px),即水平方向的像素个数
biHeight 图像高度(px),即垂直方向的像素个数
biPlanes 一般为1
biBitCount 位深度,重要,决定了bmp的类型
biCompression 是否压缩,一般为0
biSizeImages 图像颜色信息占用的实际字节数,包括了对齐所需的0
biXPelsPerMeter 水平分辨率
biYPelsPerMeter 垂直分辨率
biClrUsed 一般为0
biClrImportant 一般为0

注意:我们可能会发现 biWidth*3*biHeight与biSizeImages并不一样,这是为什么呢?接下来会解释。

3>调色板(不作讨论)

4>图像颜色信息

  1. 像素的存储顺序是从下到上,从左到右,在文件中以类似一维数组的方法线性存储。
  2. 每个像素的颜色信息每3个字节一组,按BGR的顺序存放。
  3. 其中每个字节只存一个颜色值。颜色值范围是0~255,用无符号char型存储。

三个图就能说明问题:

 

 但是这些数据真的如此紧密地排列吗?

对于宽为4的倍数的图片(如:1024px),确实如此。每一行的像素数据存完后,紧挨着存储下一行像素的数据,行与行的数据之间没有空隙。

但对于宽度不是4的倍数的图片(如:474px),每一行的像素数据存储完后,会自动空出几个字节,直到这一行的字节数为4的倍数为止。

 直接呈上图片:

 biWidth=4时:很好,不用补任何0,因为4*3=12已经是4的倍数

  biWidth=5时:糟糕,5*3=15不是4的倍数,要补一个0才能是4的倍数16

所以,在读取宽度不是4的倍数的图片时,一行的数据读完后,要跳过几个字节才能读到下一行的数据。跳过字节的个数,我取名为offset。它的计算方法如下:

offset = (fileInFoHeader.biWidth * 3) % 4;
if (offset != 0) {
    offset = 4 - offset;
}

现在我可以解释2>中末尾提到的问题了。

例子:现在有一张宽度为474px,高度为842px的图片。

不考虑offset时:

    474*3*842=1197324(Byte)

考虑时:

    (474*3)%4=2

    offset=4-2=2(Byte)

    每行字节数:474*3+2=1424(Byte)

    图像数据总字节数:1424*842=1199008(Byte)

谁对谁错?看看图就知道了。

它们的差值:1199008-1197324= 1684(Byte),而1684=842*2。因为每一行末尾有2字节的空隙,那么,842行的空隙积累起来,正好就是1684字节。

debug的结果说明,图像数据占用的实际字节数是考虑了偏移的,这些数据在存的时候就已经有空隙,因此我们写文件的时候也要刻意的写入空隙,不然系统无法读取我们生成的新图片。这一点在后面很关键!

2.实现思路 

首先要把bmp文件读进来。由以上的分析,应该把bmp的文件头、信息头、图像数据分开读取。

然后要生成新bmp文件。应该要依次写入文件头、信息头、图像数据。

最后实现图像处理功能。这些用于图像处理的函数封装在一个单独的头文件中,使用时传入函数指针即可。

3.定义相关类、结构体

定义文件头、信息头结构体(因为它们不需要任何函数),里面存放与文件相关的属性。各个属性的大小参考一开始时的bmp文件结构图,2字节一般定义成unsigned short,4字节一般定义成unsigned int.

定义bmp类(因为它需要定义函数),里面最重要的是一个存放”颜色“结构体对象的数组,用于接收读出的图像颜色数据。还有一个int型的offset,一个文件头结构体对象,一个数据头结构体对象。定义读文件和写文件两个函数。

很自然地,需要一个”颜色“结构体对象,它有三个属性B/G/R。

注意:结构体中,所有属性的定义顺序必须和文件存储的信息顺序一致,否则在读文件时得到的数据会混乱!也就是:必须根据bmp文件结构来!

文件头BmpFileHeader

#pragma pack(2)//注意这里
struct BmpFileHeader {
    unsigned short bfType;
    unsigned int bfSize;
    unsigned short bfReserved1;
    unsigned short bfReserved2;
    unsigned int bfOffBits;
};

 尤其要注意#pragma pack(2),没有这一行,这个结构体占用的空间大小就不是所有属性大小之和,换句话说,它清除了属性与属性之间内存的”空洞“。明白这个,对读文件操作极其重要!

内存对齐和#

  • 28
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值