RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1433362
Accepted
WaitingForTheLP'sComeback
WaitingForTheLP'sComeback
Asked:2022-07-27 02:15:53 +0000 UTC2022-07-27 02:15:53 +0000 UTC 2022-07-27 02:15:53 +0000 UTC

蒙版如何在 BMP 图像中发挥作用?

  • 772

我正在尝试编写用于将各种格式的图像读取到字节数组中的原始算法。在我设法解析 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();

}
c++ изображения
  • 4 4 个回答
  • 185 Views

4 个回答

  • Voted
  1. Best Answer
    Laukhin Andrey
    2022-07-27T08:39:28Z2022-07-27T08:39:28Z

    您修改的掩码结构是非标准的。可以假设不同的程序会以不同的方式处理这样的文件。让我们试着了解你的情况。

    从逻辑上讲,如果我们有一个 4 位掩码(0x3C),那么红色分量的值不应超过 15(十进制 s/s)。

    所以我决定解析二进制表示。

    像素 #1
    RGB(11, 12, 13) 或 0x0D0C0BFF (+ alpha 通道)
    掩码:00 00 60 00或 0x00003C00

    二进制形式:

    00001101000011000000101111111111
    00000000000000000011110000000000
    

    掩码下的位:0010
    Paint dot NET 给我们:00100010 (34)

    可以看出,左边4位与右边相同,对应掩码下的位。
    可以假设掩码下的位已被复制到左侧,直到填充了 8 位。


    像素 #2 (0x353433FF)

    00110101001101000011001111111111
    00000000000000000011110000000000
    

    掩码下的位:1100
    最多填充 8 位:11001100 (254)


    像素 #3 (0x676665FF)

    01100111011001100110010111111111
    00000000000000000011110000000000
    

    掩码下的位:1001
    最多填充 8 位:10011001 (153)


    像素 #4 (0x999897FF)

    10011001100110001001011111111111
    00000000000000000011110000000000
    

    掩码下的位:0101
    最多填充 8 位:01010101 (85)


    因此,该假设得到证实。
    为什么?我不会放在心上的。
    很可能,这里没有单一的标准,每个程序都以自己的方式解释它。我阅读的规范中只有一个要求:掩码中的位必须是连续的,并且掩码不能重叠。

    鉴于 99.99% 的文件将使用标准掩码,您不必担心这一点,尽管这很好奇......


    UPD
    如果上述假设对于任何掩码都是正确的,那么我可以提供以下代码来通过掩码获取组件:

    #include <iostream>
    
    unsigned long getPixelComponentByMask(unsigned long pixel, unsigned long mask);
    
    int main()
    {
      unsigned long pixel = 0x0D0C0BFF;
      unsigned long mask  = 0x00003C00;
    
      std::cout << getPixelComponentByMask(pixel, mask) << std::endl;
      return 0;
    }
    
    unsigned long getPixelComponentByMask(unsigned long pixel, unsigned long mask)
    {
      if(pixel > 0 && mask > 0) {
        unsigned long p, m, firstBit, n = 0, out = 0;
        
        while(n < 8) {
          p = pixel;
          m = mask;
    
          while(m > 0) {
            if(m & 1) {
              firstBit = p & 1;
              out ^= (-firstBit ^ out) & (1UL << n);
              n++;
            }
    
            p >>= 1;
            m >>= 1;
          }
        }
        
        return out;
      }
      
      return 0;
    }
    

    如有必要,我可以在代码中添加注释。

    • 3
  2. Alex F
    2022-07-27T15:08:44Z2022-07-27T15:08:44Z

    这是具有必要功能和测试的程序。工作需要结构和compute_mask_info功能compute_pixel。读取文件时,mask_info每种颜色数 4。然后读取像素并使用compute_pixel.

    该程序不考虑运行它的计算机的字节顺序。我把它留给你娱乐。

    另请阅读我在问题下的评论,以了解这是为了什么。

    #include <iostream>
    #include <iomanip>
    
    using namespace std;
    
    
    struct mask_info
    {
        uint32_t value;             // mask
        int placement_shift;        // shift right to this value after applying the mask
        // 8 bits normalization: > 0 - shift right to this value, < 0 = shift left to this value.
        int normalization_shift;    
    };
    
    struct pixel_info
    {
        uint32_t value;             // actual value
        uint32_t normalized_value;  // normalized to 8 bit, for 8 bpp display
    };
    
    mask_info compute_mask_info(uint32_t mask);
    void print_mask_info(const mask_info& info);
    pixel_info compute_pixel(uint32_t value, const mask_info& info);
    
    int main()
    {
        uint32_t masks[] =
        {
            // Standard masks
            0xFF000000,
            0x00FF0000,
            0x0000FF00,
            0x000000FF,
            // Non-standard masks
            0x000003FF,         // normalization 2 expected
            0x0000007F,         // normalization -1 expected
            0x00003FF0,         // placement 4, normalization 2 expected
            0x00007F00          // placement 8, normalization -1 expected
        };
    
        // Test masks
        for(auto n: masks)
        {
            auto mi = compute_mask_info(n);
            print_mask_info(mi);
        }
    
        cout << endl;
    
        // Test pixels, standard masks
        {
            uint32_t pixel = 0xA1B2C3D4;
    
            mask_info mi[] =
            {
                compute_mask_info(masks[0]),        // blue
                compute_mask_info(masks[1]),        // green
                compute_mask_info(masks[2]),        // red
                compute_mask_info(masks[3])         // alpha
            };
    
            for(const auto& minfo: mi)
            {
                auto p = compute_pixel(pixel, minfo);
    
                cout << hex << setfill('0') << setw(2) << p.value << "  " << setw(2) << p.normalized_value << endl;
            }
        }
    
        cout << endl;
    
        // Test pixels, non-standard masks
        {
            uint32_t pixel = 0x00003FF0;             // expected output with masks[6]: 3FF FF
    
            auto p = compute_pixel(pixel, compute_mask_info(masks[6]));
    
            cout << hex << setfill('0') << setw(2) << p.value << "  " << setw(2) << p.normalized_value << endl;
        }
    
        return 0;
    }
    
    pixel_info compute_pixel(uint32_t value, const mask_info& info)
    {
        pixel_info pi{};
    
        pi.value = (value & info.value) >> info.placement_shift;
        pi.normalized_value = pi.value;
    
        if (info.normalization_shift > 0)
        {
            pi.normalized_value >>= info.normalization_shift;
        }
        else if (info.normalization_shift < 0)
        {
            pi.normalized_value <<= info.normalization_shift;
        }
    
        return pi;
    }
    
    void print_mask_info(const mask_info& info)
    {
        cout << "0x" << hex << setfill('0') << setw(8) << info.value << dec << "  " << info.placement_shift << "  " << info.normalization_shift << endl;
    }
    
    mask_info compute_mask_info(uint32_t mask)
    {
        mask_info mi{};
        mi.value = mask;
        int bit1_count{};       // number of set bits
    
        for(int i = 0; i < 32; ++i)
        {
            if (mask & (1<<i))
            {
                ++bit1_count;
            }
            else
            {
                if (bit1_count)
                {
                    break;          // this is 0 after mask, stop here
                }
    
                ++mi.placement_shift;
            }
        }
    
        mi.normalization_shift = bit1_count - 8;
    
        return mi;
    }
    

    为了完整起见,程序的输出为:

    0xff000000 24 0
    0x00ff0000 16 0
    0x0000ff00 8 0
    0x000000ff 0 0
    0x000003ff 0 2
    0x0000007f 0 -1
    0x00003ff0 4 2
    0x00007f00 8-1
    
    a1 a1
    b2 b2
    c3 c3
    d4 d4
    
    3ffff
    
    • 2
  3. user7860670
    2022-07-27T04:16:11Z2022-07-27T04:16:11Z

    GAP1 只是一个孔,如果像素阵列的偏移量超过标头大小(例如,出于目的和对齐),则会出现一个孔。掩码是常规的位掩码,它们与数组中的像素颜色相加 + 偏移以获取值。

    • 1
  4. Daniil Loban
    2022-07-27T04:57:31Z2022-07-27T04:57:31Z

    这张来自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)

    新颜色究竟是如何获得的?

    最后,我写了一个小程序来试验比特,你可以通过混合它们来实现非常有趣的效果,一种图像加密。

    在此处输入图像描述

    • 1

相关问题

  • 编译器和模板处理

  • 指针。找到最小数量

  • C++,关于枚举类对象初始化的问题

  • 函数中的二维数组

  • 无法使用默认构造函数创建类对象

  • C++ 和循环依赖

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5