D3D9 Hooking (EndScene + DrawIndexedPrimitive) - c++

As none of you already know, I am quite fed up with DirectX at the moment. I have tried and tried, and no matter what I do, I cannot seem to get any hooks to work. I have scoured the web and studied hooks; I have combined bits of code together to scrap together my own hook; I have flat out plagiarized hooks to find out how they worked; I have even written a few from scratch. However, I cannot seem to get one to work. I'm trying to make a simple D3D mod-menu for CrossFire. What I have tried:
Hooking via VTable [had issues with getting a device pointer]
Hooking via pattern + mask scanning, etc and detours [unsure how to get a pattern, and cannot find one that works reliably for win10]
Creating a dummy device to get the addresses, etc, etc [caused an immediate shut-down of the game (detected)]
No matter what I do, the menu either flat out refuses to appear once programmed into the detoured EndScene, I get shut-down, I crash, or nothing happens.
Are there any good starter materials with sample code that I can learn from, as well as get this off the ground?
I already have the hack menu programmed, the variables set, the features programmed, the DllMain, the dependencies, you name it. All I need is to get a proper hook working - the only one I got to work had a weird bug where text drawing in EndScene & wall-hack in DrawIndexedPrimitive didn't work.

This is a large amount of code for a SO answer but here we go.
Get correct window
Create a dummy device
Get address of endscene from vtable
Do a standard x86 trampoline hook
Inside hook do whatever initialize stuff you need to
Get a copy of the correct device pointer
Draw stuff
call original function with correct device pointer
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
bool Hook(char* src, char* dst, int len)
{
if (len < 5) return false;
DWORD curProtection;
VirtualProtect(src, len, PAGE_EXECUTE_READWRITE, &curProtection);
memset(src, 0x90, len);
uintptr_t relativeAddress = (uintptr_t)(dst - src - 5);
*src = (char)0xE9;
*(uintptr_t*)(src + 1) = (uintptr_t)relativeAddress;
DWORD temp;
VirtualProtect(src, len, curProtection, &temp);
return true;
}
char* TrampHook(char* src, char* dst, unsigned int len)
{
if (len < 5) return 0;
// Create the gateway (len + 5 for the overwritten bytes + the jmp)
char* gateway = (char*)VirtualAlloc(0, len + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// Put the bytes that will be overwritten in the gateway
memcpy(gateway, src, len);
// Get the gateway to destination addy
uintptr_t gateJmpAddy = (uintptr_t)(src - gateway - 5);
// Add the jmp opcode to the end of the gateway
*(gateway + len) = (char)0xE9;
// Add the address to the jmp
*(uintptr_t*)(gateway + len + 1) = gateJmpAddy;
// Place the hook at the destination
if (Hook(src, dst, len))
{
return gateway;
}
else return nullptr;
}
typedef HRESULT(APIENTRY* tEndScene)(LPDIRECT3DDEVICE9 pDevice);
static HWND window;
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
{
DWORD wndProcId;
GetWindowThreadProcessId(handle, &wndProcId);
if (GetCurrentProcessId() != wndProcId)
return TRUE; // skip to next window
window = handle;
return FALSE; // window found abort search
}
HWND GetProcessWindow()
{
window = NULL;
EnumWindows(EnumWindowsCallback, NULL);
return window;
}
bool GetD3D9Device(void** pTable, size_t Size)
{
if (!pTable)
return false;
IDirect3D9* pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!pD3D)
return false;
IDirect3DDevice9* pDummyDevice = NULL;
// options to create dummy device
D3DPRESENT_PARAMETERS d3dpp = {};
d3dpp.Windowed = false;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = GetProcessWindow();
HRESULT dummyDeviceCreated = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dpp.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pDummyDevice);
if (dummyDeviceCreated != S_OK)
{
// may fail in windowed fullscreen mode, trying again with windowed mode
d3dpp.Windowed = !d3dpp.Windowed;
dummyDeviceCreated = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3dpp.hDeviceWindow, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pDummyDevice);
if (dummyDeviceCreated != S_OK)
{
pD3D->Release();
return false;
}
}
memcpy(pTable, *reinterpret_cast<void***>(pDummyDevice), Size);
pDummyDevice->Release();
pD3D->Release();
return true;
}
void DrawFilledRect(int x, int y, int w, int h, D3DCOLOR color, IDirect3DDevice9* dev)
{
D3DRECT BarRect = { x, y, x + w, y + h };
dev->Clear(1, &BarRect, D3DCLEAR_TARGET | D3DCLEAR_TARGET, color, 0, 0);
}
bool bInit = false;
tEndScene oEndScene = nullptr;
LPDIRECT3DDEVICE9 pD3DDevice = nullptr;
void* d3d9Device[119];
HRESULT APIENTRY hkEndScene(LPDIRECT3DDEVICE9 pDevice)
{
if (bInit == false)
{
pD3DDevice = pDevice;
bInit = true;
}
//draw stuff here like so:
DrawFilledRect(200, 200, 200, 200, D3DCOLOR_ARGB(255, 255, 0, 0), pDevice);
return oEndScene(pDevice);
}
DWORD WINAPI Init(HMODULE hModule)
{
if (GetD3D9Device(d3d9Device, sizeof(d3d9Device)))
{
oEndScene = (tEndScene)TrampHook((char*)d3d9Device[42], (char*)hkEndScene, 7);
}
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)Init, hModule, 0, nullptr));
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Credits to me, Broihon, Solaire & 0xDEC0DE for this barebones d3d9 endscene hook

Related

C++ Screenshot on mouse click does not work

