how do i take a grayscale screenshot with c++? - c++

I have this piece of code here that takes a screenshot and as bitmaps and saves it as a BMP file. it works just fine but I want to be able to modify the bits and make it grayscale instead of an RGB map.
I found a way to do this but I had to save the BMPINFOHEADER and RGBQUAD array, then read it with the grayscale function it kind of worked, but the size stays the same.
I am very noob at this but I think the grayscale image should be a lot smaller than an RGB one.
is there a way of removing color from the bitmap right away without having to re-read it and modify it?
int CaptureBMP(LPCTSTR szFile)
{
// Source[1]
HDC hdcScr, hdcMem;
HBITMAP hbmScr;
BITMAP bmp;
int iXRes, iYRes;
// Create a normal DC and a memory DC for the entire screen. The
// normal DC provides a "snapshot" of the screen contents. The
// memory DC keeps a copy of this "snapshot" in the associated
// bitmap.
hdcScr = CreateDC("DISPLAY", NULL, NULL, NULL);
hdcMem = CreateCompatibleDC(hdcScr);
iXRes = GetDeviceCaps(hdcScr, HORZRES);
iYRes = GetDeviceCaps(hdcScr, VERTRES);
// Create a compatible bitmap for hdcScreen.
hbmScr = CreateCompatibleBitmap(hdcScr, iXRes, iYRes);
if (hbmScr == 0) return 0;
// Select the bitmaps into the compatible DC.
if (!SelectObject(hdcMem, hbmScr)) return 0;
// Copy color data for the entire display into a
// bitmap that is selected into a compatible DC.
if (!StretchBlt(hdcMem,0, 0, iXRes, iYRes,hdcScr,0, 0, iXRes, iYRes,SRCCOPY)) return 0;
// Source[2]
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap's color format, width, and height.
if (!GetObject(hbmScr, sizeof(BITMAP), (LPSTR)&bmp)) return 0;
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits != 24)
pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * (1 << cClrBits));
// There is no RGBQUAD array for the 24-bit-per-pixel format.
else
pbmi = (PBITMAPINFO)LocalAlloc(LPTR,sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1 << cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) / 8 * pbmi->bmiHeader.biHeight * cClrBits;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE* hp; // byte pointer
DWORD dwTmp;
pbih = (PBITMAPINFOHEADER)pbmi;
lpBits = (LPBYTE)GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits) return 0;
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if (!GetDIBits(hdcMem, hbmScr, 0, (WORD)pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS)) return 0;
// Create the .BMP file.
hf = CreateFile(szFile, GENERIC_READ | GENERIC_WRITE, (DWORD)0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
if (hf == INVALID_HANDLE_VALUE) return 0;
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed *
sizeof(RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), (LPDWORD)&dwTmp, NULL)) return 0;
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID)pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof(RGBQUAD),
(LPDWORD)&dwTmp, NULL))
return 0;
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR)hp, (int)cb, (LPDWORD)&dwTmp, NULL)) return 0;
// Close the .BMP file.
if (!CloseHandle(hf)) return 0;
// Free memory.
GlobalFree((HGLOBAL)lpBits);
ReleaseDC(0, hdcScr);
ReleaseDC(0, hdcMem);
return 1;
}

