BMP的“数据4字节对齐”以及像素定位算法的个人探究

BMP的“数据4字节对齐”以及像素定位算法的个人探究

                                                                                  


                     

首先对于一个BMP图片,其扫描方式为

从下至上,从左至右,直到顶端

但是真正在写代码的过程中却因为这个对齐的问题,导致了像素数组的空间不足。故在此研究BMP数据并找寻解决的方案。

对于BMP格式的图片来说,一定是4字节对齐,这个所谓的4字节对齐不仅在于图片显示时的以4对齐,更在于图片数据的4字节对齐。以下内容将分为两部分:第一部分将探究BMP具体数据结构层面的“4字节对齐”,第二部分则基于Windows系统的BMP图片显示方式和像素定位/像素区域选取时易出现的——行列像素不对齐的问题进行算法上的研究。


Part1

这里以一个100X100(宽X高)24位真彩色(RGB三通道)的BMP为例子,基于大量的实验发现:

如果是100X100,则取出的像素数为10000,字节数为30000 bytes

但是图片改变为101X100之后,如果仍以原方案取数据,则理应读取10100个数据。可将这些数据放入另一个自建的BMP文件中会发现根本无法显示。

检查之后发现一个有趣的现象:(由于0x76f6和0x76f7的两个00 00是可以忽略的,所以认为是0x76f5截止)

 

图 1 101X100的图像末尾数据

而新的BMP,理应是原封不动的,但是实际上是这样的:

 

图 2 新的BMP末尾数据

也就是说,新的图片在0x7691时已经结束,而原图像则在0x76f5结束

这时候便出现了惊人的一幕:

新图片与原图片整整少了100字节。少了100X1的字节

 

同样使用103x100重复步骤发现:

少了整整300个字节,也就是说少了100X3的字节。

 

再使用531X485的图片同样得出,确实缺少了1455字节,即485X3字节。

 

综合上述可见,(103*3bytes)mod4 = 1

                       (101*3bytes)mod4 = 3

                       (531*3bytes)mod4 = 1

P.S.这里*3计算字节数,531像素(RGB)有531*3字节的数据

 

暂且把这些余数称为 剩余行量A

可以发现 ((4-A)*Height) =  丢失字节量

这也就说明了必须保证4字节对齐,不足的情况

 

那么现在找到这个量,开始分析问题所在,

在实际拿数据的过程中,我使用的二维数组大小为10100x3,也就是说可以容纳10100个像素共30300字节的数据,但是通过上面的实验发现,需要扩大((4-A)*Height)bytes的空间

即扩大至30400字节,为了方便,一次性扩大Height像素的字节量,也就是(100*3)=300字节,最终的数组大小应该为30600字节,也就是10200像素。

 

而对于A = 1的状况,即上面531X485的状况,理论上应该使用的字节数为  772605 ,即257535像素(RGB)大小,实际应该增补(4-A)*485 = (4-1)*485 = 1455字节,即485像素的大小,即531->532,直接增加1455的量即可。

 

倘若A = 2,实际应该增补2*Height,这样的情况下,为了方便,则直接增加 3*height字节的量,从而实现补齐。

 

所以综合上述表达:

如果数据量不能满足被4整除,则可以在定义取用数组时多加3*Height的数据量,保证数据全部拿出。至于BMP文件中多余的字节,内容均为00,系统读取显示时会自动忽略,无需担心。

 

如果不计内存容量,仅从方便起见,则可以在每个数组初始化之时,都可以多加3*Height的数据量(即1Height像素)


Part2

那么接下来说的是第二个点,关于实际显示时的问题

大量实验中发现,以100X100(Width*Height)的BMP图片为例子,由于BMP从下至上,从左至右的扫描方式,如果我想只显示最下面两行的图像,而将之后的图像屏蔽,理论上我需要取出 2*Width的像素,即3*2*Width字节(RGB)的数据,

即200像素,600字节的数据,实验结果是正常的:

 

图 3 100X100只显示最开始的两行数据

,可以看到均成功。而且任何行数均可以成功(包括0.25行)。

 

但是如果采用的图片为101X100(RGB),如果想要取出5行数据,理论上需要取出101*5 =505个像素,即1515个字节的数据,但是此时发现如下的现象:

 

图 4这连正常显示都做不到

 

图 5 这连正常显示都做不到

505个像素,即1515个字节。拥有先前的经验,第一时间可以想到是否也是4字节对齐的锅:

所以开始计算:

 1515mod4 = 3

计算A = 3

则可以在此基础至上提升到最近的能被4整除的值,即1516,但是这也出现了问题,RGB是三通道的,所以又必须被3整除,所以我尝试使用能被6整除的且向左最接近1516的字节1518,即506个像素来显示,可以发现

 

图 6 最起码可以看到拿满了5行

此时虽然显示的数据不太正常,但是最起码拿满了5行的像素,没有多余也没有不足。

 

但是此时出现了问题:数据并不正确,在这里每个显示的像素应该是粉色,这就意味着,至少相邻的像素之间发生了糟糕的错位,理论上应该是不存在错位的,不过实际图中没有发现任何影响,所以之后再探究这个问题。

现在的问题是,成功地拿出了完整的5行数据;

那如果拿50行呢?

50*101 = 5050像素,即15150像素,

15150mod6 = 0,所以据此理论应该是能拿满50行不多不少,但是出现了问题

 

图 7 没有拿满

明显看出没有拿满50行,反而少了一些,所以此时进行进一步的尝试最终发现。

假如取3行,则出现了缺少一个像素未满3行的状况,此时主动加1个像素,即304像素时,是可以成功填满的,同理我尝试了取5行的状况,如果仍旧使用505像素,则会发现缺少一个像素未满的状况,此时如果沿用第三行添加的像素,则实际应该使用506像素。

然而到了6行的时候,却仍然发现了缺少一个像素未满的状况,所以,此时也应该主动增加一个像素,即608像素,此时发现成功填满。

 

图 8 取6行且主动加2(606+2)的情况

然后当我取7行之时,继承6行,使用709像素,成功地填满了7行,不多不少:

 

图 9 取7行像素且继承6行的加2(707+2) 的情况

8行也是如此,

但是到9行时,突然发现出现了缺少一个像素未满的状况,所以仍需主动加1个像素。

 

图 10 9行时出现错误(差一个像素恰好填满9行)

 

所以综上,我猜测,每读3行,必须主动加一个像素,从而对齐,

 

此时返回50行的问题,假如取50行,则会有 [50/3] = 16像素不能取满,即主动添加16像素,达到5066像素,最终成功拿出了满50行的数据:

 

图 11 满50行数据

假如BMP为102X100,则经过实验,在3行时应该主动加2像素。

于是我就这样实验了下去,但是当BMP为104X100时,似乎无论加不加都不会影响。

此时满足条件:

 104mod4 = 0

此时将余数暂称为 剩余像素数Pb

如果想取满任何一行,则每行应该添加的像素数为 Pb*[Height/3]

P.S [ ]表示取整

 

归纳上述,假如每行的像素数(Width)为X(这里为101),则需要在每3行提取之下多加((Xmod4)*Height*3) 个像素。

这种算法可以辅助实现像素定位块状提取

               

 

 

                                                                                                  写者:QYZ

                                                                                                         2021/5/19 22:39