Search code examples
windowswinapibitmapgdicreatedibsection

Windows bitmaps: BITMAPV5HEADER and BITMAPINFO compatible?


From the documentation on CreateDIBSection I observe that CreateDIBSection takes a pointer to a BITMAPINFO as the second parameter.

However, I came across various places indicating that it might be allowed to pass pointers to other structures (in particular BITMAPV5HEADER), including

I have a feeling that this makes perfect sense (BITMAPV5HEADER can be seen as an "extended version" of BITMAPINFO wrt struct layout), but I could not find a single piece of official documentation on this topic.

Can anybody confirm that passing a BITMAPV5HEADER* instead of a BITMAPINFO is actually valid and possibly offer some documentation?


Solution

  • The short answer has to be no. BITMAPV5HEADER* is not a substitute for BITMAPINFO* and may not be passed whenever BITMAPINFO* is expected (simply because BITMAPINFO contains palette colors after the header, and BITMAPV5HEADER is just a header).


    It is certainly fine to pass BITMAPV5HEADER* instead of BITMAPINFO* provided that the BITMAPV5HEADER is a part of BITMAPINFO and has required kind of palette color data after it. That is documented, although kind of indirectly, through use instructions and common sense:

    • BITMAPV5HEADER is documented to be an "extended version of th BITMAPINFOHEADER structure", so that part is clear.

    • BITMAPINFO is documented to combine a header and color data. The header is included by value as opposed to by pointer, so at this point it's clear the header may not just grow in size as it pleases, otherwise it would be impossible to access BITMAPINFO.bmiColors and the whole idea of having an extended version of the header would be pointless.

    • And that problem is then resolved in another place in the documentation ("Remarks" section):

      An application should use the information stored in the biSize member to locate the color table in a BITMAPINFO structure, as follows:

      pColor = ((LPSTR)pBitmapInfo + (WORD)(pBitmapInfo->bmiHeader.biSize));
      

    Although I believe this part was not confusing for you to begin with.


    Now for the long answer.

    There appear to be two cases when BITMAPV5HEADER* can be passed for BITMAPINFO*. Neither of the two is documented in a concrete way, and if for the first one we can apply same common sense we applied above, the second one appears to be a bug in the documentation:

    • When BITMAPINFO.bmiColors is documented to be NULL. You can find the complete list of such cases in the documentation for BITMAPINFOHEADER.
    • When the header is BITMAPV4HEADER or BITMAPV5HEADER, the bitmap has 16 or 32 bit colors, and compression is set to BI_BITFIELDS. In that case the color masks that are documented to follow the header are instead taken from the respective dedicated fields of the headers, and the three DWORDs that follow the header are ignored.

      This is easy to prove by slightly modifying the original code:

      typedef struct tagV5BMPINFO {
          BITMAPV5HEADER bmiHeader;
          DWORD        bmiColors[3];
      } V5BMPINFO;
      
      int _tmain(int argc, _TCHAR* argv[])
      {
          V5BMPINFO bmpinfo = { 0 };
          BITMAPV5HEADER bmpheader = { 0 };
      
          bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
          bmpheader.bV5Width = width;
          bmpheader.bV5Height = height;
          bmpheader.bV5Planes = 1;
          bmpheader.bV5BitCount = 32;
          bmpheader.bV5Compression = BI_BITFIELDS;
          bmpheader.bV5SizeImage = 400*200*4;
          bmpheader.bV5RedMask   = 0x00FF0000;
          bmpheader.bV5GreenMask = 0x0000FF00;
          bmpheader.bV5BlueMask  = 0x000000FF;
          bmpheader.bV5AlphaMask = 0xFF000000;
          bmpheader.bV5CSType = 0x57696e20; // LCS_WINDOWS_COLOR_SPACE
          bmpheader.bV5Intent = LCS_GM_BUSINESS;
      
          bmpinfo.bmiHeader = bmpheader;
          // Put them in reverse order here compared to the above
          bmpinfo.bmiColors[0] = 0x000000FF;
          bmpinfo.bmiColors[1] = 0x0000FF00;
          bmpinfo.bmiColors[2] = 0x00FF0000;
      
          void* converted = NULL;
          HDC screen = GetDC(NULL);
          HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpinfo), DIB_RGB_COLORS, &converted, NULL, 0);
          ReleaseDC(NULL, screen);
      
      
          DIBSECTION actual_data;
          GetObject(result, sizeof(actual_data), &actual_data);
      
          std::cout << std::hex;
          std::cout << actual_data.dsBitfields[0] << std::endl;
          std::cout << actual_data.dsBitfields[1] << std::endl;
          std::cout << actual_data.dsBitfields[2] << std::endl;
          std::cout << std::dec;
      
          DeleteObject(result);
      
          return 0;
      }
      

      Result:

      ff0000
      ff00
      ff
      

      It would appear the documentation was absentmindedly copied between BITMAPINFOHEADER, BITMAPV4HEADER and BITMAPV5HEADER when it should have been amended for the last two. The closest explanation I was able to find is Bitmap Header Types that at least recognizes the existence of the dedicated mask fields, but still implies the values must be provided both in these fields and after the header in bmiColors:

      The red, green, and blue bitfield masks for BI_BITFIELD bitmaps immediately follow the BITMAPINFOHEADER, BITMAPV4HEADER, and BITMAPV5HEADER structures. The BITMAPV4HEADER and BITMAPV5HEADER structures contain additional members for red, green, and blue masks as follows.

      (emphasis mine).

      We can only conclude from evidence that it is not true.
      It is less of documentation than I hoped.