我如何使用 C++ 拍摄灰度屏幕截图?

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

我这里有这段代码,它截取屏幕截图和位图并将其保存为 BMP 文件。它工作得很好,但我希望能够修改位并使其成为灰度而不是 RGB 贴图。

我找到了执行此操作的方法,但我必须保存 BMPINFOHEADER 和 RGBQUAD 数组,然后使用灰度函数读取它,但大小保持不变。

我对此很菜鸟,但我认为灰度图像应该比 RGB 图像小很多。

有没有一种方法可以立即从位图中删除颜色而无需重新读取和修改它?

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;
}

我用的是数组转换,主要是从原真彩图的rgbRed、rgbGreen、rgbBlue分量到灰度图的灰度值Y。

可以通过以下公式得到:

Y=0.299 * rgbRed+0.587 * rgbGreen+0.114 * rgbBlue

你的代码中已经添加了处理代码,你可以参考下面的代码,输出文件大小是原来的1/4。

#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";
}