|
本模块从Windows图形体系结构的高级概述开始。 然后,我们看看 Direct2D,这是一个功能强大的图形 API,Windows 7 中引入。
各位同学们有需要提高编程技术水平、编程思维能力和动手开发实战能力。比如:考国家二级C语言、计算机考研C和数据结构、Windows C/C++开发工程师、计算机竞赛、蓝桥杯竞赛等等。现已推出最新课程,大家可以根据自己情况,选择适合自己的课程哦!点击下方的链接就可以学习啦,加油!
1、【C语言入门到精通】-学习视频教程
2、【C语言经典算法编程100道实战题】-学习视频教程
3、【数据结构算法(C语言版)】-学习视频教程
4、【C++语言入门到精通】-学习视频教程
5、【C/C++入门到企业项目实战】-学习视频教程
将来的你一定会感谢现在拼命努力的自己,祝同学们学习进步、前程似锦!!!
一、Windows图形体系结构概述
Windows为图形提供多个 C++/COM API。 下图显示了这些 API。
- 图形设备接口 (GDI) 是用于Windows的原始图形接口。 GDI 首先为 16 位Windows编写,然后更新 32 位和 64 位Windows。
- GDI+作为 GDI 的继任者在 Windows XP 中引入。 GDI+库通过一组包装平面 C 函数的 C++ 类进行访问。 .NET Framework还提供 System.Drawing 命名空间中GDI+的托管版本。
- Direct3D 支持三维图形。
- Direct2D 是用于二维图形的新式 API,是 GDI 和 GDI+ 的后续版本。
- DirectWrite是文本布局和光栅化引擎。 可以使用 GDI 或 Direct2D 绘制光栅化文本。
- DirectX 图形基础结构 (DXGI) 执行低级别任务,例如呈现输出帧。 大多数应用程序不直接使用 DXGI。 相反,它充当图形驱动程序和 Direct3D 之间的中间层。
Direct2D 和 DirectWrite 在 Windows 7 中引入。 它们还可以通过平台更新Windows Vista 和 Windows Server 2008。 有关详细信息,请参阅适用于 Windows Vista 的平台更新。
Direct2D 是本模块的重点。 尽管 GDI 和 GDI+在 Windows 中仍受支持,但建议对新程序使用 Direct2D 和 DirectWrite。 在某些情况下,混合技术可能更实用。 在这些情况下,Direct2D 和 DirectWrite旨在与 GDI 互操作。
后续部分介绍 Direct2D 的一些优势。
1、硬件加速
术语 硬件加速 是指图形处理单元 (GPU) 而不是 CPU 执行的图形计算。 新式 GPU 针对呈现图形中使用的计算类型进行高度优化。 通常,从 CPU 移动到 GPU 的此类工作越多,效果就越好。
虽然 GDI 支持某些操作的硬件加速,但许多 GDI 操作都绑定到 CPU。 Direct2D 分层在 Direct3D 之上,充分利用 GPU 提供的硬件加速。 如果 GPU 不支持 Direct2D 所需的功能,则 Direct2D 会回退到软件呈现。 总的来说,Direct2D 在大多数情况下优于 GDI 和GDI+。
2、透明度和反别名
Direct2D 支持完全硬件加速的 alpha 混合 (透明度) 。
GDI 对 alpha 混合的支持有限。 大多数 GDI 函数都不支持 alpha 混合,尽管 GDI 在位运算期间支持 alpha 混合。 GDI+支持透明度,但 alpha 混合由 CPU 执行,因此它不会受益于硬件加速。
硬件加速 alpha 混合还支持抗锯齿。 别名 是由对连续函数进行采样引起的项目。 例如,当曲线转换为像素时,别名可能会导致出现锯齿。[3] 任何减少别名引起的项目的技术都被视为一种抗锯齿形式。 在图形中,反别名是通过将边缘与背景混合完成的。 例如,下面是由 GDI 绘制的圆圈,以及 Direct2D 绘制的同一个圆。
下图显示了每个圆的详细信息。
由 GDI (左) 绘制的圆由近似曲线的黑色像素组成。 Direct2D (右) 绘制的圆使用混合来创建更平滑的曲线。
GDI 在绘制几何图形时不支持反锯齿 (线条和曲线) 。 GDI 可以使用 ClearType 绘制反别名文本;但是,GDI 文本也具有别名。 对于文本来说,别名特别明显,因为交错的行会中断字体设计,使文本更易于阅读。 尽管GDI+支持反锯齿,但它由 CPU 应用,因此性能不如 Direct2D。
3、矢量图形
Direct2D 支持 矢量图形。 在矢量图形中,数学公式用于表示线条和曲线。 这些公式不依赖于屏幕分辨率,因此可以将其缩放为任意尺寸。 当必须缩放图像以支持不同的监视器大小或屏幕分辨率时,矢量图形特别有用。
二、桌面窗口管理器
在 Windows Vista 之前,Windows程序将直接绘制到屏幕。 换句话说,程序将直接写入视频卡显示的内存缓冲区。 如果窗口无法正确重新绘制自身,此方法可能会导致视觉项目。 例如,如果用户将一个窗口拖到另一个窗口,并且下方的窗口不够快地重新绘制,则最顶部的窗口可能会留下一条线索:
由于这两个窗口都绘制到同一内存区域,因此导致该线索。 拖动最顶部的窗口时,必须重新绘制其下方的窗口。 如果重新绘制速度太慢,则会导致上一个图像中显示的项目。
通过引入桌面窗口管理器 (DWM) ,Windows Vista 从根本上改变了窗口绘制方式。 启用 DWM 后,窗口不再直接绘制到显示缓冲区。 相反,每个窗口都会绘制到屏幕外内存缓冲区,也称为 屏幕外图面。 然后,DWM 将这些图面组合到屏幕。
DWM 在旧图形体系结构上提供了多种优势。
- 重新绘制消息更少。 当窗口被另一个窗口阻塞时,阻塞的窗口不需要重新绘制自身。
- 减少了项目。 以前,拖动窗口可能会创建视觉项目,如前所述。
- 视觉效果。 由于 DWM 负责组合屏幕,因此它可以呈现窗口的半透明和模糊区域。
- 高 DPI 的自动缩放。 尽管缩放不是处理高 DPI 的理想方法,但对于未针对高 DPI 设置设计的较旧应用程序,这是一种可行的回退。 (稍后将在 DPI 和 Device-Independent Pixel.) 部分中返回本主题
- 备用视图。 DWM 可以通过各种有趣的方式使用屏幕外表面。 例如,DWM 是Windows翻转 3D、缩略图和动画转换背后的技术。
但是,请注意,不保证启用 DWM。 图形卡可能不支持 DWM 系统要求,用户可以通过 “系统属性 ”控制面板禁用 DWM。 这意味着程序不应依赖于 DWM 的重新绘制行为。 使用已禁用 DWM 测试程序,以确保它正确重新绘制。
三、保留模式与即时模式
图形 API 可以分为 保留模式 API 和 即时模式 API。 Direct2D 是即时模式 API。 Windows Presentation Foundation (WPF) 是保留模式 API 的示例。
保留模式 API 是声明性的。 应用程序从图形基元(如形状和线条)构造场景。 图形库将场景的模型存储在内存中。 若要绘制框架,图形库会将场景转换为一组绘图命令。 在帧之间,图形库将场景保留在内存中。 若要更改呈现的内容,应用程序会发出命令来更新场景,例如添加或删除形状。 然后,该库负责重绘场景。
即时模式 API 是过程性的。 每次绘制新框架时,应用程序都会直接发出绘图命令。 图形库不会在帧之间存储场景模型。 相反,应用程序会跟踪场景。
保留模式 API 可以使用更简单,因为 API 会为你执行更多工作,例如初始化、状态维护和清理。 另一方面,它们通常不太灵活,因为 API 会施加自己的场景模型。 此外,保留模式 API 可以具有更高的内存要求,因为它需要提供常规用途场景模型。 使用即时模式 API,可以实现有针对性的优化。
四、你的第一个 Direct2D 计划
让我们创建第一个 Direct2D 程序。 程序不做任何花哨的事情 — 它只是绘制一个圆圈来填充窗口的工作区。 但此计划引入了许多基本的 Direct2D 概念。
下面是 Circle 程序的代码列表。 程序重新使用 BaseWindow 在 “管理应用程序状态”主题中定义的类。 后面的主题将详细检查代码。
#include <windows.h>
#include <d2d1.h>
#pragma comment(lib, &#34;d2d1&#34;)
#include &#34;basewin.h&#34;
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
class MainWindow : public BaseWindow<MainWindow>
{
ID2D1Factory *pFactory;
ID2D1HwndRenderTarget *pRenderTarget;
ID2D1SolidColorBrush *pBrush;
D2D1_ELLIPSE ellipse;
void CalculateLayout();
HRESULT CreateGraphicsResources();
void DiscardGraphicsResources();
void OnPaint();
void Resize();
public:
MainWindow() : pFactory(NULL), pRenderTarget(NULL), pBrush(NULL)
{
}
PCWSTR ClassName() const { return L&#34;Circle Window Class&#34;; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
// Recalculate drawing layout when the size of the window changes.
void MainWindow::CalculateLayout()
{
if (pRenderTarget != NULL)
{
D2D1_SIZE_F size = pRenderTarget->GetSize();
const float x = size.width / 2;
const float y = size.height / 2;
const float radius = min(x, y);
ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius);
}
}
HRESULT MainWindow::CreateGraphicsResources()
{
HRESULT hr = S_OK;
if (pRenderTarget == NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
hr = pFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&pRenderTarget);
if (SUCCEEDED(hr))
{
const D2D1_COLOR_F color = D2D1::ColorF(1.0f, 1.0f, 0);
hr = pRenderTarget->CreateSolidColorBrush(color, &pBrush);
if (SUCCEEDED(hr))
{
CalculateLayout();
}
}
}
return hr;
}
void MainWindow::DiscardGraphicsResources()
{
SafeRelease(&pRenderTarget);
SafeRelease(&pBrush);
}
void MainWindow::OnPaint()
{
HRESULT hr = CreateGraphicsResources();
if (SUCCEEDED(hr))
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, &ps);
pRenderTarget->BeginDraw();
pRenderTarget->Clear( D2D1::ColorF(D2D1::ColorF::SkyBlue) );
pRenderTarget->FillEllipse(ellipse, pBrush);
hr = pRenderTarget->EndDraw();
if (FAILED(hr) || hr == D2DERR_RECREATE_TARGET)
{
DiscardGraphicsResources();
}
EndPaint(m_hwnd, &ps);
}
}
void MainWindow::Resize()
{
if (pRenderTarget != NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
pRenderTarget->Resize(size);
CalculateLayout();
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow)
{
MainWindow win;
if (!win.Create(L&#34;Circle&#34;, WS_OVERLAPPEDWINDOW))
{
return 0;
}
ShowWindow(win.Window(), nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
if (FAILED(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory)))
{
return -1; // Fail CreateWindowEx.
}
return 0;
case WM_DESTROY:
DiscardGraphicsResources();
SafeRelease(&pFactory);
PostQuitMessage(0);
return 0;
case WM_PAINT:
OnPaint();
return 0;
case WM_SIZE:
Resize();
return 0;
}
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
可以从 Direct2D 圆形示例下载完整的Visual Studio项目。
D2D1 命名空间
D2D1 命名空间包含帮助程序函数和类。 这些不是 Direct2D API 的一部分,你可以在不使用 Direct2D 的情况下对 Direct2D 进行编程,但它们有助于简化代码。 D2D1 命名空间包含:
- 用于构造颜色值的 ColorF 类。
- 用于构造转换矩阵的 Matrix3x2F 。
- 一组用于初始化 Direct2D 结构的函数。
你将在此模块中看到 D2D1 命名空间的示例。
五、呈现目标、设备和资源
呈现目标只是程序绘制的位置。 通常,呈现目标是窗口 (特别是窗口) 的工作区。 它也可以是内存中未显示的位图。 呈现目标由 ID2D1RenderTarget 接口表示。
设备是一种抽象,表示实际绘制像素的任何内容。 硬件设备使用 GPU 以提高性能,而软件设备则使用 CPU。 应用程序不会创建设备。 相反,当应用程序创建呈现目标时,会隐式创建设备。 每个呈现目标都与特定设备(硬件或软件)相关联。
资源是程序用于绘图的对象。 下面是 Direct2D 中定义的一些资源示例:
- 画笔。 控制绘制线条和区域的方式。 画笔类型包括纯色画笔和渐变画笔。
- 笔划样式。 控制线条的外观,例如虚线或实线。
- 几何图形。 表示线条和曲线的集合。
- Mesh。 由三角形构成的形状。 Mesh GPU 可以直接使用数据,这与在呈现之前必须转换的几何图形数据不同。
呈现目标也被视为一种资源类型。
某些资源受益于硬件加速。 此类型的资源始终与特定设备(硬件 (GPU) 或软件 (CPU) 相关联)。 这种类型的资源称为 依赖于设备的资源。 画笔和网格是依赖于设备的资源的示例。 如果设备不可用,则必须为新设备重新创建资源。
无论使用何种设备,其他资源都保留在 CPU 内存中。 这些资源 与设备无关,因为它们不与特定设备相关联。 当设备发生更改时,无需重新创建独立于设备的资源。 笔划样式和几何图形是独立于设备的资源。
每个资源的 MSDN 文档都指示资源是依赖于设备还是独立于设备。 每个资源类型都由派生自 ID2D1Resource 的接口表示。 例如,画笔由 ID2D1Brush 接口表示。
1、Direct2D 工厂对象
使用 Direct2D 的第一步是创建 Direct2D 工厂对象的实例。 在计算机编程中, 工厂 是创建其他对象的对象。 Direct2D 工厂创建以下类型的对象:
- 呈现目标。
- 与设备无关的资源,例如笔划样式和几何图形。
依赖于设备的资源(如画笔和位图)由呈现目标对象创建。
若要创建 Direct2D 工厂对象,请调用 D2D1CreateFactory 函数。
ID2D1Factory *pFactory = NULL;
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory);
第一个参数是指定创建选项的标志。 D2D1_FACTORY_TYPE_SINGLE_THREADED标志表示不会从多个线程调用 Direct2D。 若要支持来自多个线程的调用,请指定 D2D1_FACTORY_TYPE_MULTI_THREADED。 如果程序使用单个线程调用 Direct2D,则单线程选项效率更高。
D2D1CreateFactory 函数的第二个参数接收指向 ID2D1Factory 接口的指针。
应在第一 条WM_PAINT 消息之前创建 Direct2D 工厂对象。 WM_CREATE消息处理程序是创建工厂的好位置:
case WM_CREATE:
if (FAILED(D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory)))
{
return -1; // Fail CreateWindowEx.
}
return 0;
2、创建 Direct2D 资源
Circle 程序使用以下依赖于设备的资源:
- 与应用程序窗口关联的呈现目标。
- 用于绘制圆圈的纯色画笔。
其中每个资源都由 COM 接口表示:
- ID2D1HwndRenderTarget 接口表示呈现目标。
- ID2D1SolidColorBrush 接口表示画笔。
Circle 程序将指向这些接口的 MainWindow 指针存储为类的成员变量:
ID2D1HwndRenderTarget *pRenderTarget;
ID2D1SolidColorBrush *pBrush;
以下代码创建这两个资源。
HRESULT MainWindow::CreateGraphicsResources()
{
HRESULT hr = S_OK;
if (pRenderTarget == NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
hr = pFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&pRenderTarget);
if (SUCCEEDED(hr))
{
const D2D1_COLOR_F color = D2D1::ColorF(1.0f, 1.0f, 0);
hr = pRenderTarget->CreateSolidColorBrush(color, &pBrush);
if (SUCCEEDED(hr))
{
CalculateLayout();
}
}
}
return hr;
}
若要为窗口创建呈现目标,请在 Direct2D 工厂上调用 ID2D1Factory::CreateHwndRenderTarget 方法。
- 第一个参数指定任何类型的呈现目标通用的选项。 在这里,我们将通过调用 helper 函数 D2D1::RenderTargetProperties 来传入默认选项。
- 第二个参数指定窗口的句柄以及呈现目标的大小(以像素为单位)。
- 第三个参数接收 ID2D1HwndRenderTarget 指针。
若要创建纯色画笔,请在呈现目标上调用 ID2D1RenderTarget::CreateSolidColorBrush 方法。 颜色以 D2D1_COLOR_F 值的形式提供。 有关 Direct2D 中的颜色的详细信息,请参阅 在 Direct2D 中使用颜色。
此外,请注意,如果呈现目标已存在,该方法CreateGraphicsResources将返回S_OK而不执行任何操作。 此设计的原因将在下一主题中变得清晰。
六、用 Direct2D 进行绘制
创建图形资源后,即可绘制。绘制省略号
Circle 程序执行非常简单的绘图逻辑:
由于呈现目标是窗口 (,而不是位图或其他屏幕外图面) ,因此绘制是为了响应 WM_PAINT 消息。 以下代码显示了 Circle 程序的窗口过程。
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
OnPaint();
return 0;
// Other messages not shown...
}
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
下面是绘制圆的代码。
void MainWindow::OnPaint()
{
HRESULT hr = CreateGraphicsResources();
if (SUCCEEDED(hr))
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, &ps);
pRenderTarget->BeginDraw();
pRenderTarget->Clear( D2D1::ColorF(D2D1::ColorF::SkyBlue) );
pRenderTarget->FillEllipse(ellipse, pBrush);
hr = pRenderTarget->EndDraw();
if (FAILED(hr) || hr == D2DERR_RECREATE_TARGET)
{
DiscardGraphicsResources();
}
EndPaint(m_hwnd, &ps);
}
}
ID2D1RenderTarget 接口用于所有绘图操作。 程序 OnPaint 的方法执行以下操作:
- ID2D1RenderTarget::BeginDraw 方法指示绘图的开始。
- ID2D1RenderTarget::Clear 方法用纯色填充整个呈现目标。 颜色作为 D2D1_COLOR_F 结构提供。 可以使用 D2D1::ColorF 类初始化结构。 有关详细信息,请参阅 Direct2D 中使用颜色。
- ID2D1RenderTarget::FillEllipse 方法使用指定的填充画笔绘制填充椭圆。 椭圆由中心点和 x- 和 y 弧度指定。 如果 x- 和 y 弧度相同,则结果是一个圆。
- ID2D1RenderTarget::EndDraw 方法指示此帧的绘图完成。 必须在对 BeginDraw 和 EndDraw 的调用之间放置所有绘图操作。
BeginDraw、Clear 和 FillEllipse 方法都具有 void 返回类型。 如果在执行其中任一方法期间发生错误,则错误将通过 EndDraw 方法的返回值发出信号。 该方法 CreateGraphicsResources 显示在“ 创建 Direct2D 资源”主题中。 此方法创建呈现目标和纯色画笔。
设备可能会缓冲绘图命令,并延迟执行命令,直到调用 EndDraw 。 可以通过调用 ID2D1RenderTarget::Flush 强制设备执行任何挂起的绘图命令。 但是,刷新可以减少性能。
1、处理设备丢失
运行程序时,正在使用的图形设备可能变得不可用。 例如,如果显示分辨率发生更改,或者用户删除显示适配器,设备可能会丢失。 如果设备丢失,呈现目标也变得无效,以及与设备关联的任何依赖于设备的资源。 Direct2D 通过从 EndDraw 方法返回错误代码D2DERR_RECREATE_TARGET来向丢失的设备发出信号。 如果收到此错误代码,则必须重新创建呈现目标以及所有依赖于设备的资源。
若要放弃资源,只需释放该资源的接口。
void MainWindow::DiscardGraphicsResources()
{
SafeRelease(&pRenderTarget);
SafeRelease(&pBrush);
}
创建资源可能是一项昂贵的操作,因此不要为每个 WM_PAINT 消息重新创建资源。 创建一次资源,并缓存资源指针,直到资源因设备丢失或不再需要该资源而失效为止。
2、Direct2D 呈现Loop
无论绘制什么,程序都应执行类似于以下内容的循环。
- 创建独立于设备的资源。
- 呈现场景。
- 检查是否存在有效的呈现目标。 如果没有,请创建呈现目标和设备依赖的资源。
- 调用 ID2D1RenderTarget::BeginDraw。
- 发出绘图命令。
- 调用 ID2D1RenderTarget::EndDraw。
- 如果 EndDraw 返回 D2DERR_RECREATE_TARGET,则放弃呈现目标和依赖于设备的资源。
- 每当需要更新或重绘场景时重复步骤 2。
如果呈现目标为窗口,则每当窗口收到 WM_PAINT 消息时,步骤 2 将发生。
此处所示的循环通过放弃设备依赖的资源并在下一个循环开始时重新创建设备, (步骤 2a) 开始重新创建设备丢失。
七、DPI 和与设备无关的像素
若要有效地使用Windows图形进行编程,必须了解两个相关的概念:
- 每英寸点数 (DPI)
- 与设备无关的像素 (DIP) 。
让我们从 DPI 开始。 这需要短时间绕行到版式。 在版式中,类型的大小以称为 点的单位进行度量。 一磅等于 1/72 英寸。
1 pt = 1/72 英寸
备注
这是点的桌面发布定义。 从历史上看,点的确切度量值各不相同。
例如,12 磅字体旨在适应 1/6 英寸 (12/72) 行文本。 显然,这并不意味着字体中的每个字符都完全是 1/6 英寸高。 事实上,某些字符可能高于 1/6”。 例如,在许多字体中,字符 比字体的名义高度高。 若要正确显示,字体需要文本之间的一些额外空间。 此空间称为 前导。
下图显示了 72 磅字体。 实线显示文本周围的 1 英寸高边界框。 虚线称为 基线。 字体中的大多数字符都位于基线上。 字体的高度包括基线上方的部分 (上升) 和基线下方的部分 (下降) 。 在此处显示的字体中,上升为56分,下降为16分。
但是,当涉及到计算机显示器时,测量文本大小是有问题的,因为像素不是所有相同的大小。 像素的大小取决于两个因素:显示分辨率和监视器的物理大小。 因此,物理英寸不是有用的度量值,因为物理英寸和像素之间没有固定的关系。 相反,字体以 逻辑 单位进行度量。 72 磅字体定义为一个逻辑英寸高。 然后,逻辑英寸转换为像素。 多年来,Windows使用以下转换:一个逻辑英寸等于 96 像素。 使用此缩放因子,72 磅字体将呈现为 96 像素高。 12 磅字体高 16 像素。
12 磅 = 12/72 逻辑英寸 = 1/6 逻辑英寸 = 96/6 像素 = 16 像素
此缩放因子描述为每英寸 96 个点, (DPI) 。 术语点派生自打印,其中将物理墨点放入纸张上。 对于计算机显示,说每逻辑英寸 96 像素更准确,但术语 DPI 停滞不前。
由于实际像素大小不同,一个监视器上可读的文本可能在另一个监视器上太小。 此外,用户具有不同的偏好- 有些人更喜欢较大的文本。 因此,Windows允许用户更改 DPI 设置。 例如,如果用户将显示器设置为 144 DPI,则 72 磅字体的高度为 144 像素。 标准 DPI 设置为 100% (96 DPI) 、125% (120 DPI) ,150% (144 DPI) 。 用户还可以应用自定义设置。 从 Windows 7 开始,DPI 是按用户设置。
1、DWM 缩放
如果程序不考虑 DPI,在高 DPI 设置中可能会发现以下缺陷:
- 剪裁的 UI 元素。
- 布局不正确。
- 像素位图和图标。
- 错误的鼠标坐标,可能会影响命中测试、拖放等。
为了确保较旧的程序在高 DPI 设置下工作,DWM 可实现有用的回退。 如果未将程序标记为 DPI 感知,则 DWM 将缩放整个 UI 以匹配 DPI 设置。 例如,在 144 DPI 处,UI 按 150% 缩放,包括文本、图形、控件和窗口大小。 如果程序创建 500 × 500 窗口,则窗口实际上显示为 750 × 750 像素,窗口的内容相应地缩放。
此行为意味着较旧的程序在高 DPI 设置中“只工作”。 但是,缩放还会导致一些模糊的外观,因为缩放是在绘制窗口后应用的。
2、DPI 感知应用程序
为了避免 DWM 缩放,程序可以将自身标记为 DPI 感知。 这会告知 DWM 不执行任何自动 DPI 缩放。 所有新应用程序都应设计为 DPI 感知,因为 DPI 感知可提高 UI 在更高的 DPI 设置下的外观。
程序通过其应用程序清单声明自身 DPI 感知。 清单只是描述 DLL 或应用程序的 XML 文件。 清单通常嵌入到可执行文件中,尽管它可以作为单独的文件提供。 清单包含 DLL 依赖项、请求的权限级别以及程序设计Windows版本等信息。
若要声明程序具有 DPI 感知性,请在清单中包含以下信息。
<assembly xmlns=&#34;urn:schemas-microsoft-com:asm.v1&#34; manifestVersion=&#34;1.0&#34; xmlns:asmv3=&#34;urn:schemas-microsoft-com:asm.v3&#34; >
<asmv3:application>
<asmv3:windowsSettings xmlns=&#34;http://schemas.microsoft.com/SMI/2005/WindowsSettings&#34;>
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>此处显示的列表只是部分清单,但Visual Studio链接器会自动生成清单的其余部分。 若要在项目中包括部分清单,请在Visual Studio中执行以下步骤。
- 在Project菜单上,单击“属性”。
- 在左窗格中,展开 “配置属性”,展开 “清单工具”,然后单击“ 输入和输出”。
- 在 “其他清单文件” 文本框中,键入清单文件的名称,然后单击“ 确定”。
通过将程序标记为 DPI 感知,可以告知 DWM 不要缩放应用程序窗口。 现在,如果创建 500 × 500 窗口,无论用户的 DPI 设置如何,该窗口都将占用 500 × 500 像素。
3、GDI 和 DPI
GDI 绘图以像素为单位进行度量。 这意味着,如果程序标记为 DPI 感知,并且你要求 GDI 绘制一个 200 × 100 个矩形,生成的矩形将在屏幕上为 200 像素宽和 100 像素高。 但是,GDI 字体大小会缩放为当前的 DPI 设置。 换句话说,如果创建 72 磅字体,则字体的大小将为 96 像素,分辨率为 96 DPI,但 144 像素为 144 DPI。 下面是使用 GDI 以 144 DPI 呈现的 72 磅字体。
如果应用程序感知 DPI 且使用 GDI 进行绘制,请缩放所有绘图坐标以匹配 DPI。
4、Direct2D 和 DPI
Direct2D 自动执行缩放以匹配 DPI 设置。 在 Direct2D 中,坐标以与 设备无关的像素 ( (DIP) )为单位进行度量。 DIP 定义为 逻辑 英寸的 1/96。 在 Direct2D 中,所有绘图操作都在 DIP 中指定,然后缩放到当前的 DPI 设置。
例如,如果用户的 DPI 设置为 144 DPI,并且要求 Direct2D 绘制 200 × 100 矩形,则矩形将为 300 × 150 物理像素。 此外,DirectWrite测量 DIP 中的字体大小,而不是以磅为单位。 若要创建 12 磅字体,请指定 16 个 DPS (12 磅 = 1/6 逻辑英寸 = 96/6 DIP) 。 在屏幕上绘制文本时,Direct2D 会将 DIP 转换为物理像素。 此系统的优点是,无论当前 DPI 设置如何,度量单位都一致于文本和绘图。
警告:鼠标和窗口坐标仍以物理像素而不是 DIP 提供。 例如,如果处理 WM_LBUTTONDOWN 消息,则鼠标向下位置以物理像素为单位。 若要绘制位于该位置的点,必须将像素坐标转换为 DIP。
5、将物理像素转换为 DIP
从物理像素到 DIP 的转换使用以下公式。
DIPs = pixels / (DPI/96.0)
若要获取 DPI 设置,请调用 GetDpiForWindow 函数。 DPI 作为浮点值返回。 计算两个轴的缩放因子。
float g_DPIScale = 1.0f;
void InitializeDPIScale(HWND hwnd)
{
float dpi = GetDpiForWindow(hwnd);
g_DPIScale = dpi/96.0f;
}
template <typename T>
float PixelsToDipsX(T x)
{
return static_cast<float>(x) / g_DPIScale;
}
template <typename T>
float PixelsToDips(T y)
{
return static_cast<float>(y) / g_DPIScale;
}
下面是一种替代方法,用于获取 DPI 设置(如果不使用 Direct2D):
void InitializeDPIScale(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
g_DPIScaleX = GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;
g_DPIScaleY = GetDeviceCaps(hdc, LOGPIXELSY) / 96.0f;
ReleaseDC(hwnd, hdc);
}
备注
我们建议对桌面应用使用 GetDpiForWindow;对于 通用 Windows 平台 (UWP) 应用,请使用 DisplayInformation::LogicalDpi。 虽然不建议这样做,但可以使用 SetProcessDpiAwarenessContext 以编程方式设置默认 DPI 感知。 (进程中创建 HWND) 的窗口后,不再支持更改 DPI 感知模式。 如果要以编程方式设置进程默认 DPI 感知模式,则必须在创建任何 HWND 之前调用相应的 API。 有关详细信息,请参阅 设置进程的默认 DPI 感知。
6、调整呈现目标的大小
如果窗口的大小发生更改,则必须调整呈现目标的大小才能匹配。 在大多数情况下,还需要更新布局并重新绘制窗口。 以下代码显示了这些步骤。
void MainWindow::Resize()
{
if (pRenderTarget != NULL)
{
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
pRenderTarget->Resize(size);
CalculateLayout();
InvalidateRect(m_hwnd, NULL, FALSE);
}
}
GetClientRect 函数获取工作区的新大小,以物理像素 (非 DIP) 。 ID2D1HwndRenderTarget::Resize 方法更新呈现器目标的大小(以像素为单位)。 InvalidateRect 函数通过将整个工作区添加到窗口的更新区域来强制重新绘制。 (在模块 1.) 中查看绘制窗口
当窗口增大或缩小时,通常需要重新计算绘制的对象的位置。 例如,在圆程序中,必须更新半径和中心点:
void MainWindow::CalculateLayout()
{
if (pRenderTarget != NULL)
{
D2D1_SIZE_F size = pRenderTarget->GetSize();
const float x = size.width / 2;
const float y = size.height / 2;
const float radius = min(x, y);
ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius);
}
}
ID2D1RenderTarget::GetSize 方法以 DIP (而不是像素) 返回呈现目标的大小,这是计算布局的适当单位。 有一个密切相关的方法 ID2D1RenderTarget::GetPixelSize,以物理像素为单位返回大小。 对于 HWND 呈现目标,此值与 GetClientRect 返回的大小匹配。 但请记住,绘图是在 DIP 中执行的,而不是以像素为单位。
八、在 Direct2D 中使用颜色
Direct2D 使用 RGB 颜色模型,这些模型通过组合不同的红色、绿色和蓝色值来形成颜色。 第四个组件 alpha 测量像素的透明度。 在 Direct2D 中,每个组件都是一个浮点值,范围为 [0.0 1.0]。 对于三种颜色成分,该值测量颜色的强度。 对于 alpha 组件,0.0 表示完全透明,1.0 表示完全不透明。 下表显示了各种组合 100% 强度导致的颜色。
颜色值介于 0 和 1 之间会导致这些纯色的不同阴影。 Direct2D 使用 D2D1_COLOR_F 结构来表示颜色。 例如,以下代码指定 magenta。
// Initialize a magenta color.
D2D1_COLOR_F clr;
clr.r = 1;
clr.g = 0;
clr.b = 1;
clr.a = 1; // Opaque.
还可以使用派生自D2D1_COLOR_F结构的 D2D1::ColorF 类指定颜色。
// Equivalent to the previous example.
D2D1::ColorF clr(1, 0, 1, 1);
1、Alpha 混合
Alpha 混合通过使用以下公式将前景色与背景色混合来创建半透明区域。
color = af * Cf + (1 - af) * Cb
其中 Cb 是背景色, Cf 是前景色, af 是前景色的 alpha 值。 此公式对每个颜色组件应用成对。 例如,假设前景色 (R = 1.0,G = 0.4,B = 0.0) ,alpha = 0.6,背景色 (R = 0.0,G = 0.5,B = 1.0) 。 生成的 alpha 混合颜色为:
R = (1.0 * 0.6 + 0 * 0.4) = .6
G = (0.4 * 0.6 + 0.5 * 0.4) = .44
B = (0 * 0.6 + 1.0 * 0.4) = .40
下图显示了此混合操作的结果。
2、像素格式
D2D1_COLOR_F结构不描述如何在内存中表示像素。 在大多数情况下,这无关紧要。 Direct2D 处理将颜色信息转换为像素的所有内部详细信息。 但是,如果直接使用内存中的位图,或者将 Direct2D 与 Direct3D 或 GDI 组合在一起,则可能需要知道像素格式。
DXGI_FORMAT枚举定义像素格式的列表。 该列表相当长,但其中只有一些与 Direct2D 相关。 (Direct3D) 使用其他人。
下图显示了 BGRA 像素布局。
若要获取呈现目标的像素格式,请调用 ID2D1RenderTarget::GetPixelFormat。 像素格式可能与显示分辨率不匹配。 例如,即使呈现目标使用 32 位颜色,显示器也可能设置为 16 位颜色。
3、Alpha 模式
呈现目标还具有 alpha 模式,用于定义 alpha 值的处理方式。
下面是直 alpha 与预乘 alpha 之间的差异的示例。 假设所需的颜色是纯红色 (100% 强度) 为 50% alpha。 作为 Direct2D 类型,此颜色将表示为 (1、0、0、0.5) 。 使用直 alpha(假设 8 位颜色分量)0xFF像素的红色分量。 使用预乘 alpha 时,红色分量按 50% 缩放为等于 0x80。
D2D1_COLOR_F数据类型始终使用直 alpha 表示颜色。 如果需要,Direct2D 会将像素转换为预乘 alpha 格式。
如果知道程序不会执行任何 alpha 混合,请使用 D2D1_ALPHA_MODE_IGNORE alpha 模式创建呈现目标。 此模式可以提高性能,因为 Direct2D 可以跳过 alpha 计算。 有关详细信息,请参阅 提高 Direct2D 应用程序的性能。
九、在 Direct2D 中应用转换
在 Direct2D 绘图中,我们看到 ID2D1RenderTarget::FillEllipse 方法绘制与 x 轴和 y 轴对齐的椭圆。 但假设你想绘制椭圆倾斜的角度?
通过使用转换,可以通过以下方式更改形状。
- 围绕点旋转。
- 缩放。
- X 方向或 Y 方向) 的转换 (位移。
- 倾斜 (也称为 剪) 。
转换是一种数学运算,用于将一组点映射到一组新的点。 例如,下图显示了围绕点 P3 旋转的三角形。 应用旋转后,点 P1 映射到 P1&#39;,点 P2 映射到 P2&#39;,点 P3 映射到自身。
转换是使用矩阵实现的。 但是,你不必理解矩阵的数学才能使用它们。 若要了解有关数学的详细信息,请参阅 附录:矩阵转换。
若要在 Direct2D 中应用转换,请调用 ID2D1RenderTarget::SetTransform 方法。 此方法采用定义转换 的D2D1_MATRIX_3X2_F 结构。 可以通过在 D2D1::Matrix3x2F 类上调用方法来初始化此结构。 此类包含返回每种转换的矩阵的静态方法:
- Matrix3x2F::Rotation
- Matrix3x2F::Scale
- Matrix3x2F::Translation
- Matrix3x2F::Skew
例如,以下代码在点 (100,100) 周围应用 20 度旋转。
pRenderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(20, D2D1::Point2F(100,100)));
转换将应用于以后的所有绘图操作,直到再次调用 SetTransform 。 若要删除当前转换,请使用标识矩阵调用 SetTransform 。 若要创建标识矩阵,请调用 Matrix3x2F::Identity 函数。
pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());1、绘制时钟手
让我们通过将 Circle 程序转换为模拟时钟来放置要使用的转换。 我们可以通过为手添加线条来实现这一点。
我们可以计算角度,然后应用旋转转换,而不是计算线条的坐标。 以下代码显示了一个绘制一个时钟手的函数。 fAngle 参数以度为单位提供手的角度。
void Scene::DrawClockHand(float fHandLength, float fAngle, float fStrokeWidth)
{
m_pRenderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(fAngle, m_ellipse.point)
);
// endPoint defines one end of the hand.
D2D_POINT_2F endPoint = D2D1::Point2F(
m_ellipse.point.x,
m_ellipse.point.y - (m_ellipse.radiusY * fHandLength)
);
// Draw a line from the center of the ellipse to endPoint.
m_pRenderTarget->DrawLine(
m_ellipse.point, endPoint, m_pStroke, fStrokeWidth);
}
此代码绘制一条垂直线,从时钟面的中心开始,以点 端点结尾。 该线通过应用旋转转换在椭圆中心周围旋转。 旋转的中心点是形成时钟面的椭圆中心。
以下代码演示了如何绘制整个时钟面。
void Scene::RenderScene()
{
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::SkyBlue));
m_pRenderTarget->FillEllipse(m_ellipse, m_pFill);
m_pRenderTarget->DrawEllipse(m_ellipse, m_pStroke);
// Draw hands
SYSTEMTIME time;
GetLocalTime(&time);
// 60 minutes = 30 degrees, 1 minute = 0.5 degree
const float fHourAngle = (360.0f / 12) * (time.wHour) + (time.wMinute * 0.5f);
const float fMinuteAngle =(360.0f / 60) * (time.wMinute);
DrawClockHand(0.6f, fHourAngle, 6);
DrawClockHand(0.85f, fMinuteAngle, 4);
// Restore the identity transformation.
m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Identity() );
}
可以从 Direct2D 时钟示例下载完整的Visual Studio项目。 (只是为了有趣,下载版本向时钟人脸添加径向 gradiant。)
2、组合转换
可以通过乘以两个或多个矩阵来组合四个基本转换。 例如,以下代码将旋转与翻译组合在一起。
C++复制
const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(20);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(40, 10);
pRenderTarget->SetTransform(rot * trans);
Matrix3x2F 类为矩阵乘法提供lass=&#34;nolink&#34;>运算符* () 。 乘以矩阵的顺序非常重要。 设置转换 (M × N) 意味着“先应用 M,后跟 N”。例如,下面是旋转后跟翻译:
下面是此转换的代码:
const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(rot * trans);
现在,将该转换与反向顺序的转换进行比较,然后进行旋转转换。
旋转围绕原始矩形的中心执行。 下面是此转换的代码。
D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(trans * rot);可以看到,矩阵相同,但操作顺序已更改。 发生这种情况是因为矩阵乘法不是通勤的:M × N ≠ N × M。
十、附录:矩阵转换
本主题提供二维图形矩阵转换的数学概述。 但是,无需知道矩阵数学,才能在 Direct2D 中使用转换。 如果你对数学感兴趣,请阅读本主题;否则,请随意跳过本主题。
矩阵是实数的矩形数组。 矩阵 的顺序 是行数和列数。 例如,如果矩阵有 3 行和 2 列,则顺序为 3 × 2。 矩阵通常用括在方括号中的矩阵元素显示:
表示法:矩阵由大写字母指定。 元素由小写字母指定。 下标指示元素的行号和列号。 例如,aij 是矩阵 A 的 i&#39;th 行和 j&#39;th 列处的元素。
下图显示了一个 i × j 矩阵,其中包含矩阵的每个单元格中的单个元素。
1、矩阵操作
本部分介绍在矩阵上定义的基本操作。
添加。 通过添加 A 和 B 的相应元素来获取两个矩阵的总和 A + B:
A + B = \[ a*ij* \] + \[ b*ij* \] = \[ a*ij* + b*ij* \]
标量乘法。 此操作将矩阵乘以实数。 给定实数 k,标量积 kA 是通过乘以 A 乘 以 k 的每个元素来获取的。
kA = k\[ a*ij* \] = \[ k × a*ij* \]
矩阵乘法。 给定两个矩阵 A 和 B,顺序为 (m × n) , (n × p) ,product C = A × B 是一个矩阵,其顺序 (为 × m × p) ,如下所示:
或者,等效:
c*ij* = a*i*1 x b1*j* + a*i*2 x b2*j* + ... + a*in* + b*nj*
也就是说,若要计算每个元素 cij,请执行以下操作:
- 获取 A 的第一行和 B 的 j&#39;th 列。
- 将行和列中元素的每个配对相乘:第一行项按第一列项、第二行项乘以第二列项,依此类推。
- 求和结果。
下面是将 (2 × 2) 矩阵乘以 (2 × 3) 矩阵的示例。
矩阵乘法不是通勤的。 也就是说,A × B ≠ B × A。此外,从定义中,它所遵循的定义是,不能乘以矩阵的每个配对。 左侧矩阵中的列数必须等于右侧矩阵中的行数。 否则,未定义×运算符。
标识矩阵。 标识矩阵(指定 I)是一个定义如下的平方矩阵:
I*ij* = 1 如果 *i* = *j*,则为 0;否则为 0。
换句话说,标识矩阵包含每个元素的 1,其中行号等于列号,对于所有其他元素,则为零。 例如,下面是 3 个× 3 个标识矩阵。
任何矩阵 M 的以下相等性保留。
M x I = M I x M = M
2、Affine Transforms
相交转换是一个数学运算,用于将一个坐标空间映射到另一个坐标空间。 换句话说,它将一组点映射到另一组点。 Affine 转换具有一些功能,使它们在计算机图形中很有用。
- Affine 转换会保留 合数。 如果三个或更多个点落在一行上,它们仍然在转换后形成一条线。 直线保持直线。
- 两个相交转换的构成是一个相交转换。
二维空间的 Affine 转换采用以下形式。
如果应用前面给出的矩阵乘法的定义,则可以显示两个相交转换的乘积是另一个相交转换。 若要使用相交转换转换 2D 点,该点表示为 1 × 3 矩阵。
P = \|x y 1 \|
前两个元素包含点的 x 和 y 坐标。 1 放置在第三个元素中,使数学正常工作。 若要应用转换,请按如下所示乘以两个矩阵。
P&#39; = P × M
这会扩展到以下内容。
其中
x&#39; = ax + cy + e y&#39; = bx + dy + f
若要获取转换点,请获取矩阵 P&#39;的前两个元素。
p = (x&#39;, y&#39;) = (ax + cy + e, bx + dy + f)
备注
1 × n 矩阵称为 行向量。 Direct2D 和 Direct3D 都使用行向量来表示 2D 或 3D 空间中的点。 可以使用列向量 (n × 1) 并转置转换矩阵来获取等效的结果。 大多数图形文本使用列向量形式。 本主题介绍行向量形式,以便与 Direct2D 和 Direct3D 保持一致。
接下来的几个部分派生基本转换。
3、翻译转换
转换转换矩阵采用以下形式。
将点 P 插入到此公式中可生成:
P&#39; = (*x* + *dx*, *y* + *dy*)
对应于 X 轴中 由 dx 翻译的点 (x、y) ,以及 Y 轴中的 dy 。
4、缩放转换
缩放转换矩阵采用以下形式。
将点 P 插入到此公式中可生成:
P&#39; = (*x* \ *dx*, *y* *dy*)
对应于由 dx 和 dy 缩放的点 (x,y) 。
5、围绕原点旋转
在原点周围旋转点的矩阵采用以下形式。
转换的点为:
P&#39; = (*x*cos≤– ysin≤、*x*sin≤+ *y*cos≤)
证明。 若要显示 P 表示旋转,请考虑下图。
给:
P = (x,y)
要转换的原始点。
Φ
由线条构成的角度 (0,0) 到 P。
Θ
旋转 (x,y) 与原点的角度。
P&#39; = (x&#39;,y&#39;)
已转换的点。
R
行的长度 (0,0) 到 P。此外,旋转圆的半径。
备注
此图使用几何图形中使用的标准坐标系,其中正 y 轴指向。 Direct2D 使用Windows坐标系,其中正 y 轴向下点。
x 轴与线条之间的角度 (0,0) 到 P&#39; 的角为 ー + ー。 以下标识保留:
x = R cosЛ y = R sinー x&#39; = R cos () y&#39; = R sin ( )
现在,在 ^ 中解决 x&#39; 和 y&#39; 的问题。 按三角加法公式:
x&#39; = R (cosー cosIn) Cos = RcosーCosー – RsinФsinー y&#39; = R (sinーcosー + cosKusinФ) = RsinーcosTl + RcosーSin≤
我们得到:
x&#39; = xcos≤– ysin≤y&#39; = xsin≤+ ycos≤
对应于前面所示的转换点 P。
6、围绕任意点旋转
若要 (x,y) 以外的点旋转,请使用以下矩阵。
可以通过将点 (x,y) 获取为原点来派生此矩阵。
让 (x1、y1) 是旋转点 (x0,y0) (x,y) 时产生的点。 我们可以按如下所示派生 x1。
x1 = (x0 – x) cosー – (y0 – y) sinー x x1 = x0cosー – y0sinЛ + \[ (1 – cosЛ) + ysin≤ \]
现在,使用此公式 x1 = ax0 + cy0 + e 从前面插入转换矩阵。 使用相同的过程派生 y1。
7、倾斜转换
倾斜转换由四个参数定义:
- :沿 x 轴倾斜的量,以从 y 轴的角度测量。
- :沿 y 轴倾斜的量,以 x 轴的角度测量。
- (px、 py) :执行倾斜的点的 x 坐标和 y 坐标。
倾斜转换使用以下矩阵。
转换的点为:
P&#39; = (*x* + *y*tanー – *py*tanЛ, *y* + *x*tanー) – *py*tanЛ
或等效:
P&#39; = (*x* + (*y* – *py*) tanー, *y* + (*x* – *px*) tanー)
若要查看此转换的工作原理,请单独考虑每个组件。 参数将 x 方向上的每个点移动一个等于 tan^ 的量。 下图显示了 ^与 x 轴倾斜之间的关系。
下面是应用于矩形的相同倾斜:
参数的效果相同,但沿 y 轴:
下图显示了应用于矩形的 y 轴倾斜。
最后,参数 px 和 py 沿 x 轴和 y 轴移动倾斜的中心点。
8、在 Direct2D 中表示转换
所有 Direct2D 转换都是相交转换。 Direct2D 不支持非相交转换。 转换由 D2D1_MATRIX_3X2_F 结构表示。 此结构定义 3 × 2 矩阵。 由于相交转换的第三列始终是相同的 ([0、0、1]) ,并且由于 Direct2D 不支持非相交转换,因此无需指定整个 3 × 3 矩阵。 在内部,Direct2D 使用 3 × 3 个矩阵来计算转换。
D2D1_MATRIX_3X2_F的成员根据其索引位置命名:_11 成员是元素 (1,1) ,_12 成员是元素 (1,2) ,依此类推。 尽管可以直接初始化结构成员,但建议使用 D2D1::Matrix3x2F 类。 此类继承 D2D1_MATRIX_3X2_F ,并提供用于创建任何基本相交转换的帮助程序方法。 该类还定义了用于撰写两个或多个转换 ass=&#34;nolink&#34;>的运算符* () ,如 在 Direct2D 中应用转换中所述。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|