I used array conversion, which is mainly from the rgbRed, rgbGreen and rgbBlue components of the original true color map to the gray value Y of the gray image.
It can be obtained by using the following formula:
Y=0.299 * rgbRed+0.587 * rgbGreen+0.114 * rgbBlue
The processing code has been added to your code, you can refer to the following code, the output file size is 1/4 of the original.
#pragma pack(1)
typedef struct tag_color_32 {
BYTE Red;
BYTE Green;
BYTE Blue;
BYTE Alpha;
}color_32;
int CaptureBMP(LPCTSTR szFile)
{
// Source[1]
HDC hdcScr, hdcMem;
HBITMAP hbmScr;
BITMAP bmp;
.................
.................
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR)hp, (int)cb, (LPDWORD)& dwTmp, NULL)) return 0;
// Close the .BMP file.
if (!CloseHandle(hf)) return 0;
/*********************edit 2022/1/7******************************/
FILE* originImg;
fopen_s(&originImg, "lena-32.bmp", "rb");
if (originImg == NULL) return 0;
int sizeFileHeader = sizeof(BITMAPFILEHEADER);
int sizeInfoHeader = sizeof(BITMAPINFOHEADER);
BITMAPFILEHEADER* bitmapFileHeader = new BITMAPFILEHEADER[sizeFileHeader + 1];
BITMAPINFOHEADER* bitmapInfoHeader = new BITMAPINFOHEADER[sizeInfoHeader + 1];
memset(bitmapFileHeader, 0, sizeFileHeader + 1);
memset(bitmapInfoHeader, 0, sizeInfoHeader + 1);
fread(bitmapFileHeader, sizeof(char), sizeFileHeader, originImg);
fseek(originImg, sizeFileHeader, 0);
fread(bitmapInfoHeader, sizeof(char), sizeInfoHeader, originImg);
int srcImageLineByteCount = (((bitmapInfoHeader->biWidth * 32) + 31) / 32) * 4; //Calculates the number of bytes of pixels per line of the original 32 bitmap
int grayImageLineByteCount = (((bitmapInfoHeader->biWidth) * 8 + 31) / 32) * 4; //Calculate the number of bytes of pixels in each row of 8-bit grayscale map
//************Bitmap header**********************
//Creates a two-dimensional array with high biHeight and width srcImageLineByteCount, and initializes the array
color_32* origImgData = new color_32[bitmapInfoHeader->biHeight * bitmapInfoHeader->biWidth];
for (int i = 0; i < bitmapInfoHeader->biHeight * bitmapInfoHeader->biWidth; i++)
{
fread(origImgData + i, 4, 1, originImg);
}
fclose(originImg);
// palette
RGBQUAD* pRgbQuards = new RGBQUAD[256];
for (int i = 0; i < 256; i++)
{
pRgbQuards[i].rgbBlue = i;
pRgbQuards[i].rgbRed = i;
pRgbQuards[i].rgbGreen = i;
pRgbQuards[i].rgbReserved = 0;
}
//Modify headers
bitmapInfoHeader->biBitCount = 8;
bitmapInfoHeader->biClrUsed = 256;
bitmapInfoHeader->biSizeImage = (bitmapInfoHeader->biHeight) * grayImageLineByteCount;
//8 is a grayscale image with 256 RGBQUAD data structures. A color palette takes up 4 bytes of data, so the color palette length of 256 color images is 256*4 and 1024 bytes
bitmapFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256; //The offset plus the size of the palette
bitmapFileHeader->bfSize = bitmapFileHeader->bfOffBits + bitmapInfoHeader->biSizeImage;
//Write the data
FILE* grayImg;
fopen_s(&grayImg, "lena-gray.bmp", "wb");
//Written to the file
fwrite(bitmapFileHeader, sizeof(char), sizeof(BITMAPFILEHEADER), grayImg);
fwrite(bitmapInfoHeader, sizeof(char), sizeof(BITMAPINFOHEADER), grayImg);
fwrite(pRgbQuards, sizeof(RGBQUAD), 256, grayImg);
//Grayscale map a two-dimensional array of bitmap data
for (int i = 0; i < bitmapInfoHeader->biHeight; i++)
{
for (int j = 0; j < bitmapInfoHeader->biWidth; j++) //The number of bytes of pixels per row of a grayscale image
{
float pr = origImgData[i * bitmapInfoHeader->biWidth + j].Red;
float pg = origImgData[i * bitmapInfoHeader->biWidth + j].Green;
float pb = origImgData[i * bitmapInfoHeader->biWidth + j].Blue;
BYTE data = pr * 0.299 + pg * 0.587 + pb * 0.114;
fwrite(&data, 1, 1, grayImg);
for (int j = bitmapInfoHeader->biWidth; j < grayImageLineByteCount; j++) {
BYTE data = 0;
fwrite(&data, 1, 1, grayImg);
}
}
}
fclose(grayImg);
//Free memory.
delete[]origImgData;
/*******************edit 2022/1/7****************************/
// Free memory.
GlobalFree((HGLOBAL)lpBits);
ReleaseDC(0, hdcScr);
ReleaseDC(0, hdcMem);
return 1;
}
int main()
{
CaptureBMP(L"lena-32");
std::cout << "Hello World!\n";
}

Related

Can I output a one channel image acquired from camera into a winAppi window?

