Taking a JPEG-encoded screenshot to a buffer using GDI+ and C++ - c++

I've adapted this code from another article here on SO. It takes a screenshot of the desktop and writes it to a file named "test.jpg."
I'm interested in saving the JPEG data directly to a buffer to be sent over the network. I'm pretty sure GdipSaveImageToStream is what I need, but I can't figure out how it works. The GpImage parameter is particularly confusing.
I appreciate any help you can provide.
#include "stdafx.h"
#include "windows.h"
#include "gdiplus.h"
using namespace Gdiplus;
using namespace Gdiplus::DllExports;
int GetEncoderClsid(WCHAR *format, CLSID *pClsid)
{
unsigned int num = 0, size = 0;
GetImageEncodersSize(&num, &size);
if(size == 0) return -1;
ImageCodecInfo *pImageCodecInfo = (ImageCodecInfo *)(malloc(size));
if(pImageCodecInfo == NULL) return -1;
GetImageEncoders(num, size, pImageCodecInfo);
for(unsigned int j = 0; j < num; ++j)
{
if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0){
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
int GetScreeny(LPWSTR lpszFilename, ULONG uQuality) // by Napalm
{
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HWND hMyWnd = GetDesktopWindow(); // get my own window
RECT r; // the area we are going to capture
int w, h; // the width and height of the area
HDC dc; // the container for the area
int nBPP;
HDC hdcCapture;
LPBYTE lpCapture;
int nCapture;
int iRes;
CLSID imageCLSID;
Bitmap *pScreenShot;
HGLOBAL hMem;
int result;
// get the area of my application's window
//GetClientRect(hMyWnd, &r);
GetWindowRect(hMyWnd, &r);
dc = GetWindowDC(hMyWnd);// GetDC(hMyWnd) ;
w = r.right - r.left;
h = r.bottom - r.top;
nBPP = GetDeviceCaps(dc, BITSPIXEL);
hdcCapture = CreateCompatibleDC(dc);
// create the buffer for the screenshot
BITMAPINFO bmiCapture = {
sizeof(BITMAPINFOHEADER), w, -h, 1, nBPP, BI_RGB, 0, 0, 0, 0, 0,
};
// create a container and take the screenshot
HBITMAP hbmCapture = CreateDIBSection(dc, &bmiCapture,
DIB_PAL_COLORS, (LPVOID *)&lpCapture, NULL, 0);
// failed to take it
if(!hbmCapture)
{
DeleteDC(hdcCapture);
DeleteDC(dc);
GdiplusShutdown(gdiplusToken);
printf("failed to take the screenshot. err: %d\n", GetLastError());
return 0;
}
// copy the screenshot buffer
nCapture = SaveDC(hdcCapture);
SelectObject(hdcCapture, hbmCapture);
BitBlt(hdcCapture, 0, 0, w, h, dc, 0, 0, SRCCOPY);
RestoreDC(hdcCapture, nCapture);
DeleteDC(hdcCapture);
DeleteDC(dc);
GpImage *bob;
IStream *ssStr;
// save the buffer to a file
pScreenShot = new Bitmap(hbmCapture, (HPALETTE)NULL);
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Guid = EncoderQuality;
encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParams.Parameter[0].Value = &uQuality;
GetEncoderClsid(L"image/jpeg", &imageCLSID);
iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok);
delete pScreenShot;
DeleteObject(hbmCapture);
GdiplusShutdown(gdiplusToken);
return iRes;
}
int _tmain(int argc, _TCHAR* argv[])
{
GetScreeny(L"test.jpg", 75);
return 0;
}

Short answer: Use the IStream version of Gdiplus::Image::Save. Use CreateHStreamOnGlobal to make a temporary IStream that you can convert back to a buffer;
Long winded version with code sample.
Replace this line:
iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok);
With this block of code:
// Note: For the sake of brevity and readability, I'm deliberately not checking
// the return value of any of these calls. In production code, you should do diligent error
// checking on each function call. Also, there may be an optimization where you can just
// use the memory of the stream itself (by calling GetHGlobalFromStream and GlobalLock)
// But that's an exercise left to the reader.
{
IStream *pStream = NULL;
LARGE_INTEGER liZero = {};
ULARGE_INTEGER pos = {};
STATSTG stg = {};
ULONG bytesRead=0;
HRESULT hrRet=S_OK;
BYTE* buffer = NULL; // this is your buffer that will hold the jpeg bytes
DWORD dwBufferSize = 0; // this is the size of that buffer;
hrRet = CreateStreamOnHGlobal(NULL, TRUE, &pStream))
hrRet = pScreenShot->Save(pStream, &imageCLSID, &encoderParams) == 0 ? S_OK : E_FAIL;
hrRet = pStream->Seek(liZero, STREAM_SEEK_SET, &pos);
hrRet = pStream->Stat(&stg, STATFLAG_NONAME);
// allocate a byte buffer big enough to hold the jpeg stream in memory
buffer = new BYTE[stg.cbSize.LowPart];
hrRet = (buffer == NULL) ? E_OUTOFMEMORY : S_OK;
dwBufferSize = stg.cbSize.LowPart;
// copy the stream into memory
hrRet = pStream->Read(buffer, stg.cbSize.LowPart, &bytesRead);
// now go save "buffer" and "dwBufferSize" off somewhere. This is the jpeg buffer
// don't forget to free it when you are done
// After success or if any of the above calls fail, don't forget to release the stream
if (pStream)
{
pStream->Release();
}
}

