2

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!

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
user3865729
  • 23
  • 1
  • 1
  • 4
  • Your question makes no sense, sorry! The `lpBits` is most probably just a bunch of `unsigned char`'s. – πάντα ῥεῖ Jul 24 '14 at 21:33
  • let me try to elaborate then. The previous code is from my understanding code that will save a bitmap file on my computer. the SaveImage function has 4 parameters... from my understanding, the first is the location where I want the file to be saved, the second (which I am not sure of) are probably the array of bits of the bitmap imge, and the third and forth are just the hight and width of the image. The first, third, and forth are easy enough to understand... but the second is probably an array... but is it a double array, triple? what is its structure exactly? IF it even is an array... – user3865729 Jul 24 '14 at 21:42
  • See my answer here: http://stackoverflow.com/questions/24921013/incorrect-bitmap-copy-output/24921985#24921985 – Brandon Jul 24 '14 at 22:11
  • "wb" means write byte and "rb" means read byte. If you are trying to save the bitmap as your functions suggest SaveImage(), then you only need to worry about the filename and assert if your filename is null or not. To load an image you also need to worry about filename as your main input parameter. You also need the width and height. lpBits according to your comments: specifies the bitmap bits. So the Question is, do you have to specify the bitmap`s "BITS" in order to save the file. Maybe you don`t, so I would just assign it a value like: 0 or null. However the fwrite function calls lbBits. – Juniar Jul 25 '14 at 00:16
  • lpBits is a variable that get called by the fwrite() function. So it is used to create a data structure such as an array. And in most cases you will need to create a 2D array that will be used to store data. This also is related with color format RGB. – Juniar Jul 25 '14 at 00:44
  • agreed. So how would I go about creating lpBits. The picture I wish to get is a screenshot. Usually I would think that the screen shot could be taken from the clipboard... but I have no idea on how to extract that array of bits or image... and I have no idea on how to make it compatible with the lpBits variable so I can eventually insert it into the function SaveImage(). Would you happen to know xD ? – user3865729 Jul 25 '14 at 00:57

3 Answers3

4

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

Paolo Brandoli
  • 4,681
  • 26
  • 38
  • what modification would I have to make to fix my code? would it be just switching LONG lImageSize = BMIH.biSizeImage; with LONG lImageSize = h * ((w * 3 + 3) & 0xfffffffc);? Or possibly other lines of code as well or? – user3865729 Jul 24 '14 at 21:52
  • Do you know of a function that could give you the array when a HBITMAP handle is passed to it? Thing is, I have the handle to the clipboard image (which was taken with a screenshot)... and I need that lpBits array to pass to the function stated in this thread... – user3865729 Jul 25 '14 at 01:05
2

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;
}
NetVipeC
  • 4,402
  • 1
  • 17
  • 19
0

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);
}
Logicrat
  • 4,438
  • 16
  • 22
  • Oh ok so.... the CreateDISection returns a longpointer? would the following code be apropriate then: fclose(CreateDIBSection(blah blah...), 1, lImageSize, pFile);? – user3865729 Jul 24 '14 at 21:44
  • and would you mind giving me an example of how CreateDIBSection() works? – user3865729 Jul 24 '14 at 21:47