Read DirectX Game Overlay - c++

I have been working on accessing a game's pixel colors, with little success. I have researched and attempted several methods, including BitBlt, the SharpDX library, Desktop Duplication (could not use due to Windows 8 limitations), and GetPixel() (this has worked, but it is far too slow to be useful).
None of these (except GetPixel()) work due to the game's Overlay (I possibly have just not discovered the appropriate SharpDX/ D3D function).
I have looked into mirror drivers, but I could not find sufficient documentation to utilize them, or know if they would work.
Is there any way to access the pixels of a DirectX game's Overlay?

Since direct-x games are usually double buffered, there is a 50% chance that GetPixel or Bitblt would work at all.. If you really need real-time pixels from the game, you can hook it with a dll/detour and feed the pixels to whatever application you want via sharedmemory or some form of inter-process communication.
As a side note, since you say that GetPixel works for you, you can always try the following for grabbing a screenshot of a specified window and saving it.. You'll need to link to gdi32 and gdiplus
#include <windows.h>
#include <iostream>
#include <fstream>
typedef struct
{
HBITMAP hBmp;
int Width, Height;
unsigned int* Pixels;
unsigned short BitsPerPixel;
} BmpData;
bool ScreenShot(BmpData &Data, HDC DC)
{
bool Result = false;
memset(&Data, 0, sizeof(Data));
HDC hdcMem = CreateCompatibleDC(DC);
Data.Width = GetDeviceCaps(DC, HORZRES);
Data.Height = GetDeviceCaps(DC, VERTRES);
unsigned char* Pixels = NULL;
unsigned short BitsPerPixel = 32;
BITMAPINFO Info = {{sizeof(BITMAPINFOHEADER), Data.Width, -Data.Height, 1, BitsPerPixel, BI_RGB, 0, 0, 0, 0, 0}};
Data.hBmp = CreateDIBSection(DC, &Info, DIB_RGB_COLORS, reinterpret_cast<void**>(&Pixels), NULL, 0);
if(Data.hBmp)
{
HBITMAP hOld = reinterpret_cast<HBITMAP>(SelectObject(hdcMem, Data.hBmp));
BitBlt(hdcMem, 0, 0, Data.Width, Data.Height, DC, 0, 0, SRCCOPY);
SelectObject(hdcMem, hOld);
Data.Height *= -1;
Data.BitsPerPixel = BitsPerPixel;
Data.Pixels = reinterpret_cast<unsigned int*>(Pixels);
Result = true;
}
DeleteDC(hdcMem);
return Result;
}
/*Portable way to save a bitmap =)*/
void SaveBitmap(const char* FilePath, const BmpData &Data)
{
std::fstream hFile(FilePath, std::ios::out | std::ios::binary);
if (!hFile.is_open())
{
printf("%s", "Error. Cannot Create Bitmap.");
return;
}
unsigned int Trash = 0;
unsigned short Planes = 1;
unsigned int biSize = 40;
unsigned short Type = 0x4D42;
unsigned int compression = 0;
unsigned int PixelsOffsetBits = 54;
int Width = Data.Width;
int Height = -Data.Height;
unsigned short BitsPerPixel = Data.BitsPerPixel;
unsigned int size = ((Width * BitsPerPixel + 31) / 32) * 4 * Height;
unsigned int bfSize = 54 + size;
Height *= -1;
hFile.write(reinterpret_cast<char*>(&Type), sizeof(Type));
hFile.write(reinterpret_cast<char*>(&bfSize), sizeof(bfSize));
hFile.write(reinterpret_cast<char*>(&Trash), sizeof(unsigned int));
hFile.write(reinterpret_cast<char*>(&PixelsOffsetBits), sizeof(PixelsOffsetBits));
hFile.write(reinterpret_cast<char*>(&biSize), sizeof(biSize));
hFile.write(reinterpret_cast<char*>(&Width), sizeof(Width));
hFile.write(reinterpret_cast<char*>(&Height), sizeof(Height));
hFile.write(reinterpret_cast<char*>(&Planes), sizeof(Planes));
hFile.write(reinterpret_cast<char*>(&BitsPerPixel), sizeof(BitsPerPixel));
hFile.write(reinterpret_cast<char*>(&compression), sizeof(compression));
hFile.write(reinterpret_cast<char*>(&size), sizeof(size));
hFile.write(reinterpret_cast<char*>(&Trash), sizeof(unsigned int));
hFile.write(reinterpret_cast<char*>(&Trash), sizeof(unsigned int));
hFile.write(reinterpret_cast<char*>(&Trash), sizeof(unsigned int));
hFile.write(reinterpret_cast<char*>(&Trash), sizeof(unsigned int));
hFile.write(reinterpret_cast<char*>(Data.Pixels), size);
hFile.close();
}
int main()
{
HWND MyWindow = nullptr; //Handle to the window that you want to screenshot..
BmpData data;
HDC Screen = GetDC(MyWindow); //Get a DC..
ScreenShot(data, Screen);
SaveBitmap("C:/Users/Brandon/desktop/Foo.bmp", data);
DeleteDC(Screen);
DeleteObject(data.hBmp);
}

