Trying to convert 32,24,16,8 bit images to their grayscale presentation. I read about using BitBlt, but maybe exist some light way built-in opportunity
in GDI+?
Code:
#include <vector>
...
class gdiplus_init
{
ULONG_PTR token;
public:
gdiplus_init()
{
Gdiplus::GdiplusStartupInput tmp;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
}
~gdiplus_init()
{
Gdiplus::GdiplusShutdown(token);
}
};
bool getbits(const wchar_t *filename, Gdiplus::PixelFormat pixelformat,
std::vector<BYTE> &bitmapinfo, std::vector<BYTE> &bits, int &w, int &h)
{
gdiplus_init init;
WORD bpp = 0;
int usage = DIB_RGB_COLORS;
int palettesize = 0;
switch(pixelformat)
{
case PixelFormat8bppIndexed:
bpp = 8;
usage = DIB_PAL_COLORS;
palettesize = 256 * sizeof(RGBQUAD);
break;
case PixelFormat16bppRGB555: bpp = 16; break;
case PixelFormat16bppRGB565: bpp = 16; break;
case PixelFormat24bppRGB: bpp = 24; break;
case PixelFormat32bppRGB: bpp = 32; break;
default:return false;
}
auto src = Gdiplus::Bitmap::FromFile(filename);
if(src->GetLastStatus() != Gdiplus::Status::Ok)
return false;
auto dst = src->Clone(0, 0, src->GetWidth(), src->GetHeight(),
pixelformat);
w = src->GetWidth();
h = src->GetHeight();
HBITMAP hbitmap;
Gdiplus::Color color;
dst->GetHBITMAP(color, &hbitmap);
//allocate enough memory for bitmapinfo and initialize to zero
//it's sizeof BITMAPINFO structure + size of palette
bitmapinfo.resize(sizeof(BITMAPINFO) + palettesize, 0);
//fill the first 6 parameters
BITMAPINFO* ptr = (BITMAPINFO*)bitmapinfo.data();
ptr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); //don't skip
ptr->bmiHeader.biWidth = w;
ptr->bmiHeader.biHeight = h;
ptr->bmiHeader.biPlanes = 1;
ptr->bmiHeader.biBitCount = bpp;
ptr->bmiHeader.biCompression = BI_RGB;
//magic formula to calculate the size:
//this is roughly w * h * bytes_per_pixel, it's written this way
//to account for "bitmap padding"
DWORD size = ((w * bpp + 31) / 32) * 4 * h;
//allocate memory for image
bits.resize(size, 0);
//finally call GetDIBits to fill bits and bitmapinfo
HDC hdc = GetDC(0);
GetDIBits(hdc, hbitmap, 0, h, &bits[0], (BITMAPINFO*)&bitmapinfo[0], usage);
ReleaseDC(0, hdc);
//cleanup
delete src;
delete dst;
return true;
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
...
std::vector<BYTE> bi; //automatic storage
std::vector<BYTE> bits;
int w, h;
//24-bit test
if(getbits(L"c:\\test\\24bit.bmp", PixelFormat24bppRGB, bi, bits, w, h))
StretchDIBits(dc, 0, 0, w, h, 0, 0, w, h,
bits.data(), (BITMAPINFO*)bi.data(), DIB_RGB_COLORS, SRCCOPY);
//8-bit test
if(getbits(L"c:\\test\\8bit.bmp", PixelFormat8bppIndexed, bi, bits, w, h))
StretchDIBits(dc, 0, 220, w, h, 0, 0, w, h,
bits.data(), (BITMAPINFO*)bi.data(), DIB_PAL_COLORS, SRCCOPY);
}
You can draw the GDI+ directly with various transformation. Use Gdiplus::Graphics to draw on device context.
For grayscale conversion, RGB values all have to be the same. Gdiplus::ColorMatrix can transform the colors. Green is usually more important, it gets more weight.
void draw(CDC *pdc)
{
//this line should be in OnCreate or somewhere other than paint routine
Gdiplus::Bitmap source(L"file.jpg");
//gray scale conversion:
Gdiplus::ColorMatrix matrix =
{
.3f, .3f, .3f, 0, 0,
.6f, .6f, .6f, 0, 0,
.1f, .1f, .1f, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attr;
attr.SetColorMatrix(&matrix,
Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
Gdiplus::Graphics gr(pdc->GetSafeHdc());
Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
Gdiplus::RectF rect(0, 0, w, h);
gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);
}
Note, I used rough values for grayscale matrix. See the answer mentioned in comment for a better matrix.
To convert the file, the process is similar, except use Gdiplus::Graphics to create memory dc and save it.
int GetEncoderClsid(const WCHAR* format, CLSID* clsid)
{
int result = -1;
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
Gdiplus::GetImageEncodersSize(&num, &size);
if(size)
{
Gdiplus::ImageCodecInfo* codec = (Gdiplus::ImageCodecInfo*)(malloc(size));
GetImageEncoders(num, size, codec);
for(UINT j = 0; j < num; ++j)
if(wcscmp(codec[j].MimeType, format) == 0)
{
*clsid = codec[j].Clsid;
result = j;
}
free(codec);
}
return result;
}
bool convert_grayscale(const wchar_t *file_in, const wchar_t *file_out)
{
CStringW extension = PathFindExtensionW(file_out);
extension.Remove(L'.');
extension.MakeLower();
if(extension == L"jpg") extension = L"jpeg";
extension = L"image/" + extension;
CLSID clsid;
if(GetEncoderClsid(extension, &clsid) == -1)
return false;
Gdiplus::Bitmap source(file_in);
if(source.GetLastStatus() != Gdiplus::Status::Ok)
return false;
Gdiplus::REAL w = (Gdiplus::REAL)source.GetWidth();
Gdiplus::REAL h = (Gdiplus::REAL)source.GetHeight();
Gdiplus::RectF rect(0, 0, w, h);
Gdiplus::Bitmap copy((INT)w, (INT)h, source.GetPixelFormat());
Gdiplus::ColorMatrix matrix =
{
.3f, .3f, .3f, 0, 0,
.6f, .6f, .6f, 0, 0,
.1f, .1f, .1f, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attr;
attr.SetColorMatrix(&matrix,
Gdiplus::ColorMatrixFlagsDefault, Gdiplus::ColorAdjustTypeBitmap);
Gdiplus::Graphics gr(©);
gr.DrawImage(&source, rect, 0, 0, w, h, Gdiplus::UnitPixel, &attr);
auto st = copy.Save(file_out, &clsid);
return st == Gdiplus::Status::Ok;
}
...
convert_grayscale(L"source.jpg", L"destination.jpg");
Related
I got stuck with a problem - I need to create a bitmap in memory, draw some text i it and save it as a BMP file and then print out the bitmap with physical printer. I can do this drawing in the dialog window context - it works fine. But when I try to do the same drawing in printer context the text doesn't appear. I really can't figure out why it is so. Please, help me guys. Thanks in advance. Here is the code:
void CMy2Dlg::OnButton1()
{
// TODO: Add your control notification handler code here
CPrintDialog pd(false);
if (pd.DoModal()==IDOK)
{
CDC PrintDC;
HDC hdc = pd.CreatePrinterDC();
PrintDC.Attach(hdc);
DOCINFO infStru;
::ZeroMemory (&infStru, sizeof (DOCINFO));
CString title="Print test";
infStru.cbSize = sizeof (DOCINFO);
infStru.lpszDocName=title;
infStru.lpszOutput=NULL;
PrintDC.StartDoc(&infStru);
PrintDC.StartPage();
{
CRect r, r2;
CBitmap memBMP, * pOldBitmap;
CPaintDC dc(this);
CDC memDC, *pDC = &memDC;
CFont font, * pOldFont;
int width = 2000;
int height = 1500;
int textwidth = 300;
int textheight = 150;
int oldMapMode = 0;
int oldbkmode = 0;
int i, j;
LOGFONT logFont, lf;
COLORREF oldTextColor;
memset(&logFont, 0, sizeof(logFont));
logFont.lfHeight = 16;
logFont.lfWidth = 0;
logFont.lfEscapement = 0;
logFont.lfOrientation = 0;
logFont.lfWeight = FW_NORMAL;
logFont.lfItalic = FALSE;
logFont.lfUnderline = FALSE;
logFont.lfStrikeOut = 0;
logFont.lfCharSet = ANSI_CHARSET;
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logFont.lfQuality = DEFAULT_QUALITY;
logFont.lfPitchAndFamily = DEFAULT_PITCH | FF_SWISS;
strcpy(logFont.lfFaceName, "Arial");
if(memDC.CreateCompatibleDC(&PrintDC)) {
if (memBMP.CreateCompatibleBitmap(&PrintDC, width, height)) {
pOldBitmap = pDC->SelectObject(&memBMP);
pDC->FillSolidRect(0, 0, width, height, RGB(200, 200, 200));
oldTextColor = pDC->SetTextColor(RGB(255,0,0));
oldMapMode = pDC->SetMapMode(MM_LOMETRIC);
oldbkmode = pDC->SetBkMode(TRANSPARENT);
lf = logFont;
lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(pDC->GetSafeHdc(), LOGPIXELSY), 72);
//lf.lfHeight = 100;
font.CreateFontIndirect(&lf);
pOldFont = pDC->SelectObject(&font);
r.left = 10;
r.top = 10;
r.right = r.left + textwidth;
r.bottom = r.top + textheight;
r.top *= -1;
r.bottom *= -1;
pDC->MoveTo(r.left, r.top);
pDC->LineTo(r.right, r.top);
pDC->LineTo(r.right, r.bottom);
pDC->LineTo(r.left, r.bottom);
pDC->LineTo(r.left, r.top);
pDC->DrawText("qwerty", &r, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
pDC->SetMapMode(oldMapMode);
pDC->SetTextColor(oldTextColor);
pDC->SetBkMode(oldbkmode);
PrintDC.BitBlt(10, 10, width, height, pDC, 0, 0, SRCCOPY);
pDC->SelectObject(pOldBitmap);
pDC->SelectObject(pOldFont);
font.DeleteObject();
memBMP.DeleteObject();
pDC->DeleteDC();
}
}
}
PrintDC.EndPage();
PrintDC.EndDoc();
PrintDC.Detach();
DeleteDC(hdc);
}
}
If SetMapMode(MM_LOMETRIC) is used on memory DC, then memory DC has to be drawn upside down when using BitBlt to copy to printer/display DC. The width/height will have to be adjusted as well. Just use the default map mode (MM_TEXT). Use SetMapMode(MM_LOMETRIC) when you are drawing on printer DC and you want that specific measurement units.
void CMy2Dlg::OnButton1()
{
//create the bitmap
int w = 600, h = 400;
CClientDC dc(this);
CBitmap bmp;
CDC memdc;
memdc.CreateCompatibleDC(&dc);
bmp.CreateCompatibleBitmap(&dc, w, h);
auto oldbmp = memdc.SelectObject(bmp);
//draw on bitmap
memdc.FillSolidRect(0, 0, w, h, RGB(200, 200, 200));
memdc.SetTextColor(RGB(255, 0, 0));
CRect rc(0, 0, w, h);
memdc.DrawText(L"qwerty", &rc, 0);
dc.BitBlt(0, 0, w, h, &memdc, 0, 0, SRCCOPY);//optional: draw the bitmap on dialog
CPrintDialog pd(false);
if(pd.DoModal() == IDOK)
{
CDC PrintDC;
HDC hdc = pd.GetPrinterDC();
PrintDC.Attach(hdc);
DOCINFO docinfo = { sizeof(docinfo) };
docinfo.lpszDocName = L"Print test";
PrintDC.StartDoc(&docinfo);
PrintDC.StartPage();
PrintDC.BitBlt(0, 0, w, h, &memdc, 0, 0, SRCCOPY);
PrintDC.EndPage();
PrintDC.EndDoc();
}
dc.SelectObject(oldbmp);
}
I am trying to get HBIPMAP working by reusing the HBITMAP and the HDC for performance reasons.
This is a small test project I wanted to do to learn more about CPU based Rasterization. For the window, im using SDL2.
The below code works if we comment out:
DeleteDC(hdcMem);
hdcMem = CreateCompatibleDC(device);
I can not find any example in the year 2018+.
mBackBuffer is just a Vector(DWORD)
void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
// This is hacked code for an example.
auto device = GetDC(hwnd);
DWORD colorSize = 4; // ARGB;
// Create page section
// https://learn.microsoft.com/en-us/windows/desktop/memory/creating-named-shared-memory
HANDLE hMapFile;
LPCTSTR pBuf;
// https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-createfilemappinga
hMapFile = CreateFileMappingA
(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
width * height * colorSize,
NULL
);
if (hMapFile == NULL)
{
return;
}
DWORD* buffer = (DWORD*)MapViewOfFile(
hMapFile,
FILE_MAP_ALL_ACCESS,
0,
0,
width * height * colorSize
);
BITMAPINFOHEADER header;
memset(&header, 0, sizeof(BITMAPINFOHEADER));
// https://msdn.microsoft.com/en-us/02f8ed65-8fed-4dda-9b94-7343a0cfa8c1
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = width;
header.biHeight = height;
header.biPlanes = 1;
header.biBitCount = 32;
header.biCompression = BI_RGB;
header.biSizeImage = width * height * sizeof(BYTE);
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biClrUsed = 0;
header.biClrImportant = 0;
tagBITMAPINFO bitmap;
memset(&bitmap, 0, sizeof(tagBITMAPINFO));
// https://learn.microsoft.com/en-us/windows/desktop/api/wingdi/ns-wingdi-tagbitmapinfo
tagRGBQUAD RGBQUAD;
memset(&RGBQUAD, 0, sizeof(tagRGBQUAD));
bitmap.bmiHeader = header;
bitmap.bmiColors[0] = RGBQUAD;
LPVOID p;
// https://learn.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createdibsection
auto hBitMap = CreateDIBSection
(
device,
&bitmap,
DIB_RGB_COLORS,
&p,
hMapFile,
0
);
for (DWORD i = 0; i < width * height; ++i)
{
buffer[i] = 0xFF0000;
}
HDC hdcMem = CreateCompatibleDC(device);
auto oldHBITMAP = (HBITMAP)SelectObject(hdcMem, hBitMap);
BitBlt(
device,
0,
0,
width,
height,
hdcMem,
0,
0,
SRCCOPY
);
DeleteDC(hdcMem);
for (DWORD i = 0; i < width * height; ++i)
{
buffer[i] = 0;
}
hdcMem = CreateCompatibleDC(device);
BitBlt(
device,
400,
300,
width,
height,
hdcMem,
0,
0,
SRCCOPY
);
}
The output is a red screen, but you should see the black section in corner right.
There are several issues here, some not related to bitmap.
The handle from GetDC should be cleaned up by ReleaseDC when the handle is no longer needed.
The handle from CreateFileMapping should be cleaned up by CloseHandle, and MapViewOfFile should be cleaned up by UnmapViewOfFile.
HBITMAP handle must be cleaned up by DeleteObject
It is recommend to cleanup after SelectObject by calling SelectOject(hMemDC, oldHBitmap)
If you don't restore the old bitmap, and try to delete hMemDC, Windows cannot fulfill the request because there is another bitmap selected in device context. Windows will try to fix this error but it may fail if the code is too convoluted.
Note that Windows gives you a limit of 10,000 GDI handles. The application will crash very quickly if you don't manage these handles properly. Refer to WinAPI documentation for these functions. If in doubt, use the task manager to monitor "GDI handles" for your program.
The code should work as expected once you fix these problems, see the example below.
This of course is for demonstration only. In real world application you probably want to save the HBITMAP in the heap, instead of stack, as well as some other values. You want to minimize repeated creating these handles.
As noted in other answers and comments, painting should be done in response to WM_PAINT, where you get HDC from BeginPaint (and cleanup with EndPaint). Therefore you should avoid GetDC/ReleaseDC
void Device::createDeviceFromHWND(const HWND& hwnd, const int& width, const int& height)
{
auto hdc = GetDC(hwnd);
auto hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
width * height * sizeof(DWORD), NULL);
auto buffer = (DWORD*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0,
width * height * sizeof(DWORD));
BITMAPINFOHEADER biheader = { sizeof(biheader), width, height, 1, 32, BI_RGB };
LPVOID bits;
auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&biheader, DIB_RGB_COLORS,
&bits, hMapFile, 0);
for(int i = 0; i < width * height; ++i)
buffer[i] = 0xFF0000;
auto memdc = CreateCompatibleDC(hdc);
auto oldhbitmap = SelectObject(memdc, hbitmap);
BitBlt(hdc, 0, 0, width, height, memdc, 0, 0, SRCCOPY);
for(int i = 0; i < width * height; ++i)
buffer[i] = 0;
BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);
SelectObject(memdc, oldhbitmap); //<- ***EDIT***
//oldhbitmap is selected in to memdc, now we can destroy hbitmap and memdc
DeleteObject(hbitmap);
DeleteDC(memdc);
ReleaseDC(hwnd, hdc);
UnmapViewOfFile(buffer);
CloseHandle(hMapFile);
}
Side note, you don't gain anything by using reference operator & for constant values. Just change the function prototype as follows:
void createDeviceFromHWND(const HWND hwnd, const int width, const int height);
Also, this can be done without CreateFileMapping, and use buffer shown below. buffer will be valid as long as hbitmap is valid.
void test(const HWND hwnd, const int w, const int h)
{
auto hdc = GetDC(hwnd);
//use the negative value of height, so bitmap bits are not upside-down
BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };
DWORD* buffer;
auto hbitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bi, DIB_RGB_COLORS,
(void**)&buffer, NULL, 0);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
for(int i = 0; i < w * h; ++i) buffer[i] = 0xFF0000;
BitBlt(hdc, 0, 0, w, h, memdc, 0, 0, SRCCOPY);
//draw black square on top-left
for(int y = 0; y < 100; y++)
for(int x = 0; x < 100; x++)
buffer[y * w + x] = 0;
BitBlt(hdc, 0, 0, 100, 100, memdc, 0, 0, SRCCOPY);
//cleanup:
SelectObject(memdc, oldbmp);
DeleteObject(hbitmap); //<- buffer is not valid after hbitmap is destroyed
DeleteDC(memdc);
ReleaseDC(hwnd, hdc);
}
This approach is wrong. When the target window gets WM_PAINT all your work will be undone.
Always paint your windows the way you want them with WM_PAINT and BeginPaint.
You are not editing the bitmap here:
for (unsigned int i = 0; i < width * height; ++i)
{
mBackBuffer[i] = 0;
}
just filling the array, the bitmap was created from, with nulls.
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;
}
I'm using GDIPlus to scale image from a buffer.
I'm taking a screenshot of my desktop.
Here is my code:
BYTE *Duplication::scaleBuffer(unsigned char *data, int width, int height)
{
Gdiplus::Bitmap bitmap(width, height, 4 * width, PixelFormat32bppARGB, data);
INT n_width = mWidthResolution;
INT n_height = mHeightResolution;
double ratio = ((double)width) / ((double)height);
if (width > height) {
n_height = (double)n_width / ratio;
}
else {
n_width = n_height * ratio;
}
Gdiplus::Bitmap newBitmap(n_width, n_height, bitmap.GetPixelFormat());
Gdiplus::Graphics graphics(&newBitmap);
graphics.SetInterpolationMode(Gdiplus::InterpolationModeBilinear);
graphics.DrawImage(&bitmap, 0, 0, n_width, n_height);
Gdiplus::Rect rect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight());
Gdiplus::BitmapData bitmapData;
BYTE *buffer = nullptr;
if (Gdiplus::Ok == newBitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData)) {
int len = bitmapData.Height * std::abs(bitmapData.Stride);
buffer = new BYTE[len];
RtlZeroMemory(buffer, len);
memcpy(buffer, bitmapData.Scan0, len);
newBitmap.UnlockBits(&bitmapData);
}
return buffer;
}
On windows with white background, I got a black background and color is corrupted.. It happens only on some program like "File Browser".. I don't understand why ...
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.