I have a pointer to an image acquired from acamera using a third party SDK. The image is one band (Mono 8). I want to output it as a bittmap into a winAppi window. What i do is
HBITMAP hBitmap = NULL;
BITMAPINFOHEADER bmih;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biWidth = 1280;
bmih.biHeight = 960;
bmih.biPlanes = 1;
bmih.biBitCount = 8;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
BITMAPINFO dbmi;
ZeroMemory(&dbmi, sizeof(dbmi));
dbmi.bmiHeader = bmih;
dbmi.bmiColors->rgbBlue = 0;
dbmi.bmiColors->rgbGreen = 0;
dbmi.bmiColors->rgbRed = 0;
dbmi.bmiColors->rgbReserved = 0;
void* bits = &aquiredImageCPU.m_sMemory.ptr()[0];
hBitmap = CreateDIBitmap(dc, &bmih, CBM_INIT, bits, &dbmi, DIB_RGB_COLORS);
src = CreateCompatibleDC(dc);
SelectObject(src, hBitmap);
BitBlt(dc, 10,10,512, 512, src, 0, 0, SRCCOPY);
If i output the image as matrix and previou it using my library (a library where i use imsave similiar to matlabs imsave) i can see that the image is ok(grayscale image). But when i output it to winAppi window it tranforms it to RGB. I think it has to do with
HDC dc = GetDC(hwnd);
src = CreateCompatibleDC(dc);
Any suggestions?
Thank you
What i did in a similiar case is to take every 8 bits and to copy them 2 more times after the chunk of 8 bits. That works but it is costly and i need to have a real tile application.
auto outputImageHight = 1280;
auto outputImageWidth = 960;
unsigned char *myArray = new unsigned char[3 * outputImageHight * outputImageWidth];
for (int i = 0; i < outputImageHight; i++)
for (int j = 0; j < outputImageWidth; j++)
{
unsigned char ucTmp = (unsigned char)(image(i, j));
myArray[3 * (i + outputImageHight * j) + 0] = ucTmp;
myArray[3 * (i + outputImageHight * j) + 1] = ucTmp;
myArray[3 * (i + outputImageHight * j) + 2] = ucTmp;
}
An 8-bit bitmap requires a color table. Since you want grayscale, you have to set up the color table to have 256 levels of gray. You've set the first one to black, which is correct, but you haven't set the rest.
BITMAPINFO is actually a variably sized structure. The bmiColors field is just a placeholder for the first color in the color table. You have to allocate extra space for the entire color table and fill it out.
std::size_t size = sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD);
std::vector<char> buffer(size);
BITMAPINFO *dbmi = reinterpret_cast<BITMAPINFO *>(buffer.data());
ZeroMemory(dbmi, size); // probably unnecessary
dbmi->bmiHeader = bmih;
for (int i = 0; i < 256; ++i) {
dbmi->bmiColors[i].rgbBlue = i;
dbmi->bmiColors[i].rgbGreen = i;
dbmi->bmiColors[i].rgbRed = i;
dbmi->bmiColors[i].rgbReserved = 0;
}

Windows GetDIBits not returning expected values