I am using Visual Studio 2017, I wrote code which should create a folder, capture a screenshot when the mouse button is pressed and save the screenshot to a .bmp file. But I don't know why that script does not work. Visual Studio compile it without errors / warnings.
Here is the code:
// variable to store the HANDLE to the hook. Don't declare it anywhere else then globally
// or you will get problems since every function uses this variable.
HHOOK _hook;
// This struct contains the data received by the hook callback. As you see in the callback function
// it contains the thing you will need: vkCode = virtual key code.
KBDLLHOOKSTRUCT kbdStruct;
int filenum = 1;
// This is the callback function. Consider it the event that is raised when, in this case,
// a key is pressed.
void TakeScreenShot(const char* filename)
{
//keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY, 0);
//keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
HBITMAP h;
POINT a, b;
a.x = 0;
a.y = 0;
b.x = GetSystemMetrics(SM_CXSCREEN);
b.y = GetSystemMetrics(SM_CYSCREEN);
HDC hScreen = GetDC(NULL);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, abs(b.x - a.x), abs(b.y - a.y));
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, abs(b.x - a.x), abs(b.y - a.y), hScreen, a.x, a.y, SRCCOPY);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
// clean up
SelectObject(hDC, old_obj);
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
DeleteObject(hBitmap);
OpenClipboard(NULL);
h = (HBITMAP)GetClipboardData(CF_BITMAP);
CloseClipboard();
HDC hdc = NULL;
FILE*fp = NULL;
LPVOID pBuf = NULL;
BITMAPINFO bmpInfo;
BITMAPFILEHEADER bmpFileHeader;
do
{
hdc = GetDC(NULL);
ZeroMemory(&bmpInfo, sizeof(BITMAPINFO));
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
GetDIBits(hdc, h, 0, 0, NULL, &bmpInfo, DIB_RGB_COLORS);
if (bmpInfo.bmiHeader.biSizeImage <= 0)
bmpInfo.bmiHeader.biSizeImage = bmpInfo.bmiHeader.biWidth*abs(bmpInfo.bmiHeader.biHeight)*(bmpInfo.bmiHeader.biBitCount + 7) / 8;
if ((pBuf = malloc(bmpInfo.bmiHeader.biSizeImage)) == NULL)
{
MessageBox(NULL, TEXT("Unable to Allocate Bitmap Memory"), TEXT("Error"), MB_OK | MB_ICONERROR);
break;
}
bmpInfo.bmiHeader.biCompression = BI_RGB;
GetDIBits(hdc, h, 0, bmpInfo.bmiHeader.biHeight, pBuf, &bmpInfo, DIB_RGB_COLORS);
if ((fp = fopen(filename, "wb")) == NULL)
{
MessageBox(NULL, TEXT("Unable to Create Bitmap File"), TEXT("Error"), MB_OK | MB_ICONERROR);
break;
}
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmpInfo.bmiHeader.biSizeImage;
bmpFileHeader.bfType = 'MB';
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite(&bmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, fp);
fwrite(pBuf, bmpInfo.bmiHeader.biSizeImage, 1, fp);
}
while (false);
if (hdc)ReleaseDC(NULL, hdc);
if (pBuf) free(pBuf);
if (fp)fclose(fp);
}
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
// the action is valid: HC_ACTION.
if (wParam == WM_LBUTTONDOWN)
{
std::string OutputFolder = "C:\\temp";
std::string filename = "ss";
if (CreateDirectory(OutputFolder.c_str(), NULL) ||
ERROR_ALREADY_EXISTS == GetLastError())
{
}
else
{
// Failed to create directory.
}
auto numfile = std::to_string(filenum);
TakeScreenShot((OutputFolder + "\\" + filename + std::to_string(filenum) + ".bmp").c_str());
filenum++;
}
}
// call the next hook in the hook chain. This is nessecary or your hook chain will break and the hook stops
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
void ReleaseHook()
{
UnhookWindowsHookEx(_hook);
}
int main()
{
// Don't mind this, it is a meaningless loop to keep a console application running.
// I used this to test the keyboard hook functionality. If you want to test it, keep it in ;)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
}
}
If click with it does not make the directory (if it does not exist) and does not create the .bmp file.
As others have said, you never install your hook! Also, it appears from my tests that you need to be dispatching messages to a window of some sort in order for a WH_MOUSE hook to get called.
Here's a minimal version of main () that works for me:
int main()
{
_hook = SetWindowsHookEx (WH_MOUSE, HookCallback, NULL, GetCurrentThreadId ());
if (_hook == NULL)
...
MessageBox (NULL, "Click OK to quit", "Screen Grabber", MB_OK);
UnhookWindowsHookEx (_hook);
}
Then the rest of your code works fine, albeit is a little bit messy as others have said.
However, this will only catch mouse-clicks within the message box itself, and I don't think that's what you want.
If you want to catch these globally, you will need to install a "low level" mouse hook. This needs to be a global hook but otherwise the code looks much the same. The code to install and run the hook is:
int main()
{
_hook = SetWindowsHookEx (WH_MOUSE_LL, HookCallback, NULL, 0);
MSG msg;
while (GetMessage (&msg, NULL, 0, 0))
DispatchMessage (&msg);
UnhookWindowsHookEx(_hook);
}

Opengl: Rendering to a Win32 API context