Related

C++ Win32Api GUI, I Am trying to display received screenshots on server from client. I am trying to create a Remote Desktop Viewer

I am receiving those screenshots in WM_PAINT, and then save it to memory. The screenshots have around 8-10kB size. And I am trying to display those screenshots in a 15 FPS, but the problem is that its displaying sometimes glitched screenshots like this:
[the gray square with dots inside it, that should not be there, its covering the half of the screenshot :c ]
Image displayed like this, is like every second or third screenshot. I have no idea how to fix this im stuck a couple days at this.
Here is the server code (window procedure) where i am trying to display these screenshots
LRESULT CALLBACK ClientWndProc(HWND Chwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
std::vector<char> buffer(50000);
switch (uMsg)
{
case WM_CREATE:
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
SetTimer(Chwnd, SCREEN_TIMER, 1000/15, NULL);
break;
}
case WM_TIMER:
{
if (wParam == SCREEN_TIMER)
{
SOCKET selectedSocket = clientSockets[itemIndex];
auto it = std::find(clientSockets.begin(), clientSockets.end(), selectedSocket);
send(selectedSocket, "capscr", strlen("capscr"), 0);
InvalidateRect(Chwnd, NULL, TRUE);
}
break;
}
case WM_PAINT:
{
SOCKET selectedSocket = clientSockets[itemIndex];
auto it = std::find(clientSockets.begin(), clientSockets.end(), selectedSocket);
if (it != clientSockets.end())
{
int bytesReceived = 0;
int totalBytesReceived = 0;
int expectedBytes = buffer.size();
Sleep(1000/15);
do
{
bytesReceived = recv(selectedSocket, buffer.data() + totalBytesReceived, buffer.size() - totalBytesReceived, 0);
totalBytesReceived += bytesReceived;
} while (totalBytesReceived < expectedBytes && bytesReceived > 0);
//MessageBoxA(NULL, buffer.data(), "s", MB_OK);
HGLOBAL hGlobal = GlobalAlloc(GHND, expectedBytes);
void* pData = GlobalLock(hGlobal);
memcpy(pData, buffer.data(), buffer.size());
GlobalUnlock(hGlobal);
IStream* pStream = NULL;
CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
Gdiplus::Bitmap bitmap(pStream);
Gdiplus::Status status = bitmap.GetLastStatus();
PAINTSTRUCT ps;
HDC hdc = BeginPaint(Chwnd, &ps);
int imgWidth = bitmap.GetWidth();
int imgHeight = bitmap.GetHeight();
Gdiplus::Graphics graphics(hdc);
RECT clientRect;
GetClientRect(Chwnd, &clientRect);
graphics.DrawImage(&bitmap, 0, 0, imgWidth, imgHeight);
EndPaint(Chwnd, &ps);
GlobalFree(hGlobal);
}
break;
}
case WM_ERASEBKGND:
return TRUE;
case WM_CLOSE:
{
DestroyWindow(hwndClient);
break;
}
case WM_DESTROY:
DestroyWindow(hwndClient);
return 0;
default:
return DefWindowProc(Chwnd, uMsg, wParam, lParam);
}
return 0;
}
client code sending screenshots:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return 0;
}
void shutdownGdiPlus()
{
Gdiplus::GdiplusShutdown(gdiPlusToken);
gdiPlusToken = NULL;
}
bool initGdiPlusIfNeeded()
{
// If already initialized then return true
if (gdiPlusToken != NULL)
return true;
static Gdiplus::GdiplusStartupInput gdiPlusStartupInput;
return (success = GdiplusStartup(&gdiPlusToken, &gdiPlusStartupInput, NULL)) == Gdiplus::Status::Ok;
}
std::pair<ULONGLONG, char*> getScreenShotAsByteArray()
{
if (!initGdiPlusIfNeeded())
return {};
IStream* iStream;
HRESULT res = CreateStreamOnHGlobal(NULL, true, &iStream);
const HDC srcDC = ::GetDC(NULL);
const int screenHeight = GetSystemMetrics(SM_CYSCREEN);
const int screenWidth = GetSystemMetrics(SM_CXSCREEN);
const HDC memDC = CreateCompatibleDC(srcDC);
const HBITMAP membit = CreateCompatibleBitmap(srcDC, screenWidth, screenHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(memDC, membit);
BitBlt(memDC, 0, 0, screenWidth, screenHeight, srcDC, 0, 0, SRCCOPY);
// Create a bitmap to store the previous screenshot
HBITMAP prevBitmap = CreateCompatibleBitmap(srcDC, screenWidth, screenHeight);
HDC prevDC = CreateCompatibleDC(srcDC);
SelectObject(prevDC, prevBitmap);
// Get the size of the bitmaps in bytes
BITMAP bmp;
GetObject(prevBitmap, sizeof(BITMAP), &bmp);
int prevBmpSize = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
GetObject(membit, sizeof(BITMAP), &bmp);
int currBmpSize = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
// Allocate memory for the bitmap data
char* prevBmpData = new char[prevBmpSize];
char* currBmpData = new char[currBmpSize];
// Get the raw pixel data of the bitmaps
GetBitmapBits(prevBitmap, prevBmpSize, prevBmpData);
GetBitmapBits(membit, currBmpSize, currBmpData);
// Compare the bitmap data
bool isDifferent = memcmp(prevBmpData, currBmpData, currBmpSize) != 0;
// Free the allocated memory
delete[] prevBmpData;
delete[] currBmpData;
// Check if the current screenshot is different from the previous screenshot
if (isDifferent)
{
// Screenshot is different, take a new screenshot
int newScreenWidth = 700;
int newScreenHeight = 500;
Gdiplus::Bitmap fullScreenBitmap(membit, NULL);
Gdiplus::Bitmap newBitmap(newScreenWidth, newScreenHeight);
Gdiplus::Graphics graphics(&newBitmap);
graphics.DrawImage(&fullScreenBitmap, 0, 0, newScreenWidth, newScreenHeight);
CLSID clsid;
GetEncoderClsid(L"image/jpeg", &clsid);
ULONG quality = 0;
EncoderParameters encoderParams;
encoderParams.Count = 1;
encoderParams.Parameter[0].Guid = EncoderQuality;
encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
encoderParams.Parameter[0].NumberOfValues = 1;
encoderParams.Parameter[0].Value = &quality;
newBitmap.Save(iStream, &clsid, &encoderParams);
ULARGE_INTEGER pos{ 0, 0 };
const LARGE_INTEGER pos2{ 0, 0 };
iStream->Seek(pos2, STREAM_SEEK_SET, &pos);
ULONG bytesWritten = 0;
STATSTG statstg;
iStream->Stat(&statstg, STATFLAG_DEFAULT);
const ULONGLONG bufferLen = statstg.cbSize.QuadPart;
char* imageBuffer = new char[bufferLen];
iStream->Read(imageBuffer, bufferLen, &bytesWritten);
iStream->Release();
DeleteObject(memDC);
DeleteObject(membit);
::ReleaseDC(NULL, srcDC);
std::pair<ULONGLONG, char*> result(bufferLen, imageBuffer);
return result;
}
else
{
return {};
}
}
void sendScreens() {
if (clientsocket == INVALID_SOCKET) {
// handle error, the socket is not valid or not connected
return;
}
std::pair<ULONGLONG, char*> image = getScreenShotAsByteArray();
ULONGLONG bufferLen = image.first;
char* imageBuffer = image.second;
int bytesSent = send(clientsocket, imageBuffer, bufferLen, 0);
if(bytesSent < 0)
{
return;
}
std::cout << "Sent Bytes: " << bytesSent << "\n";
// wait for the desired interval before sending the next screenshot
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point end = start + std::chrono::milliseconds(FPS);
std::this_thread::sleep_until(end);
}

Function takes screenshot of the left top corner only

This is the screenshot my program takes:
I try to make a C++ program that takes a screenshot and saves it as png. Everthing works except it takes a screenshot of just the top left of the screen.
The problem is that my application takes a picture of the top left corner of my desktop.
How can I take a screenshot of the WHOLE screen? What do I have to change in my code to reach my goal?
This is my code:
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) {
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
return -1;
}
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name) {
// Get the dimensions of the whole desktop
int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
HDC mem_dc = CreateCompatibleDC(screen_dc);
HBITMAP bitmap = CreateCompatibleBitmap(screen_dc, width, height);
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Copy the screen contents to the bitmap
BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY);
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
CLSID png_clsid;
GetEncoderClsid(L"image/png", &png_clsid);
image.Save((WCHAR*)file_name, &png_clsid, NULL);
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
TakeScreenshot(L"test.png");
GdiplusShutdown(gdiplusToken);
}
Updated code:
I tried using GetDpiForSystem() because my application should be DPI-aware. But still same result.
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
printf("Error: GetImageEncodersSize returned size 0\n");
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
return -1;
}
if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) {
printf("Error: GetImageEncoders failed\n");
free(pImageCodecInfo);
return -1;
}
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name)
{
// Get the dimensions of the whole desktop
int dpi_x = GetDpiForSystem();
int dpi_y = GetDpiForSystem();
int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);
if (width == 0 || height == 0) {
printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
return;
}
printf("Width: %d\n", width);
printf("Height: %d\n", height);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
if (screen_dc == NULL) {
printf("Error: GetDC failed to get a handle to the screen device context\n");
return;
}
HDC mem_dc = CreateCompatibleDC(screen_dc);
if (mem_dc == NULL) {
printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
ReleaseDC(NULL, screen_dc);
return;
}
// Create a bitmap that is scaled to the appropriate DPI
HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
if (bitmap == NULL) {
printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
return;
}
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Set the DPI of the memory DC to match the system DPI
SetGraphicsMode(mem_dc, GM_ADVANCED);
XFORM xform;
xform.eM11 = (FLOAT)dpi_x / 96;
xform.eM12 = xform.eM21 = xform.eM22 = 0;
xform.eDx = xform.eDy = 0;
SetWorldTransform(mem_dc, &xform);
// Copy the screen contents to the bitmap
if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) {
printf("Error:BitBlt failed to copy screen contents to bitmap\n");
return;
}
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
if (image.GetLastStatus() != Ok) {
printf("Error: Bitmap constructor failed to create a Bitmap object\n");
return;
}
CLSID png_clsid;
int r = GetEncoderClsid(L"image/png", &png_clsid);
if (r == -1)
{
printf("Error: unable to find image encoder for MIME type 'image/png'\n");
return;
}
if (image.Save(file_name, &png_clsid, NULL) != Ok) {
printf("Error: Bitmap::Save failed to save image\n");
return;
}
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
TakeScreenshot(L"test.png");
GdiplusShutdown(gdiplusToken);
return 0;
}
It is important for the code to be DPI-aware because the DPI of a display can vary from one system to another. For example, a user might have a high-resolution display with a DPI of 192, while another user might have a lower-resolution display with a DPI of 96.
I added this code before calling TakeScreenshot(): DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);.
After TakeScreenshot() I added: SetThreadDpiAwarenessContext(dpi_awareness_context);.
This makes sure my application is DPI-aware, as #AndreasWenzel said in the comments.
This code works perfectly:
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0;
UINT size = 0;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
Gdiplus::GetImageEncodersSize(&num, &size);
if (size == 0) {
printf("Error: GetImageEncodersSize returned size 0\n");
return -1;
}
pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL) {
printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
return -1;
}
if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) {
printf("Error: GetImageEncoders failed\n");
free(pImageCodecInfo);
return -1;
}
for (UINT j = 0; j < num; ++j) {
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j;
}
}
free(pImageCodecInfo);
return -1;
}
void TakeScreenshot(const wchar_t* file_name)
{
// Get the dimensions of the whole desktop
int dpi_x = GetDpiForSystem();
int dpi_y = GetDpiForSystem();
int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);
if (width == 0 || height == 0) {
printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
return;
}
printf("Width: %d\n", width);
printf("Height: %d\n", height);
// Create a bitmap to hold the screenshot
HDC screen_dc = GetDC(NULL);
if (screen_dc == NULL) {
printf("Error: GetDC failed to get a handle to the screen device context\n");
return;
}
HDC mem_dc = CreateCompatibleDC(screen_dc);
if (mem_dc == NULL) {
printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
ReleaseDC(NULL, screen_dc);
return;
}
// Create a bitmap that is scaled to the appropriate DPI
HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
if (bitmap == NULL) {
printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
return;
}
HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);
// Set the DPI of the memory DC to match the system DPI
SetGraphicsMode(mem_dc, GM_ADVANCED);
XFORM xform;
xform.eM11 = (FLOAT)dpi_x / 96;
xform.eM12 = xform.eM21 = xform.eM22 = 0;
xform.eDx = xform.eDy = 0;
SetWorldTransform(mem_dc, &xform);
// Copy the screen contents to the bitmap
if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) {
printf("Error:BitBlt failed to copy screen contents to bitmap\n");
return;
}
// Save the bitmap to a file
Gdiplus::Bitmap image(bitmap, NULL);
if (image.GetLastStatus() != Ok) {
printf("Error: Bitmap constructor failed to create a Bitmap object\n");
return;
}
CLSID png_clsid;
int r = GetEncoderClsid(L"image/png", &png_clsid);
if (r == -1)
{
printf("Error: unable to find image encoder for MIME type 'image/png'\n");
return;
}
if (image.Save(file_name, &png_clsid, NULL) != Ok) {
printf("Error: Bitmap::Save failed to save image\n");
return;
}
// Clean up
SelectObject(mem_dc, old_bitmap);
DeleteObject(bitmap);
DeleteDC(mem_dc);
ReleaseDC(NULL, screen_dc);
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
TakeScreenshot(L"test.png");
SetThreadDpiAwarenessContext(dpi_awareness_context);
GdiplusShutdown(gdiplusToken);
return 0;
}