I'm currently writing a small program that scans the screen and looks for pixels. My issue is that GetDIBits function doesn't appear to return a proper screenshot of the screen.
Copying the bitmap to the clipboard does put the right screen image in the clipboard.
I decided to print out the function's output to a BMP file to get an idea of what's going on and it clearly isn't what I'm expecting.
I'll also mention that I have 3 monitors, incase that could explain why it's not behaving like expected.
class Test {
int screenWidth;
int screenHeight;
HWND targetWindow;
HDC targetDC;
HDC captureDC;
RGBQUAD *pixels;
HBITMAP captureBitmap;
bool TakeScreenshot() {
ZeroMemory(pixels, screenHeight*screenWidth);
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
targetWindow = GetDesktopWindow();
targetDC = GetDC(NULL);
captureDC = CreateCompatibleDC(targetDC);
captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
HGDIOBJ old = SelectObject(captureDC, captureBitmap);
if (!old)
printf("Error selecting object\n");
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, captureBitmap);
CloseClipboard();
if (BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC, 0, 0, SRCCOPY)) {
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = screenWidth;
bmi.bmiHeader.biHeight = -screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
if (!SelectObject(captureDC, old))
printf("Error unselecting object\n");
if (!GetDIBits(captureDC,
captureBitmap,
0,
screenHeight,
pixels,
&bmi,
DIB_RGB_COLORS
)) {
printf("%s: GetDIBits failed\n", __FUNCTION__);
return false;
}
}
else {
printf("%s: BitBlt failed\n", __FUNCTION__);
return false;
}
return true;
}
// This is from somewhere on stackoverflow - can't find where.
void MakePicture() {
typedef struct /**** BMP file header structure ****/
{
unsigned int bfSize; /* Size of file */
unsigned short bfReserved1; /* Reserved */
unsigned short bfReserved2; /* ... */
unsigned int bfOffBits; /* Offset to bitmap data */
} BITMAPFILEHEADER;
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
unsigned short bfType = 0x4d42;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfSize = 2 + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2560 * 1440 * 3;
bfh.bfOffBits = 0x36;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = screenWidth;
bih.biHeight = screenHeight;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 5000;
bih.biYPelsPerMeter = 5000;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
FILE *file;
fopen_s(&file, "test.bmp", "wb");
if (!file)
{
printf("Could not write file\n");
return;
}
/*Write headers*/
fwrite(&bfType, 1, sizeof(bfType), file);
fwrite(&bfh, 1, sizeof(bfh), file);
fwrite(&bih, 1, sizeof(bih), file);
/*Write bitmap*/
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y].rgbRed;
unsigned char g = pixels[x + y].rgbGreen;
unsigned char b = pixels[x + y].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
fclose(file);
}
Test() {
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
pixels = new RGBQUAD[screenWidth * screenHeight];
}
~Test() {
//cleanup
}
};
Here is the result that the code is giving(instead of a screenshot):
It appears like it takes a few pixels from the top of my screen and stretches them into an image. The screenshot is from Visual Studio being open(orange part being the notifications).
If I put a giant red square (255, 0, 0) in my screen, if it's height isn't 0, the pixels array will not contain a single red pixel.
BitBlt performs the actual copying. Clipboard functions should be called after BitBlt
Also note, in mutli-monitor settings, SM_CXSCREEN/Y... give the size for the primary monitor. Use SM_XVIRTUALSCREEN/XV... for the whole screen. SM_XVIRTUALSCREEN/Y will give the X/Y coordinate (it's usually zero)
Be sure to release all the handles and delete the used objects when you are finished. In fact, there is no need to declare targetDC etc. as class members.
If the application is not DPI aware, the bitmap may look smaller depending on DPI settings. Call SetProcessDPIAware() at the start of the program for a quick fix, or set the manifest.
As noted in comment, with SetClipboardData(CF_BITMAP, captureBitmap); the system takes over captureBitmap. Avoid calling this function or make a copy of the bitmap to pass to clipboard.
int main()
{
int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
screen_x = 0;
screen_y = 0;
RGBQUAD* pixels = new RGBQUAD[screenWidth * screenHeight];
DWORD size = screenWidth * screenHeight * 4;
ZeroMemory(pixels, size);
HDC targetDC = GetDC(NULL);
HDC captureDC = CreateCompatibleDC(targetDC);
HBITMAP captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
HGDIOBJ old = SelectObject(captureDC, captureBitmap);
if(!BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC,
screen_x, screen_y, SRCCOPY))
printf("BitBlt error\n");
SelectObject(captureDC, old);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = screenWidth;
bmi.bmiHeader.biHeight = -screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
if(OpenClipboard(NULL))
{
EmptyClipboard();
SetClipboardData(CF_BITMAP,
CopyImage(captureBitmap, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE));
CloseClipboard();
}
if(!GetDIBits(targetDC,
captureBitmap,
0,
screenHeight,
pixels,
&bmi,
DIB_RGB_COLORS
))
printf("%s: GetDIBits failed\n", __FUNCTION__);
BITMAPFILEHEADER filehdr = { 'MB', 54 + size, 0, 0, 54 };
std::ofstream f("test.bmp", std::ios::binary);
f.write((char*)&filehdr, sizeof(filehdr));
f.write((char*)&bmi, sizeof(bmi));
f.write((char*)pixels, size);
//cleanup:
SelectObject(captureDC, old);
DeleteObject(captureBitmap);
DeleteDC(captureDC);
ReleaseDC(0, targetDC);
}
GetDIBits function reference, remarks section:
The bitmap identified by the hbmp parameter must not be selected into
a device context when the application calls this function.
Deselect bitmap before calling GetBIBits.
HBITMAP oldBitmap = SelectObject(captureDC, captureBitmap);
...
// Deselect captureBitmap by selecting oldBitmap.
SelectObject(captureDC, oldBitmap);
Remember to add cleanup code (restore bitmap, destroy bitmap, destroy or release device contexts).
Additional bug:
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y].rgbRed;
unsigned char g = pixels[x + y].rgbGreen;
unsigned char b = pixels[x + y].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
I think it should be
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y*screenWidth].rgbRed;
unsigned char g = pixels[x + y*screenWidth].rgbGreen;
unsigned char b = pixels[x + y*screenWidth].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
But rows require padding to multiplies of 4 bytes:
// Important: each row has to be padded to multiple of DWORD.
// Valid only for 24 bits per pixel bitmaps.
// Remark: 32 bits per pixel have rows always aligned (padding==0)
int padding = 3 - (screenWidth*3 + 3)%4;
// or
// int padding = 3 - ((screenWidth*3 + 3) & 3);
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y*screenWidth].rgbRed;
unsigned char g = pixels[x + y*screenWidth].rgbGreen;
unsigned char b = pixels[x + y*screenWidth].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
// Important: each row has to be padded to multiple of DWORD.
fwrite("\0\0\0\0", 1, padding, file);
}
Adjust file size (valid for 24 bits per pixel):
bfh.bfSize =
2
+ sizeof(BITMAPFILEHEADER)
+ sizeof(BITMAPINFOHEADER)
+ ((screenWidth*3 + 3) & ~3) * screenHeight;

