【问题标题】:Broken BMP when save bitmap by SOIL. Screenshot area通过 SOIL 保存位图时 BMP 损坏。截图区
【发布时间】:2017-10-28 10:06:44
【问题描述】:

这是我上一个关于将屏幕截图保存到 SOIL 的问题的延续。here 现在我想知道,如何制作屏幕部分的屏幕截图并消除奇怪行为的原因。我的代码:

bool saveTexture(string path, glm::vec2 startPos, glm::vec2 endPos)
{
   const char *charPath = path.c_str();

   GLuint widthPart = abs(endPos.x - startPos.x); 
   GLuint heightPart = abs(endPos.y - startPos.y);

   BITMAPINFO bmi;
   auto& hdr = bmi.bmiHeader;
   hdr.biSize = sizeof(bmi.bmiHeader);
   hdr.biWidth = widthPart;
   hdr.biHeight = -1.0 * heightPart;
   hdr.biPlanes = 1;
   hdr.biBitCount = 24;
   hdr.biCompression = BI_RGB;
   hdr.biSizeImage = 0;
   hdr.biXPelsPerMeter = 0;
   hdr.biYPelsPerMeter = 0;
   hdr.biClrUsed = 0;
   hdr.biClrImportant = 0;

   unsigned char* bitmapBits = (unsigned char*)malloc(3 * widthPart * heightPart);

   HDC hdc = GetDC(NULL);
   HDC hBmpDc = CreateCompatibleDC(hdc);
   HBITMAP hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)&bitmapBits, nullptr, 0);
   SelectObject(hBmpDc, hBmp);
   BitBlt(hBmpDc, 0, 0, widthPart, heightPart, hdc, startPos.x, startPos.y, SRCCOPY);

   //UPDATE:
   - int bytes = widthPart * heightPart * 3;
   - // invert R and B chanels
   - for (unsigned i = 0; i< bytes - 2; i += 3)
   - {
   -   int tmp = bitmapBits[i + 2];
   -   bitmapBits[i + 2] = bitmapBits[i];
   -   bitmapBits[i] = tmp;
   - }

   + unsigned stride = (widthPart * (hdr.biBitCount / 8) + 3) & ~3;
   + // invert R and B chanels
   + for (unsigned row = 0; row < heightPart; ++row) {
   +     for (unsigned col = 0; col < widthPart; ++col) {
   +         // Calculate the pixel index into the buffer, taking the 
             alignment into account
   +         const size_t index{ row * stride + col * hdr.biBitCount / 8 };
   +         std::swap(bitmapBits[index], bitmapBits[index + 2]);
   +      }
   + }

   int texture = SOIL_save_image(charPath, SOIL_SAVE_TYPE_BMP, widthPart, heightPart, 3, bitmapBits);

   return texture;
}

当我运行这个时,如果 widthPart 和 heightPart 是偶数,那就完美了。但如果这是奇数,我会得到这个 BMP。:

我检查了任何转换和代码两次,但在我看来,原因在于我的 blit 函数错误。 RGB 转换功能对问题没有影响。可能是什么原因?这是BitBlt中区域的正确方式吗?

更新偶数或奇数没有区别。当这个数字相等时,会产生正确的图片。不知道哪里出了问题。((

更新2

SOIL_save_image 函数检查参数是否有错误并发送到 stbi_write_bmp

int stbi_write_bmp(char *filename, int x, int y, int comp, void *data)
{
   int pad = (-x*3) & 3;
   return outfile(filename,-1,-1,x,y,comp,data,0,pad,
       "11 4 22 4" "4 44 22 444444",
       'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40,  // file header
        40, x,y, 1,24, 0,0,0,0,0,0);             // bitmap header
}

输出文件功能:

static int outfile(char const *filename, int rgb_dir, int vdir, int x, int 
y, int comp, void *data, int alpha, int pad, char *fmt, ...)
{
   FILE *f = fopen(filename, "wb");
   if (f) {
      va_list v;
      va_start(v, fmt);
      writefv(f, fmt, v);
      va_end(v);
      write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad);
      fclose(f);
   }
   return f != NULL;
}

【问题讨论】:

    标签: c++ winapi opengl soil


    【解决方案1】:

    位图图像损坏是由于 Windows 位图之间的数据布局与 SOIL 库的预期不一致造成的1。从CreateDIBSection 返回的像素缓冲区遵循 Windows 规则(参见Bitmap Header Types):

    扫描线是 DWORD 对齐的 [...]。对于不能被四整除的扫描线宽度(以字节为单位),它们必须被填充 [...]。

    换句话说:每条扫描线的宽度(以字节为单位)为(biWidth * (biBitCount / 8) + 3) &amp; ~3。另一方面,SOIL 库不期望像素缓冲区是 DWORD 对齐的。

    要解决这个问题,像素数据需要在传递到 SOIL 之前进行转换,方法是剥离(潜在的)填充并交换 R 和 B 颜色通道。以下代码就地执行此操作2

    unsigned stride = (widthPart * (hdr.biBitCount / 8) + 3) & ~3;
    
    for (unsigned row = 0; row < heightPart; ++row) {
        for (unsigned col = 0; col < widthPart; ++col) {
            // Calculate the source pixel index, taking the alignment into account
            const size_t index_src{ row * stride + col * hdr.biBitCount / 8 };
            // Calculate the destination pixel index (no alignment)
            const size_t index_dst{ (row * width + col) * (hdr.biBitCount / 8) };
            // Read color channels
            const unsigned char b{ bitmapBits[index_src] };
            const unsigned char g{ bitmapBits[index_src + 1] };
            const unsigned char r{ bitmapBits[index_src + 2] };
            // Write color channels switching R and B, and remove padding
            bitmapBits[index_dst] = r;
            bitmapBits[index_dst + 1] = g;
            bitmapBits[index_dst + 2] = b;
        }
    }
    

    使用此代码,index_src 是像素缓冲区的索引,其中包括强制正确 DWORD 对齐的填充。 index_dst 是没有应用任何填充的索引。将像素从 index_src 移动到 index_dst 会移除(潜在的)填充。


    1标志是扫描线向左或向右移动一个或两个像素(或以不同速度的单个颜色通道)。这通常是一个安全的指示,即存在扫描线对齐不一致的情况。
    2此操作具有破坏性,即像素缓冲区无法再传递给 Windows转换后的 GDI 函数,虽然原始数据可以重建,即使涉及更多。

    【讨论】:

    • 还是感谢您的帮助,但我尝试了您的代码的不同变体(在编辑之前),但没有任何改变。 :(( 每次当 widthPart 不能被 4 整除时(无论 heightPart 是多少)我都会得到上面的 BMP。我用你的部分更新问题。
    • @hardCode:请不要接受不能解决您的问题的答案,也不要编辑您的问题以使其成为不同的问题。相反,张贴 cmets 要求澄清。我猜想,这个问题仍然与位图图像中的扫描线对齐有关。也许SOIL_save_image 不希望缓冲区是 DWORD 对齐的。我们可以查看SOIL_save_image 的文档吗?
    • 我更新了问题。在 SOIL 文档中没有关于编写 BMP 的内容:lonesock.net/soil.html
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-09
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 2011-06-22
    相关资源
    最近更新 更多