Search code examples
cwinapibitmapbmphbitmap

C: Trouble when trying to comvert HBITMAP into .bmp file


I am creating a function to save a screenshot into a .bmp file, I've managed to save the screen into an HBITMAP but I'm having trouble when saving it. I would appreciate some help.

Here is the header with the function:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <wingdi.h>
#include <winuser.h>

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
typedef unsigned long long u64;

void getScreen()
{
    u16 screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    u16 screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);

    HDC hdc = GetDC(NULL); //get a desktop dc (NULL for entire screen)
    HDC hDest = CreateCompatibleDC(hdc); //create a dc for capture

    ReleaseDC(NULL, hdc);

    HBITMAP hbCapture = CreateCompatibleBitmap(hdc, screenWidth, screenHeight);
    SelectObject(hDest, hbCapture);

    //Copy screen to bitmap
    BitBlt(hDest, 0, 0, screenWidth, screenHeight, hdc, 0, 0, SRCCOPY);

//test
    ReleaseDC(NULL, hdc);

    char memBuffer[10000];
    BITMAPINFO bitmapInfo;
    bitmapInfo.bmiHeader.biHeight = screenHeight;
    bitmapInfo.bmiHeader.biWidth = screenWidth;
    bitmapInfo.bmiHeader.biSize = screenWidth * screenHeight * 3;
    bitmapInfo.bmiHeader.biCompression = BI_RGB;//NOT SURE
    bitmapInfo.bmiHeader.biPlanes = 1;
    bitmapInfo.bmiHeader.biBitCount = 8; //NOT SURE
    bitmapInfo.bmiHeader.biClrImportant = 0;
    GetDIBits(hDest, hbCapture, 0, screenHeight, &memBuffer, &bitmapInfo, DIB_RGB_COLORS);

    FILE * fPointer = fopen("screen.png", "wb");//TO CHANGE
    WriteFile(fPointer, &memBuffer, (WORD) sizeof(memBuffer), 0, NULL);

    fclose(fPointer);
//test

    //Clean up
    ReleaseDC(NULL, hdc);
    DeleteDC(hDest);
    DeleteObject(hbCapture);
}

And here is also the main.c:

#include "main.h"

int main()
{
    getScreen();

    return 0;
}

I've already read all the questions about this topic in StackOverflow and none of them has clarified the issue for me.


Solution

  • bitmapInfo.bmiHeader.biSize = screenWidth * screenHeight * 3;
    

    biSize is not the total pixel count, it's the size of bitmap info structure, it's always sizeof(BITMAPINFOHEADER).

    You want to set bitmapInfo.bmiHeader.biSizeImage instead. This value is roughly (width * height * bits_per_pixel/8) but it has to be adjusted because each row is padded. Also don't mix fopen handle with CreateFile handle as noted in comments.

    fopen("screen.png", "wb");
    

    You cannot save to png format using this method. You need a different library like Gdiplus for png format.

    To save as bitmap, you have to setup BITMAFILEHEADER, BITMAPINFOHEADER and write them to file, and then write the bits obtained from GetDIBits

    You may want to avoid using typedef unsigned char u8; etc. You can include <stdint.h> and use standard types uint8_t, uint16_t, uint32_t etc. Or you can use standard Windows types like BYTE

    void getScreen()
    {
        int screenWidth  = GetSystemMetrics(SM_CXVIRTUALSCREEN);
        int screenHeight  = GetSystemMetrics(SM_CYVIRTUALSCREEN);
    
        HDC hdc = GetDC(NULL);
        HDC hDest = CreateCompatibleDC(hdc);
        HBITMAP hbCapture = CreateCompatibleBitmap(hdc, screenWidth , screenHeight );
        HBITMAP oldbmp = (HBITMAP)SelectObject(hDest, hbCapture);
        BitBlt(hDest, 0, 0, screenWidth , screenHeight , hdc, 0, 0, SRCCOPY);
    
        WORD bpp = 24; //<- bits per pixel
        int size = ((screenWidth  * bpp + 31) / 32) * 4 * screenHeight ;
    
        BITMAPINFOHEADER bmp_info_hdr;
        bmp_info_hdr.biSize = sizeof(bmp_info_hdr);
        bmp_info_hdr.biHeight  = screenHeight ;
        bmp_info_hdr.biWidth  = screenWidth ;
        bmp_info_hdr.biPlanes = 1;
        bmp_info_hdr.biBitCount = bpp;
        bmp_info_hdr.biCompression = BI_RGB;
        bmp_info_hdr.biSizeImage = size;
    
        BITMAPFILEHEADER bmp_file_hdr;
        bmp_file_hdr.bfType = (WORD)'MB';
        bmp_file_hdr.bfOffBits = sizeof(bmp_file_hdr) + sizeof(bmp_info_hdr);
        bmp_file_hdr.bfSize = bmp_file_hdr.bfOffBits + size;
    
        char *buffer = malloc(size);
        GetDIBits(hDest, hbCapture, 0, screenHeight , buffer, 
            (BITMAPINFO*)&bmp_info_hdr, DIB_RGB_COLORS);
    
        FILE * fPointer = fopen("screen.bmp", "wb");
        fwrite((char*)&bmp_file_hdr, sizeof(bmp_file_hdr), 1, fPointer);
        fwrite((char*)&bmp_info_hdr, sizeof(bmp_info_hdr), 1, fPointer);
        fwrite(buffer, size, 1, fPointer);
        fclose(fPointer);
    
        free(buffer);
    
        SelectObject(hDest, oldbmp);
        DeleteObject(hbCapture);
        DeleteDC(hDest);
        ReleaseDC(NULL, hdc);
    }