Black image when creating a Bitmap from a selection of the screen in C++

I am trying to get a screen capture of a selected region of the desktop.
The problem I am having is that the output (.bmp file) is just black.
I took CreateBitmapInfoStruct(); and CreateBMPFile(); from MSDN. The rest has been snips from the internet Frankenstein-ed together by me.
Here is what my code looks like presently:
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
cout << "ERROR:1";
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(LPTSTR pszFile, PBITMAPINFO pbi,
HBITMAP hBMP, HDC hDC)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
cout << "ERROR:2";
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS);
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hf == INVALID_HANDLE_VALUE)
cout << "ERROR:4";
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL))
{
cout << "ERROR:5";
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)))
cout << "ERROR:6";
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
cout << "ERROR:7";
// Close the .BMP file.
if (!CloseHandle(hf))
cout << "ERROR:8";
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}
void getBit(string name) {
int nX = 300;
int nX2 = 600;
int nY = 300;
int nY2 = 700;
HDC hScrDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hScrDC);
int nWidth = nX2 - nX;
int nHeight = nY2 - nY;
HBITMAP hBitmap = CreateCompatibleBitmap(hScrDC, nWidth, nHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, nWidth, nHeight,
hScrDC, nX, nY, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemDC, hOldBitmap);
DeleteDC(hScrDC);
DeleteDC(hMemDC);
// now your image is held in hBitmap. You can save it or do whatever with it
PBITMAPINFO pbi = CreateBitmapInfoStruct(hBitmap);
//File name
LPTSTR path = L"test.bmp";
CreateBMPFile(path,pbi,hBitmap,hMemDC);
}
void main(void) {
getBit("SAVE");
cout << "Done";
int wait;
cin >> wait;
}
The biggest problem is that GetDIBits fails because you're passing a handle to a deleted memory context instead of a handle to a valid device context. If you fix that, it works.
When debugging, it's always a good idea to check all of the return values.
Other things I've noticed:
You should not delete hSrcDC, since you didn't create it. Use ReleaseDC, which is the companion to GetDC.
Consider using CAPTUREBLT in your BitBlt call.

raw bitmap data to jpeg or png C++

