Search code examples
c++windowswinapiclipboard

What is the differences in structure between screenshot and copied part of bitmap?


I tried to use a code from one of the answers to a question here and when I run it after opening a bitmap file in "paint", selected part of it and copied it, the program saves the copied image as needed, but when I press PrntScrn (and the screenshot appears in the clipboard and identified in the program as bitmap file) the program saves it as bitmap file but when I try to open it I get a message that the image format isn't supported. I guess it happens because the way I save the image. My question is what is the difference between the two types of images that one of them works properly and the others doesn't?

Here is my code:

#include <iostream>
#include <fstream>
#include <windows.h>

typedef struct
{
    std::uint32_t biSize;
    std::int32_t  biWidth;
    std::int32_t  biHeight;
    std::uint16_t  biPlanes;
    std::uint16_t  biBitCount;
    std::uint32_t biCompression;
    std::uint32_t biSizeImage;
    std::int32_t  biXPelsPerMeter;
    std::int32_t  biYPelsPerMeter;
    std::uint32_t biClrUsed;
    std::uint32_t biClrImportant;
} DIB;

typedef struct
{
    std::uint16_t type;
    std::uint32_t bfSize;
    std::uint32_t reserved;
    std::uint32_t offset;
} HEADER;

typedef struct
{
    HEADER header;
    DIB dib;
} BMP;


int main()
{
    std::cout << "Format Bitmap: " << IsClipboardFormatAvailable(CF_BITMAP) << "\n";
    std::cout << "Format DIB: " << IsClipboardFormatAvailable(CF_DIB) << "\n";
    std::cout << "Format DIBv5: " << IsClipboardFormatAvailable(CF_DIBV5) << "\n";

    if (IsClipboardFormatAvailable(CF_BITMAP) || IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_DIBV5))
    {
        if (OpenClipboard(NULL))
        {
            HANDLE hClipboard = GetClipboardData(CF_DIB);

            if (!hClipboard)
            {
                hClipboard = GetClipboardData(CF_DIBV5);
            }

            if (hClipboard != NULL && hClipboard != INVALID_HANDLE_VALUE)
            {
                void* dib = GlobalLock(hClipboard);

                if (dib)
                {
                    DIB* info = reinterpret_cast<DIB*>(dib);
                    BMP bmp = { 0 };
                    bmp.header.type = 0x4D42;
                    bmp.header.offset = 54;
                    bmp.header.bfSize = info->biSizeImage + bmp.header.offset;
                    bmp.dib = *info;

                    std::cout << "Type: " << std::hex << bmp.header.type << std::dec << "\n";
                    std::cout << "bfSize: " << bmp.header.bfSize << "\n";
                    std::cout << "Reserved: " << bmp.header.reserved << "\n";
                    std::cout << "Offset: " << bmp.header.offset << "\n";
                    std::cout << "biSize: " << bmp.dib.biSize << "\n";
                    std::cout << "Width: " << bmp.dib.biWidth << "\n";
                    std::cout << "Height: " << bmp.dib.biHeight << "\n";
                    std::cout << "Planes: " << bmp.dib.biPlanes << "\n";
                    std::cout << "Bits: " << bmp.dib.biBitCount << "\n";
                    std::cout << "Compression: " << bmp.dib.biCompression << "\n";
                    std::cout << "Size: " << bmp.dib.biSizeImage << "\n";
                    std::cout << "X-res: " << bmp.dib.biXPelsPerMeter << "\n";
                    std::cout << "Y-res: " << bmp.dib.biYPelsPerMeter << "\n";
                    std::cout << "ClrUsed: " << bmp.dib.biClrUsed << "\n";
                    std::cout << "ClrImportant: " << bmp.dib.biClrImportant << "\n";

                    std::ofstream file("Test2.bmp", std::ios::out | std::ios::binary);
                    if (file)
                    {
                        file.write(reinterpret_cast<char*>(&bmp.header.type), sizeof(bmp.header.type));
                        file.write(reinterpret_cast<char*>(&bmp.header.bfSize), sizeof(bmp.header.bfSize));
                        file.write(reinterpret_cast<char*>(&bmp.header.reserved), sizeof(bmp.header.reserved));
                        file.write(reinterpret_cast<char*>(&bmp.header.offset), sizeof(bmp.header.offset));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biSize), sizeof(bmp.dib.biSize));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biWidth), sizeof(bmp.dib.biWidth));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biHeight), sizeof(bmp.dib.biHeight));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biPlanes), sizeof(bmp.dib.biPlanes));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biBitCount), sizeof(bmp.dib.biBitCount));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biCompression), sizeof(bmp.dib.biCompression));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biSizeImage), sizeof(bmp.dib.biSizeImage));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biXPelsPerMeter), sizeof(bmp.dib.biXPelsPerMeter));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biYPelsPerMeter), sizeof(bmp.dib.biYPelsPerMeter));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biClrUsed), sizeof(bmp.dib.biClrUsed));
                        file.write(reinterpret_cast<char*>(&bmp.dib.biClrImportant), sizeof(bmp.dib.biClrImportant));
                        file.write(reinterpret_cast<char*>(info + 1), bmp.dib.biSizeImage);
                        std::cout << "finished" << std::endl;
                    }

                    GlobalUnlock(dib);
                }
            }

            CloseClipboard();
        }
    }

    return 0;
}

Solution

  • As you think, there was an error saving the image.

    Let's analyze your code first.

    This is the image data of the screenshot.

    1

    Please note: Compression: 3 -> BI_BITFIELDS

    BI_BITFIELDS: The bitmap is not compressed, and the color table consists of three DWORD (defined in [MS-DTYP] section 2.2.9) color masks that specify the red, green, and blue components, respectively, of each pixel. This is valid when used with 16 and 32-bits per pixel bitmaps.

    Then I noticed that there are very few cases where screenshots are saved with the type BI_BITFIELDS.

    Include official GDI example -> Capturing an Image

    Part of the code:

     // Get the BITMAP from the HBITMAP
        GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen);
    
        BITMAPFILEHEADER   bmfHeader;    
        BITMAPINFOHEADER   bi;
    
        bi.biSize = sizeof(BITMAPINFOHEADER);    
        bi.biWidth = bmpScreen.bmWidth;    
        bi.biHeight = bmpScreen.bmHeight;  
        bi.biPlanes = 1;    
        bi.biBitCount = 32;    
        bi.biCompression = BI_RGB;    
        bi.biSizeImage = 0;  
        bi.biXPelsPerMeter = 0;    
        bi.biYPelsPerMeter = 0;    
        bi.biClrUsed = 0;    
        bi.biClrImportant = 0;
    
        DWORD dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
    

    So I suggest you follow the official example to modify the current code.

    The fastest solution: Add bmp.dib.biCompression = BI_RGB;

    Where to add?

    Code for reference:

            if (dib)
            {
                DIB* info = reinterpret_cast<DIB*>(dib);
                BMP bmp = { 0 };
                bmp.header.type = 0x4D42;
                bmp.header.offset = 54;
                bmp.header.bfSize = info->biSizeImage + bmp.header.offset;
    
                bmp.dib = *info;
                bmp.dib.biCompression = BI_RGB;
    
                std::cout << "Type: " << std::hex << bmp.header.type << std::dec << "\n";
    
                ....