#ifndef _CD2DC_H_
#define _CD2DC_H_
#define _CRT_SECURE_NO_WARNINGS
#define NO_WARN_MBCS_MFC_DEPRECATION
#include <SDKDDKVer.h>
#include <afxwin.h>
#include <d2d1.h>
#include <dwrite.h>
#include <wincodec.h>
#include <unordered_map>
#define RGBA(r,g,b,a) (COLORREF)(((BYTE)(r) |((WORD)((BYTE)(g)) << 8)) |(((DWORD)((BYTE)(b)) << 16)) |(((DWORD)((BYTE)(a)) << 24)))
// 提取ARGB颜色中的alpha通道(高位字节)
#define GetAValue(argb) ((BYTE)((argb) >> 24))
namespace MFCEx
{
/**
* @class CD2DC
* @brief 封装Direct2D渲染功能的类,用于在MFC应用中实现硬件加速的2D图形渲染
*/
class CD2DC final//禁止继承
{
enum ResourceType
{
BRUSH,
BITMAP,
TEXT_FORMAT,
GEOMETRY
};
using ResourceMap = std::unordered_map<IUnknown*, ResourceType>;
public:
CD2DC();
~CD2DC();
/**
* @brief 初始化Direct2D资源
* @param _pWnd [in] 关联的MFC窗口指针
* @param _iWidth [in] 渲染区域的宽度(像素)
* @param _iHeight [in] 渲染区域的高度(像素)
* @return BOOL 初始化成功返回TRUE,失败返回FALSE
*/
BOOL Initialize(CWnd* _pWnd, const size_t _iWidth, const size_t _iHeight);
/**
* @brief 调整渲染区域大小
* @param _iWidth [in] 新的渲染区域宽度(像素)
* @param _iHeight [in] 新的渲染区域高度(像素)
* @return BOOL 调整成功返回TRUE,失败返回FALSE
*/
BOOL Resize(const size_t _iWidth, const size_t _iHeight);
/**
* @brief 开始绘制操作(必须在绘制前调用)
*/
void BeginDraw();
/**
* @brief 结束绘制操作(必须在绘制后调用)
*/
void EndDraw();
FLOAT GetDpiX() const;
FLOAT GetDpiY() const;
size_t GetLogicalWidth() const;
size_t GetLogicalHeight() const;
inline void Release(IUnknown* pResource);
ID2D1Bitmap* CreateBitmap(UINT _nWidth, UINT _nHeight, const void* _srcData = nullptr, UINT _nRGBA = 4);
ID2D1SolidColorBrush* CreateSolidColorBrush(COLORREF _Color);
void FillRectangle(CRect _rcRange, ID2D1Brush* _pBrush);
private:
/**
* @brief 创建Direct2D渲染目标
* @return BOOL 创建成功返回TRUE,失败返回FALSE
*/
BOOL CreateRenderTarget();
void Clear();
void RecreateResources();
void UpdateWindowDpi();
// 需要添加资源注册接口
void RegisterResource(IUnknown* pResource, ResourceType Type);
// 资源注销接口
void UnregisterResource(IUnknown* pResource);
private:
CWnd* m_pWnd = nullptr; //< 关联的MFC窗口指针
size_t m_iLogicalWidth = 0; // 逻辑尺寸
size_t m_iLogicalHeight = 0;
FLOAT m_fDpiX = 96.0f; // DPI 值
FLOAT m_fDpiY = 96.0f;
ID2D1Factory* m_pFactory = nullptr; ///< Direct2D工厂接口
ID2D1HwndRenderTarget* m_pTarget = nullptr; //< 窗口渲染目标接口
IDWriteFactory* m_pWriteFactory = nullptr; //< DirectWrite文本工厂接口
bool m_bInitialized = false; ///< 初始化状态标志
ResourceMap m_Map;//资源管理集
};
}
#endif // !_CD2DC_H_
#include "CD2DC.h"
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
//#include <ShellScalingApi.h> // 包含GetDpiForMonitor的头文件
//#pragma comment(lib, "Shcore.lib") // 链接Shcore.lib库
// 3. 添加资源安全释放宏
#define SAFE_RELEASE(p) { if(p) { p->Release(); p = nullptr; } }
/**
* @brief 构造函数(初始化成员变量)
*/
MFCEx::CD2DC::CD2DC()
{
}
/**
* @brief 析构函数(释放所有Direct2D资源)
*/
MFCEx::CD2DC::~CD2DC()
{
Clear();
}
void MFCEx::CD2DC::Clear()
{
for (ResourceMap::iterator it = m_Map.begin(); it != m_Map.end(); ++it)
{
it->first->Release();
}
m_Map.clear();
SAFE_RELEASE(m_pTarget);
SAFE_RELEASE(m_pFactory);
SAFE_RELEASE(m_pWriteFactory);
}
/**
* @brief 初始化Direct2D资源
* @param _pWnd [in] 关联的MFC窗口指针
* @param _iWidth [in] 渲染区域的宽度(像素)
* @param _iHeight [in] 渲染区域的高度(像素)
* @return BOOL 初始化成功返回TRUE,失败返回FALSE
*/
BOOL MFCEx::CD2DC::Initialize(CWnd* _pWnd, const size_t _iWidth, const size_t _iHeight)
{
// 验证窗口句柄有效性
if (!_pWnd || !_pWnd->GetSafeHwnd()) return FALSE;
// 创建Direct2D工厂
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pFactory);
if (FAILED(hr)) return FALSE;
// 创建DirectWrite工厂
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&m_pWriteFactory)
);
if (FAILED(hr)) return FALSE;
// 设置成员变量
m_pWnd = _pWnd;
// DPI 感知处理
UpdateWindowDpi(); // 替换原有的GetDesktopDpi调用
m_iLogicalWidth = _iWidth; // 存储逻辑尺寸
m_iLogicalHeight = _iHeight;
// 创建渲染目标
if (!CreateRenderTarget())
{
SAFE_RELEASE(m_pWriteFactory);
SAFE_RELEASE(m_pFactory);
return FALSE;
}
m_bInitialized = TRUE;
return TRUE;
}
/**
* @brief 调整渲染区域大小
* @param _iWidth [in] 新的渲染区域宽度(像素)
* @param _iHeight [in] 新的渲染区域高度(像素)
* @return BOOL 调整成功返回TRUE,失败返回FALSE
*/
BOOL MFCEx::CD2DC::Resize(const size_t _iWidth, const size_t _iHeight)
{
if (!m_pTarget) return FALSE;
// 更新 DPI 值
UpdateWindowDpi(); // 替换原有的GetDesktopDpi调用
m_iLogicalWidth = _iWidth;
m_iLogicalHeight = _iHeight;
size_t physWidth = static_cast<size_t>(_iWidth * m_fDpiX / 96.0f);
size_t physHeight = static_cast<size_t>(_iHeight * m_fDpiY / 96.0f);
D2D1_SIZE_U size = D2D1::SizeU(physWidth, physHeight);
return SUCCEEDED(m_pTarget->Resize(size));
}
/**
* @brief 开始绘制操作(必须在绘制前调用)
*/
void MFCEx::CD2DC::BeginDraw()
{
if (m_pTarget) m_pTarget->BeginDraw();
}
/**
* @brief 结束绘制操作(必须在绘制后调用)
*/
void MFCEx::CD2DC::EndDraw()
{
if (!m_pTarget) return;
if (D2DERR_RECREATE_TARGET == m_pTarget->EndDraw())
{
SAFE_RELEASE(m_pTarget);
if (CreateRenderTarget())
{
RecreateResources(); // 触发资源重建
}
}
}
/**
* @brief 创建Direct2D渲染目标
* @return BOOL 创建成功返回TRUE,失败返回FALSE
*/
BOOL MFCEx::CD2DC::CreateRenderTarget()
{
// 验证工厂和窗口有效性
if (!m_pFactory || !m_pWnd) return FALSE;
// 创建窗口渲染目标
D2D1_PIXEL_FORMAT pixelFormat = D2D1::PixelFormat(
DXGI_FORMAT_B8G8R8A8_UNORM,
D2D1_ALPHA_MODE_IGNORE
);
D2D1_RENDER_TARGET_PROPERTIES rtProps = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
pixelFormat
);
size_t physWidth = static_cast<size_t>(m_iLogicalWidth * m_fDpiX / 96.0f);
size_t physHeight = static_cast<size_t>(m_iLogicalHeight * m_fDpiY / 96.0f);
HRESULT hr = m_pFactory->CreateHwndRenderTarget(
rtProps, // 使用自定义属性
D2D1::HwndRenderTargetProperties(m_pWnd->GetSafeHwnd(), D2D1::SizeU(physWidth, physHeight)),
&m_pTarget
);
return SUCCEEDED(hr);
}
FLOAT MFCEx::CD2DC::GetDpiX() const
{
return m_fDpiX;
}
FLOAT MFCEx::CD2DC::GetDpiY() const
{
return m_fDpiY;
}
size_t MFCEx::CD2DC::GetLogicalWidth() const
{
return m_iLogicalWidth;
}
size_t MFCEx::CD2DC::GetLogicalHeight() const
{
return m_iLogicalHeight;
}
void MFCEx::CD2DC::RecreateResources()
{
for (ResourceMap::iterator it = m_Map.begin(); it != m_Map.end(); ++it)
{
}
}
void MFCEx::CD2DC::UpdateWindowDpi()
{
if (!m_pWnd) return;
// 获取窗口DPI(Windows 10以上使用GetDpiForWindow)
m_fDpiX = static_cast<FLOAT>(GetDpiForWindow(m_pWnd->GetSafeHwnd()));
m_fDpiY = static_cast<FLOAT>(GetDpiForWindow(m_pWnd->GetSafeHwnd()));
// 如果获取失败,则回退到GetDeviceCaps方式
if (m_fDpiX == 0 || m_fDpiY == 0)
{
const HDC hdc = ::GetDC(m_pWnd->GetSafeHwnd());
m_fDpiX = static_cast<FLOAT>(GetDeviceCaps(hdc, LOGPIXELSX));
m_fDpiY = static_cast<FLOAT>(GetDeviceCaps(hdc, LOGPIXELSY));
::ReleaseDC(m_pWnd->GetSafeHwnd(), hdc);
}
}
// 需要添加资源注册接口
void MFCEx::CD2DC::RegisterResource(IUnknown* pResource, ResourceType Type)
{
if (!pResource) return;
// 检查资源是否已注册(避免重复注册)
auto it = m_Map.find(pResource);
if (it != m_Map.end())
{
// 已存在则更新类型(实际开发中应记录日志)
//it->second = Type;
return;
}
// 添加新资源引用计数
pResource->AddRef();
m_Map.emplace(pResource, Type);
}
// 资源注销接口
void MFCEx::CD2DC::UnregisterResource(IUnknown* pResource)
{
if (!pResource) return;
auto it = m_Map.find(pResource);
if (it != m_Map.end())
{
// 释放资源并移除映射
it->first->Release();
m_Map.erase(it);
}
}
inline void MFCEx::CD2DC::Release(IUnknown* pResource)
{
UnregisterResource(pResource);
}
/**
* @brief 创建Direct2D位图(支持外部数据源初始化)
* @param _nWidth [in] 位图宽度(像素)
* @param _nHeight [in] 位图高度(像素)
* @param _srcData [in] 外部数据源指针(可为空)
* @param _nRGBA [in] 像素格式标识(3=RGB24, 4=RGBA32)
* @return ID2D1Bitmap* 成功返回位图指针,失败返回NULL
*/
ID2D1Bitmap* MFCEx::CD2DC::CreateBitmap(
UINT _nWidth,
UINT _nHeight,
const void* _srcData,
UINT _nRGBA
) {
// ===== 1. 参数验证 =====
if (!m_pTarget) {
TRACE("CreateBitmap failed: Render target not initialized.\n");
return nullptr;
}
if (_nWidth == 0 || _nHeight == 0) {
TRACE("CreateBitmap failed: Invalid dimensions (%dx%d).\n", _nWidth, _nHeight);
return nullptr;
}
if (_srcData && IsBadReadPtr(_srcData, 1)) {
TRACE("CreateBitmap failed: Invalid source data pointer.\n");
return nullptr;
}
// ===== 2. 确定像素格式 =====
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_UNKNOWN;
switch (_nRGBA)
{
case 3: // RGB24 (无Alpha)
format = DXGI_FORMAT_B8G8R8X8_UNORM;
alphaMode = D2D1_ALPHA_MODE_IGNORE;
break;
case 4: // RGBA32 (带Alpha)
format = DXGI_FORMAT_R8G8B8A8_UNORM;
alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
break;
default:
TRACE("CreateBitmap failed: Invalid _nRGBA=%d. Must be 3 or 4.\n", _nRGBA);
return nullptr;
}
// ===== 3. 计算对齐步幅 =====
const UINT minStride = _nWidth * _nRGBA;
const UINT stride = (minStride + 3) & ~3; // 4字节对齐
// 验证外部数据大小(若有)
if (_srcData)
{
const size_t requiredSize = static_cast<size_t>(stride) * _nHeight;
// 使用SafeMemCheck验证内存可读性(Windows API)
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(_srcData, &mbi, sizeof(mbi)))
{
if (mbi.RegionSize < requiredSize)
{
TRACE("CreateBitmap failed: Source buffer too small (%zu < %zu).\n",mbi.RegionSize, requiredSize);
return nullptr;
}
}
}
// ===== 4. 创建位图属性 =====
D2D1_BITMAP_PROPERTIES props =
{
{ format, alphaMode },
m_fDpiX,
m_fDpiY
};
// ===== 5. 创建位图 =====
ID2D1Bitmap* pBitmap = nullptr;
HRESULT hr = S_OK;
if (_srcData)
{
// 带数据初始化模式
D2D1_SIZE_U size = D2D1::SizeU(_nWidth, _nHeight);
hr = m_pTarget->CreateBitmap(
size,
_srcData, // 外部数据源
stride, // 对齐后的步幅
props,
&pBitmap
);
}
else
{
// 空位图模式
hr = m_pTarget->CreateBitmap(
D2D1::SizeU(_nWidth, _nHeight),
nullptr, // 无初始数据
0, // 步幅为0(自动计算)
props,
&pBitmap
);
}
// ===== 6. 错误处理 =====
if (FAILED(hr))
{
TRACE("CreateBitmap failed (HRESULT=0x%X).\n", hr);
SAFE_RELEASE(pBitmap);
return nullptr;
}
// ===== 7. 资源管理 =====
RegisterResource(pBitmap, BITMAP);
return pBitmap;
}
/**
* @brief 创建纯色画刷
* @param _Color [in] RGBA格式的颜色值(使用RGBA宏构造)
* @return ID2D1SolidColorBrush* 成功返回画刷指针,失败返回NULL
*/
ID2D1SolidColorBrush* MFCEx::CD2DC::CreateSolidColorBrush(COLORREF _Color)
{
// 1. 验证渲染目标有效性
if (!m_pTarget)
{
TRACE("CreateSolidColorBrush failed: Render target not initialized.\n");
return nullptr;
}
// 2. 解析RGBA颜色分量(使用RGBA宏构造的颜色格式:0xAARRGGBB)
const FLOAT fAlpha = GetAValue(_Color) / 255.0f;
const FLOAT fRed = GetRValue(_Color) / 255.0f;
const FLOAT fGreen = GetGValue(_Color) / 255.0f;
const FLOAT fBlue = GetBValue(_Color) / 255.0f;
// 3. 创建D2D颜色结构
const D2D1_COLOR_F color = D2D1::ColorF(fRed, fGreen, fBlue, fAlpha);
// 4. 创建画刷
ID2D1SolidColorBrush* pBrush = nullptr;
HRESULT hr = m_pTarget->CreateSolidColorBrush(color, &pBrush);
// 5. 错误处理
if (FAILED(hr))
{
TRACE("CreateSolidColorBrush failed (HRESULT=0x%X)\n", hr);
SAFE_RELEASE(pBrush);
return nullptr;
}
// 6. 注册资源并返回
RegisterResource(pBrush, BRUSH);
return pBrush;
}
/**
* @brief 使用指定画刷填充矩形区域
* @param _rcRange [in] 矩形区域(逻辑坐标,单位:设备无关像素 DIPs)
* @param _pBrush [in] 填充画刷指针
*/
void MFCEx::CD2DC::FillRectangle(CRect _rcRange, ID2D1Brush* _pBrush)
{
// ===== 1. 前置条件检查 =====
if (!m_pTarget)
{
TRACE("FillRectangle failed: Render target not initialized.\n");
return;
}
if (!_pBrush)
{
TRACE("FillRectangle failed: Invalid brush pointer.\n");
return;
}
// 检查画刷是否由本类创建(资源管理)
auto it = m_Map.find(_pBrush);
if (it == m_Map.end() || it->second != BRUSH)
{
TRACE("FillRectangle warning: Using unmanaged brush resource.\n");
}
// ===== 2. 坐标转换 =====
// 将逻辑坐标(DIPs)转换为物理像素坐标
const FLOAT dpiScaleX = m_fDpiX / 96.0f;
const FLOAT dpiScaleY = m_fDpiY / 96.0f;
D2D1_RECT_F rect = D2D1::RectF(
static_cast<FLOAT>(_rcRange.left) * dpiScaleX,
static_cast<FLOAT>(_rcRange.top) * dpiScaleY,
static_cast<FLOAT>(_rcRange.right) * dpiScaleX,
static_cast<FLOAT>(_rcRange.bottom) * dpiScaleY
);
// ===== 3. 执行绘制 =====
m_pTarget->FillRectangle(rect, _pBrush);
}
// ===== 4. 创建位图属性 =====
D2D1_BITMAP_PROPERTIES props =
{
{ format, alphaMode },
m_fDpiX,
m_fDpiY
};检查基本功能的完整性
最新发布