I have bytearray where every three bytes describes 1 pixel (RGB). The task is to convert it to jpeg or png.
Actually, I am using Zint (open source lib for generating barcodes) that uses libpng to generate image file and save it to file system, but in Zintthe function png_plot() except generating image also save it on disk which is undesirable.
As result I think there two ways:
1. from bitmap bytearray to bmp -> jpeg / png (using some other lib)
2. writing hook or some similar to png_plot()
Can you give me some advices?
Thank you.
Upd: for #peacemaker
FILE *f;
zint_symbol *my_symbol;
my_symbol = ZBarcode_Create();
ZBarcode_Encode_and_Buffer(my_symbol, (unsigned char *)argv[1], 0, 0);
f = fopen("bitmap.bmp", "w");
fwrite(my_symbol->bitmap, sizeof(*(my_symbol->bitmap)), my_symbol->bitmap_height * my_symbol->bitmap_width, f);
ZBarcode_Delete(my_symbol);
fclose(f);
In order to convert between image formats, the easiest way would be using the class CImage shared by MFC and ATL and defined in the header file atlimage.h.
CImage image;
HRESULT res = image.Load("in.bmp");
image.Save("out.jpg");
image.Save("out.gif");
image.Save("out.png");
image.Save("out.tif");
If you have a RGB buffer and want to create a bitmap: just create and save a bitmap header into a file and add the RGB buffer to it.
To create the header you can use the BITMAPFILEHEADER, BITMAPINFOHEADER and RGBQUAD structures from GDI defined in the header WinGDI.h
Here is an example on how to fill the header data:
BITMAPINFOHEADER bmpInfoHdr;
bmpInfoHdr.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHdr.biHeight = nHeight;
bmpInfoHdr.biWidth = nWidthPadded;
bmpInfoHdr.biPlanes = 1;
bmpInfoHdr.biBitCount = bitsPerPixel;
bmpInfoHdr.biSizeImage = nHeight * nWidthPadded * nSPP;
bmpInfoHdr.biCompression = BI_RGB;
bmpInfoHdr.biClrImportant = 0;
bmpInfoHdr.biClrUsed = 0;
bmpInfoHdr.biXPelsPerMeter = 0;
bmpInfoHdr.biYPelsPerMeter = 0;
bmpFileHdr.bfType = BITMAP_FORMAT_BMP;
bmpFileHdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + bmpInfoHdr.biSize +
sizeof(RGBQUAD)*numColors + bmpInfoHdr.biSizeImage);
bmpFileHdr.bfReserved1 = 0;
bmpFileHdr.bfReserved2 = 0;
bmpFileHdr.bfOffBits = (DWORD) (sizeof(BITMAPFILEHEADER) + bmpInfoHdr.biSize +
sizeof(RGBQUAD)*numColors);
Keep into account that the bitmaps are stored upside-down and that the width of the image must be aligned on a DWORD except for RLE-compressed bitmaps.(they must be multiple of 4 bytes, add a padding if necessary).
if ((nWidth%4) != 0)
nPadding = ((nWidth/4) + 1) * 4;
When saving your buffer, add the needed padding to each row...
Summarizing, these are the needed steps to create a bitmap file from a rgb buffer:
//1. create bmp header
//2. save header to file:
write(file, &bmpFileHdr, sizeof(BITMAPFILEHEADER));
write(file, &bmpInfoHdr, sizeof(BITMAPINFOHEADER));
write(file, &colorTable, numColors * sizeof(RGBQUAD));
//3. add rgb buffer to file:
for(int h=0; h<nHeight; h++) {
for(int w=0; w<nWidth; w++) {
//3.a) add row to file
//3.b) add padding for this row to file
}
}
I used the CImage Class from ATL.
int width=0, height=0;
char * val = "9788994774480";
zint_symbol *my_symbol;
my_symbol = ZBarcode_Create();
//ZBarcode_Encode_and_Buffer(my_symbol,(unsigned char *) val, 0, 0);
ZBarcode_Encode(my_symbol, (unsigned char *) val, 0);
ZBarcode_Buffer(my_symbol, 0);
height = my_symbol->bitmap_height;
width = my_symbol->bitmap_width;
char * imgBits = my_symbol->bitmap;
CImage img;
img.Create(width, height, 24 /* bpp */, 0 /* No alpha channel */);
int nPixel = 0;
for(int row = 0; row < height; row++)
{
for(int col = 0; col < width; col++)
{
BYTE r = (BYTE)imgBits[nPixel];
BYTE g = (BYTE)imgBits[nPixel+1];
BYTE b = (BYTE)imgBits[nPixel+2];
img.SetPixel(col, row , RGB(r, g, b));
nPixel += 3;
}
}
img.Save("CImage.bmp", Gdiplus::ImageFormatBMP);
ZBarcode_Delete(my_symbol);
is there anyway to do this other than using SetPixel? I am experiencing major performance issues with SetPixel and need an alternative method... I have tried using CreateDIBSection to no avail. The barcode displays slanted and is unusable. here is my code for that:
void *bits = (unsigned char*)(my_symbol->bitmap);
HBITMAP hBitmap = CreateDIBSection(pDC->GetSafeHdc(), &info, DIB_RGB_COLORS, (void **)&pDestData, NULL, 0);
memcpy(pDestData, my_symbol->bitmap, info.bmiHeader.biSizeImage);
img.Attach(hBitmap);
Another option that produces the same result is this:
BITMAPINFO info;
BITMAPINFOHEADER BitmapInfoHeader;
BitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfoHeader.biWidth = my_symbol->bitmap_width;
BitmapInfoHeader.biHeight = -(my_symbol->bitmap_height);
BitmapInfoHeader.biPlanes = 1;
BitmapInfoHeader.biBitCount = 24;
BitmapInfoHeader.biCompression = BI_RGB;
BitmapInfoHeader.biSizeImage = 0;
BitmapInfoHeader.biXPelsPerMeter = 0;
BitmapInfoHeader.biYPelsPerMeter = 0;
BitmapInfoHeader.biClrUsed = 0;
BitmapInfoHeader.biClrImportant = 0;
info.bmiHeader = BitmapInfoHeader;
HBITMAP hbmp = CreateDIBitmap(dc, &BitmapInfoHeader, CBM_INIT, (LPVOID *)my_symbol->bitmap, (LPBITMAPINFO)&info, DIB_RGB_COLORS);
img.Attach(hbmp);

