我正在尝试为 Direct2D 渲染目标创建平移功能。渲染目标上的绘图应在按住鼠标中键的同时跟随鼠标键。出现的问题是,您走得越远,绘图就越远离鼠标。
我尝试以多种不同的方式计算在渲染目标上渲染的翻译。首先,我使用
GET_X_LPARAM(lParam);
进行计算,这就是我遇到问题的地方。然后我在网上找到了另一种方法,使用GetCursorPos();
和ScreenToClient();
,但这导致翻译完全失败。我还尝试更改实际渲染目标的大小,希望这可能是原因,但这也没有帮助。我包含了头文件和源文件的简化版本。我无法比这更简化,因为我无法充分确定问题的根源。
标题:
#pragma once
// Windows Header Files:
#include <windows.h>
// C RunTime Header Files:
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
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
class DemoApp
{
public:
DemoApp();
~DemoApp();
// Register the window class and call methods for instantiating drawing resources
HRESULT Initialize();
// Process and dispatch messages
void RunMessageLoop();
private:
// Initialize device-independent resources.
HRESULT CreateDeviceIndependentResources();
// Initialize device-dependent resources.
HRESULT CreateDeviceResources();
// Release device-dependent resource.
void DiscardDeviceResources();
// Update transform.
void UpdateTransform();
// Draw content.
HRESULT OnRender();
// Resize the render target.
void OnResize(
UINT width,
UINT height
);
// The windows procedure.
static LRESULT CALLBACK WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
private:
HWND m_hwnd;
ID2D1Factory* m_pDirect2dFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
static POINT pan_offset;
};
源文件:
#pragma comment(lib, "d2d1.lib")
#include "Header.h"
#include <iostream>
#include <windowsx.h>
#include <cmath>
POINT DemoApp::pan_offset;
DemoApp::DemoApp() :
m_hwnd(NULL),
m_pDirect2dFactory(NULL),
m_pRenderTarget(NULL),
m_pLightSlateGrayBrush(NULL),
m_pCornflowerBlueBrush(NULL)
{}
DemoApp::~DemoApp()
{
SafeRelease(&m_pDirect2dFactory);
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pLightSlateGrayBrush);
SafeRelease(&m_pCornflowerBlueBrush);
}
void DemoApp::RunMessageLoop()
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
HRESULT DemoApp::Initialize()
{
HRESULT hr;
// Initialize device-independent resources, such
// as the Direct2D factory.
hr = CreateDeviceIndependentResources();
if (SUCCEEDED(hr))
{
// Register the window class.
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DemoApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.hCursor = LoadCursor(NULL, IDI_APPLICATION);
wcex.lpszClassName = L"D2DDemoApp";
RegisterClassEx(&wcex);
// In terms of using the correct DPI, to create a window at a specific size
// like this, the procedure is to first create the window hidden. Then we get
// the actual DPI from the HWND (which will be assigned by whichever monitor
// the window is created on). Then we use SetWindowPos to resize it to the
// correct DPI-scaled size, then we use ShowWindow to show it.
m_hwnd = CreateWindow(
L"D2DDemoApp",
L"Direct2D demo application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
NULL,
NULL,
HINST_THISCOMPONENT,
this);
if (m_hwnd)
{
// Because the SetWindowPos function takes its size in pixels, we
// obtain the window's DPI, and use it to scale the window size.
float dpi = GetDpiForWindow(m_hwnd);
SetWindowPos(
m_hwnd,
NULL,
NULL,
NULL,
static_cast<int>(ceil(640.f * dpi / 96.f)),
static_cast<int>(ceil(480.f * dpi / 96.f)),
SWP_NOMOVE);
ShowWindow(m_hwnd, SW_SHOWNORMAL);
UpdateWindow(m_hwnd);
}
}
return hr;
}
int WINAPI WinMain(
HINSTANCE /* hInstance */,
HINSTANCE /* hPrevInstance */,
LPSTR /* lpCmdLine */,
int /* nCmdShow */
)
{
// Use HeapSetInformation to specify that the process should
// terminate if the heap manager detects an error in any heap used
// by the process.
// The return value is ignored, because we want to continue running in the
// unlikely event that HeapSetInformation fails.
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (SUCCEEDED(CoInitialize(NULL)))
{
{
DemoApp app;
if (SUCCEEDED(app.Initialize()))
{
app.RunMessageLoop();
}
}
CoUninitialize();
}
return 0;
}
HRESULT DemoApp::CreateDeviceIndependentResources()
{
HRESULT hr = S_OK;
// Create a Direct2D factory.
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pDirect2dFactory);
return hr;
}
HRESULT DemoApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
if (!m_pRenderTarget)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right,
rc.bottom
);
// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
auto renderTargetSize = m_pRenderTarget->GetSize();
if (SUCCEEDED(hr))
{
// Create a gray brush.
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::LightSlateGray),
&m_pLightSlateGrayBrush
);
}
if (SUCCEEDED(hr))
{
// Create a blue brush.
hr = m_pRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
&m_pCornflowerBlueBrush
);
}
}
return hr;
}
void DemoApp::DiscardDeviceResources()
{
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pLightSlateGrayBrush);
SafeRelease(&m_pCornflowerBlueBrush);
}
HRESULT DemoApp::OnRender()
{
HRESULT hr = S_OK;
hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
m_pRenderTarget->BeginDraw();
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
// Draw a grid background.
int width = static_cast<int>(rtSize.width);
int height = static_cast<int>(rtSize.height);
// Draw two rectangles.
D2D1_RECT_F rectangle1 = D2D1::RectF(
rtSize.width / 2 - 50.0f,
rtSize.height / 2 - 50.0f,
rtSize.width / 2 + 50.0f,
rtSize.height / 2 + 50.0f
);
D2D1_RECT_F rectangle2 = D2D1::RectF(
rtSize.width / 2 - 100.0f,
rtSize.height / 2 - 100.0f,
rtSize.width / 2 + 100.0f,
rtSize.height / 2 + 100.0f
);
// Draw a filled rectangle.
m_pRenderTarget->FillRectangle(&rectangle1, m_pLightSlateGrayBrush);
// Draw the outline of a rectangle.
m_pRenderTarget->DrawRectangle(&rectangle2, m_pCornflowerBlueBrush);
hr = m_pRenderTarget->EndDraw();
}
if (hr == D2DERR_RECREATE_TARGET)
{
hr = S_OK;
DiscardDeviceResources();
}
return hr;
}
void DemoApp::OnResize(UINT width, UINT height)
{
if (m_pRenderTarget)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
m_pRenderTarget->Resize(D2D1::SizeU(width, height));
}
}
void DemoApp::UpdateTransform()
{
D2D1_MATRIX_3X2_F transform =
D2D1::Matrix3x2F::Translation(pan_offset.x, pan_offset.y);
m_pRenderTarget->SetTransform(transform);
}
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
DemoApp* pDemoApp = (DemoApp*)pcs->lpCreateParams;
::SetWindowLongPtrW(
hwnd,
GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(pDemoApp)
);
result = 1;
}
else
{
DemoApp* pDemoApp = reinterpret_cast<DemoApp*>(static_cast<LONG_PTR>(
::GetWindowLongPtrW(
hwnd,
GWLP_USERDATA
)));
bool wasHandled = false;
if (pDemoApp)
{
switch (message)
{
case WM_MOUSEMOVE:
{
static POINT lastMousePos;
if (wParam & MK_MBUTTON)
{
POINT currentMousePos{};
currentMousePos.x = GET_X_LPARAM(lParam);
currentMousePos.y = GET_Y_LPARAM(lParam);
pan_offset.x += static_cast<float>(currentMousePos.x - lastMousePos.x);
pan_offset.y += static_cast<float>(currentMousePos.y - lastMousePos.y);
pDemoApp->UpdateTransform();
lastMousePos = currentMousePos;
InvalidateRect(hwnd, NULL, FALSE); // Trigger redraw
}
else
{
lastMousePos.x = GET_X_LPARAM(lParam);
lastMousePos.y = GET_Y_LPARAM(lParam);
}
result = 0;
wasHandled = true;
break;
}
case WM_SIZE:
{
UINT width = LOWORD(lParam);
UINT height = HIWORD(lParam);
pDemoApp->OnResize(width, height);
}
result = 0;
wasHandled = true;
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect(hwnd, NULL, FALSE);
}
result = 0;
wasHandled = true;
break;
case WM_PAINT:
{
pDemoApp->OnRender();
ValidateRect(hwnd, NULL);
}
result = 0;
wasHandled = true;
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
result = 1;
wasHandled = true;
break;
}
}
if (!wasHandled)
{
result = DefWindowProc(hwnd, message, wParam, lParam);
}
}
return result;
}
你的平移逻辑似乎不正确。当您移动鼠标时,它会逐渐平移变换位置。相反,在
WM_LBUTTONDOWN
中,首先捕获变换位置和鼠标位置之间的增量/距离。然后,在 WM_MOUSEMOVE
中,将变换位置设置为鼠标位置减去该增量。
例如:
case WM_LBUTTONDOWN:
{
delta.x = pan_offset.x - GET_X_LPARAM(lParam);
delta.y = pan_offset.y - GET_Y_LPARAM(lParam);
result = 0;
wasHandled = true;
break;
}
case WM_MOUSEMOVE:
{
static POINT lastMousePos;
if (wParam & MK_MBUTTON)
{
POINT currentMousePos{};
currentMousePos.x = GET_X_LPARAM(lParam);
currentMousePos.y = GET_Y_LPARAM(lParam);
pan_offset.x = static_cast<float>(currentMousePos.x - delta.x);
pan_offset.y = static_cast<float>(currentMousePos.y - delta.y);
pDemoApp->UpdateTransform();
InvalidateRect(hwnd, NULL, FALSE); // Trigger redraw
}
result = 0;
wasHandled = true;
break;
}