I'm trying to create an opengl context (ogl 4.5 at least) and render to it; it works fine with SDL2 but I can't figure out why it doesn't with Win32: I'm trying the screen color to RGBA(1, 0, 0, 1) with glClearColor()/glClear, but nothing seems to happen. Here is my code:
--EDIT I updated the code as you suggested but still, nothing seems rendered; also the problem is not because of gl 3.x/4.x features because I initialized GLEW after having created a wiggle context
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
HGLRC ourOpenGLRenderingContext;
bool running = true;
int WinMain(__in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in_opt LPSTR lpCmdLine, __in int nShowCmd)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = L"oglversionchecksample";
wc.style = CS_OWNDC;
if (!RegisterClass(&wc))
return 1;
CreateWindowW(wc.lpszClassName, L"openglversioncheck",
WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0) > 0)
DispatchMessage(&msg);
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
glViewport(0, 0, 100, 100); //render to a viewport of width (100; 100)
glClearColor(1.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
break;
case WM_CREATE:
{
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, //Flags
PFD_TYPE_RGBA, //The kind of framebuffer. RGBA or palette.
32, //Colordepth of the framebuffer.
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
24, //Number of bits for the depthbuffer
8, //Number of bits for the stencilbuffer
0, //Number of Aux buffers in the framebuffer.
PFD_MAIN_PLANE,
0,
0, 0, 0
};
HDC ourWindowHandleToDeviceContext = GetDC(hWnd);
int letWindowsChooseThisPixelFormat;
letWindowsChooseThisPixelFormat = ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd);
SetPixelFormat(ourWindowHandleToDeviceContext, letWindowsChooseThisPixelFormat, &pfd);
ourOpenGLRenderingContext = wglCreateContext(ourWindowHandleToDeviceContext);
wglMakeCurrent(ourWindowHandleToDeviceContext, ourOpenGLRenderingContext);
MessageBoxA(0, (char*)glGetString(GL_VERSION), "OPENGL VERSION", 0);
if(glewInit() != GLEW_OK) {
MessageBoxA(0, (char*)glGetString(GL_VERSION), "Error: Could not init OGL API (GLEW)", 0);
}
}
break;
case WM_CLOSE:
//quit code
wglDeleteContext(ourOpenGLRenderingContext);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Creating a modern OpenGL rendering context in Windows takes a bit more effort. Essentially you first have to create a proxy context to load the necessary extensions, then you create the proper context using those. I wrote a small library that does the internal proxy context housekeeping behind the curtains and lets you get right to the core business without further ado: http://github.com/datenwolf/wglarb
For the sake of StackOverflow best practice of include code in answeres instead of linking here's the main implementation file (it's a mere 238 lines):
/*
Copyright (c) 2014 Wolfgang 'datenwolf' Draxinger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <GL/gl.h>
#include "wglarb.h"
#define wglarb_BuildAssert(cond) ((void)sizeof(char[1 - 2*!(cond)]))
static HANDLE wglarb_intermediary_mutex = NULL;
static DWORD wglarb_intermediary_lock(void)
{
wglarb_BuildAssert( sizeof(PVOID) == sizeof(HANDLE) );
if( !wglarb_intermediary_mutex ) {
/* Between testing for the validity of the mutex handle,
* creating a new mutex handle and using the interlocked
* exchange there is a race... */
/* //// START \\\\ */
HANDLE const new_mutex =
CreateMutex(NULL, TRUE, NULL);
HANDLE const dst_mutex =
InterlockedCompareExchangePointer(
&wglarb_intermediary_mutex,
new_mutex,
NULL );
/* //// FINISH \\\\ */
if( !dst_mutex ) {
/* mutex created in one time initialization and held
* by calling thread. Return signaled status. */
return WAIT_OBJECT_0;
}
/* In this case we lost the race and another thread
* beat this thread in creating a mutex object.
* Clean up and wait for the proper mutex. */
ReleaseMutex(new_mutex);
CloseHandle(new_mutex);
}
return WaitForSingleObject(wglarb_intermediary_mutex, INFINITE);
}
static BOOL wglarb_intermediary_unlock(void)
{
return ReleaseMutex(wglarb_intermediary_mutex);
}
#define WGLARB_INTERMEDIARY_CLASS "wglarb intermediary"
#define WGLARB_INTERMEDIARY_STYLE (WS_CLIPSIBLINGS|WS_CLIPCHILDREN)
#define WGLARB_INTERMEDIARY_EXSTYLE 0
static HWND wglarb_intermediary_hWnd = 0;
static BOOL wglarb_intermediary_create_Wnd(void)
{
HINSTANCE const hInstance = GetModuleHandle(NULL);
WNDCLASS wc;
memset(&wc,0,sizeof(wc));
wc.hInstance = hInstance;
wc.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = WGLARB_INTERMEDIARY_CLASS;
RegisterClass(&wc);
wglarb_intermediary_hWnd =
CreateWindowEx(
WGLARB_INTERMEDIARY_EXSTYLE,
WGLARB_INTERMEDIARY_CLASS,
NULL,
WGLARB_INTERMEDIARY_STYLE,
0,0,0,0,
NULL,NULL,
hInstance,
NULL );
if( !wglarb_intermediary_hWnd ) {
return FALSE;
}
return TRUE;
}
static HDC wglarb_intermediary_hDC = 0;
static BOOL wglarb_intermediary_create_DC(void)
{
if( !wglarb_intermediary_hWnd
&& !wglarb_intermediary_create_Wnd() ) {
return FALSE;
}
wglarb_intermediary_hDC = GetDC(wglarb_intermediary_hWnd);
if( !wglarb_intermediary_hDC ) {
return FALSE;
}
return TRUE;
}
static HGLRC wglarb_intermediary_hRC = 0;
static BOOL wglarb_intermediary_create_RC(void)
{
if( !wglarb_intermediary_hDC
&& !wglarb_intermediary_create_DC() ) {
return FALSE;
}
PIXELFORMATDESCRIPTOR pfd;
memset(&pfd,0,sizeof(pfd));
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL|PFD_GENERIC_ACCELERATED|PFD_DRAW_TO_WINDOW;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.iLayerType = PFD_MAIN_PLANE;
int iPF;
if( !(iPF = ChoosePixelFormat(wglarb_intermediary_hDC, &pfd))
|| !(SetPixelFormat(wglarb_intermediary_hDC, iPF, &pfd))
|| !(wglarb_intermediary_hRC = wglCreateContext(wglarb_intermediary_hDC))
) {
return FALSE;
}
return TRUE;
}
static BOOL wglarb_intermediary_makecurrent(HDC *hOrigDC, HGLRC *hOrigRC)
{
*hOrigDC = wglGetCurrentDC();
*hOrigRC = wglGetCurrentContext();
if( !wglarb_intermediary_hRC
&& !wglarb_intermediary_create_RC() ) {
return FALSE;
}
return wglMakeCurrent(wglarb_intermediary_hDC, wglarb_intermediary_hRC);
}
HGLRC WINAPI wglarb_CreateContextAttribsARB(
HDC hDC,
HGLRC hShareContext,
const int *attribList)
{
if( WAIT_OBJECT_0 != wglarb_intermediary_lock() ) {
return NULL;
}
HDC hOrigDC;
HGLRC hOrigRC;
if( !wglarb_intermediary_makecurrent(&hOrigDC, &hOrigRC) ) {
wglarb_intermediary_unlock();
return NULL;
}
PFNWGLCREATECONTEXTATTRIBSARBPROC impl =
(PFNWGLCREATECONTEXTATTRIBSARBPROC)
wglGetProcAddress("wglCreateContextAttribsARB");
HGLRC ret = NULL;
if( impl ) {
ret = impl(hDC, hShareContext, attribList);
}
wglMakeCurrent(hOrigDC, hOrigRC);
wglarb_intermediary_unlock();
return ret;
}
BOOL WINAPI wglarb_ChoosePixelFormatARB(
HDC hdc,
const int *piAttribIList,
const FLOAT *pfAttribFList,
UINT nMaxFormats,
int *piFormats,
UINT *nNumFormats)
{
if( WAIT_OBJECT_0 != wglarb_intermediary_lock() ) {
return FALSE;
}
HDC hOrigDC;
HGLRC hOrigRC;
if( !wglarb_intermediary_makecurrent(&hOrigDC, &hOrigRC) ) {
wglarb_intermediary_unlock();
return FALSE;
}
PFNWGLCHOOSEPIXELFORMATARBPROC impl = NULL;
impl = (PFNWGLCHOOSEPIXELFORMATARBPROC)
wglGetProcAddress("wglChoosePixelFormatARB");
if( !impl ) {
/* WGL_EXT_pixel_format uses the same function prototypes
* as the WGL_ARB_pixel_format extension */
impl = (PFNWGLCHOOSEPIXELFORMATARBPROC)
wglGetProcAddress("wglChoosePixelFormatEXT");
}
BOOL ret = FALSE;
if( impl ) {
ret = impl(
hdc,
piAttribIList,
pfAttribFList,
nMaxFormats,
piFormats,
nNumFormats );
}
wglMakeCurrent(hOrigDC, hOrigRC);
wglarb_intermediary_unlock();
return ret;
}
Look at the code for SDL to see what it does - I think there's some faffing about to get a context with modern (>=3 or >=4) OpenGL.
https://www.opengl.org/wiki/Creating_an_OpenGL_Context_%28WGL%29

