user3865729
user3865729

Reputation: 23

C++ Saving a Bitmap File

so what I am trying to do is to have my program take a screenshot and save it on the computer. The part of actually taking the screenshot I will program later, and I am first trying to solve the problem of how to actually save a bmp file on the computer. I found the following code that would help me out with that:

// szPathName : Specifies the pathname

// lpBits    : Specifies the bitmap bits

// w    : Specifies the image width

// h    : Specifies the image height

bool SaveImage(char* szPathName, void* lpBits, int w, int h)

{

    //Create a new file for writing

    FILE *pFile = fopen(szPathName, "wb");

    if(pFile == NULL)

    {

        return false;

    }

        BITMAPINFOHEADER BMIH;

        BMIH.biSize = sizeof(BITMAPINFOHEADER);

        BMIH.biSizeImage = w * h * 3;

        // Create the bitmap for this OpenGL context

        BMIH.biSize = sizeof(BITMAPINFOHEADER);

        BMIH.biWidth = w;

        BMIH.biHeight = h;

        BMIH.biPlanes = 1;

        BMIH.biBitCount = 24;

        BMIH.biCompression = BI_RGB;

        BMIH.biSizeImage = w * h* 3;

        BITMAPFILEHEADER bmfh;

        int nBitsOffset = sizeof(BITMAPFILEHEADER) + BMIH.biSize;

        LONG lImageSize = BMIH.biSizeImage;

        LONG lFileSize = nBitsOffset + lImageSize;

        bmfh.bfType = 'B'+('M'<<8);

        bmfh.bfOffBits = nBitsOffset;

        bmfh.bfSize = lFileSize;

        bmfh.bfReserved1 = bmfh.bfReserved2 = 0;

        //Write the bitmap file header

        UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1,

        sizeof(BITMAPFILEHEADER), pFile);

        //And then the bitmap info header

        UINT nWrittenInfoHeaderSize = fwrite(&BMIH,

        1, sizeof(BITMAPINFOHEADER), pFile);

        //Finally, write the image data itself

        //-- the data represents our drawing

        UINT nWrittenDIBDataSize =

        fwrite(lpBits, 1, lImageSize, pFile);

        fclose(pFile);



    return true;

}

So what is the issue.... Well I do not understand the varialbe IpBits. there is a brief explanation of the lpBits in the comments of the code (lpBits: Specifies the bitmap bits)... but I don't know what that actually means. I tried going into msdn and looking into the fopen and fclose functions since fclose is the function that will eventually use the lpbits that I pass to the SaveImage function.... and well it seemed that the lpBits variable in the fclose function varies depending on what variable was passed in the fopen function. I tried to find out what the "wb" of the fopen function means but was unsuccessful (even searching on msdn).

QUESTION: in the case that I use "wb" as the second variable in the fopen function in my previous code, what exactly would the lpBits in the fclose function be? When I ask what exactly would it be, I mean... what type of variable is it (in the code it is placed as void* which basically allows it to be any variable) and I would appriciate any feedback you could give.

Thanks guys!

Upvotes: 2

Views: 21727

Answers (3)

Logicrat
Logicrat

Reputation: 4468

Study the Windows API CreateDIBSection(). With this API, Windows will allocate the memory for the pixels you need. When it allocates the memory, it will give you the memory address in a long pointer. That is what "lpbits" refers to - a long pointer to the bits that were allocated.

The "wb" in fopen() means "Write Binary". Without the "b" (i.e., if you use only a "w" in the second parameter), fopen will open the file in text mode, which will cause the file to be written as though it were text, which may change the '\n' character in a system-dependent way.

Here is a constructor I use for class PixMapAny, which is primarily used for off-screen drawing but can also be used to read pixmaps.

PixMapAny::PixMapAny(int width, int height, int depth)
{
    m_dc.CreateCompatibleDC(NULL);

    m_width     =   width;
    m_height    =   height;
    m_depth     =   depth;

    //  The declaration of 'fake' creates a storage area big enough to
    //  contain a BITMAPINFO structure composed of a BITMAPINFOHEADER
    //  and a 256-element array of RGBQUAD values.
    long                    fake[266];
    LPBITMAPINFO            pbmi    =   (LPBITMAPINFO) fake;

    //  Initialize the area to all zeros
    for(int x = 0; x < 266; x++)    fake[x] =   0;

    //  Fill in the header with the characteristics of the bitmap we want
    //  to write.
    pbmi->bmiHeader.biSize          =   sizeof(pbmi->bmiHeader);
    pbmi->bmiHeader.biWidth         =   m_width;
    pbmi->bmiHeader.biHeight        =   -m_height;
    pbmi->bmiHeader.biPlanes        =   1;
    pbmi->bmiHeader.biBitCount      =   24;
    pbmi->bmiHeader.biCompression   =   BI_RGB;

    //  Tell the system to allocate room for the pixmap.
    //  'ppvbits' receives a pointer to the pixmap memory.
    m_dib   =   CreateDIBSection(m_dc.m_hDC, pbmi, DIB_RGB_COLORS, &m_ppvbits, NULL, 0);

    //  ____________________________________________________________________________

    //  Select the bitmap into the device context

    m_prev  =   (CBitmap *) m_dc.SelectObject(m_dib);

    //  ____________________________________________________________________________
}

In this example, the height is negative because the rows in the pixmap will be ordered in a top-down fashion, i.e., the address of the top row is less than the address of the bottom row.