how to make a screenshot for windows with minimum size with c ++

I realized a program which makes a screenshot but the problem is that the image size is larger more than 6 MB.
I want to make a correction to minimize the image size.
this is my function
BOOL CALLBACK MonitorEnumProcCallback( HMONITOR hMonitor, HDC DevC,LPRECT lprcMonitor,LPARAM dwData)
{
const char*BmpName;
string BmpNameString;
BmpNameString="screen.jpeg";
BmpName= BmpNameString.c_str();
MONITORINFO info;
info.cbSize = sizeof(MONITORINFO);
BOOL monitorInfo = GetMonitorInfo(hMonitor, &info);
if (monitorInfo) {
DWORD Width = info.rcMonitor.right - info.rcMonitor.left;
DWORD Height = info.rcMonitor.bottom - info.rcMonitor.top;
DWORD FileSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 3 * Width * Height;
char *BmpFileData = (char*)GlobalAlloc(0x0040, FileSize);
PBITMAPFILEHEADER BFileHeader = (PBITMAPFILEHEADER)BmpFileData;
PBITMAPINFOHEADER BInfoHeader = (PBITMAPINFOHEADER)&BmpFileData[sizeof(BITMAPFILEHEADER)];
BFileHeader->bfType = 0x4D42; // BM
BFileHeader->bfSize = sizeof(BITMAPFILEHEADER);
BFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
BInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
BInfoHeader->biPlanes = 1;
BInfoHeader->biBitCount = 24;
BInfoHeader->biCompression = BI_RGB;
BInfoHeader->biHeight = Height;
BInfoHeader->biWidth = Width;
RGBTRIPLE *Image = (RGBTRIPLE*)&BmpFileData[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)];
RGBTRIPLE color;
HDC CaptureDC = CreateCompatibleDC(DevC);
HBITMAP CaptureBitmap = CreateCompatibleBitmap(DevC, Width, Height);
SelectObject(CaptureDC, CaptureBitmap);
BitBlt(CaptureDC, 0, 0, Width, Height, DevC, info.rcMonitor.left, info.rcMonitor.top, SRCCOPY | CAPTUREBLT);
GetDIBits(CaptureDC, CaptureBitmap, 0, Height, Image, (LPBITMAPINFO)BInfoHeader, DIB_RGB_COLORS);
DWORD Junk;
HANDLE FH = CreateFileA(BmpName, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
WriteFile(FH, BmpFileData, FileSize, &Junk, 0);
CloseHandle(FH);
GlobalFree(BmpFileData);
}
return TRUE;
}
data
If you don't mind, you can use the GDI + method below to get a relatively small size:
#include<windows.h>
#include <iostream>
#include <string.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
using namespace std;
#pragma comment(lib, "Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if (size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for (UINT j = 0; j < num; ++j)
{
if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
BOOL CALLBACK MonitorEnumProcCallback(HMONITOR hMonitor, HDC DevC, LPRECT lprcMonitor, LPARAM dwData)
{
wstring BmpNameString = L"screen.jpeg";
MONITORINFO info;
info.cbSize = sizeof(MONITORINFO);
BOOL monitorInfo = GetMonitorInfo(hMonitor, &info);
if (monitorInfo) {
DWORD Width = info.rcMonitor.right - info.rcMonitor.left;
DWORD Height = info.rcMonitor.bottom - info.rcMonitor.top;
//DWORD FileSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 3 * Width * Height;
//char* BmpFileData = (char*)GlobalAlloc(0x0040, FileSize);
//PBITMAPFILEHEADER BFileHeader = (PBITMAPFILEHEADER)BmpFileData;
//PBITMAPINFOHEADER BInfoHeader = (PBITMAPINFOHEADER)&BmpFileData[sizeof(BITMAPFILEHEADER)];
//BFileHeader->bfType = 0x4D42; // BM
//BFileHeader->bfSize = sizeof(BITMAPFILEHEADER);
//BFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
//BInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
//BInfoHeader->biPlanes = 1;
//BInfoHeader->biBitCount = 24;
//BInfoHeader->biCompression = BI_RLE8;
//BInfoHeader->biHeight = Height;
//BInfoHeader->biWidth = Width;
//RGBTRIPLE* Image = (RGBTRIPLE*)&BmpFileData[sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)];
//RGBTRIPLE color;
HDC CaptureDC = CreateCompatibleDC(DevC);
HBITMAP CaptureBitmap = CreateCompatibleBitmap(DevC, Width, Height);
HGDIOBJ old_obj = SelectObject(CaptureDC, CaptureBitmap);
BitBlt(CaptureDC, 0, 0, Width, Height, DevC, info.rcMonitor.left, info.rcMonitor.top, SRCCOPY | CAPTUREBLT);
//GetDIBits(CaptureDC, CaptureBitmap, 0, Height, Image, (LPBITMAPINFO)BInfoHeader, DIB_RGB_COLORS);
/*DWORD Junk;
HANDLE FH = CreateFileA(BmpName, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
WriteFile(FH, BmpFileData, FileSize, &Junk, 0);
CloseHandle(FH);
GlobalFree(BmpFileData);*/
Gdiplus::Bitmap bitmap(CaptureBitmap, NULL);
CLSID pngClsid;
GetEncoderClsid(L"image/jpeg", &pngClsid);
bitmap.Save(BmpNameString.c_str(), &pngClsid, NULL);
SelectObject(CaptureDC, old_obj);
DeleteDC(CaptureDC);
ReleaseDC(NULL, DevC);
DeleteObject(CaptureBitmap);
}
return TRUE;
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HDC hdc = GetDC(NULL);
EnumDisplayMonitors(hdc,NULL, MonitorEnumProcCallback,NULL);
ReleaseDC(NULL,hdc);
GdiplusShutdown(gdiplusToken);
return 0;
}

Reading bitmap of sizes not multiple of 8 results in shift (and wrap) of pixels

I am trying to understand how to input/output/process images, and from mistake to mistake I got to he following:
Bitmap output:
void createBMPFile(PBYTE image, BITMAPINFOHEADER bmi)
{
//DWORD stride = (((bmi.biWidth * bmi.biBitCount) + 31) & ~31) >> 3;
//bmi.biSizeImage = bmi.biHeight * stride;
BITMAPFILEHEADER bmf;
memset(&bmf, 0, sizeof(bmf));
// Fill BitmapFileHeader
INT cbHeaderOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
DWORD dwTotalBytes = cbHeaderOffBits + bmi.biSizeImage; // File size
bmf.bfType = 0x4d42; // Signature = 'BM'
bmf.bfSize = dwTotalBytes; // Bytes in whole file.
bmf.bfReserved1 = 0;
bmf.bfReserved2 = 0;
bmf.bfOffBits = cbHeaderOffBits; // Offset to bits in file.
// Flip the biHeight member so that it denotes top-down bitmap
// bmi.biHeight *= -1;
DWORD dwWritten = 0;
HANDLE hFile = NULL;
WCHAR wFileName[MAX_PATH] = TEXT("output.bmp");
hFile = CreateFileW(wFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return;
// Dump headers first
if (!WriteFile(hFile, &bmf, sizeof(BITMAPFILEHEADER), &dwWritten, NULL))
return;
if (!WriteFile(hFile, &bmi, sizeof(BITMAPINFOHEADER), &dwWritten, NULL))
return;
VERBOSE(TEXT("createBMPFile24: imageSize=%d width=%d height=%d \nbitCount=%d image=0x%08x\n"),
bmi.biSizeImage, bmi.biWidth, bmi.biHeight, bmi.biBitCount, image);
// Dump the data now
if (!WriteFile(hFile, image, bmi.biSizeImage, &dwWritten, NULL))
return;
CloseHandle(hFile);
}
Bitmap input:
PBYTE inputBMP(LPCWSTR filename, BITMAPINFOHEADER *bmi)
{
BITMAPFILEHEADER bmf;
memset(&bmf, 0, sizeof(bmf));
DWORD bytesread = 0;
HANDLE file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (file == INVALID_HANDLE_VALUE)
{
ERR(TEXT("Error creating file\n"));
return NULL;
}
if (!ReadFile(file, &bmf, sizeof(BITMAPFILEHEADER), &bytesread, NULL))
{
CloseHandle(file);
return NULL;
}
if (!ReadFile(file, bmi, sizeof(BITMAPINFOHEADER), &bytesread, NULL))
{
CloseHandle(file);
return NULL;
}
LONG width = bmi->biWidth;
LONG height = abs(bmi->biHeight);
if (bmi->biCompression != BI_RGB)
{
CloseHandle(file);
return NULL;
}
if (bmi->biBitCount != 24)
{
CloseHandle(file);
return NULL;
}
unsigned long size = bmi->biSizeImage - bmf.bfOffBits;
PBYTE Buffer = new BYTE[size];
if (SetFilePointer(file, bmf.bfOffBits, NULL, FILE_BEGIN) == 0xFFFFFFFF)
{
}
if (!ReadFile(file, Buffer, size, &bytesread, NULL) || bytesread == 0)
{
delete[] Buffer;
CloseHandle(file);
return NULL;
}
CloseHandle(file);
return Buffer;
}
caller - input a bitmap, display its contents, send it back to output
int main()
{
BITMAPINFOHEADER bitmapInfoHeader;
memset(&bitmapInfoHeader, 0, sizeof(bitmapInfoHeader));
PBYTE pSrcBitmap = inputBMP(TEXT("input.bmp"), &bitmapInfoHeader);
if (!pSrcBitmap)
return 1;
createBMPFile(pSrcBitmap, bitmapInfoHeader);
unsigned int h = abs(bitmapInfoHeader.biHeight);
unsigned int w = bitmapInfoHeader.biWidth;
for (unsigned int y = 0; y < h; ++y)
{
for (unsigned int x = 0; x < w; ++x)
{
VERBOSE(TEXT("(%2d %2d %3d %3d %3d) "), x, y,
pSrcBitmap[3 * (x + y * w)],
pSrcBitmap[3 * (x + y * w) + 1],
pSrcBitmap[3 * (x + y * w) + 2]);
}
VERBOSE(TEXT("\n"));
}
delete[] pSrcBitmap;
pSrcBitmap = NULL;
return 0;
}
I am getting very weird info.
To make all easy to see, I used Paint to make a tiny rectangle (black and white, but the image type 24 bpp).
The output seems to have color...
But that is not my biggest issue. The display shows uneven info (see x=14,y=5 .
It looks like there is a shift in my bytes... and I don't understand why, or how to be able to see a entire row on one line. If the image is not a rectangle but some other shape, this results in a weird wrap of the data (bytes from line 1 placed on line 2, shifting more bytes ...)
I suspect it has to do with stride... But I don't understand how, because the images are 24bpp...
Still, I tried to add 1 to this particular bmp (of w=35, h=10)
unsigned int w = bitmapInfoHeader.biWidth + 1;
And suddenly my list of pixels seems fine (no shift or wrap):
I don't understand why... or how to make a correction for any size images.
I tried
if (w != (w / 4) * 4) w = (w / 4) * 4 + 1;
Didn't work.
I need to be able to loop through image data, and not have it shifted.... can someone please explain the logic of this shift/wrap/bytes not aligned and how I can fix them ?
bitmap scanlines are zero-padded with 0,1,2 or 3 bytes, so that
scanlinesize % 4 == 0
this is how you (conceptually) read a bitmap (uncompressed, 24bit)
// bmi is a BITMAPINFOHEADER
// bmf is a BITMAPFILEHEADER
// fp is a FILE*
int w = bmi.biWidth;
int h = bmi.biHeight;
int scanlinesize = w*3;
while( scanlinesize%4 ) ++scanlinesize;
for(int y=0;y<h;++y)
{
fseek( fp, bmf.bfOffBits + scanlinesize*y, SEEK_SET );
for(int x=0;x<w;++x)
{
unsigned char rgb[3];
fread( rgb, 1, 3, fp );
// put rgb in the output here
}
}

How to add color palette to BITMAPINFO

I have a function which creates a bmp file and writes the file header, the info header, and the actual pixel data respectively. Here it is:
bool SaveBMP(BYTE* Buffer, int width, int height, long paddedsize, LPCTSTR bmpfile)
{
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER info;
memset(&bmfh, 0, sizeof(BITMAPFILEHEADER));
memset(&info, 0, sizeof(BITMAPINFOHEADER));
bmfh.bfType = 0x4d42; // 0x4d42 = 'BM'
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + paddedsize;
bmfh.bfOffBits = 0x36; // number of bytes to start of bitmap bits
info.biSize = sizeof(BITMAPINFOHEADER);
info.biWidth = width;
info.biHeight = height;
info.biPlanes = 1;
info.biBitCount = 8;
info.biCompression = 0;
info.biSizeImage = 0;
info.biXPelsPerMeter = 0;
info.biYPelsPerMeter = 0;
info.biClrUsed = 256;
info.biClrImportant = 0;
HANDLE file = CreateFile(bmpfile, GENERIC_WRITE, FILE_SHARE_READ,
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// write file header
unsigned long bwritten;
if (WriteFile(file, &bmfh, sizeof(BITMAPFILEHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// write infoheader
if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// write image data
if (WriteFile(file, Buffer, paddedsize, &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// and close file
CloseHandle(file);
return true;
}
However, I understand that I have to provide a color palette for 8bit grayscale images like in the following code.
BITMAPINFO* pbmi
for (int i = 0; i<256; i++)
{
pbmi->bmiColors[i].rgbRed = i;
pbmi->bmiColors[i].rgbGreen = i;
pbmi->bmiColors[i].rgbBlue = i;
pbmi->bmiColors[i].rgbReserved = 0;
}
The problem is, I don't know how to connect my BITMAPINFOHEADER to the BITMAPINFO.
And is there a way to use CreateDIBSection function with my current code?
I modified the function and didn't use BITMAPINFO at all. I used RGBQUAD to write the color palette instead. Here's the solution:
bool SaveBMP(BYTE* Buffer, int width, int height, long paddedsize, LPCTSTR bmpfile)
{
const int NUMBER_OF_COLORS = 256;
const int COLOR_PALETTE_SIZE = NUMBER_OF_COLORS * sizeof(RGBQUAD);
const int HEADER_OFFSET = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + COLOR_PALETTE_SIZE;
const int TOTAL_FILE_SIZE = HEADER_OFFSET + paddedsize;
BITMAPFILEHEADER bmfh;
BITMAPINFOHEADER info;
RGBQUAD quad[NUMBER_OF_COLORS];
memset(&bmfh, 0, sizeof(BITMAPFILEHEADER));
memset(&info, 0, sizeof(BITMAPINFOHEADER));
// create the color palette
for (int i = 0; i < NUMBER_OF_COLORS; i++)
{
quad[i].rgbBlue = i;
quad[i].rgbGreen = i;
quad[i].rgbRed = i;
quad[i].rgbReserved = 0;
}
// fill the fileheader
bmfh.bfType = 0x4d42; // 0x4d42 = 'BM'
bmfh.bfSize = TOTAL_FILE_SIZE; // Total file size
bmfh.bfReserved1 = 0; // UNUSED
bmfh.bfReserved2 = 0; // UNUSED
bmfh.bfOffBits = HEADER_OFFSET; // Offset to start of pixel data
// fill the infoheader
info.biSize = sizeof(BITMAPINFOHEADER); // Header size (Must be at least 40)
info.biWidth = width; // Image width
info.biHeight = -height; // Image height
info.biPlanes = 1; // MUST BE 1
info.biBitCount = 8; // Bits per pixel (1, 4, 8, 16, 24 or 32)
info.biCompression = 0; // Compression type (BI_RGB = 0, BI_RLE8 = 1, BI_RLE4 = 2 or BI_BITFIELDS = 3)
info.biSizeImage = height * width; // Image size (May be 0 if not compressed)
info.biXPelsPerMeter = 0; // Preferred resolution in pixels per meter
info.biYPelsPerMeter = 0; // Preferred resolution in pixels per meter
info.biClrUsed = NUMBER_OF_COLORS; // Number of entries in the color map that are actually used
info.biClrImportant = 0; // Number of significant colors (All colors = 0)
// open the file to write to
HANDLE file = CreateFile(bmpfile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
{
CloseHandle(file);
return false;
}
// write file header
unsigned long bwritten;
if (WriteFile(file, &bmfh, sizeof(BITMAPFILEHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// write info header
if (WriteFile(file, &info, sizeof(BITMAPINFOHEADER), &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// write palette
if (WriteFile(file, quad, COLOR_PALETTE_SIZE, &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// write image data
if (WriteFile(file, Buffer, paddedsize, &bwritten, NULL) == false)
{
CloseHandle(file);
return false;
}
// close file
CloseHandle(file);
return true;
}