How to get device pixel ratio in Qt 4.8 for Windows?

We have desktop app which uses Qt 4.8.
We are trying to support different DPI screens like Mac retina, Surface Pro 4 devices.
For Mac we can get the device pixel ratio by just single function call:
CGFloat devicePixelRatio = [[NSScreen mainScreen] backingScaleFactor];
Is there any utility function available in WinAPI to get device pixel ratio?
thanks.
Finally I found the solution use below code snippet to get the scaling factor,
FLOAT dpiX, dpiY;
HDC screen = GetDC(0);
dpiX = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSX));
dpiY = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSY));
ReleaseDC(0, screen);
FLOAT scaleFactor = dpiX / 96.0f; // this is same as devicePixelRatio
Now need to make your app DPI aware. For that set Enable DPI Awareness flag to Yes in Project Settings > Manifest Tool > Input and Output property page.
Here's how I calculate the DPI.
Do replace the assertions with actual error checking, as some of those situations actually occur sometimes.
#include <QString>
#include <Windows.h>
#include <SetupApi.h>
#include <cfgmgr32.h>
#include <assert.h>
#include <vector>
#include <stdint.h>
const GUID GUID_CLASS_MONITOR = { 0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 };
// Assumes hEDIDRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hEDIDRegKey, short& WidthMm, short& HeightMm)
{
DWORD dwType, AcutalValueNameLength = 128;
TCHAR valueName[128];
BYTE EDIDdata[1024];
DWORD edidsize = sizeof(EDIDdata);
for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
{
retValue = RegEnumValue(hEDIDRegKey, i, &valueName[0],
&AcutalValueNameLength, NULL, &dwType,
EDIDdata, // buffer
&edidsize); // buffer size
if (retValue != ERROR_SUCCESS || QString(valueName) != "EDID")
continue;
WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];
return true; // valid EDID found
}
return false; // EDID not found
}
bool GetSizeForDevID(const QString& TargetDevID, short& WidthMm, short& HeightMm)
{
HDEVINFO devInfo = SetupDiGetClassDevsExA(
&GUID_CLASS_MONITOR, //class GUID
NULL, //enumerator
NULL, //HWND
DIGCF_PRESENT | DIGCF_PROFILE, // Flags //DIGCF_ALLCLASSES|
NULL, // device info, create a new one.
NULL, // machine name, local machine
NULL);// reserved
if (NULL == devInfo)
return false;
bool success = false;
for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
{
SP_DEVINFO_DATA devInfoData;
memset(&devInfoData, 0, sizeof(devInfoData));
devInfoData.cbSize = sizeof(devInfoData);
if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData))
{
CHAR Instance[MAX_DEVICE_ID_LEN];
SetupDiGetDeviceInstanceIdA(devInfo, &devInfoData, Instance, MAX_DEVICE_ID_LEN, NULL);
if (!QString(Instance).contains(TargetDevID))
continue;
HKEY hEDIDRegKey = SetupDiOpenDevRegKey(devInfo, &devInfoData,
DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
if (!hEDIDRegKey || (hEDIDRegKey == INVALID_HANDLE_VALUE))
continue;
success = GetMonitorSizeFromEDID(hEDIDRegKey, WidthMm, HeightMm);
RegCloseKey(hEDIDRegKey);
if (success)
break;
}
}
SetupDiDestroyDeviceInfoList(devInfo);
return success;
}
static bool DisplayDeviceFromHMonitor(HMONITOR hMonitor, DISPLAY_DEVICE& ddMonOut)
{
MONITORINFOEX mi;
mi.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfoA(hMonitor, &mi);
DISPLAY_DEVICE dd;
dd.cb = sizeof(dd);
for (DWORD devIdx = 0; EnumDisplayDevicesA(nullptr, devIdx, &dd, 0); ++devIdx)
{
if (QString(dd.DeviceName) != QString(mi.szDevice))
continue;
DISPLAY_DEVICE ddMon;
memset(&ddMon, 0, sizeof(ddMon));
ddMon.cb = sizeof(ddMon);
if (EnumDisplayDevicesA(dd.DeviceName, 0, &ddMon, 0))
{
ddMonOut = ddMon;
return true;
}
memset(&dd, 0, sizeof(dd));
dd.cb = sizeof(dd);
}
return false;
}
BOOL CALLBACK MonitorEnumProc(
_In_ HMONITOR hMonitor,
_In_ HDC /*hdcMonitor*/,
_In_ LPRECT /*lprcMonitor*/,
_In_ LPARAM context
)
{
std::vector<HMONITOR> * monitors = (std::vector<HMONITOR>*)context;
assert(monitors);
monitors->push_back(hMonitor);
return TRUE;
}
uint32_t CSystemMetrics::screenDpi(void* window, const CRect& rect)
{
// Identify the HMONITOR of interest via the callback MyMonitorEnumProc
HDC dc = GetWindowDC((HWND)window);
assert(dc);
RECT windowRect;
windowRect.top = rect.top;
windowRect.left = rect.left;
windowRect.right = rect.right;
windowRect.bottom = rect.bottom;
std::vector<HMONITOR> monitors;
EnumDisplayMonitors(dc, rect.size().area() > 0 ? (&windowRect) : nullptr, MonitorEnumProc, (LPARAM)&monitors);
ReleaseDC((HWND)window, dc);
assert(!monitors.empty());
DISPLAY_DEVICE ddMon;
assert(DisplayDeviceFromHMonitor(monitors.front(), ddMon));
MONITORINFO monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFO);
assert(GetMonitorInfoA(monitors.front(), &monitorInfo));
const auto deviceIdSections = QString(ddMon.DeviceID).split("\\");
assert(deviceIdSections.size() > 1);
short widthMm, heightMm;
assert(GetSizeForDevID(deviceIdSections[1], widthMm, heightMm));
const float hDPI = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left) * 25.4f / widthMm;
const float vDPI = (monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top) * 25.4f / heightMm;
const uint32_t dpi = uint32_t(((float)hDPI + (float)vDPI / 2.0f) + 0.5f);
assert(dpi < 700 && widthMm > 100 && heightMm > 100);
return dpi;
}
Now there's a complicated issue camouflaged in a simple question :-) I cannot really answer that, but merely provide some minor hints:
Firstly, Windows does not have the device-pixel vs. logical-pixel paradigm known from OSX, so your question about the "device pixel ratio" may cause irritation among non-OSX-developers.
For Qt, there have been some substantial changes from versions 4.x to 5.x. In fact, you might actually want to consider "upgrading" to Qt 5.
http://doc.qt.io/qt-5/highdpi.html
To quote from this link:
"Qt 5.4 introduces experimental support for scaling by by device pixel ratio similar to OS X to the platform plugins for Windows and Unix (XCB)."
Qt 4 on the other hand offers only those tips for "scalable applications":
http://doc.qt.io/qt-4.8/scalability.html
Related SO question:
Automatic rescaling of an application on high-dpi Windows platform?