Related

I cannot convert a GDI+ bitmap to base 64 in C++ (Microsoft Visual C++)

I cannot convert a GDI+ bitmap to base 64 in C++
I have so far:
Downloaded and included this library: https://github.com/ReneNyffenegger/cpp-base64
Written the function that you can see below.
Called it, passing in a bitmap that I know has data. I know that the bitmap has data because I am using in another function called directly after this one.
The problem is that the charPixels array is always full of zeros.
Secondly can you please explain to me what the safe way of unlocking the bits is. I fear that if I just do it in the finally, if the bits aren't actually locked at that point I will get an exception.
Stride is a positive number it is: 1280.
Also I have access to 'finally' because it is a MS extension to C++.
[Note: Code is updated because I pasted the wrong code in by mistake]
std::string GdiBitmapToBase64(Gdiplus::Bitmap* gdiBitmap, int width, int height)
{
unsigned char* charPixels = nullptr;
try
{
Gdiplus::Rect rect = Gdiplus::Rect(0, 0, width, height);
Gdiplus::BitmapData gdiBitmapData;
gdiBitmap->LockBits(&rect, Gdiplus::ImageLockMode::ImageLockModeRead, PixelFormat32bppARGB, &gdiBitmapData);
auto stride = gdiBitmapData.Stride;
if (stride < 0) stride = -stride;
charPixels = new unsigned char[height * stride];
memset(charPixels, 0, height * stride);
memcpy(charPixels, gdiBitmapData.Scan0, stride);
std::string ret = base64_encode(charPixels, stride);
gdiBitmap->UnlockBits(&gdiBitmapData);
return ret;
}
finally
{
if(charPixels != nullptr)
{
delete[] charPixels;
}
}
}
Here is the code which calls this method. This may help:
void CLIScumm::Wrapper::ScreenUpdated(const void* buf, int pitch, int x, int y, int w, int h, PalletteColor* color)
{
const unsigned char* bufCounter = static_cast<const unsigned char*>(buf);
for (int hightCounter = 0; hightCounter < h; hightCounter++, bufCounter = bufCounter + pitch)
{
for (int widthCounter = 0; widthCounter < w; widthCounter++)
{
PalletteColor currentColor = *(color + *(bufCounter + widthCounter));
gdiBitmap->SetPixel(x + widthCounter, y + hightCounter, Gdiplus::Color(currentColor.r, currentColor.g, currentColor.b));
}
}
_screenUpdated->Invoke(gcnew System::String(GdiBitmapToBase64(gdiBitmap, DISPLAY_DEFAULT_WIDTH, DISPLAY_DEFAULT_HEIGHT).c_str()));
}
And the declarations:
namespace CLIScumm {
public ref class Wrapper {
...
private:
...
Gdiplus::Graphics* gdiGraphics;
Gdiplus::Bitmap* gdiBitmap;
...
};
And the initialization:
void CLIScumm::Wrapper::init()
{
if (!hasStarted)
{
try
{
if (!hasStarted)
{
...
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
(malloc(sizeof(100000) * DISPLAY_DEFAULT_HEIGHT * DISPLAY_DEFAULT_WIDTH));
gdiBitmap = new Gdiplus::Bitmap(DISPLAY_DEFAULT_WIDTH, DISPLAY_DEFAULT_HEIGHT, PixelFormat32bppARGB);
gdiGraphics = new Gdiplus::Graphics(gdiBitmap);
InitImage();
...
}
}
...
}
}
Thank you all for you help, but I solved my own problem.
I was confusing bitmap pixel data with the actual bytes of a bitmap, and Scan0 is the former. The reason I was getting only zero's is because the first few frames were black.
I followed the example from C++ gdi::Bitmap to PNG Image in memory to get the actual bitmap bytes.
I modified the example function to:
bool SavePngMemory(Gdiplus::Bitmap* gdiBitmap, std::vector<BYTE>& data)
{
//write to IStream
IStream* istream = nullptr;
CreateStreamOnHGlobal(NULL, TRUE, &istream);
CLSID clsid_bmp;
CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid_bmp);
Gdiplus::Status status = gdiBitmap->Save(istream, &clsid_bmp);
if (status != Gdiplus::Status::Ok)
return false;
//get memory handle associated with istream
HGLOBAL hg = NULL;
GetHGlobalFromStream(istream, &hg);
//copy IStream to buffer
int bufsize = GlobalSize(hg);
data.resize(bufsize);
//lock & unlock memory
LPVOID pimage = GlobalLock(hg);
memcpy(&data[0], pimage, bufsize);
GlobalUnlock(hg);
istream->Release();
return true;
}
I was then able to convert data to base64 by calling:
base64_encode(&data[0], data.size());
I can't vouch for the quality of the code, because I don't understand how everything works.

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;

