基于窗口位置的 Direct X 裁剪位图并将其渲染回窗口
Posted
技术标签:
【中文标题】基于窗口位置的 Direct X 裁剪位图并将其渲染回窗口【英文标题】:Direct X Cropping Bitmap Based on Window Position and Rendering it back to window 【发布时间】:2021-01-14 06:42:11 【问题描述】:我正在做一个使用DirectX
和Direct Composition
的项目。我正在尝试创建类似于 windows 丙烯酸模糊的类似效果。使用直接合成,我能够将模糊效果、饱和度和混合应用于给定的输入。 windows 丙烯酸模糊使用窗口后面的内容作为模糊的输入,但在 win32 中是不可能的,所以我决定使用桌面背景作为模糊功能的输入。但问题是我能够根据窗口位置裁剪背景,但是如果我将窗口移动到新位置,位图会根据窗口的新RECT
进行裁剪,但在移动到新位置时会闪烁。
这是预览:
Problem : Flickering while moving the window Solution: This is what I need to get Concept : This is what I am trying to Achieve这是实际窗口的截图:
这里的红色框包含使用窗口 RECT 饱和和裁剪的位图 (看色差)。
下面是重现问题的简单代码:
#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
注意:示例视频实际上是经过编辑的,不是我得到的输出。
【问题讨论】:
您可以使用丙烯酸笔刷(由主机背景笔刷组成,配方在这里github.com/microsoft/microsoft-ui-xaml/blob/master/dev/…),但只能使用 Windows.UI.Composition(某种程度上是 Direct Composition V3,只是没有命名为那),不是与 dcomp.h。 Win32 桌面应用程序无需打包即可访问 Windows.UI.Composition。展示如何在 Win32 中使用 WUC 的开创性示例在这里 gist.github.com/kennykerr/62923cdacaba28fedc4f3dab6e0c12ec 谢谢你让我看看 @Simon Mourier 我已经在 WFP 应用程序中使用过这个库,但是这个库非常滞后,我什至无法将窗口从一个位置移动到另一个位置。较新的 Windows 版本已修复滞后问题,但在调整大小时闪烁太多。调整大小时,丙烯酸模糊正在消失。这是我使用的示例:github.com/Microsoft/Windows.UI.Composition-Win32-Samples/tree/… 尝试将亚克力模糊扩展到整个窗口,如果您调整窗口大小,它会超出窗口。所以这就是我尝试另一种解决方法的原因 如果我能够从我在问题中发布的示例中删除此闪烁,接下来我要做的就是使用从桌面复制获得的帧作为模糊的输入,这将帮助我创建相同的丙烯酸模糊效果。通过设置 WDA_EXCLUDEFROMCAPTURE 我可以在桌面复制中隐藏我的窗口。所以我得到了我窗户后面的确切信息。 【参考方案1】:我终于想通了。现在我可以移动窗口,而不会闪烁到我渲染为背景的图像上。问题出在我的代码中的两个地方。
GetWindowRect()
:
在WM_MOVING
中,我使用GetWindowRect()
获取窗口矩形,这会导致获取窗口矩形的延迟并导致闪烁问题。我找到的解决方案很简单,我通过类型转换将lParm
转换为RECT
并将其用作更新位置的值解决了这个问题。我用来解决问题的代码如下:
RECT *hostRect = reinterpret_cast<RECT*>(lParam)
MagSetWindowSource()
:这是一件很有趣的事情。我不知道放大镜在这有什么用。在我调用MagSetWindowSource()
函数之前,代码不会消除闪烁,我不知道为什么需要它,仅此一项就在我的程序中消耗了更多内存。如果我删除了这行代码,它会再次开始闪烁。函数的输入无关紧要,您只需调用函数即可。
更新:
终于弄明白了 MagSetWindowSource() 是做什么的,其实 调用一个名为 DwmFlush() 的 DWM 函数,该函数发出一个刷新调用 阻止调用者直到下一个出现。这解决了我的问题。 现在它是无闪烁且平滑的。
这是完全更新的代码:
#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);
【讨论】:
以上是关于基于窗口位置的 Direct X 裁剪位图并将其渲染回窗口的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Direct2D 从 BITMAPINFOHEADER 和 BYTE 渲染位图