How can I render the images on the monitor (or TV) connected to HDMI port of my computer

I am pretty new to DirectX 10 programming, and I have been trying to do the following with my limited skills..
I am trying to display an Image from one machine on to another output device(Another Monitor/TV) connected via HDMI. I researched on it and came to know that DXGI can be pretty useful for rendering purposes. Moreover while researching further I came across this link here. It does show how to display different images on multiple monitors connected to the main machine but it requires extended display for that.
My requirement is that when I run the code It should render the image on another output device without making it an extended display.
Approcah I am trying is that I am enumerating Video Adapters(which in my case is 1 ) and and then enumerating outputs available which should be two in numbers as I have an HDMI out connected to my PC. But if I do not set extended display it shows only one output available in my enumerated outputs array and if I extend the display it shows two of them.Once I am done with enumerating outputs I want to Render that image on desired output.
As far as i know every video adapter has a port for HDMI connected to it.
I wonder, if there is way I can access that port programatically and could render the image using that port??
Other than MSDN, documentation or explanation of those concepts are rather limited, so any help would be very welcome. Here is some code I have :
// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include<vector>
#include<iostream>
#include <sstream>
#include<dxgi.h>
using namespace std;
// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dxgi.lib")
std::vector<IDXGIAdapter*> EnumerateAdapters();
void PrintAdapterInfo(IDXGIAdapter* pAdapter);
std::vector<IDXGIOutput*> EnumerateOutputs(IDXGIAdapter* pAdapter);
void PrintOutputInfo(IDXGIOutput* output);
std::vector<DXGI_MODE_DESC*> GetDisplayModeList(IDXGIOutput* output);
void PrintDisplayModeInfo(DXGI_MODE_DESC* pModeDesc);
// global declarations
IDXGISwapChain *swapchain; // the pointer to the swap chain interface
ID3D11Device *dev; // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon; // the pointer to our Direct3D device context
ID3D11RenderTargetView *backbuffer; // the pointer to our back buffer
IDXGIAdapter *pAdapter;
IDXGIAdapter* adapter = NULL;
IDXGIFactory1* factory = NULL;
HINSTANCE hInstance;
HINSTANCE hPrevInstance;
LPSTR lpCmdLine;
HWND hWnd;
std::vector<IDXGIOutput*> outputArray;
int nCmdShow;
// function prototypes
void InitD3D(HWND hWnd); // sets up and initializes Direct3D
void RenderFrame(void); // renders a single frame
void CleanD3D(void); // closes Direct3D and releases memory
void CreateWindowsForOutputs();
void GetAdapter();
void MultiRender();
void CreateSwapChainsAndViews();
std::vector<IDXGIAdapter*> EnumerateAdapters();
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
struct WindowDataContainer
{
//Direct3D 10 stuff per window data
IDXGISwapChain* swapChain;
ID3D11RenderTargetView* renderTargetView;
ID3D11DepthStencilView* depthStencilView;
// window goodies
HWND hWnd;
int width;
int height;
};
std::vector<WindowDataContainer*> WindowsArray;
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
std::cout << "Hello \nworld!"; // Will show a popup
std::vector<IDXGIAdapter*> adapters = EnumerateAdapters();
for(std::vector<IDXGIAdapter*>::iterator itor = adapters.begin(); itor != adapters.end(); ++itor)
{
//PrintAdapterInfo(*itor);
// Get Output info
std::vector<IDXGIOutput*> outputArray = EnumerateOutputs(*itor);
for(std::vector<IDXGIOutput*>::iterator outputItor = outputArray.begin(); outputItor != outputArray.end(); ++outputItor)
{
// PrintOutputInfo(*outputItor);
// Get display mode list
std::vector<DXGI_MODE_DESC*> modeList = GetDisplayModeList(*outputItor);
for(std::vector<DXGI_MODE_DESC*>::iterator modeItor = modeList.begin(); modeItor != modeList.end(); ++modeItor)
{
// PrintDisplayModeInfo(*modeItor);
}
}
CreateWindowsForOutputs();
MSG msg;
while(TRUE)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if(msg.message == WM_QUIT)
break;
}
MultiRender();
}
//std::getchar();
// return 0;
}
//std:getchar();
}
std::vector<IDXGIAdapter*> EnumerateAdapters()
{
// Create DXGI factory
IDXGIFactory1* pFactory = NULL;
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory));
if (FAILED(hr))
{
MessageBox(NULL, L"Create DXGI factory failed", L"Error", 0);
}
// Enumerate devices
IDXGIAdapter* pAdapter = NULL;
std::vector<IDXGIAdapter*> vAdapters;
for (UINT i = 0; pFactory->EnumAdapters(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i)
{
vAdapters.push_back(pAdapter);
}
if (pFactory)
{
pFactory->Release();
pFactory = NULL;
}
return vAdapters;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
// clear out the struct for use
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
scd.BufferCount = 1; // one back buffer
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
scd.OutputWindow = hWnd; // the window to be used
scd.SampleDesc.Count = 1; // how many multisamples
scd.SampleDesc.Quality = 0; // multisample quality level
scd.Windowed = TRUE; // windowed/full-screen mode
// create a device, device context and swap chain using the information in the scd struct
D3D11CreateDeviceAndSwapChain( NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
// get the address of the back buffer
ID3D11Texture2D *pBackBuffer;
swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
// use the back buffer address to create the render target
dev->CreateRenderTargetView(pBackBuffer, NULL, &backbuffer);
pBackBuffer->Release();
// set the render target as the back buffer
devcon->OMSetRenderTargets(1, &backbuffer, NULL);
// Set the viewport
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = 800;
viewport.Height = 600;
devcon->RSSetViewports(1, &viewport);
}
// this is the function used to render a single frame
void RenderFrame(void)
{
// clear the back buffer to a deep blue
devcon->ClearRenderTargetView(backbuffer, D3DXCOLOR(0.0f, 0.2f, 0.4f, 1.0f));
// do 3D rendering on the back buffer here
// switch the back buffer and the front buffer
swapchain->Present(0, 0);
}
// this is the function that cleans up Direct3D and COM
void CleanD3D(void)
{
// close and release all existing COM objects
swapchain->Release();
backbuffer->Release();
dev->Release();
devcon->Release();
}
//Acquiring the outputs on our adapter
std::vector<IDXGIOutput*> EnumerateOutputs(IDXGIAdapter* pAdapter)
{
//std::vector<IDXGIOutput*> outputs;
IDXGIOutput* pOutput = NULL;
for (UINT i = 0; pAdapter->EnumOutputs(i, &pOutput) != DXGI_ERROR_NOT_FOUND; ++i)
{
outputArray.push_back(pOutput);
}
return outputArray;
}
void CreateWindowsForOutputs()
{ // std::vector<IDXGIOutput*> outputArray;
for( int i = 0; i < outputArray.size(); ++i )
{
IDXGIOutput* output = outputArray.at(i);
DXGI_OUTPUT_DESC outputDesc;
output->GetDesc( &outputDesc );
int x = outputDesc.DesktopCoordinates.left;
int y = outputDesc.DesktopCoordinates.top;
int width = outputDesc.DesktopCoordinates.right - x;
int height = outputDesc.DesktopCoordinates.bottom - y;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
RECT wr = {0, 0, 800, 600};
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
// Don't forget to clean this up. And all D3D COM objects.
WindowDataContainer* window = new WindowDataContainer;
window->hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Our First Direct3D Program",
WS_OVERLAPPEDWINDOW,
300,
300,
wr.right - wr.left,
wr.bottom - wr.top,
NULL,
NULL,
hInstance,
NULL);
// show the window
ShowWindow( window->hWnd, SW_SHOWDEFAULT );
// InitD3D(hWnd);
CreateSwapChainsAndViews();
// set width and height
window->width = width;
window->height = height;
std::vector<WindowDataContainer*> windowsArray;
// shove it in the std::vector
windowsArray.push_back(window);
//if first window, associate it with DXGI so it can jump in
// when there is something of interest in the message queue
// think fullscreen mode switches etc. MSDN for more info.
// if(i == 0)
// factory->MakeWindowAssociation( window->hWnd, 0 );
}
}
std::vector<DXGI_MODE_DESC*> GetDisplayModeList(IDXGIOutput* output)
{
UINT num = 0;
DXGI_FORMAT format = DXGI_FORMAT_R32G32B32A32_TYPELESS;
UINT flags = DXGI_ENUM_MODES_INTERLACED | DXGI_ENUM_MODES_SCALING;
// Get number of display modes
output->GetDisplayModeList(format, flags, &num, 0);
// Get display mode list
DXGI_MODE_DESC * pDescs = new DXGI_MODE_DESC[num];
output->GetDisplayModeList(format, flags, &num, pDescs);
std::vector<DXGI_MODE_DESC*> displayList;
for(int i = 0; i < num; ++i)
{
displayList.push_back(&pDescs[i]);
}
return displayList;
}
void CreateSwapChainsAndViews()
{
std::vector<WindowDataContainer*> windowsArray;
for( int i = 0; i < windowsArray.size(); i++ )
{
WindowDataContainer* window = windowsArray.at(i);
// get the dxgi device
IDXGIDevice* DXGIDevice = NULL;
dev->QueryInterface( IID_IDXGIDevice, ( void** )&DXGIDevice ); // COM stuff, hopefully you are familiar
// create a swap chain
DXGI_SWAP_CHAIN_DESC swapChainDesc;
// fill it in
WindowDataContainer *p_Window = new WindowDataContainer;
HRESULT hr = factory->CreateSwapChain( DXGIDevice, &swapChainDesc, &p_Window->swapChain );
DXGIDevice->Release();
DXGIDevice = NULL;
// get the backbuffer
ID3D11Texture2D* backBuffer = NULL;
hr = window->swapChain->GetBuffer( 0, IID_ID3D11Texture2D, ( void** )&backBuffer );
// get the backbuffer desc
D3D11_TEXTURE2D_DESC backBufferDesc;
backBuffer->GetDesc( &backBufferDesc );
// create the render target view
D3D11_RENDER_TARGET_VIEW_DESC RTVDesc;
// fill it in
dev->CreateRenderTargetView( backBuffer, &RTVDesc, &window->renderTargetView );
backBuffer->Release();
backBuffer = NULL;
// Create depth stencil texture
ID3D11Texture2D* depthStencil = NULL;
D3D11_TEXTURE2D_DESC descDepth;
// fill it in
dev->CreateTexture2D( &descDepth, NULL, &depthStencil );
// Create the depth stencil view
D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
// fill it in
dev->CreateDepthStencilView( depthStencil, &descDSV, &window->depthStencilView );
}
}
void MultiRender( )
{
std::vector<WindowDataContainer*> windowsArray;
// Clear them all
for( int i = 0; i < windowsArray.size(); i++ )
{
WindowDataContainer* window = windowsArray.at(i);
// There is the answer to your second question:
devcon->OMSetRenderTargets( 1, &window->renderTargetView, NULL );
// Don't forget to adjust the viewport, in fullscreen it's not important...
D3D11_VIEWPORT Viewport;
Viewport.TopLeftX = 0;
Viewport.TopLeftY = 0;
Viewport.Width = window->width;
Viewport.Height = window->height;
Viewport.MinDepth = 0.0f;
Viewport.MaxDepth = 1.0f;
devcon->RSSetViewports( 1, &Viewport );
// TO DO: AMAZING STUFF PER WINDOW
}
}
//std::vector<IDXGIOutput*> outputArray; // contains outputs per adapter
void EnumOutputsOnAdapter()
{
IDXGIOutput* output = NULL;
for(int i = 0; DXGI_ERROR_NOT_FOUND != adapter->EnumOutputs(i, &output); ++i)
{
// get the description
DXGI_OUTPUT_DESC outputDesc;
HRESULT hr = output->GetDesc( &outputDesc );
outputArray.push_back( output );
}
}
// applicable for multiple ones with little effort
void GetAdapter()
{
// remember, we assume there's only one adapter (example purposes)
for( int i = 0; DXGI_ERROR_NOT_FOUND != factory->EnumAdapters( i, &adapter ); ++i );
{
// get the description of the adapter, assuming no failure
DXGI_ADAPTER_DESC adapterDesc;
HRESULT hr = adapter->GetDesc( &adapterDesc );
// Getting the outputs active on our adapter
EnumOutputsOnAdapter();
}
}

