I want to take a capture of part of screen and save it into BMP. To save picture I plan with SOIL. Bit bliting functions I get here.
Code:
bool saveScreen(string path)
{
string name;
SYSTEMTIME sm;
GetSystemTime(&sm);
name = to_string(sm.wHour) + to_string(sm.wMinute) + to_string(sm.wSecond) + to_string(sm.wMilliseconds)
+ "_" + to_string(sm.wDay) + to_string(sm.wMonth) + to_string(sm.wYear);
path = /*path + "/" +*/ name + ".bmp";
const char *charPath = path.c_str();
BITMAPINFO bmi;
auto& hdr = bmi.bmiHeader;
hdr.biSize = sizeof(bmi.bmiHeader);
hdr.biWidth = screenWidth;
hdr.biHeight = screenHeight;
hdr.biPlanes = 1;
hdr.biBitCount = 32;
hdr.biCompression = BI_RGB;
hdr.biSizeImage = 0;
hdr.biXPelsPerMeter = 0;
hdr.biYPelsPerMeter = 0;
hdr.biClrUsed = 0;
hdr.biClrImportant = 0;
unsigned char* bitmapBits;
HDC hdc = GetDC(NULL);
HDC hBmpDc = CreateCompatibleDC(hdc);
BITMAP bm;
HBITMAP hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)&bitmapBits, nullptr, 0);
SelectObject(hBmpDc, hBmp);
BitBlt(hBmpDc, 0, 0, screenWidth, 1024, hdc, 0, 0, SRCCOPY);
vector< unsigned char > buf(screenWidth* screenHeight* 3);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, bitmapBits);
int texture = SOIL_save_image(charPath, SOIL_SAVE_TYPE_BMP, screenWidth, screenHeight, 3, bitmapBits);
return texture;
}
On output I get this:
Broken BMP
It looks as RGBA/RGB issue, but I don't set RGBA nowhere.
What I missed in the code? It's the right way to get screenshot?
You create 32 bpp image, however pass 3 to SOIL_save_image indicating that it is 24 bpp image.
Related
I'm using this function to make a screenshot of a window. It works fine but at games like Minecraft it's zoomed in.
Normal
Screenshot
cv::Mat getMat(HWND hWND) {
HDC deviceContext = GetDC(hWND);
HDC memoryDeviceContext = CreateCompatibleDC(deviceContext);
RECT windowRect;
GetClientRect(hWND, &windowRect);
int height = windowRect.bottom;
int width = windowRect.right;
HBITMAP bitmap = CreateCompatibleBitmap(deviceContext, width, height);
SelectObject(memoryDeviceContext, bitmap);
//copy data into bitmap
BitBlt(memoryDeviceContext, 0, 0, width, height, deviceContext, 0, 0, SRCCOPY);
//specify format by using bitmapinfoheader!
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = -height;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0; //because no compression
bi.biXPelsPerMeter = 1;
bi.biYPelsPerMeter = 2;
bi.biClrUsed = 3;
bi.biClrImportant = 4;
cv::Mat mat = cv::Mat(height, width, CV_8UC4); // 8 bit unsigned ints 4 Channels -> RGBA
//transform data and store into mat.data
GetDIBits(memoryDeviceContext, bitmap, 0, height, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
//clean up!
DeleteObject(bitmap);
DeleteDC(memoryDeviceContext); //delete not release!
ReleaseDC(hWND, deviceContext);
return mat;
}
According to the documentation, windowRect.bottom is not the height. Or windowRect.right is not the width. https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
So, you should do something like this:
int height = windowRect.bottom - windowRect.top;
int width = windowRect.right - windowRect.left;
EDIT1:
I have found an issue similar to yours. Maybe it could help you as well.
ClientRect mysteriously smaller than WindowRect?
Cheers.
In C++, I want to create a simple transparent image with Gdiplus and save it as a png. I have the following code:
// These variables are class members and got initialized and are used elsewhere
BITMAPINFO bmi;
HDC hdc;
void* pvBits;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = some_width;
bmi.bmiHeader.biHeight = some_height;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = ((((bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount) + 31) & ~31) >> 3) * bmi.bmiHeader.biHeight;
HBITMAP hBM = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pvBits, NULL, 0x0);
FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255);
HGDIOBJ oldObj = SelectObject(hDC, hBM);
ReleaseDC(NULL, hDC);
GdiFlush();
GdiPlusBitmap* bitmap = new Gdiplus::Bitmap(&bmi, pvBits);
When I save the image to png, I can see that there is an alpha channel, but its set to 0, so nothing transparent (RGB is all set). I also tried changing the 255 to 0, but that only gives me a black image with no transpirancy. Why is the Fillmemory call not filling the alpha channel, or am I missing something else?
FillMemory(pvBits, bmi.bmiHeader.biSizeImage, 255) will fill the memory with solid white color. If you write over the image with GDI functions, the alpha values remain unchanged. You won't see any transparency even if the file supports it.
To create 32-bit image you only need Gdiplus::Bitmap(w, h, PixelFormat32bppARGB). There is no need for BITMAPINFO and CreateDIBSection
If you mix GDI+ with GDI functions, you may want to reset alpha after writing with GDI functions. For example:
void test()
{
int w = 100;
int h = 100;
int bitcount = 32;
int size = ((((w * bitcount) + 31) & ~31) >> 3) * h;
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = bitcount;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = size;
HDC hdc = GetDC(0);
BYTE* pvBits = NULL;
HBITMAP hbitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS,
(void**)&pvBits, NULL, 0x0);
FillMemory(pvBits, size, 0);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
SetBkColor(memdc, RGB(255, 0, 0));
TextOut(memdc, 0, 0, L"123", 3);
//GDI cleanup, don't delete hbitmap yet
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
ReleaseDC(0, hdc);
//make the non-zero colors transparent:
for(int i = 0; i < size; i += 4)
{
int n = *(int*)(pvBits + i);
if (n != 0)
pvBits[i + 3] = 255;
}
CLSID clsid_png;
CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &clsid_png);
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(w, h, PixelFormat32bppARGB);
Gdiplus::BitmapData data;
bitmap->LockBits(&Gdiplus::Rect(0, 0, w, h),
Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &data);
memcpy(data.Scan0, pvBits, size);
bitmap->UnlockBits(&data);
//safe to delete hbitmap
DeleteObject(hbitmap);
bitmap->Save(L"test.png", &clsid_png);
delete bitmap;
}
Your code suffers from the same hdc problems as in previous question. A filling image with 0 should give a completely transparent black image as expected.
But I think it would be simpler to create bitmap directly specifying dimensions and pixel format and set content later:
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(width, height, PixelFormat32bppARGB);
In transparent images, the value 0 means "full transparent" and 255 means
"full opaque".
I'm trying to get an image with 16 color bitmap.
I can not find a solution on the internet post.
Simple implementation here.
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
LPBITMAPINFO ConstructBitmapInfo(int nBits, int nX, int nY);
VOID RocketBuffer(LPVOID lpData);
int _tmain(int argc, _TCHAR* argv[])
{
HWND hDesktop = NULL;
HDC hCurrenDC = NULL;
INT nX, nY;
LPBITMAPINFO lpBmpInfo;
HBITMAP hBmpScreen;
LPVOID lpBmpBuffer = NULL;
if(NULL == (hDesktop = GetDesktopWindow()))
return GetLastError();
if (NULL == (hCurrenDC = GetDC(hDesktop)))
return GetLastError();
nX = GetSystemMetrics(SM_CXSCREEN);
nY = GetSystemMetrics(SM_CYSCREEN);
HDC hOrgMemDC = NULL;
hOrgMemDC = CreateCompatibleDC(hCurrenDC);
lpBmpInfo = ConstructBitmapInfo(4, nX, nY);
hBmpScreen = CreateDIBSection(hCurrenDC, lpBmpInfo, DIB_RGB_COLORS, &lpBmpBuffer, NULL, 0);
SelectObject(hOrgMemDC, hBmpScreen);
if(!BitBlt(hOrgMemDC, 0, 0, nX, nY, hCurrenDC, 0, 0, SRCCOPY))
{
return GetLastError();
}
LPBYTE lpWriteData = new BYTE[lpBmpInfo->bmiHeader.biSizeImage * 2];
memcpy(lpWriteData, lpBmpBuffer, lpBmpInfo->bmiHeader.biSizeImage);
RocketBuffer(lpBmpBuffer);
delete[] lpWriteData;
return 0;
}
LPBITMAPINFO ConstructBitmapInfo(int nBits, int nX, int nY)
{
int color_num = nBits <= 8 ? 1 << nBits : 0;
int nBISize = sizeof(BITMAPINFOHEADER) + (color_num * sizeof(RGBQUAD));
BITMAPINFO *lpbmi = (BITMAPINFO *) new BYTE[nBISize];
BITMAPINFOHEADER *lpbmih = &(lpbmi->bmiHeader);
lpbmih->biSize = sizeof(BITMAPINFOHEADER);
lpbmih->biWidth = nX;
lpbmih->biHeight = nY;
lpbmih->biPlanes = 1;
lpbmih->biBitCount = nBits;
lpbmih->biCompression = BI_RGB;
lpbmih->biXPelsPerMeter = 0;
lpbmih->biYPelsPerMeter = 0;
lpbmih->biClrUsed = 0;
lpbmih->biClrImportant = 0;
lpbmih->biSizeImage = (((lpbmih->biWidth * lpbmih->biBitCount + 31) & ~31) >> 3) * lpbmih->biHeight;
if (nBits >= 16)
return lpbmi;
HDC hDC = GetDC(NULL);
HBITMAP hBmp = CreateCompatibleBitmap(hDC, 1, 1);
GetDIBits(hDC, hBmp, 0, 0, NULL, lpbmi, DIB_RGB_COLORS);
ReleaseDC(NULL, hDC);
DeleteObject(hBmp);
return lpbmi;
}
VOID RocketBuffer(LPVOID lpData)
{
// ...
}
I can not output lpWriteData as a * .bmp file. It's not BMP format.
Therefore, the function RocketBuffer (LPVOID lpData) will not be able to perform its function correctly.
The problem is how to get screen shot output in memory with 16 color bitmap.
32 Bit Color Image
I want 4 bit image like this.
4 Bit Color Image
16 color 4-bit image includes a color table with 16 colors. The color table size is 16 * 4 bytes. BITMAPINFO should be large enough to receive the color palette through GetDIBits
The code below works for the following bitmaps:
1-bit bitmaps (monochrome) //bpp = 1
4-bit bitmaps (16 colors) //bpp = 4
8-bit bitmaps (256 colors) //bpp = 8
.
#include <iostream>
#include <fstream>
#include <vector>
#include <windows.h>
int main()
{
//4-bit bitmap: bpp = 4
//valid values with this method bpp = 1, 4, 8
WORD bpp = 4;
//color table:
int colorsize = (1 << bpp) * sizeof(RGBQUAD);
int width = GetSystemMetrics(SM_CXFULLSCREEN);
int height = GetSystemMetrics(SM_CYFULLSCREEN);
HDC hdc = GetDC(HWND_DESKTOP);
HBITMAP hbitmap = CreateCompatibleBitmap(hdc, width, height);
HDC memdc = CreateCompatibleDC(hdc);
HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, width, height, hdc, 0, 0, CAPTUREBLT | SRCCOPY);
SelectObject(memdc, oldbmp);
//size in bytes for pixel data:
DWORD size = ((width * bpp + 31) / 32) * 4 * height;
std::vector<BYTE> bi_memory;
bi_memory.resize(sizeof(BITMAPINFOHEADER) + colorsize, 0);
BITMAPINFO* bi = (BITMAPINFO*)&bi_memory[0];
bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi->bmiHeader.biWidth = width;
bi->bmiHeader.biHeight = height;
bi->bmiHeader.biPlanes = 1;
bi->bmiHeader.biBitCount = bpp;
bi->bmiHeader.biCompression = BI_RGB;
bi->bmiHeader.biClrUsed = 16;
std::vector<BYTE> pixels(size + colorsize);
GetDIBits(hdc, hbitmap, 0, height, &pixels[0], bi, DIB_RGB_COLORS);
std::ofstream fout(TEXT("c:\\test\\_4bit.bmp"), std::ios::binary);
if(fout)
{
//bitmap file header
//(54 = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))
BITMAPFILEHEADER filehdr = { 'MB', 54 + colorsize + size, 0, 0, 54 };
fout.write((char*)&filehdr, sizeof(BITMAPFILEHEADER));
//bitmap info header
fout.write((char*)&bi->bmiHeader, sizeof(BITMAPINFOHEADER));
//color table
fout.write((char*)bi->bmiColors, colorsize);
//pixel data
fout.write((char*)pixels.data(), pixels.size());
}
//cleanup:
DeleteObject(memdc);
DeleteObject(hbitmap);
ReleaseDC(HWND_DESKTOP, hdc);
return 0;
}
Need to convert a color bitmap to a grey scale. Here is the approach:
HDC hdcWindow = GetDC(hWnd);
HBITMAP hDIBBitmap;
{
// Create a compatible DC which is used in a BitBlt from the window DC
HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
SelectObject(hdcMemDC, bmHatch);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = bm.bmWidth;
bmi.bmiHeader.biHeight = bm.bmHeight; // top-down
bmi.bmiHeader.biPlanes = bm.bmPlanes;
bmi.bmiHeader.biBitCount = bm.bmBitsPixel;
UINT* pBits;
HBITMAP hDIBBitmap = CreateDIBSection(hdcMemDC, &bmi, DIB_RGB_COLORS, (void**)&pBits, NULL, NULL);
for (int i = 0; i < bm.bmWidth; i++) {
for (int j = 0; j < bm.bmHeight; j++) {
UINT val = pBits[i + j * bm.bmWidth];
if (val != 0)
{
COLORREF clr = val;
UINT newVal = (GetRValue(clr) + GetBValue(clr) + GetGValue(clr)) / 3;
pBits[i + j * bm.bmWidth] = newVal;
}
}
}
SelectObject(hdcMemDC, hDIBBitmap);
// draw content of memory bitmap in the window
BitBlt(hdcWindow, 0, 0, bm.bmWidth, bm.bmHeight, hdcMemDC, 0, 0, SRCCOPY);
DeleteObject(hDIBBitmap);
DeleteDC(hdcMemDC);
}
ReleaseDC(hWnd, hdcWindow);
In the above code sample, the input bitmap is given by bm which is a Bitmap instance.
Created a compatible DC. Loaded the bitmap into using the selectObject statement.
Then wanted to change the bits by creating the DIB section for traversing the bitmap values. After changing the values, the hDIBBitmap is selected and then finally drawing using the BitBlt function.
When I comment out the following line, I can see the original bitmap rendered correctly:
SelectObject(hdcMemDC, hDIBBitmap);
What I observed was the pBits is always 0 and hence the transformed gray scale image is not being rendered and instead getting a black picture.
Please advise on what is wrong with this approach.
CreateDIBSection is creating a blank bitmap with that usage. None of the bits from the original bitmap are used. If you paint it you get a black bitmap.
If you comment out SelectObject(hdcMemDC, hDIBBitmap) then this new hDIBBitmap is ignored as well. You print the original bitmap which was selected in device context earlier: SelectObject(hdcMemDC, bmHatch)
To convert HBITMAP to gray scale use the following code. Note this will not work for palette bitmaps.
Converting 24-bit or 32-bit HBITMAP to grayscale:
void grayscale(HBITMAP hbitmap)
{
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
if(bm.bmBitsPixel < 24)
{
DebugBreak();
return;
}
HDC hdc = GetDC(HWND_DESKTOP);
DWORD size = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight;
BITMAPINFO bmi
{sizeof(BITMAPINFOHEADER),bm.bmWidth,bm.bmHeight,1,bm.bmBitsPixel,BI_RGB,size};
int stride = bm.bmWidth + (bm.bmWidth * bm.bmBitsPixel / 8) % 4;
BYTE *bits = new BYTE[size];
GetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
for(int y = 0; y < bm.bmHeight; y++) {
for(int x = 0; x < stride; x++) {
int i = (x + y * stride) * bm.bmBitsPixel / 8;
BYTE gray = BYTE(0.1 * bits[i+0] + 0.6 * bits[i+1] + 0.3 * bits[i+2]);
bits[i+0] = bits[i+1] = bits[i+2] = gray;
}
}
SetDIBits(hdc, hbitmap, 0, bm.bmHeight, bits, &bmi, DIB_RGB_COLORS);
ReleaseDC(HWND_DESKTOP, hdc);
delete[]bits;
}
Usage
//convert to grayscale
//this should be called once
grayscale(hbitmap);
//paint image
HDC memdc = CreateCompatibleDC(hdc);
HBITMAP oldbmp = SelectObject(memdc, hbitmap);
BITMAP bm;
GetObject(hbitmap, sizeof(bm), &bm);
BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
...
I have a pointer to RGB data (640x480x3 bytes) that I want to draw into a window using BitBlt or something else equally fast. How do I convert the RGB data into something usable with BitBlt (for example).
Here is what I tried so far (without success)
unsigned char *buf = theVI->getPixels(0);
int size = theVI->getSize(0);
int h = theVI->getHeight(0);
int w = theVI->getWidth(0);
HDC dc = GetDC(hwnd);
HDC dcMem = CreateCompatibleDC(dc);
HBITMAP bmp = CreateBitmap(w, h, 1, 24, buf);
SelectObject(dcMem, bmp);
BitBlt(dc, 0, 0, w, h, dcMem, 0, 0, SRCCOPY);
Thanks
UPDATE: this is the working code...
HDC dc = GetDC(hwnd);
BITMAPINFO info;
ZeroMemory(&info, sizeof(BITMAPINFO));
info.bmiHeader.biBitCount = 24;
info.bmiHeader.biWidth = w;
info.bmiHeader.biHeight = h;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biSizeImage = size;
info.bmiHeader.biCompression = BI_RGB;
StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h, buf, &info, DIB_RGB_COLORS, SRCCOPY);
ReleaseDC(hwnd, dc);
You can render your bytes ( that are so called DIB device independent bitmap ) to HDC using StretchDIBits API function.
And yet check DIB article in MSDN