C++ GDI+ SelectPalette

I am playing with GDI+. Trying to use
pDC->SelectPalette(CPalette::FromHandle(hLogPal), FALSE);
pDC->RealizePalette();
instead of
memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize);
But it seem that with it's working with memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize); but with SelectPalette only black screen.
I thought that information about color can be used from bitmapinfo or from pallet.
All code:
void ConvertTo8BitImage(BYTE** pBitmapInfo, BYTE** imageData)
{
Gdiplus::GdiplusStartupInput tmp;
ULONG_PTR token;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
Gdiplus::Bitmap *source = Gdiplus::Bitmap::FromFile(L"D:/TestImage.bmp");
Gdiplus::Bitmap *destination = source->Clone(0, 0, source->GetWidth(), source->GetHeight(),
PixelFormat8bppIndexed);
int width = source->GetWidth();
int height = source->GetHeight();
HBITMAP hBitmap;
Gdiplus::Color color;
destination->GetHBITMAP(color, &hBitmap);
int palettesize = 256 * sizeof(RGBQUAD);
CLSID clsid_bmp;
CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid_bmp);
*pBitmapInfo = new BYTE[(sizeof(BITMAPINFO) + palettesize)];
BITMAPINFO* ptr = (BITMAPINFO*)*pBitmapInfo;
ptr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
ptr->bmiHeader.biWidth = width;
ptr->bmiHeader.biHeight = height;
ptr->bmiHeader.biPlanes = 1;
ptr->bmiHeader.biBitCount = 8;
ptr->bmiHeader.biCompression = BI_RGB;
ptr->bmiColors[0].rgbRed = 0;
DWORD size = ((width * 8 + 31) / 32) * 4 * height;
*imageData = new BYTE[size];
HDC hdc = GetDC(0);
GetDIBits(hdc, hBitmap, 0, height, *imageData, (BITMAPINFO*)*pBitmapInfo, DIB_PAL_COLORS);
ReleaseDC(0, hdc);
Gdiplus::GdiplusShutdown(token);
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
CMFCApplicationColorsDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
BYTE *bitmapInfo = NULL;
BYTE *imageData = NULL;
ConvertTo8BitImage(&bitmapInfo, &imageData);
int palettesize = 256 * sizeof(RGBQUAD);
BYTE *newBitmapInfo = new BYTE[(sizeof(BITMAPINFO) + palettesize)];
ZeroMemory(newBitmapInfo, (sizeof(BITMAPINFO) + palettesize));
BITMAPINFO *ptr = (BITMAPINFO*)newBitmapInfo;
ptr->bmiHeader.biBitCount = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biBitCount;
ptr->bmiHeader.biClrImportant = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biClrImportant;
ptr->bmiHeader.biClrUsed = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biClrUsed;
ptr->bmiHeader.biCompression = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biCompression;
ptr->bmiHeader.biHeight = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biHeight;
ptr->bmiHeader.biPlanes = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biPlanes;
ptr->bmiHeader.biSize = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biSize;
ptr->bmiHeader.biSizeImage = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biSizeImage;
ptr->bmiHeader.biWidth = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biWidth;
ptr->bmiHeader.biXPelsPerMeter = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biXPelsPerMeter;
ptr->bmiHeader.biYPelsPerMeter = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biYPelsPerMeter;
ptr->bmiColors[0] = ((BITMAPINFO*)bitmapInfo)->bmiColors[0];
RGBQUAD rgbquad[256];
memcpy(rgbquad, bitmapInfo + sizeof(BITMAPINFO), palettesize);
//memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize);
NPLOGPALETTE pPal = (NPLOGPALETTE)LocalAlloc(LMEM_FIXED,
(sizeof(LOGPALETTE) +
(sizeof(PALETTEENTRY) * (palettesize))));
pPal->palVersion = 0x300;
pPal->palNumEntries = 256;
for (int i = 0; i < 256; i++)
{
pPal->palPalEntry[i].peRed = rgbquad[i].rgbRed;
pPal->palPalEntry[i].peGreen = rgbquad[i].rgbGreen;
pPal->palPalEntry[i].peBlue = rgbquad[i].rgbBlue;
pPal->palPalEntry[i].peFlags = 0;
}
HPALETTE hLogPal = CreatePalette((LPLOGPALETTE)pPal);
pDC->SelectPalette(CPalette::FromHandle(hLogPal), FALSE);
pDC->RealizePalette();
StretchDIBits(pDC->GetSafeHdc(), 0, 0, 1920, 1080, 0, 0, 1920, 1080,
imageData, ptr, DIB_PAL_COLORS, SRCCOPY);
delete[] bitmapInfo;
delete[] imageData;
}
HBITMAP hBitmap;
Gdiplus::Color color;
destination->GetHBITMAP(color, &hBitmap);
You did convert to 8-bit bitmap, however GetHBITMAP will return a bitmap handle compatible with your video card, which is probably 32-bit. GDI+ has already processed the palette and returned a bitmap handle which is turned back in to 32-bit. HBITMAP handle can be painted directly, for example using CreateCompatibleDC and BitBlt. So there is no need to obtaining the palette and passing it to GDI, and no need for 8-bit conversion in the first place.
If this is necessary for some reason, you can get the bits and palette from 32-bit bitmap, put that in 8-bit bitmap, and draw with StretchDIBits
The main issue in your code is that it should use DIB_RGB_COLORS flag for GetDIBits/StretchDIBits, because the device context is most likely 32-bit. There is no need for SelectPalette/RealizePalette either (unless it's 8-bit display from 30 years ago)
It makes more sense to get the bits directly from GDI+ using LockBits, and get the palette directly using GetPalette, as seen in the example below.
Aside, source and destination have to be deleted before exit.
void draw(HDC hdc)
{
Gdiplus::Bitmap *source = Gdiplus::Bitmap::FromFile(L"D:/TestImage.bmp");
if(!source)
return;
int width = source->GetWidth();
int height = source->GetHeight();
Gdiplus::Bitmap *destination = source->Clone(0, 0, width, height,
PixelFormat8bppIndexed);
//get bitmap bits from GDI+
Gdiplus::BitmapData data;
Gdiplus::Rect rect(0, 0, width, height);
destination->LockBits(&rect, Gdiplus::ImageLockModeRead,
destination->GetPixelFormat(), &data);
int bufsize = data.Stride * data.Height;
BYTE *buf = new BYTE[bufsize];
memcpy(buf, data.Scan0, bufsize);
destination->UnlockBits(&data);
//setup BITMAPINFO
int bmpinfo_size = sizeof(BITMAPINFO) + 256 * 4;
BITMAPINFO* bmpinfo = (BITMAPINFO*)new BYTE[bmpinfo_size];
memset(bmpinfo, 0, bmpinfo_size);
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo->bmiHeader.biWidth = width;
bmpinfo->bmiHeader.biHeight = -height;
bmpinfo->bmiHeader.biPlanes = 1;
bmpinfo->bmiHeader.biBitCount = 8;
bmpinfo->bmiHeader.biCompression = BI_RGB;
bmpinfo->bmiHeader.biSizeImage = bufsize;
//get palette from GDI+
int palsize = destination->GetPaletteSize();
Gdiplus::ColorPalette *palette = (Gdiplus::ColorPalette*)new BYTE[palsize];
destination->GetPalette(palette, palsize);
//set palette for BITMAPINFO
memset(&bmpinfo->bmiColors[0], 0, 256 * 4);
for(int i = 0; i < palette->Count; i++)
{
auto clr = Gdiplus::Color(palette->Entries[i]);
bmpinfo->bmiColors[i].rgbRed = clr.GetR();
bmpinfo->bmiColors[i].rgbGreen = clr.GetG();
bmpinfo->bmiColors[i].rgbBlue = clr.GetB();
bmpinfo->bmiColors[i].rgbReserved = 0;
}
StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height,
buf, bmpinfo, DIB_RGB_COLORS, SRCCOPY);
delete[] buf;
delete[] bmpinfo;
delete[] palette;
delete destination;
delete source;
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
CMFCApplicationColorsDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
Gdiplus::GdiplusStartupInput tmp;
ULONG_PTR token;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
draw(pDC->GetSafeHdc());
Gdiplus::GdiplusShutdown(token);
}