Drawing a jpg in MFC

I've been trying to show a jpg image in MFC, but I can't get it drawn. I'm a complete MFC newbie an everything I got up until now is mostly adapted from things I found on the net. Currently I have this:
Picture.h:
#pragma once
#include <afxwin.h>
class Picture
{
public:
Picture();
bool load(LPCTSTR filePath);
bool draw( CDC* deviceContext
, CRect clientRect
, LPCRECT prcMFBounds);
CSize getSize(CDC* pDC);
private:
LPPICTURE m_picture;
};
Picture.cpp:
#include "Picture.h"
Picture::Picture()
: m_picture(0)
{
}
bool Picture::load(LPCTSTR szFile)
{
HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return false;
DWORD dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize == (DWORD)-1)
{
CloseHandle(hFile);
return false;
}
LPVOID pvData = NULL;
// alloc memory based on file size
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
if (hGlobal == NULL)
{
CloseHandle(hFile);
return false;
}
pvData = GlobalLock(hGlobal);
if (pvData == NULL)
{
GlobalUnlock(hGlobal);
CloseHandle(hFile);
return false;
}
DWORD dwBytesRead = 0;
// read file and store in global memory
bool bRead = ReadFile(hFile, pvData, dwFileSize, &dwBytesRead, NULL) != 0;
GlobalUnlock(hGlobal);
CloseHandle(hFile);
if (!bRead)
return false;
LPSTREAM pstm = NULL;
// create IStream* from global memory
HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm);
if (!(SUCCEEDED(hr)))
{
if (pstm != NULL)
pstm->Release();
return false;
}
else if (pstm == NULL)
return false;
// Create IPicture from image file
if (m_picture)
m_picture->Release();
hr = ::OleLoadPicture(pstm, dwFileSize, FALSE, IID_IPicture, (LPVOID *)&(m_picture));
if (!(SUCCEEDED(hr)))
{
pstm->Release();
return false;
}
else if (m_picture == NULL)
{
pstm->Release();
return false;
}
pstm->Release();
return true;
}
bool Picture::draw(CDC* deviceContext, CRect clientRect, LPCRECT prcMFBounds)
{
if (clientRect.IsRectNull())
{
CSize imageSize = getSize(deviceContext);
clientRect.right = imageSize.cx;
clientRect.bottom = imageSize.cy;
}
long pictureWidth = 0;
long pictureHeigth = 0;
m_picture->get_Width(&pictureWidth);
m_picture->get_Height(&pictureHeigth);
m_picture->Render( *deviceContext
, clientRect.left
, clientRect.top
, clientRect.Width()
, clientRect.Height()
, 0
, pictureHeigth
, pictureWidth
, -pictureHeigth
, prcMFBounds);
return true;
}
CSize Picture::getSize(CDC* deviceContext)
{
if (!m_picture)
return CSize(0,0);
LONG width, height; // HIMETRIC units
m_picture->get_Width(&width);
m_picture->get_Height(&height);
CSize size(width, height);
if (deviceContext==NULL)
{
CWindowDC dc(NULL);
dc.HIMETRICtoDP(&size); // convert to pixels
}
else
{
deviceContext->HIMETRICtoDP(&size);
}
return size;
}
PictureView.h:
#pragma once
#include <afxwin.h>
#include <string>
class Picture;
class PictureView : public CStatic
{
public:
PictureView(std::string path);
protected:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
private:
Picture* m_picture;
};
PictureView.cpp:
#include "PictureView.h"
#include "Picture.h"
PictureView::PictureView(std::string path)
{
m_picture = new Picture();
m_picture->load(path.c_str());
}
void PictureView::DrawItem(LPDRAWITEMSTRUCT /*lpDrawItemStruct*/)
{
CRect rect;
GetClientRect(&rect);
m_picture->draw(GetDC(), rect, rect);
}
Constructor call:
CWnd* GenericChildWidget::addImage(std::string path, int horizontalPos, int width, int verticalPos, int height, int childId)
{
PictureView* image = new PictureView(path);
image->Create("", SS_OWNERDRAW, CRect(horizontalPos, verticalPos, width + horizontalPos, height + verticalPos), this, childId);
return image;
}
The problem is that I can't get the DrawItem function to be called. What am I doing wrong?
Any help will be appreciated.
Here's an easy way to draw an image in an MFC dialog box:
link with gdiplus.dll
#include "atlimage.h>
#include "gdiplus.h>
using namespace Gdiplus
.
.
.
CImage ci;
ci.Load((CString)"D:\\Pictures\\mycat.jpg");
CDC *dc = AfxGetMainWnd()->GetDC();
HDC hdc = *dc;
ci.Draw(hdc,10,10);
Hope it works!
with the help of a colleague of mine I've found the answer. He told me it's not possible to have an ownerdrawn CStatic. So when I now inherit PictureView from CButton and make it BS_OWNERDRAW my image is rendered.
Personally I think this is an ugly solution but I'm so tired of this problem now that I don't really care that much. These are the changes I've made to make it work:
Constructor call:
CWnd* GenericChildWidget::addImage(std::string path, int horizontalPos, int width, int verticalPos, int height, int childId)
{
PictureView* image = new PictureView(path);
image->Create("", BS_OWNERDRAW | WS_CHILD | WS_VISIBLE, CRect(horizontalPos, verticalPos, width + horizontalPos, height + verticalPos), this, childId);
return image;
}
PictureView.h:
#pragma once
#include <afxwin.h>
#include <string>
class Picture;
class PictureView : public CButton
{
public:
PictureView(std::string path);
protected:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
private:
Picture* m_picture;
};
Thank's everybody for all the help.
I'm not sure that the GetDC in the call to m_picture->draw will necessarily refer to the same DC that's given in the CREATESTRUCT. What I've done is:
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
EDIT: Since I can't comment, hopefully an edit will suffice. I think your colleague is mistaken, as I just recently implemented a CStatic control using SS_OWNERDRAW. I'm guessing that the addition of WS_CHILD | WS_VISIBLE on the Create call is key.
I've never used MFC but a quick perusal of the CStatic::DrawItem documentation says that it must be created with the SS_OWNERDRAW style for DrawItem to get called. You haven't shown the Create line for your PictureView so perhaps it's that?