I want to make UI control blur-behind effect, like css backdrop-filter:blur(). Logic is seems to be simple:
Get current ID2D1DeviceContext* render bitmap;
Crop to the area, that needed;
Create ID2D1Effect blur, and pass there bitmap;
Draw effect to current context using DrawImage.
I am facing issues on the first step.
I found two ways to get bitmap from ID2D1DeviceContext*: ID2D1DeviceContext::GetTarget(ID2D1Image**) and ID2D1Bitmap::CopyFromRenderTarget().
Seems, that calling that functions between BeginDraw() and EndDraw() returns white bitmap from calling Clear before BeginDraw(), as I assume due to double-buffering. If in the middle of render loop, add another EndDraw() and BeginDraw() pair, then, for some reason forward draw calls until the next EndDraw() will not do anything (perhaps because middle EndDraw() returned D2DERR_RECREATE_TARGET, and I currently didn't handle it.
Anyway, rendering the whole window in the middle of render loop for every blurred ui element seems to be bad idea, and also, probably it will cause flickering.
I there way to obtain current-state (compatible) bitmap from ID2D1DeviceContext* without bliting it to target? Or maybe there is better way to achieve effect I want?
Another example of blur effect in case of uwp in-app blur (not blur-behind window effect):
Minimal example with ID2D1DeviceContext::GetTarget(ID2D1Image**), that just fills target white, instead of drawing blurred red rectangle on blue background:
#include <Windows.h>
HDC hdcDevice = GetDC(NULL);
int xw = GetDeviceCaps(hdcDevice, HORZRES);
int yw = GetDeviceCaps(hdcDevice, VERTRES);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
HWND hwnd;
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dxguid.lib")
template<class Interface>
inline void SafeRelease(
Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
#ifndef Assert
#if defined( DEBUG ) || defined( _DEBUG )
#define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
#else
#define Assert(b)
#endif //DEBUG || _DEBUG
#endif
#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1DeviceContext* target;
ID2D1SolidColorBrush* brush;
void Release()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&target);
SafeRelease(&brush);
}
void Init()
{
Release();
m_pDirect2dFactory = nullptr;
m_pRenderTarget = nullptr;
SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory));
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
SUCCEEDED(m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&m_pRenderTarget));
m_pRenderTarget->QueryInterface(&target);
m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(255,0,0),
&brush
);
}
void Render()
{
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
target->SetTransform(D2D1::Matrix3x2F::Identity());
D2D1_SIZE_F rtSize = target->GetSize();
target->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
ID2D1Effect *blur = nullptr;
target->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
if (blur)
blur->SetValue(D2D1_GAUSSIANBLUR_PROP::D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 10);
ID2D1Image* img = nullptr;
target->GetTarget(&img); // Checked, img is not nullptr, after call
blur->SetInput(0, img);
target->DrawImage(blur); // DrawImage(img) also draws white
// If to remove DrawImage call, red rectangle on blue background will be displayed,
// yet, of course, not blurred
SafeRelease(&blur);
auto hr = target->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
Init();
}
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
ReleaseDC(NULL, hdcDevice);
WNDCLASS c = { NULL };
c.lpszClassName = L"GROKEN";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
int cx = 500, cy = 500;
int x = xw / 2 - cx / 2, y = yw / 2 - cy / 2;
hwnd = CreateWindowEx(NULL, L"GROKEN", L"asd", WS_POPUP | WS_VISIBLE, x, y, cx, cy, NULL, NULL, hin, 0);
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
CoInitialize(NULL);
Init();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
Render();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return NULL;
}
More particulary, in my case I have array of pointers to ui controll class objects, each has own Render(ID2D1DeviceContext*), so window's render loop looks like this:
inline void WINDOW::Render()
{
Init(); // (re)Initialize target as ID2D1DeviceContext*, if needed;
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
target->SetTransform(D2D1::Matrix3x2F::Identity());
for(int i = 0; i < ui_elements_count; i++)
ui_element[i]->Draw(target);
this->hr = target->EndDraw();
}
...
inline void UI_ELEMENT::Draw(ID2D1DeviceContext *target)
{
...
if(this->blurRadius > 0)
{
BlurRectangle(this->x, this->y, this->cx, this->cy, this->blurRadius);
}
}
Update
Using Simon's Mourier answer I tried to create the solution I need, but stuck, trying to SetInput backbuffer' bitmap to ID2D1Effect*. For some reasone, it either does not set in SetInput method correctly, or in DrawImage call. The saddest thing is that backbuffer bitmap is actually valid, it could be drawn by same DrawImage call. Maybe I should specify another bitmap options when create it?
// other code is same
ID2D1DeviceContext* target;
void Init()
{
Release();
m_pRenderTarget = NULL;
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
SUCCEEDED(m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&m_pRenderTarget));
m_pRenderTarget->QueryInterface(&target);
}
inline void Blur(ID2D1DeviceContext* backTarget, int rad, RECT r)
// r is not used, should contain element bound box
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Draw rectangle for test
backTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
ID2D1Bitmap1* bb = nullptr;
// Create bitmap
backTarget->CreateBitmap(size, 0, 0, D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
), &bb);
// Copy current taget's state to created bitmap
bb->CopyFromRenderTarget(0, backTarget, 0);
ID2D1Effect* blur = nullptr;
target->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, rad);
blur->SetInput(0, bb);
// Draw blurred result. Does nothing
backTarget->DrawImage(blur);
// Just test if bb is valid, draw
// it with some offset.
// Draws correctly
auto a = D2D1::Point2F(100, 0);
backTarget->DrawImage(bb, a);
SafeRelease(&blur);
}
inline void Render()
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
ID2D1BitmapRenderTarget* tar = nullptr; // Create back buffer
target->CreateCompatibleRenderTarget(&tar);
ID2D1DeviceContext* tt = nullptr;
// Get exactly back buffer as ID2D1DeviceContext*,
// because it has more draw calls, such as DrawImage()
tar->QueryInterface(&tt);
tt->CreateSolidColorBrush(
D2D1::ColorF(255, 0, 0),
&brush
);
tt->BeginDraw();
tt->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
tt->SetTransform(D2D1::Matrix3x2F::Identity());
// loop through ui elements should here,
// assume we have an element with blur needed
Blur(tt, 10, RECT());
tt->EndDraw();
target->BeginDraw();
ID2D1Bitmap* bmp = nullptr;
tar->GetBitmap(&bmp);
target->DrawImage(bmp); // Draw back buffer to target
target->EndDraw();
SafeRelease(&tar);
SafeRelease(&tt);
SafeRelease(&bmp);
SafeRelease(&brush);
}
There are multiple issues in your code, but the main reason it doesn't work is because you can't use the device context's target bitmap (GPU resource) as a source (since it's a target).
Your code doesn't check for errors (you should) so for example you don't see the error from this call:
auto hr = target->EndDraw();
which returns error D2DERR_INVALID_GRAPH_CONFIGURATION:
The solution is therefore to create an intermediary bitmap (render target), render on it, and draw that bitmap on the target device context, something like this:
// at device context init time
target->CreateCompatibleRenderTarget(&bitmapTarget);
// create blur & set bitmap as input
deviceContext->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
//get bitmap from bitmap rt
bitmapTarget->GetBitmap(&bitmap);
blur->SetInput(0, bitmap);
// at render time
void Render()
{
// draw to bitmap rt
bitmapTarget->BeginDraw();
bitmapTarget->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
D2D1_SIZE_F rtSize = deviceContext->GetSize();
bitmapTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
bitmapTarget->EndDraw();
// draw to dc
deviceContext->BeginDraw();
// draw bitmap + effect
deviceContext->DrawImage(blur);
deviceContext->EndDraw();
}
And here is the result:
FWIW, I have put a complete correct code here:
#include <Windows.h>
#include <stdlib.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dxguid.lib")
template<class Interface>
inline void SafeRelease(Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
ID2D1DeviceContext* deviceContext;
ID2D1SolidColorBrush* brush;
ID2D1BitmapRenderTarget* bitmapTarget;
ID2D1Effect* blur;
ID2D1Bitmap* bitmap;
void Release()
{
SafeRelease(&bitmapTarget);
SafeRelease(&deviceContext);
SafeRelease(&brush);
SafeRelease(&blur);
SafeRelease(&bitmap);
}
void Init(HWND hwnd)
{
Release();
ID2D1Factory* factory;
D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory);
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top);
// Create a Direct2D render deviceContext.
ID2D1HwndRenderTarget* renderTarget;
factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hwnd, size), &renderTarget);
renderTarget->QueryInterface(&deviceContext);
renderTarget->CreateSolidColorBrush(D2D1::ColorF(255, 0, 0), &brush);
renderTarget->CreateCompatibleRenderTarget(&bitmapTarget);
// create blur & set bitmap as input
deviceContext->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
//get bitmap from bitmap rt
bitmapTarget->GetBitmap(&bitmap);
blur->SetInput(0, bitmap);
SafeRelease(&renderTarget);
SafeRelease(&factory);
}
void Render()
{
// draw to bitmap rt
bitmapTarget->BeginDraw();
bitmapTarget->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
D2D1_SIZE_F rtSize = deviceContext->GetSize();
bitmapTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
bitmapTarget->EndDraw();
// draw to dc
deviceContext->BeginDraw();
// draw bitmap + effect
deviceContext->DrawImage(blur);
deviceContext->EndDraw();
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return NULL;
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
WNDCLASS c = { NULL };
c.lpszClassName = L"GROKEN";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
HDC hdcDevice = GetDC(NULL);
int xw = GetDeviceCaps(hdcDevice, HORZRES);
int yw = GetDeviceCaps(hdcDevice, VERTRES);
int cx = 500, cy = 500;
int x = xw / 2 - cx / 2, y = yw / 2 - cy / 2;
HWND hwnd = CreateWindowEx(NULL, L"GROKEN", L"asd", WS_OVERLAPPEDWINDOW | WS_VISIBLE, x, y, cx, cy, NULL, NULL, hin, 0);
ShowWindow(hwnd, SW_SHOW);
Init(hwnd);
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
Render();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
Related
I want is to create backdrop-blur effect as in UWP apps in-app blur for my ui library, but I've been stuck with it for a few days.
For some reason, it either does not set backbuffer bitmap in SetInput method correctly, or in DrawImage call. The saddest thing is that backbuffer bitmap is actually valid, it could be drawn by same DrawImage call. Maybe I should specify another bitmap options when create it?
My current code:
#include <Windows.h>
HDC hdcDevice = GetDC(NULL);
int xw = GetDeviceCaps(hdcDevice, HORZRES);
int yw = GetDeviceCaps(hdcDevice, VERTRES);
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp);
HWND hwnd;
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>
#include <d2d1_1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dxguid.lib")
template<class Interface>
inline void SafeRelease(
Interface** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release();
(*ppInterfaceToRelease) = NULL;
}
}
#ifndef Assert
#if defined( DEBUG ) || defined( _DEBUG )
#define Assert(b) do {if (!(b)) {OutputDebugStringA("Assert: " #b "\n");}} while(0)
#else
#define Assert(b)
#endif //DEBUG || _DEBUG
#endif
#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
#endif
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1DeviceContext* target;
ID2D1SolidColorBrush* brush;
void Release()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&target);
SafeRelease(&brush);
}
void Init()
{
Release();
m_pRenderTarget = NULL;
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
SUCCEEDED(m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hwnd, size),
&m_pRenderTarget));
m_pRenderTarget->QueryInterface(&target);
}
inline void Blur(ID2D1DeviceContext* backTarget, int rad, RECT r)
// r is not used, should contain element bound box in future,
// so backtarget->DrawImage will only draw part that
// belongs to element's area
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
// Draw rectangle for test
backTarget->FillRectangle(D2D1::RectF(30, 30, 100, 100), brush);
ID2D1Bitmap1* bb = nullptr;
// Create bitmap
backTarget->CreateBitmap(size, 0, 0, D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
), &bb);
// Copy current taget's state to created bitmap
bb->CopyFromRenderTarget(0, backTarget, 0);
ID2D1Effect* blur = nullptr;
target->CreateEffect(CLSID_D2D1GaussianBlur, &blur);
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 10);
blur->SetInput(0, bb);
// Draw blurred result. Does nothing
backTarget->DrawImage(blur);
// Just test if bb is valid, draw
// it with some offset.
// Draws correctly
auto a = D2D1::Point2F(100, 0);
backTarget->DrawImage(bb, a);
SafeRelease(&blur);
}
inline void Render()
{
RECT rc;
GetClientRect(hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top);
ID2D1BitmapRenderTarget* tar = nullptr; // Create back buffer
target->CreateCompatibleRenderTarget(&tar);
ID2D1DeviceContext* tt = nullptr;
// Get exactly back buffer as ID2D1DeviceContext*,
// because it has more draw call, such as DrawImage()
tar->QueryInterface(&tt);
tt->CreateSolidColorBrush(
D2D1::ColorF(255, 0, 0),
&brush
);
tt->BeginDraw();
tt->Clear(D2D1::ColorF(D2D1::ColorF::Blue));
tt->SetTransform(D2D1::Matrix3x2F::Identity());
// for(int i = 0; i < ui_elements_count; i++)
// {
// ui_element->DrawBlurredAreaBehindIt(tt);
// ui_element->DrawInnerText(tt);
// ui_element->DrawBorder(tt);
// ui_element->DrawSomethingElse(tt);
// }
// loop through ui elements should be here,
// assume we have an element with blur needed
Blur(tt, 10, RECT());
tt->EndDraw();
target->BeginDraw();
ID2D1Bitmap* bmp = nullptr;
tar->GetBitmap(&bmp);
target->DrawImage(bmp);
SafeRelease(&tar);
SafeRelease(&tt);
SafeRelease(&bmp);
SafeRelease(&brush);
target->EndDraw();
}
int WINAPI WinMain(HINSTANCE hin, HINSTANCE, LPSTR, int)
{
ReleaseDC(NULL, hdcDevice);
WNDCLASS c = { NULL };
c.lpszClassName = L"asd";
c.lpfnWndProc = WndProc;
c.hInstance = hin;
c.style = CS_VREDRAW | CS_HREDRAW;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = CreateSolidBrush(RGB(255, 255, 255));
RegisterClass(&c);
int cx = 500, cy = 500;
int x = xw / 2 - cx / 2, y = yw / 2 - cy / 2;
hwnd = CreateWindowEx(NULL, L"asd", L"asd", WS_POPUP | WS_VISIBLE, x, y, cx, cy, NULL, NULL, hin, 0);
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
CoInitialize(NULL);
SUCCEEDED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory));
Init();
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
Render();
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoUninitialize();
return 0;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
switch (message)
{
default:
return DefWindowProc(hwnd, message, wp, lp);
}
return NULL;
}
To obtain current drawing bitmap ID2D1BitmapRenderTarget* need to be created as backbuffer. But, this interface does not have all draw methods suchs as DrawImage, or CreateEffect, so I have tried to QueryInterface ID2D1DeviceContext from it, and it actually works.
For getting backbuffer bitmap I use ID2D1Bitmap::CopyFromRenderTarget because if to draw bitmap from ID2D1BitmapRenderTarget::GetBitmap it will draw just nothing.
Important update
I just changed blur effect to scale effect, and fortunately or unfortunately, with scale effect it works. Please, don't tell it is a direct2d bug, I sure I doing here something wrong.
I noticed that in all examples this effect created and initialized by SetInput and SetValue calls before the render loop, out of BeginDraw and EndDraw. Maybe after that calls the image is somehow prepared asynchronously, so it just does not have time be ready to be drawn being in render loop? But it sounds crazy.
You will not believe. YOU WILL NOT BELIEVE.
The problem was that, the the D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION value in blur->SetValue call was in int, and should in float. And it is not regular reverse case when You trying to pass 0.5 to some int value and it casted to 1. Just think about this stupidnes.
That's not a joke,
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 3.0f); // works
blur->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 3); // does not work
What? Why? Because of what? - I don't know answer to these questions as You, seems like that's just how things work.
And yes, on MSDN it is written that D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION type is FLOAT, but how? How 10 can not work, but 10.0f works?
I want to create a layered window, the size of which is bigger than the size of its contents (hdcSrc). So that the window is for example 900x900, and we can place a 300x300 image inside of it anywhere we want, while the rest of the window is transparent.
Here is my code so far:
#include <Windows.h>
#include <string>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment( lib, "Gdiplus.lib" )
HWND hwnd;
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
void updateTestWindow();
LRESULT CALLBACK windowProcedure(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
{
MSG msg;
// GDI+ elements
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Register Window Class
WNDCLASS testWindowClass;
testWindowClass.lpszClassName = TEXT("TestWindow");
testWindowClass.hInstance = hInstance;
testWindowClass.style = CS_HREDRAW | CS_VREDRAW;
testWindowClass.lpfnWndProc = windowProcedure;
testWindowClass.hIcon = 0;
testWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
testWindowClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
testWindowClass.cbClsExtra = 0;
testWindowClass.cbWndExtra = 0;
testWindowClass.lpszMenuName = NULL;
RegisterClass(&testWindowClass);
// Create a layered window
hwnd = CreateWindowEx(
WS_EX_LAYERED,
TEXT("TestWindow"), // window class name
TEXT("TestWindow"), // window caption
WS_POPUP, // window style
0, // initial x position
0, // initial y position
screenWidth, // initial x size
screenHeight, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
ShowWindow(hwnd, iCmdShow);
// Update the layered window using a custom function
updateTestWindow();
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void updateTestWindow()
{
// Let's imagine that in reality we are drawing an image, but to test it, just draw a rectangle
int imageWidth = 300;
int imageHeight = 300;
HDC screenDC = GetDC(NULL);
// Create a DC for drawing
HDC drawingDC = CreateCompatibleDC(screenDC);
// Create a new bitmap for drawing on it
HBITMAP newBitmap = CreateCompatibleBitmap(screenDC, imageWidth, imageHeight);
// Select the new bitmap to draw on it and save the old one
HBITMAP oldBitmap = (HBITMAP)SelectObject(drawingDC, newBitmap);
// Create graphics object
Graphics graphics(drawingDC);
// Draw a rectangle
Pen redPen(Color(255, 255, 0, 0), 15);
Rect windowRect(0, 0, imageWidth, imageWidth);
graphics.DrawRectangle(&redPen, windowRect);
// The position of the layered window
POINT windowPosition = { 0, 0 };
// The size of the layered window
SIZE windowSize = { imageWidth, imageHeight }; // <----------------------------------!!!!
POINT drawingDCPosition = { 0,0 };
// Create a blend function
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
// Call UpdateLayeredWindow
UpdateLayeredWindow(hwnd, screenDC, &windowPosition, &windowSize,
drawingDC, &drawingDCPosition, 0, &blend, ULW_ALPHA);
// Clean up
SelectObject(drawingDC, oldBitmap);
DeleteObject(newBitmap);
DeleteDC(drawingDC);
ReleaseDC(NULL, screenDC);
}
This creates a window that is exactly the size of the image that's drawn on it.
We can reduce the window size: SIZE windowSize = { imageWidth-100, imageHeight-100 }; and the window will be drawn as expected - smaller size, while the image is the same size but now it's clipped (in this case, the rectangle is clipped).
However, if we want to achieve what I described in the beginning, SIZE windowSize = { imageWidth+100, imageHeight+100 }; doesn't work. The window is just not rendered at all, yet it opens (window taskbar icon is visible).
I probably just can't understand how a layered window works completely yet, which is why I can't understand why in this example it's not being rendered.
Is it possible to create such a layered window at all? And if yes, then how?
Or maybe this is not something I should be doing at all?
Just as a note: The reason I want to do this, is to use the layered window as a sort of an overlay, that would constantly be the size of the screen but the images, figures and text would be drawn on to it, updated, moved and so on.
I was working on a project which uses DirectX and Direct Composition. I am trying to create a similar effect like windows acrylic blur. Using direct composition I was able to apply the blur effect, saturation and blending to a given input. windows acrylic blur use the content behind the window as input to blur but it is not possible in win32 so I decided to use desktop background as input for my blur function. But the problem is that I was able to crop the background based on window position but if I moved the window to new position the bitmap is cropped based on the new RECT of window but there is flickering while moving to new position.
Here is the previews:
Problem : Flickering while moving the window
Solution: This is what I need to get
Concept : This is what I am trying to Achieve
Here is the screenshot of actual window:
Here the red box contains the bitmap saturated and cropped using the window RECT
(See the color difference).
Here is a simple code to reproduce the problem:
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <wrl.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_1.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <wincodec.h>
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dwmapi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dcomp")
using namespace Microsoft::WRL;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
ComPtr<ID3D11Device> direct3dDevice;
ComPtr<IDXGIDevice> dxgiDevice;
ComPtr<IDXGIFactory2> dxFactory;
ComPtr<IDXGISwapChain1> swapChain;
ComPtr<ID2D1Factory2> d2Factory;
ComPtr<ID2D1Device1> d2Device;
ComPtr<IDCompositionTarget> target;
ComPtr<ID2D1DeviceContext> dc;
//Direct Composition Device,Visual
ComPtr<IDCompositionDevice> dcompDevice;
ComPtr<IDCompositionDevice3> dcompDevice3;
ComPtr<IDCompositionVisual> visual;
//Direct Composition Effects
ComPtr<IDCompositionGaussianBlurEffect> blur;
ComPtr<IDCompositionSaturationEffect> saturation;
IWICImagingFactory* d2dWICFactory = NULL;
IWICBitmapDecoder* d2dDecoder = NULL;
IWICFormatConverter* d2dConverter = NULL;
IWICBitmapFrameDecode* d2dBmpSrc = NULL;
ID2D1Bitmap* d2dBmp = NULL;
HWND hwnd;
RECT windowRect;
void LoadBackground();
void Render();
struct ComException
{
HRESULT result;
ComException(HRESULT const value) : result(value)
{}
};
void HR(HRESULT const result)
{
if (S_OK != result)
{
throw ComException(result);
}
}
void CreateDevice(HWND hwnd)
{
HR(D3D11CreateDevice(nullptr, // Adapter
D3D_DRIVER_TYPE_HARDWARE,
nullptr, // Module
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION,
&direct3dDevice,
nullptr, // Actual feature level
nullptr)); // Device context
HR(direct3dDevice.As(&dxgiDevice));
HR(CreateDXGIFactory2(
DXGI_CREATE_FACTORY_DEBUG,
__uuidof(dxFactory),
reinterpret_cast<void**>(dxFactory.GetAddressOf())));
DXGI_SWAP_CHAIN_DESC1 description = {};
description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
description.BufferCount = 2;
description.SampleDesc.Count = 1;
description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
RECT rect = {};
GetClientRect(hwnd, &rect);
description.Width = rect.right - rect.left;
description.Height = rect.bottom - rect.top;
HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(),&description,nullptr,swapChain.GetAddressOf()));
D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,options,d2Factory.GetAddressOf()));
HR(d2Factory->CreateDevice(dxgiDevice.Get(),d2Device.GetAddressOf()));
HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,dc.GetAddressOf()));
ComPtr<IDXGISurface2> surface;
HR(swapChain->GetBuffer(0,__uuidof(surface),reinterpret_cast<void**>(surface.GetAddressOf())));
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
ComPtr<ID2D1Bitmap1> bitmap;
HR(dc->CreateBitmapFromDxgiSurface(surface.Get(),properties,bitmap.GetAddressOf()));
LoadBackground(); // loads my desktop background to d2dBmp using WIC
dc->SetTarget(bitmap.Get());
Render(); //render once
//Creating Direct Compostion Devices and Visual
HR(DCompositionCreateDevice(dxgiDevice.Get(),__uuidof(dcompDevice),reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
HR(DCompositionCreateDevice3(dxgiDevice.Get(), __uuidof(dcompDevice),reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
HR(dcompDevice->QueryInterface(__uuidof(IDCompositionDevice3), (LPVOID*)&dcompDevice3)); // use IDCompositionDevice3 here
HR(dcompDevice3->CreateSaturationEffect(saturation.GetAddressOf()));
HR(dcompDevice3->CreateGaussianBlurEffect(blur.GetAddressOf()));
//setting effect properties
blur->SetStandardDeviation(30.0f); // blur amount
blur->SetBorderMode(D2D1_BORDER_MODE_HARD);
saturation->SetSaturation(2.0f); //saturationamount
HR(dcompDevice->CreateTargetForHwnd(hwnd,true,target.GetAddressOf()));
blur->SetInput(NULL, bitmap.Get(), NULL);
saturation->SetInput(NULL, blur.Get(), NULL);
HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
HR(visual->SetContent(swapChain.Get()));
visual->SetEffect(saturation.Get());
HR(target->SetRoot(visual.Get()));
HR(dcompDevice->Commit());
}
void LoadBackground()
{
HR(CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)(&d2dWICFactory)));
HR(d2dWICFactory->CreateDecoderFromFilename(L"C:/Users/selas/Downloads/wallpaper.jpg", NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &d2dDecoder));
HR(d2dWICFactory->CreateFormatConverter(&d2dConverter));
HR(d2dDecoder->GetFrame(0, &d2dBmpSrc));
HR(d2dConverter->Initialize(d2dBmpSrc, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut));
HR(dc->CreateBitmapFromWicBitmap(d2dConverter, NULL, &d2dBmp));
}
void Render()
{
if (dc)
{
dc->BeginDraw();
dc->Clear();
D2D1_POINT_2F offset = D2D1::Point2F(0, 0);
D2D1_RECT_F imgRect = D2D1::RectF(windowRect.left, windowRect.top, windowRect.right, windowRect.bottom);
dc->DrawImage(d2dBmp, offset, imgRect, D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_SOURCE_OVER);
HR(dc->EndDraw());
HR(swapChain->Present(1, 0));
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
wc.lpszClassName, L"Sample",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
nullptr, nullptr, hInstance, nullptr);
if (hwnd == NULL)
{
return 0;
}
CreateDevice(hwnd);
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_MOVING:
{
GetWindowRect(hwnd, &windowRect);
Render();
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
d2dWICFactory->Release();
d2dDecoder->Release();
d2dConverter->Release();
d2dBmpSrc->Release();
d2dBmp->Release();
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Please don't forget to replace "C:/Users/selas/Downloads/wallpaper.jpg" with your desktop wallpaper
Note: The sample videos are actually edited ones its not the output I've got.
Finally I figured it out. Now I am able to move the window without any flickering to the image I rendered as background. The problem was in two places in my code.
GetWindowRect():
In WM_MOVING I was using GetWindowRect() to get the window Rectangle, this causes delay to obtain the window Rectangle and caused the flickering issue. The Solution I found was simple, I Converted the lParm to RECT by typecasting and using it as the value to update the location solved the issue. The code I used to solve the issue is given below :
RECT *hostRect = reinterpret_cast<RECT*>(lParam)
MagSetWindowSource(): This is an interesting thing. I Don't know what is the use of magnifier in this. The code will not remove the flicker until I call MagSetWindowSource() function, I don't know why it is required, that alone consumes more memory in my program. if I removed this line of code it again starts to flicker. The input to the function doesn't matter you just have to call the function that's it.
Updated :
Finally figured out what is MagSetWindowSource() is doing, it actually
calls a DWM function called DwmFlush() which Issues a flush call that
blocks the caller until the next present. and that solved my issue.
now it is flickerless and smooth.
Here is the fully updated code:
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <wrl.h>
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_1.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#include <dwmapi.h>
#include <wincodec.h>
#include <magnification.h>
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dwmapi")
#pragma comment(lib, "dxguid")
#pragma comment(lib, "dcomp")
#pragma comment(lib, "magnification")
using namespace Microsoft::WRL;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
ComPtr<ID3D11Device> direct3dDevice;
ComPtr<IDXGIDevice> dxgiDevice;
ComPtr<IDXGIFactory2> dxFactory;
ComPtr<IDXGISwapChain1> swapChain;
ComPtr<ID2D1Factory2> d2Factory;
ComPtr<ID2D1Device1> d2Device;
ComPtr<IDCompositionTarget> target;
ComPtr<ID2D1DeviceContext> dc;
//Direct Composition Device,Visual
ComPtr<IDCompositionDevice> dcompDevice;
ComPtr<IDCompositionDevice3> dcompDevice3;
ComPtr<IDCompositionVisual> visual;
//Direct Composition Effects
ComPtr<IDCompositionGaussianBlurEffect> blur;
ComPtr<IDCompositionSaturationEffect> saturation;
IWICImagingFactory* d2dWICFactory = NULL;
IWICBitmapDecoder* d2dDecoder = NULL;
IWICFormatConverter* d2dConverter = NULL;
IWICBitmapFrameDecode* d2dBmpSrc = NULL;
ID2D1Bitmap* d2dBmp = NULL;
HWND hwnd;
RECT windowRect,temp;
RECT* hostRect;
void LoadBackground();
void Render();
struct ComException
{
HRESULT result;
ComException(HRESULT const value) : result(value)
{}
};
void HR(HRESULT const result)
{
if (S_OK != result)
{
throw ComException(result);
}
}
void CreateDevice(HWND hwnd)
{
HR(D3D11CreateDevice(nullptr, // Adapter
D3D_DRIVER_TYPE_HARDWARE,
nullptr, // Module
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION,
&direct3dDevice,
nullptr, // Actual feature level
nullptr)); // Device context
HR(direct3dDevice.As(&dxgiDevice));
HR(CreateDXGIFactory2(
DXGI_CREATE_FACTORY_DEBUG,
__uuidof(dxFactory),
reinterpret_cast<void**>(dxFactory.GetAddressOf())));
DXGI_SWAP_CHAIN_DESC1 description = {};
description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
description.BufferCount = 2;
description.SampleDesc.Count = 1;
description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
RECT rect = {};
GetClientRect(hwnd, &rect);
description.Width = rect.right - rect.left;
description.Height = rect.bottom - rect.top;
HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(), &description, nullptr, swapChain.GetAddressOf()));
D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, d2Factory.GetAddressOf()));
HR(d2Factory->CreateDevice(dxgiDevice.Get(), d2Device.GetAddressOf()));
HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, dc.GetAddressOf()));
ComPtr<IDXGISurface2> surface;
HR(swapChain->GetBuffer(0, __uuidof(surface), reinterpret_cast<void**>(surface.GetAddressOf())));
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
ComPtr<ID2D1Bitmap1> bitmap;
HR(dc->CreateBitmapFromDxgiSurface(surface.Get(), properties, bitmap.GetAddressOf()));
LoadBackground(); // loads my desktop background to d2dBmp using WIC
dc->SetTarget(bitmap.Get());
Render(); //render once
//Creating Direct Compostion Devices and Visual
HR(DCompositionCreateDevice(dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
HR(DCompositionCreateDevice3(dxgiDevice.Get(), __uuidof(dcompDevice), reinterpret_cast<void**>(dcompDevice.GetAddressOf())));
HR(dcompDevice->QueryInterface(__uuidof(IDCompositionDevice3), (LPVOID*)&dcompDevice3)); // use IDCompositionDevice3 here
HR(dcompDevice3->CreateSaturationEffect(saturation.GetAddressOf()));
HR(dcompDevice3->CreateGaussianBlurEffect(blur.GetAddressOf()));
//setting effect properties
blur->SetStandardDeviation(0.0f); // blur amount
blur->SetBorderMode(D2D1_BORDER_MODE_HARD);
saturation->SetSaturation(2.0f); //saturationamount
HR(dcompDevice->CreateTargetForHwnd(hwnd, true, target.GetAddressOf()));
blur->SetInput(NULL, bitmap.Get(), NULL);
saturation->SetInput(NULL, blur.Get(), NULL);
HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
HR(visual->SetContent(swapChain.Get()));
visual->SetEffect(saturation.Get());
HR(target->SetRoot(visual.Get()));
HR(dcompDevice->Commit());
}
void LoadBackground()
{
HR(CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, __uuidof(IWICImagingFactory), (void**)(&d2dWICFactory)));
HR(d2dWICFactory->CreateDecoderFromFilename(L"C:/Users/selas/Downloads/wallpaper.jpg", NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &d2dDecoder));
HR(d2dWICFactory->CreateFormatConverter(&d2dConverter));
HR(d2dDecoder->GetFrame(0, &d2dBmpSrc));
HR(d2dConverter->Initialize(d2dBmpSrc, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeMedianCut));
HR(dc->CreateBitmapFromWicBitmap(d2dConverter, NULL, &d2dBmp));
}
void Render()
{
if (dc)
{
dc->BeginDraw();
dc->Clear();
if (hostRect)
{
D2D1_POINT_2F offset = D2D1::Point2F(0, 0);
D2D1_RECT_F imgRect = D2D1::RectF(hostRect->left, hostRect->top, hostRect->right, hostRect->bottom);
dc->DrawImage(d2dBmp, offset, imgRect, D2D1_INTERPOLATION_MODE_LINEAR, D2D1_COMPOSITE_MODE_SOURCE_OVER);
}
HR(dc->EndDraw());
HR(swapChain->Present(1, 0));
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
hwnd = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
wc.lpszClassName, L"Sample",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
nullptr, nullptr, hInstance, nullptr);
if (hwnd == NULL)
{
return 0;
}
CreateDevice(hwnd);
ShowWindow(hwnd, nCmdShow);
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_MOVING:
{
hostRect = reinterpret_cast<RECT*>(lParam);
windowRect.left = hostRect->left;
windowRect.top = hostRect->top;
windowRect.right = hostRect->right;
windowRect.bottom = hostRect->bottom;
if (hostRect->left == 0 || hostRect->top == 0)
{
GetWindowRect(hwnd, &temp);
hostRect->left = temp.left;
hostRect->top = temp.top;
}
DwmFlush();
Render();
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
EndPaint(hwnd, &ps);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
d2dWICFactory->Release();
d2dDecoder->Release();
d2dConverter->Release();
d2dBmpSrc->Release();
d2dBmp->Release();
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
I use extended frame to render a custom caption and window border.
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
I also use layered window to render background transparent. My COLORKEY is RGB(0,0,0)
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 255, LWA_COLORKEY);
The reason I use layered window is, I want to make window's bottom border's corners rounded.
The issue is I want to do something beatiful and I tried to render a Anti Aliased window border in client area with GDI. However, It work as Anti Aliased with painted background (Solid) but not with TRANSPARENT background.
IMAGE: https://ibb.co/fD9GsFg
What should I do to fix this ?
If you try it please use VS debugger as I did not put functional window buttons.
#include <windows.h>
#include <tchar.h>
#include <windowsx.h>
#include <objidl.h>
#include <gdiplus.h>
#include <dwmapi.h>
#pragma comment(lib,"Dwmapi")
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
HINSTANCE hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int nWidth = 600, nHeight = 400;
#define RECTWIDTH(rc) (rc.right - rc.left)
#define RECTHEIGHT(rc) (rc.bottom - rc.top)
//CHANGE THEM TO 0 when thats maximized!
const int TOPEXTENDWIDTH = 48;
const int LEFTEXTENDWIDTH = 8;
const int RIGHTEXTENDWIDTH = 8;
const int BOTTOMEXTENDWIDTH = 8;
HBRUSH RED_BRUSH = CreateSolidBrush(RGB(237, 28, 36));
HBRUSH DARKBLUE_BRUSH = CreateSolidBrush(RGB(26, 31, 96));
HBRUSH PURPLE_BRUSH = CreateSolidBrush(RGB(163, 73, 164));
HBRUSH LIGHTPURPLE_BRUSH_1 = CreateSolidBrush(RGB(189, 106, 189));
HBRUSH LIGHTPURPLE_BRUSH_2 = CreateSolidBrush(RGB(255, 174, 201));
HBRUSH DARKEST_BRUSH = CreateSolidBrush(RGB(0, 0, 0));
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain( _In_opt_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_opt_ LPTSTR lpCmdLine, _In_opt_ int nCmdShow)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
// Initialize GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
hInst = hInstance;
WNDCLASSEX wcex =
{
sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),
LoadCursor(NULL, IDC_ARROW), (HBRUSH)GetStockObject(BLACK_BRUSH), NULL, TEXT("WindowClass"), NULL,
};
if (!RegisterClassEx(&wcex))
return MessageBox(NULL, L"Cannot register class !", L"Error", MB_ICONERROR | MB_OK);
int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;
HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test"), WS_OVERLAPPEDWINDOW, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);
if (!hWnd) return MessageBox(NULL, L"Cannot create window !", L"Error", MB_ICONERROR | MB_OK);
//NO SHADOW
SystemParametersInfoA(SPI_SETDROPSHADOW,0,(const PVOID) false,SPIF_SENDWININICHANGE);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
void FillRoundRectangle(Gdiplus::Graphics* g, Brush* p, Gdiplus::Rect& rect, UINT8 radius[4])
{
if (g == NULL) return;
GraphicsPath path;
//TOP RIGHT
path.AddLine(rect.X + radius[0], rect.Y, rect.X + rect.Width - (radius[0] * 2), rect.Y);
path.AddArc(rect.X + rect.Width - (radius[0] * 2), rect.Y, radius[0] * 2, radius[0] * 2, 270, 90);
//BOTTOM RIGHT
path.AddLine(rect.X + rect.Width, rect.Y + radius[1], rect.X + rect.Width, rect.Y + rect.Height - (radius[1] * 2));
path.AddArc(rect.X + rect.Width - (radius[1] * 2), rect.Y + rect.Height - (radius[1] * 2), radius[1] * 2, radius[1] * 2, 0, 90);
//BOTTOM LEFT
path.AddLine(rect.X + rect.Width - (radius[2] * 2), rect.Y + rect.Height, rect.X + radius[2], rect.Y + rect.Height);
path.AddArc(rect.X, rect.Y + rect.Height - (radius[2] * 2), radius[2] * 2, radius[2] * 2, 90, 90);
//TOP LEFT
path.AddLine(rect.X, rect.Y + rect.Height - (radius[3] * 2), rect.X, rect.Y + radius[3]);
path.AddArc(rect.X, rect.Y, radius[3] * 2, radius[3] * 2, 180, 90);
path.CloseFigure();
g->FillPath(p, &path);
}
VOID OnPaint(HDC hdc,int width, int height)
{
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingMode::SmoothingModeHighQuality);
graphics.SetCompositingQuality(CompositingQuality::CompositingQualityInvalid);
graphics.SetPixelOffsetMode(PixelOffsetMode::PixelOffsetModeHighQuality);
SolidBrush mySolidBrush(Color(255, 255, 0, 0)); ;
Gdiplus::Rect rect1;
rect1.X = 0;
rect1.Y = TOPEXTENDWIDTH;
rect1.Width = width;
rect1.Height = height- TOPEXTENDWIDTH-111;
UINT8 rad[4]{ 0,12,12,0 };
FillRoundRectangle(&graphics, &mySolidBrush, rect1, rad);
SolidBrush DarkSolidBrush(Color(255, 0, 1, 0)); ;
Gdiplus::Rect rectX = {0,455,55,55};
graphics.FillEllipse(&DarkSolidBrush,rectX);
}
LRESULT CustomCaptionProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool* pfCallDWP)
{
LRESULT lRet = 0;
HRESULT hr = S_OK;
bool fCallDWP = true; // Pass on to DefWindowProc?
static HICON hIcon = NULL;
fCallDWP = !DwmDefWindowProc(hWnd, message, wParam, lParam, &lRet);
if (message == WM_CREATE)
{
RECT rcClient;
GetWindowRect(hWnd, &rcClient);
// Inform the application of the frame change.
SetWindowPos(hWnd, NULL, rcClient.left, rcClient.top, RECTWIDTH(rcClient), RECTHEIGHT(rcClient), SWP_FRAMECHANGED);
HMODULE hDLL = LoadLibrary(L"Setupapi.dll");
if (hDLL)
{
hIcon = (HICON)LoadImage(hDLL, MAKEINTRESOURCE(2), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR | LR_SHARED);
}
SetWindowLong(hWnd, GWL_EXSTYLE,
GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
/*Use pointer to function*/
SetLayeredWindowAttributes(hWnd, 0,
(255 * 70) / 100, LWA_ALPHA);
fCallDWP = true;
lRet = 0;
}
// Handle window activation.
if (message == WM_ACTIVATE)
{
// Extend the frame into the client area.
MARGINS margins;
margins.cxLeftWidth = 0;
margins.cxRightWidth = 0;
margins.cyBottomHeight = 0;
margins.cyTopHeight = 0;
hr = DwmExtendFrameIntoClientArea(hWnd, &margins);
if (!SUCCEEDED(hr))
{
// Handle error.
}
fCallDWP = true;
lRet = 0;
}
if (message == WM_PAINT)
{
PAINTSTRUCT ps;
BITMAP bm;
RECT rect, rectCaptionButtonBounds, rectText,myRect,ContentRect,ClientRect,CaptionBorderBottom, rect_EXIT_BTN, rect_RESTORE_BTN, rect_MINIMIZE_BTN;
HFONT windowTitleText;
GetClientRect(hWnd,&ClientRect);
BeginPaint(hWnd, &ps);
SetGraphicsMode(ps.hdc, GM_ADVANCED);
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 255, LWA_COLORKEY);
SetBkMode(ps.hdc, TRANSPARENT);
if (SUCCEEDED(DwmGetWindowAttribute(hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, &rectCaptionButtonBounds, sizeof(rectCaptionButtonBounds))))
{
GetClientRect(hWnd, &rect);
//HRGN hrgn_cptBtmBrdrRND = CreateRoundRectRgn(0, 0, RECTWIDTH(ClientRect), RECTHEIGHT(ClientRect), 16, 16);
//FillRgn(ps.hdc, hrgn_cptBtmBrdrRND, DARKBLUE_BRUSH);
HRGN hrgn = CreateRectRgn(0, 0, RECTWIDTH(ClientRect), TOPEXTENDWIDTH);
FillRgn(ps.hdc, hrgn, PURPLE_BRUSH);
DrawIconEx(ps.hdc, rect.right - (rectCaptionButtonBounds.right - rectCaptionButtonBounds.left) - 32, 0, hIcon, 32, 32, 0, NULL, DI_NORMAL);
SetRect(&myRect, LEFTEXTENDWIDTH, 10, RECTWIDTH(rect)-200, TOPEXTENDWIDTH);
SetTextColor(ps.hdc, RGB(1, 0, 0));
DrawText(ps.hdc,L"test",-1,&myRect, DT_SINGLELINE | DT_RIGHT);
SetTextColor(ps.hdc, RGB(255, 255, 255));
WCHAR wsText[255] = L"ARMNET";
SetRect(&rectText, LEFTEXTENDWIDTH, 0, RECTWIDTH(rect), TOPEXTENDWIDTH);
windowTitleText =
CreateFontA
(
32,
0,
GM_ADVANCED,
0,
FW_DONTCARE,
false,
false,
false,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY, //BETTER BLENDING THAN ANTIALIASED
VARIABLE_PITCH,
"RETRO COMPUTER");
SelectObject(ps.hdc, windowTitleText);
DrawText(ps.hdc, wsText, -1, &rectText, DT_SINGLELINE | DT_VCENTER);
DeleteObject(windowTitleText);
DeleteObject(hrgn);
}
//CONTENT AREA
//SetRect(&ContentRect, 0, TOPEXTENDWIDTH, RECTWIDTH(ClientRect) - 0, RECTHEIGHT(ClientRect) - 0);
//FillRect(ps.hdc, &ContentRect, DARKBLUE_BRUSH);
HRGN hrgn_cptBtmBrdr = CreateRectRgn(0, TOPEXTENDWIDTH-1, RECTWIDTH(ClientRect), TOPEXTENDWIDTH);
FillRgn(ps.hdc, hrgn_cptBtmBrdr, CreateSolidBrush(RGB(132, 68, 133)));
hrgn_cptBtmBrdr = CreateRectRgn(0, TOPEXTENDWIDTH - 2, RECTWIDTH(ClientRect), TOPEXTENDWIDTH-1);
FillRgn(ps.hdc, hrgn_cptBtmBrdr,CreateSolidBrush(RGB(185, 91, 186)));
//BUTTONS
hrgn_cptBtmBrdr = CreateRectRgn(RECTWIDTH(ClientRect)-32, 0, RECTWIDTH(ClientRect), 32);
FillRgn(ps.hdc, hrgn_cptBtmBrdr, CreateSolidBrush(RGB(11, 11, 111)));
hrgn_cptBtmBrdr = CreateRectRgn(RECTWIDTH(ClientRect) - 64, 0, RECTWIDTH(ClientRect)-32, 32);
FillRgn(ps.hdc, hrgn_cptBtmBrdr, CreateSolidBrush(RGB(111, 11, 111)));
hrgn_cptBtmBrdr = CreateRectRgn(RECTWIDTH(ClientRect) - 96, 0, RECTWIDTH(ClientRect)-64, 32);
FillRgn(ps.hdc, hrgn_cptBtmBrdr, CreateSolidBrush(RGB(11, 111, 11)));
OnPaint(ps.hdc, RECTWIDTH(ClientRect), RECTHEIGHT(ClientRect));
DeleteObject(hrgn_cptBtmBrdr);
EndPaint(hWnd, &ps);
fCallDWP = true;
lRet = 0;
}
// Handle the non-client size message.
if ((message == WM_NCCALCSIZE) && (wParam == TRUE))
{
// Calculate new NCCALCSIZE_PARAMS based on custom NCA inset.
NCCALCSIZE_PARAMS *pncsp = reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam);
pncsp->rgrc[0].left = pncsp->rgrc[0].left + 1;
pncsp->rgrc[0].top = pncsp->rgrc[0].top + 0;
pncsp->rgrc[0].right = pncsp->rgrc[0].right - 1;
pncsp->rgrc[0].bottom = pncsp->rgrc[0].bottom - 1;
lRet = 0;
// No need to pass the message on to the DefWindowProc.
fCallDWP = false;
}
// Handle hit testing in the NCA if not handled by DwmDefWindowProc.
if ((message == WM_NCHITTEST) && (lRet == 0))
{
lRet = HitTestNCA(hWnd, wParam, lParam);
if (lRet != HTNOWHERE)
{
fCallDWP = false;
}
}
if (message == WM_SIZE)
{
if (unsigned int(wParam) == SIZE_MAXIMIZED) {
}
else
{
}
}
if (message == WM_GETMINMAXINFO)
{
LPMINMAXINFO lpMMI = (LPMINMAXINFO)lParam;
lpMMI->ptMinTrackSize.x = 800;
lpMMI->ptMinTrackSize.y = 600;
}
if (message == WM_DESTROY)
PostQuitMessage(0);
*pfCallDWP = fCallDWP;
return lRet;
}
// Hit test the frame for resizing and moving.
LRESULT HitTestNCA(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
// Get the point coordinates for the hit test.
POINT ptMouse = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Get the window rectangle.
RECT rcWindow;
GetWindowRect(hWnd, &rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = { 0 };
AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL);
// Determine if the hit test is for resizing. Default middle (1,1).
USHORT uRow = 1;
USHORT uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if ((ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + TOPEXTENDWIDTH) )
{
if((ptMouse.x < rcWindow.right - 100) || (ptMouse.y > rcWindow.top + 32)){
fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top));
uRow = 0;
}
}
else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - BOTTOMEXTENDWIDTH)
{
uRow = 2;
}
// Determine if the point is at the left or right of the window.
if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + LEFTEXTENDWIDTH)
{
uCol = 0; // left side
}
else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - RIGHTEXTENDWIDTH)
{
uCol = 2; // right side
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
LRESULT hitTests[3][3] =
{
{ fOnResizeBorder ? HTTOPLEFT : HTLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, fOnResizeBorder ? HTTOPRIGHT : HTRIGHT },
{ HTLEFT, HTNOWHERE, HTRIGHT },
{ HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT },
};
return hitTests[uRow][uCol];
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
bool fCallDWP = true;
BOOL fDwmEnabled = FALSE;
LRESULT lRet = 0;
HRESULT hr = S_OK;
// Winproc worker for custom frame issues.
hr = DwmIsCompositionEnabled(&fDwmEnabled);
if (SUCCEEDED(hr))
{
lRet = CustomCaptionProc(hWnd, message, wParam, lParam, &fCallDWP);
}
// Winproc worker for the rest of the application.
if (fCallDWP)
{
// lRet = AppWinProc(hWnd, message, wParam, lParam);
lRet = DefWindowProc(hWnd, message, wParam, lParam);
}
return lRet;
}
Well, after searching for solutions, in the end I think I found an answer.
SOLUTION:
A solution for Anti aliasing issue could be capturing the background of entire window bound by exluding it from capture. At this time you will not need layered window since you are painting the background with a bitmap. HDC will be able to blend it with background. Hopefully Windows 10 2004 version you have an option called:
WDA_EXCLUDEFROMCAPTURE
USAGE:
SetWindowDisplayAffinity(hWnd, WDA_EXCLUDEFROMCAPTURE); //At creation time
SOURCE: https://blogs.windows.com/windowsdeveloper/2019/09/16/new-ways-to-do-screen-capture/
After that you can paint background with bitmap and then paint everything over it. However, this produced low performance and I did not benefit. Still, that works and produces ANTI-ALIASED looking when drawn. For the performance issue Direct2D can be used for drawing from returned bitmap.
EXAMPLE:
int DsktpBkSS(HWND hWnd) {
HDC hdcScreen;
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
BITMAP bmpScreen;
RECT windowPos;
SetWindowDisplayAffinity(hWnd, WDA_EXCLUDEFROMCAPTURE);
GetWindowRect(hWnd, &windowPos);
// Retrieve the handle to a display device context for the client
// area of the window.
hdcScreen = GetDC(NULL);
hdcWindow = GetDC(hWnd);
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcWindow);
if (!hdcMemDC)
{
MessageBox(hWnd, L"CreateCompatibleDC has failed", L"Failed", MB_OK);
}
// Get the client area for size calculation
RECT rcClient;
GetClientRect(hWnd, &rcClient);
//This is the best stretch mode
SetStretchBltMode(hdcWindow, HALFTONE);
//The source DC is the entire screen and the destination DC is the current window (HWND)
if (!StretchBlt(hdcWindow,
0, 0,
GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
hdcScreen,
windowPos.left+1,windowPos.top,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),
SRCCOPY))
{
MessageBox(hWnd, L"StretchBlt has failed", L"Failed", MB_OK);
}
// Create a compatible bitmap from the Window DC
hbmScreen = CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
if (!hbmScreen)
{
MessageBox(hWnd, L"CreateCompatibleBitmap Failed", L"Failed", MB_OK);
}
// Select the compatible bitmap into the compatible memory DC.
if(hdcMemDC && hbmScreen){
SelectObject(hdcMemDC, hbmScreen);
}
// Bit block transfer into our compatible memory DC.
if(hdcMemDC)
if (!BitBlt(hdcMemDC,
0, 0,
rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
hdcWindow,
0, 0,
SRCCOPY))
{
MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK);
}
// Get the BITMAP from the HBITMAP
if(hbmScreen)
GetObjectW(hbmScreen, sizeof(BITMAP), &bmpScreen);
if (hbmScreen)DeleteObject(hbmScreen);
if (hdcMemDC)DeleteObject(hdcMemDC);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
return 0;
}
Later,
(...){
if(message==WM_PAINT)
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
Graphics graphics(ps.hdc);
/*SET SMOOTHING (AA)*/
graphics.SetSmoothingMode(SmoothingMode::SmoothingModeHighQuality);
if(DsktpBkSS(hWnd))
{
/*DRAW ROUNDED RECTANGLE*/
}
}
//...
};
One more important thing for window drawn with "Desktop Window Manager" when resizing from left too fast and continuously that will produce "flickering" after a time. On Microsoft docs that is recommended that to use StretchBlt(...) for drawing since GDI+ cause this.
flicker mentioned for DWM: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-stretchblt
"Use BitBlt or StretchBlt function instead of Windows GDI+ to present
your drawing for rendering. GDI+ renders one scan line at a time with
software rendering. This can cause flickering in your applications."
I am trying to understand how to load and render an image from a file using the Windows API, Direct2D, and Visual C++. I have been more or less attempting to follow an MSDN article on this topic. I am new to both C++ (experienced in C) and the Windows API.
I wrote 3 functions.
HRESULT imagefactorysetup(IWICImagingFactory * pImageFactory)
{
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID *) &pImageFactory);
return hr;
}
HRESULT imageload(LPCWSTR filename, IWICImagingFactory * pImageFactory, IWICBitmapFrameDecode * pFrame)
{
IWICBitmapDecoder * pDecoder = NULL;
HRESULT hr = pImageFactory->CreateDecoderFromFilename(filename, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
if (SUCCEEDED(hr))
hr = pDecoder->GetFrame(0, &pFrame);
//Format convert the frame to 32bppPBGRA
IWICFormatConverter * pFormatConverter = NULL;
if (SUCCEEDED(hr))
{
SafeRelease(&pFormatConverter);
hr = pImageFactory->CreateFormatConverter(&pFormatConverter);
}
if (SUCCEEDED(hr))
hr = pFormatConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom);
return hr;
}
HRESULT imagerender(HWND hWnd, IWICBitmapFrameDecode * pFrame, int x, int y)
{
//Create a D2D render target properties
D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties();
//Set the DPI to be the default system DPI to allow direct mapping
//between image pixels and desktop pixels in different system DPI settings
renderTargetProperties.dpiX = DEFAULT_DPI;
renderTargetProperties.dpiY = DEFAULT_DPI;
//Create a D2D render target
D2D1_SIZE_U sz = D2D1::SizeU(MAINWINDOWWIDTH, MAINWINDOWHEIGHT); //Change size
ID2D1Factory * pD2DFactory;
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), NULL, (LPVOID *) &pD2DFactory);
ID2D1RenderTarget * pRenderTarget;
//renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);
hr = pD2DFactory->CreateHwndRenderTarget(&renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);
ID2D1Bitmap * pD2DBitmap = NULL;
pRenderTarget->CreateBitmapFromWicBitmap(pFrame, NULL, &pD2DBitmap);
D2D1_SIZE_F size = pD2DBitmap->GetSize();
D2D1_POINT_2F origin = D2D1::Point2F((float) x, (float) y);
if (pD2DBitmap)
pRenderTarget->DrawBitmap(pD2DBitmap, D2D1::RectF(origin.x, origin.y, origin.x + size.width, origin.y + size.height));
return hr;
}
Question:
1) The following line gives me an error. I tried reading some documentation on MSDN but am unsure what the issue is.
hr = pD2DFactory->CreateHwndRenderTarget(&renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);
Error:
IntelliSense: no instance of overloaded function "ID2D1Factory::CreateHwndRenderTarget" matches the argument list
argument types are: (D2D1_RENDER_TARGET_PROPERTIES *, D2D1_HWND_RENDER_TARGET_PROPERTIES, ID2D1RenderTarget **)
object type is: ID2D1Factory 68 18
2) In general, is there a more idiomatic / efficient way of approaching the problem of rendering an image from a file onto a window than what I have attempted? My previous programming experience has been strictly in C.
No problem. You can use GDI+ to load any image that windows supports natively.
You can then use GDI to draw it.
Here's a short example of drawing a (transparent) PNG to the background of a dialog. I've built it using MinGW32 and Code::Blocks. You'll need to link the msimg32 and gdiplus libraries to make use of AlphaBlend and the Bitmap class (and the functions to init/shutdown GDI+).
Points that may be worth mentioning are:
mLoadImage will load anything that windows will show in Windows Photo
Viewer (// BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF) - it uses
the Bitmap class, as found in Gdiplus.
The WM_ERASEBKGND message comes with wParam holding a device context
that you can draw straight into - that's why there's no need to get
one by usig BeginPaint, as we do in response to a WM_PAINT message.
You can use BitBlt or StretchBlt for images that dont contain transparent areas.
Main.cpp
#define WINVER 0x0500 // for AlphaBlend
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <gdiplus.h>
#include "resource.h"
using namespace Gdiplus;
HINSTANCE hInst;
void setClientSize(HWND mHwnd, int width, int height)
{
RECT wndRect, clientRect, mRect;
int clientX, clientY, windowX, windowY, difX, difY;
GetWindowRect(mHwnd, &wndRect);
GetClientRect(mHwnd, &clientRect);
clientX = clientRect.right - clientRect.left;
clientY = clientRect.bottom - clientRect.top;
windowX = wndRect.right - wndRect.left;
windowY = wndRect.bottom - wndRect.top;
difX = windowX - clientX;
difY = windowY - clientY;
// GetWindowRect(mHwnd, &mRect);
POINT topLeft = {wndRect.left, wndRect.top};
// ScreenToClient(mParentHwnd, &topLeft);
SetWindowPos(mHwnd, HWND_TOP, topLeft.x, topLeft.y, width+difX, height+difY, SWP_NOZORDER);
}
HBITMAP mLoadImg(wchar_t *filename)
{
Bitmap mBitmap(filename,false);
HBITMAP result;
mBitmap.GetHBITMAP(0x00000000, &result);
return result;
}
void onPaint(HWND hwnd, HBITMAP bkg)
{
HDC memDC, hdc;
PAINTSTRUCT ps;
HBITMAP old;
RECT clientRect;
int width, height;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &clientRect);
width = clientRect.right - clientRect.left;
height = clientRect.bottom - clientRect.top;
memDC = CreateCompatibleDC(NULL);
old = (HBITMAP)SelectObject(memDC, bkg);
byte alpha = 255;
BLENDFUNCTION bf = {AC_SRC_OVER,0,alpha,AC_SRC_ALPHA};
AlphaBlend(hdc, 0,0,width,height, memDC, 0,0, width,height, bf);
// try the below instead of AlphaBlend - they each rely on the fact I've resized the
// client area to the same size as the image I'll draw on it.
// BitBlt(hdc, 0,0, clientRect.right,clientRect.bottom, memDC, 0,0, SRCCOPY);
SelectObject(memDC, old);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HBITMAP mBkg;
switch(uMsg)
{
case WM_INITDIALOG:
{
mBkg = mLoadImg(L"wiki.png");
BITMAP bm;
GetObject(mBkg, sizeof(bm), &bm);
setClientSize(hwndDlg, bm.bmWidth, bm.bmHeight);
}
return TRUE;
case WM_ERASEBKGND:
{
RECT clientRect;
HBRUSH bkgBrush = CreateSolidBrush( RGB(255,0,0) );
GetClientRect(hwndDlg, &clientRect);
FillRect( (HDC)wParam, &clientRect, bkgBrush);
DeleteObject(bkgBrush);
}
return 1;
case WM_PAINT:
onPaint(hwndDlg, mBkg);
return 0;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
hInst=hInstance;
InitCommonControls();
int retVal = DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
GdiplusShutdown(gdiplusToken);
return retVal;
}
resource.h
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
#define DLG_MAIN 100
resource.rc
// Generated by ResEdit 1.6.2
// Copyright (C) 2006-2014
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
//
// Dialog resources
//
DLG_MAIN DIALOG 0, 0, 186, 95
STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_THICKFRAME | WS_SYSMENU
EXSTYLE WS_EX_WINDOWEDGE
CAPTION "Dialog"
FONT 8, "Ms Shell Dlg"
{
}
Wiki.png
Result