Once a pixmap has been constructed in this manner, copying into an area of an open windows is easy. Here, pDC is a pointer to the target window's device context, and x and y are coordinates within that window:

void    PixMapAny::Blit(int x, int y, CDC * pDC)
{
    pDC->BitBlt(x,y,m_width,m_height,&m_dc,0,0,SRCCOPY);
}

Upvotes: 0

NetVipeC
NetVipeC

Reputation: 4432

Commenting the code:

// lpBits stand for long pointer bits

// szPathName : Specifies the pathname        -> the file path to save the image
// lpBits    : Specifies the bitmap bits      -> the buffer (content of the) image
// w    : Specifies the image width
// h    : Specifies the image height
bool SaveImage(char* szPathName, void* lpBits, int w, int h) {
    // Create a new file for writing
    FILE* pFile = fopen(szPathName, "wb"); // wb -> w: writable b: binary, open as writable and binary
    if (pFile == NULL) {
        return false;
    }

    BITMAPINFOHEADER BMIH;                         // BMP header
    BMIH.biSize = sizeof(BITMAPINFOHEADER);
    BMIH.biSizeImage = w * h * 3;
    // Create the bitmap for this OpenGL context
    BMIH.biSize = sizeof(BITMAPINFOHEADER);
    BMIH.biWidth = w;
    BMIH.biHeight = h;
    BMIH.biPlanes = 1;
    BMIH.biBitCount = 24;
    BMIH.biCompression = BI_RGB;
    BMIH.biSizeImage = w * h * 3;

    BITMAPFILEHEADER bmfh;                         // Other BMP header
    int nBitsOffset = sizeof(BITMAPFILEHEADER) + BMIH.biSize;
    LONG lImageSize = BMIH.biSizeImage;
    LONG lFileSize = nBitsOffset + lImageSize;
    bmfh.bfType = 'B' + ('M' << 8);
    bmfh.bfOffBits = nBitsOffset;
    bmfh.bfSize = lFileSize;
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;

    // Write the bitmap file header               // Saving the first header to file
    UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1, sizeof(BITMAPFILEHEADER), pFile);

    // And then the bitmap info header            // Saving the second header to file
    UINT nWrittenInfoHeaderSize = fwrite(&BMIH, 1, sizeof(BITMAPINFOHEADER), pFile);

    // Finally, write the image data itself
    //-- the data represents our drawing          // Saving the file content in lpBits to file
    UINT nWrittenDIBDataSize = fwrite(lpBits, 1, lImageSize, pFile);
    fclose(pFile); // closing the file.

    return true;
}

Some improvement to substitute the C code with C++:

The improvement were:

  • Using std::string instead of char* that originally need to be const char*
  • Using vector instead of void* (could be a problem in the original code, if the width and height provided was wrong or miscalculated the program will read invalid memory because there is no notion of size of lpBits. The content of the file not need to change when saving, adding const-correctness
  • Using std::ofstream instead of FILE.

Code:

// lpBits stand for long point bits

// szPathName : Specifies the pathname        -> the file path to save the image
// lpBits    : Specifies the bitmap bits      -> the buffer (content of the) image
// w    : Specifies the image width
// h    : Specifies the image height
bool SaveImage(const std::string& szPathName, const std::vector<char>& lpBits, int w, int h) {
    // Create a new file for writing
    std::ofstream pFile(szPathName, std::ios_base::binary);
    if (!pFile.is_open()) {
        return false;
    }

    BITMAPINFOHEADER bmih;
    bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biWidth = w;
    bmih.biHeight = h;
    bmih.biPlanes = 1;
    bmih.biBitCount = 24;
    bmih.biCompression = BI_RGB;
    bmih.biSizeImage = w * h * 3;

    BITMAPFILEHEADER bmfh;
    int nBitsOffset = sizeof(BITMAPFILEHEADER) + bmih.biSize;
    LONG lImageSize = bmih.biSizeImage;
    LONG lFileSize = nBitsOffset + lImageSize;
    bmfh.bfType = 'B' + ('M' << 8);
    bmfh.bfOffBits = nBitsOffset;
    bmfh.bfSize = lFileSize;
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;

    // Write the bitmap file header
    pFile.write((const char*)&bmfh, sizeof(BITMAPFILEHEADER));
    UINT nWrittenFileHeaderSize = pFile.tellp();

    // And then the bitmap info header
    pFile.write((const char*)&bmih, sizeof(BITMAPINFOHEADER));
    UINT nWrittenInfoHeaderSize = pFile.tellp();

    // Finally, write the image data itself
    //-- the data represents our drawing
    pFile.write(&lpBits[0], lpBits.size());
    UINT nWrittenDIBDataSize = pFile.tellp();
    pFile.close();

    return true;
}

Upvotes: 2

Paolo Brandoli
Paolo Brandoli

Reputation: 4750

lpBits refers to an array of bytes with size lImageSize.

Each byte of the array will contain a single color component, in this order: B, G and R: each pixel takes three bytes, one for each color component.

Please note that the code you posted doesn't take into consideration the 4 bytes alignment of each image's row. Each image's row must be aligned on a 4 bytes boundary, so the correct formula for lImageSize is:

lImageSize = h * ((w * 3 + 3) & 0xfffffffc);

You can create the lpbits by yourself:

lpbits = new BYTE[lImageSize];

or by using CreateDIBSection() as stated in the answer from Logicrat

Upvotes: 4

Related Questions