C++ screen shot

How do I capture screen shot using C++? I'm going to be working with Win32.
Please, no MFC code.
#include "windows.h" // should be less than and greater than instead of \"
int CaptureBMP(LPCTSTR szFile)
{
// Source[1]
HDC hdcScr, hdcMem;
HBITMAP hbmScr;
BITMAP bmp;
int iXRes, iYRes;
// Create a normal DC and a memory DC for the entire screen. The
// normal DC provides a "snapshot" of the screen contents. The
// memory DC keeps a copy of this "snapshot" in the associated
// bitmap.
hdcScr = CreateDC("DISPLAY", NULL, NULL, NULL);
hdcMem = CreateCompatibleDC(hdcScr);
iXRes = GetDeviceCaps(hdcScr, HORZRES);
iYRes = GetDeviceCaps(hdcScr, VERTRES);
// Create a compatible bitmap for hdcScreen.
hbmScr = CreateCompatibleBitmap(hdcScr, iXRes, iYRes);
if (hbmScr == 0) return 0;
// Select the bitmaps into the compatible DC.
if (!SelectObject(hdcMem, hbmScr)) return 0;
// Copy color data for the entire display into a
// bitmap that is selected into a compatible DC.
if (!StretchBlt(hdcMem,
0, 0, iXRes, iYRes,
hdcScr,
0, 0, iXRes, iYRes,
SRCCOPY))
return 0;
// Source[2]
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap's color format, width, and height.
if (!GetObject(hbmScr, sizeof(BITMAP), (LPSTR) &bmp)) return 0;
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits != 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1 << cClrBits));
// There is no RGBQUAD array for the 24-bit-per-pixel format.
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1 << cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) / 8
* pbmi->bmiHeader.biHeight * cClrBits;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
pbih = (PBITMAPINFOHEADER) pbmi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits) return 0;
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if (!GetDIBits(hdcMem, hbmScr, 0, (WORD) pbih->biHeight, lpBits, pbmi, DIB_RGB_COLORS)) return 0;
// Create the .BMP file.
hf = CreateFile(szFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hf == INVALID_HANDLE_VALUE) return 0;
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed *
sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed *
sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER), (LPDWORD) &dwTmp, NULL)) return 0;
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, NULL))
return 0;
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp, NULL)) return 0;
// Close the .BMP file.
if (!CloseHandle(hf)) return 0;
// Free memory.
GlobalFree((HGLOBAL)lpBits);
ReleaseDC(0, hdcScr);
ReleaseDC(0, hdcMem);
return 1;
}
There you go, win32 GDI:
http://www.eggheadcafe.com/software/aspnet/32040380/screenshot-of-a-region-to-a-bmp.aspx
Or you could go straight to the MSDN:
http://msdn.microsoft.com/en-us/library/dd183402(v=VS.85).aspx