[MFC/C++]Sending CBitmap's bits over socket, and re-construct it on receiver side

I am new with MFC and try to learn it with a project of MFC dialog base on VS2008. Here are the archivements I have done:
First, I have managed to display a list of pictures from a folder to a Listbox Control. After that, I also handled the click event on each line of the listbox to load and show the picture to the Picture Control(type Bitmap) on the right side. You can see the image below for easy understanding: Please click here for the image of my MFC dialog
Here is the code. Note m_ListCtrl and static_picture are variables of the listbox and the picture control:
void CMyClientDlg::OnLbnSelchangeList1(){
CString imagePath;
m_ListCtrl.GetText(m_ListCtrl.GetCurSel(),imagePath);
CImage picture;
picture.Load(imagePath);
if (!picture.IsNull())
{
float screenWidth = 200, screenHeight = 200;
float imageWidth = picture.GetWidth();
float imageHeight = picture.GetHeight();
//scaling:
float pictureRatio = imageWidth/ imageHeight;
float newImageWidth;
float newImageHeight;
int aligmentX = 0;
int aligmentY = 0;
if (pictureRatio <= 1)
{
newImageWidth = imageWidth*(screenHeight/imageHeight);
newImageHeight = screenHeight;
aligmentX = (screenWidth-newImageWidth)/2;
}
else
{
newImageWidth = screenWidth;
newImageHeight = imageHeight*(screenWidth/imageWidth);
aligmentY = (screenHeight - newImageHeight)/2;
}
//end scaling.
CDC *screenDC = GetDC();
CDC mDC;
mDC.CreateCompatibleDC(screenDC);
CBitmap bitMap;
bitMap.CreateCompatibleBitmap(screenDC, screenWidth, screenHeight);
CBitmap *pob = mDC.SelectObject(&bitMap);
mDC.SetStretchBltMode(HALFTONE);
picture.StretchBlt(mDC.m_hDC, aligmentX, aligmentY, newImageWidth, newImageHeight, 0, 0, imageWidth, imageHeight, SRCCOPY);
mDC.SelectObject(pob);
/*.......code to convert bitmap to BYTE* ........*/
/*.......code to send BYTE* over socket........*/
//display the bit map
static_picture.SetBitmap((HBITMAP)bitMap.Detach());
//clean up
ReleaseDC(screenDC);
}
}
So now I would like to advance one more step, and tried to work with socket... and yes, I successfully sent and received simple char* or CString over socket.
What I want to do is: instead showing the picture on this dialog, it shows the image on the other dialog(server).
Somehow I learned that there are 2 funtions that sound work: SetBitmapBits() and GetBitmapBits() (I honestly just read it on some source and have no idead if they suitable for my goal here).
So, I added this piece of code to turn the above bitmap into array of BYTE bmpBuffer:
BITMAP bmpProperties;
bitMap.GetBitmap(&bmpProperties);
int bmpDemension = bmpProperties.bmWidthBytes*bmpProperties.bmHeight;
BYTE* bmpBuffer=(BYTE*)GlobalAlloc(GPTR, bmpDemension);
bitMap.GetBitmapBits(bmpDemension,bmpBuffer);
Then send that array over socket:
UpdateData(TRUE);
char *socketBuffer = reinterpret_cast<char*>(bmpBuffer);
send(m_ClientSocket, socketBuffer, sizeof(socketBuffer), 0);
//clean up after send
GlobalFree((HGLOBAL)bmpBuffer);
On the other dialog. Note: I have hardcoded the demension of the bitmap to 160000, just to simplify the problem:
void CMyServer2Dlg::OnReceive(){
char *socketBuffer = new char [1025];
int iLen;
iLen = recv(m_sConnected, socketBuffer, 1025, NULL);
if(iLen==SOCKET_ERROR)
{
AfxMessageBox("Could not Receive");
}
else
{
BYTE* bmpBuffer = reinterpret_cast<BYTE*>(socketBuffer);
//re-construct the bitmap
CBitmap clone;
CDC *screenDC = GetDC();
CDC mDC;
mDC.CreateCompatibleDC(screenDC);
clone.CreateCompatibleBitmap(screenDC, 200, 200);
clone.SetBitmapBits(160000,bmpBuffer);
//Picture control(type bitmap) has variable "static_picture"
static_picture.SetBitmap((HBITMAP)clone.Detach());
UpdateData(FALSE);
ReleaseDC(screenDC);
GlobalFree((HGLOBAL)bmpBuffer);
}
delete socketBuffer;
And, it just doesn't work... Please tell me where did I mess it up? And sorry for the long post.....
I think the most possible reason is that your receiver doesn't get all data of the picture. I suggest you put a size of the bitmap into the package while sending it, for receiver to get correct size.
Here are some sample code. Be aware they are just for showing the idea, you may need some debugging to make sure they work.
step 1: Pack the size of bitmap. I suppose here the size is less than 64K, so a int is used. If size may be bigger than 64k, you may want to use INT64.
int bmpDemension = bmpProperties.bmWidthBytes*bmpProperties.bmHeight;
int bufferSize = bmpDemension + sizeof(int);
BYTE* bmpBuffer=(BYTE*)GlobalAlloc(GPTR, bufferSize );
bitMap.GetBitmapBits(bmpDemension,bmpBuffer + sizeof(int));
memcpy(bmpBuffer, &bmpDemension, sizeof(int)); // put the size into the head of package.
step 2: Send it out
Be aware, I use bufferSize here, because sizeof(bmpBuffer) returns the pointer size, which is 4, not the space size.
UpdateData(TRUE);
char *socketBuffer = reinterpret_cast<char*>(bmpBuffer);
send(m_ClientSocket, socketBuffer, bufferSize , 0);
//clean up after send
GlobalFree((HGLOBAL)bmpBuffer);
At the receiver side:
First, you read the size of the bitmap, then do receive according to the size of data.
void CMyServer2Dlg::OnReceive(){
char socketBuffer[1025];
int iLen;
iLen = recv(m_sConnected, socketBuffer, sizeof(int), NULL); //read the bigmap size
if(iLen==SOCKET_ERROR)
{
AfxMessageBox("Could not Receive");
}
else
{
int dimension = *((int *) socketBuffer);
char * bitmapBuffer = new char[dimension];
int readSize = dimension;
char * pBuffer = bitmapBuffer;
while (readSize > 0)
{
int sizeToRead = readSize > sizeof(socketBuffer) ? sizeof(socketBuffer) : readSize;
iLen = recv(m_sConnected, socketBuffer, sizeToRead , NULL);
memcpy(pBuffer, socketBuffer, iLen);
pBuffer += iLen;
readSize -= iLen;
}
// when the loop done, you shall have all data in bitmapBuffer.
....
// I leave the remaining code to you.
Again, these code is just to demo the idea.

Loading an image from resource and converting to bitmap in memory

I've searched around using google but I'm completely confused on how to load an image (PNG in my case) from resource and then converting it to a bitmap in memory for use in my splash screen. I've read about GDI+ and libpng but I don't really know how to do what I want. Could anyone help?
GDI+ supports PNG directly. See here and here.
EDIT: The GDI+ documentation offers some advice for how to use GDI+ in a DLL. In your case, the best solution is probably to define initialisation and teardown functions that the client code is required to call.
I ended up using PicoPNG to convert the PNG to a two dimensional vector which I then manually contructed a bitmap from. My final code looked like this:
HBITMAP LoadPNGasBMP(const HMODULE hModule, const LPCTSTR lpPNGName)
{
/* First we need to get an pointer to the PNG */
HRSRC found = FindResource(hModule, lpPNGName, "PNG");
unsigned int size = SizeofResource(hModule, found);
HGLOBAL loaded = LoadResource(hModule, found);
void* resource_data = LockResource(loaded);
/* Now we decode the PNG */
vector<unsigned char> raw;
unsigned long width, height;
int err = decodePNG(raw, width, height, (const unsigned char*)resource_data, size);
if (err != 0)
{
log_debug("Error while decoding png splash: %d", err);
return NULL;
}
/* Create the bitmap */
BITMAPV5HEADER bmpheader = {0};
bmpheader.bV5Size = sizeof(BITMAPV5HEADER);
bmpheader.bV5Width = width;
bmpheader.bV5Height = height;
bmpheader.bV5Planes = 1;
bmpheader.bV5BitCount = 32;
bmpheader.bV5Compression = BI_BITFIELDS;
bmpheader.bV5SizeImage = width*height*4;
bmpheader.bV5RedMask = 0x00FF0000;
bmpheader.bV5GreenMask = 0x0000FF00;
bmpheader.bV5BlueMask = 0x000000FF;
bmpheader.bV5AlphaMask = 0xFF000000;
bmpheader.bV5CSType = LCS_WINDOWS_COLOR_SPACE;
bmpheader.bV5Intent = LCS_GM_BUSINESS;
void* converted = NULL;
HDC screen = GetDC(NULL);
HBITMAP result = CreateDIBSection(screen, reinterpret_cast<BITMAPINFO*>(&bmpheader), DIB_RGB_COLORS, &converted, NULL, 0);
ReleaseDC(NULL, screen);
/* Copy the decoded image into the bitmap in the correct order */
for (unsigned int y1 = height - 1, y2 = 0; y2 < height; y1--, y2++)
for (unsigned int x = 0; x < width; x++)
{
*((char*)converted+0+4*x+4*width*y2) = raw[2+4*x+4*width*y1]; // Blue
*((char*)converted+1+4*x+4*width*y2) = raw[1+4*x+4*width*y1]; // Green
*((char*)converted+2+4*x+4*width*y2) = raw[0+4*x+4*width*y1]; // Red
*((char*)converted+3+4*x+4*width*y2) = raw[3+4*x+4*width*y1]; // Alpha
}
/* Done! */
return result;
}