我正在尝试编写用于将各种格式的图像读取到字节数组中的原始算法。在我设法解析 PPM 和 TGA 图纸后,我决定启动 BMP 格式。目前,我已经学习了如何在以下情况下将 BMP 图像转换为 RGB 序列数组:
- 在图片中,有关特定像素的信息使用四位或八位进行编码,并且文件包含一个颜色表。
- 在绘图中,有关特定像素的信息使用 24 位进行编码,并且文件不包含颜色表。这是最简单的选择。
32 位 BMP 图像出现问题。我使用 Paint dot NET 图形编辑器生成 BMP 文件。在带有保存选项的窗口中,您可以选择图片的位深度。我的 32 位图像是 2*2 像素,看起来像这样:
像素 #1 是 RGB(11, 12, 13),像素 #2 是 RGB(51, 52, 53),像素 #3 是 RGB(101, 102, 103),像素 #4 是 RGB(151, 152, 153) )。
使用类std::ifstream
及其方法get()
,我将文件的内容显示到控制台(首先是字节数,然后是具有该数字的字节的内容):
相同的输出,但在文本中:
1) 66 2) 77 3) 166 4) 0 5) 0 6) 0 7) 0 8) 0 9) 0 10) 0 11) 150 12) 0 13) 0 14) 0 15) 124 16) 0 17) 0 18) 0 19) 2 20) 0 21) 0 22) 0 23) 2 24) 0 25) 0 26) 0 27) 1 28) 0 29) 32 30) 0 31) 3 32) 0 33) 0 34) 0 35) 0 36) 0 37) 0 38) 0 39) 196 40) 14 41) 0 42) 0 43) 196 44) 14 45) 0 46) 0 47) 0 48) 0 49) 0 50) 0 51) 0 52) 0 53) 0 54) 0 55) 0 56) 0 57) 255 58) 0 59) 0 60) 255
61) 0 62) 0 63) 255 64) 0 65) 0 66) 0 67) 0 68) 0 69) 0 70) 255 71) 32 72) 110 73) 105 74) 87 75) 0 76) 0 77) 0 78) 0 79) 0 80) 0 81) 0 82) 0 83) 0 84) 0 85) 0 86) 0 87) 0 88) 0 89) 0 90) 0 91) 0 92) 0 93) 0 94) 0 95) 0 96) 0 97) 0 98) 0 99) 0 100) 0 101) 0 102) 0 103) 0 104) 0 105) 0 106) 0 107) 0 108) 0 109) 0 110) 0 111) 0 112) 0 113) 0 114) 0 115) 0 116) 0 117) 0 118) 0 119) 0 120) 0 121) 0 122) 0 123) 0 124) 0 125) 0 126) 0 127) 0 128) 0 129) 0 130) 0 131) 0 132) 0 133) 0 134) 0 135) 0 136) 0 137) 0 138) 0 139) 255 140) 0 141) 0 142) 0 143) 0 144) 255 145) 0 146) 0 147) 0 148) 0 149) 255 150) 0 151) 103 152) 102 153) 101 154) 255 155) 153 156) 152 157) 151 158) 255 159) 13 160) 12 161) 11 162) 255 163) 53 164) 52 165) 51 166) 255
从第 15 到第 18 字节占用的字段(取决于 BMP 版本称为 bcSize/biSize/bV4Size/bV5Size)的值为 124,对应于 BMP 版本 5。这表明图片可能包含“bitmasks to提取通道值:红色、绿色、蓝色强度和 alpha 通道值”(来源:https ://ru.wikipedia.org/wiki/BMP#BITMAPINFO )。压缩字段的值(从第 31 到第 34 个字节)也指示掩码的存在,因为它等于数字 3。包含掩码的字段占用 4 个字节,并从第 55 个字节开始相互跟随. 从控制台输出来看,在我的图像中,掩码具有以下值(十进制):
红色掩码:(0 0 255 0
第 55 到第 58 个字节)
绿色掩码:(0 255 0 0
从第 59 到第 62 字节)
蓝色掩码:(255 0 0 0
从第 63 到第 66 字节)
Alpha 通道掩码:(0 0 0 255
从第 67 到第 70 个字节)
我不知道如何解释这些值并将它们应用于位于文件末尾的像素颜色。如果我忽略这些值并直接提取颜色(从有时称为图像数据或像素数据的数据块中),我会得到正确的颜色。这一定是因为我没有在图形编辑器中调整蒙版,程序为它们写下了不会扭曲像素颜色的默认值。
如果手动更正蒙版的值并覆盖文件,每个像素的颜色都会发生变化。例如,我将红色蒙版更改为0 0 60 0
(十进制),在图形编辑器中打开图像,得到以下结果:
像素 #1 现在是 RGB(34, 12, 13),像素 #2 是 RGB(204, 52, 53),像素 #3 是 RGB(153, 102, 103),像素 #4 是 RGB(85, 152) , 153)。
确实,每个像素的红色值都发生了变化。可能,编辑器使用数字的二进制表示执行了一些操作,但我不知道哪些操作。在我看过的有关 BMP 的文章中,出于某种原因顺便提到了掩码。位于图像数据之前的数据块也提出了问题。它占据文件的第 139 到第 150 个字节(在我的情况下),根据维基百科,它的名称是 GAP1(https://commons.wikimedia.org/wiki/File:BMPfileFormat.svg?uselang=ru) . 只有在我在保存选项中将位深度设置为 32 后,GAP1 才会出现在我的 BMP 绘图中。这个块的数据和颜色掩码非常相似,但我不确定它是否有任何联系,因为我找不到关于 GAP1 的信息。我可能做错了什么。
请告诉我如何理解蒙版在 BMP 图纸中的工作原理?
#include <iostream>
#include <fstream>
#include <string>
void ReadImage_BMP(const char* file_path_c_str);
int main()
{
ReadImage_BMP(".../image.bmp");
return 0;
}
void ReadImage_BMP(const char* file_path_c_str)
{
std::string file_path = file_path_c_str;
std::ifstream input_fs;
input_fs.open(file_path, std::ios_base::binary);
if (input_fs.is_open() == false)
{
perror(("Error while opening file " + file_path).c_str());
return;
}
char current_byte;
int count = 1;
while ((bool)input_fs.get(current_byte) == true)
{
std::cout << count << ") " << (int)(unsigned char)current_byte << std::endl;
count++;
}
if (input_fs.eof())
{
std::cout << "End of file reached." << std::endl;
}
else if (input_fs.fail())
{
std::cout << "Type error." << std::endl;
}
else
{
std::cout << "Unknown error" << std::endl;
}
input_fs.close();
}
您修改的掩码结构是非标准的。可以假设不同的程序会以不同的方式处理这样的文件。让我们试着了解你的情况。
从逻辑上讲,如果我们有一个 4 位掩码(0x3C),那么红色分量的值不应超过 15(十进制 s/s)。
所以我决定解析二进制表示。
像素 #1
RGB(11, 12, 13) 或 0x0D0C0BFF (+ alpha 通道)
掩码:
00 00 60 00
或 0x00003C00二进制形式:
掩码下的位:0010
Paint dot NET 给我们:00100010 (34)
可以看出,左边4位与右边相同,对应掩码下的位。
可以假设掩码下的位已被复制到左侧,直到填充了 8 位。
像素 #2 (0x353433FF)
掩码下的位:1100
最多填充 8 位:11001100 (254)
像素 #3 (0x676665FF)
掩码下的位:1001
最多填充 8 位:10011001 (153)
像素 #4 (0x999897FF)
掩码下的位:0101
最多填充 8 位:01010101 (85)
因此,该假设得到证实。
为什么?我不会放在心上的。
很可能,这里没有单一的标准,每个程序都以自己的方式解释它。我阅读的规范中只有一个要求:掩码中的位必须是连续的,并且掩码不能重叠。
鉴于 99.99% 的文件将使用标准掩码,您不必担心这一点,尽管这很好奇......
UPD
如果上述假设对于任何掩码都是正确的,那么我可以提供以下代码来通过掩码获取组件:
如有必要,我可以在代码中添加注释。
这是具有必要功能和测试的程序。工作需要结构和
compute_mask_info
功能compute_pixel
。读取文件时,mask_info
每种颜色数 4。然后读取像素并使用compute_pixel
.该程序不考虑运行它的计算机的字节顺序。我把它留给你娱乐。
另请阅读我在问题下的评论,以了解这是为了什么。
为了完整起见,程序的输出为:
GAP1 只是一个孔,如果像素阵列的偏移量超过标头大小(例如,出于目的和对齐),则会出现一个孔。掩码是常规的位掩码,它们与数组中的像素颜色相加 + 偏移以获取值。
这张来自wiki的图片很好地解释了位掩码是什么,即 它们决定如何存储颜色位(其中 1 将是有关相应颜色的信息),从而形成整个文件。
因此,我假设通过在某些地方交换颜色蒙版,您可以自己更改颜色 - 这很容易检查。
您还可以从文档中看到:
bV5RedMask
仅当 bV5Compression 设置为 BI_BITFIELDS 时,指定每个像素的红色分量的颜色掩码才有效。
bV5GreenMask
仅当 bV5Compression 设置为 BI_BITFIELDS 时,指定每个像素的绿色分量的颜色掩码才有效。
bV5BlueMask
仅当 bV5Compression 设置为 BI_BITFIELDS 时,定义每个像素的蓝色分量的颜色掩码才有效。
bV5AlphaMask
指定每个像素的 alpha 分量的颜色掩码。
颜色交换演示(交换绿色和红色蒙版)
原图:
使用修改后的掩码:
更换的方式(原件)
按位与运算
如您所知,这些操作很好地允许您提取部分数字,因此我们有一个每个像素 3-4 字节的像素数组,具体取决于图像的位数
[[255,255,255], [0,100,200] ...在这里我用额外的括号标记了组
在
hex
视图中,这是0xFFFFFF
一样的0xС86400
(考虑到文件中字节的写回)在位图标题中是我们的掩码,例如:
0xFF0000
- 红色的0x00FF00
- 绿色0x0000FF
- 蓝色的因此,字节
0xС86400
分解为:0xС86400
&0xFF0000
->0xC80000
->0xC8
(R)0xС86400
&0x00FF00
->0x006400
->0x64
(G)0xС86400
&0x0000FF
->0x000000
->0x00
(B)新颜色究竟是如何获得的?
最后,我写了一个小程序来试验比特,你可以通过混合它们来实现非常有趣的效果,一种图像加密。