Windows图形编程:画刷核心机制与实战优化指南 1. 项目概述从“刷子”到图形世界的构建基石在Windows桌面应用开发的早期岁月里图形界面是程序员与用户沟通的核心桥梁。而“画刷”Brush这个听起来朴实无华的工具恰恰是构建这座桥梁最基础、最不可或缺的“建筑材料”之一。它远不止是一把用来填充颜色的刷子而是GDI图形设备接口乃至后续GDI、Direct2D等图形子系统中的核心绘图资源负责定义封闭图形内部的填充样式。无论是绘制一个带渐变色的按钮背景还是创建具有复杂纹理的游戏地图亦或是实现一个自定义风格的进度条其背后都离不开对画刷的精准操控。对于刚接触Windows图形编程的朋友来说可能会觉得画刷无非就是CreateSolidBrush创建一个实心刷子那么简单。但当你真正深入去开发一个需要复杂UI表现的应用时比如一个数据可视化仪表盘、一个皮肤可换的音乐播放器或者一个自定义控件库你就会发现画刷的学问深着呢。它直接关系到应用的视觉表现力、渲染性能以及内存管理的优劣。理解并熟练运用各种画刷是从“能画出来”到“画得好看、画得高效”的关键一步。本文将从一个资深开发者的视角拆解Windows编程中画刷的核心机制、实战技巧以及那些官方文档里不会明说的“坑”带你真正掌握这把构建图形界面的利器。2. 画刷的核心类型与底层机制解析在Windows GDI中画刷本质上是一个定义了填充模式的逻辑对象。它并不直接持有像素数据而是描述了一种填充规则。当系统需要填充一个矩形、椭圆或多边形时就会查询当前选入设备上下文DC的画刷对象按照其规则进行着色。2.1 四种基本画刷及其应用场景Windows GDI主要提供了四种类型的画刷它们各有其适用的场景和创建方式。实心画刷Solid Brush这是最常用、最简单的画刷类型。它用单一的纯色进行填充。创建函数是CreateSolidBrush(COLORREF crColor)。虽然简单但在性能上它通常是最优的因为系统可以对其进行高度优化。任何需要纯色背景的区域如窗口客户区背景、按钮底色都优先使用实心画刷。库存画刷Stock Brush系统预定义的一些常用画刷如白色(WHITE_BRUSH)、黑色(BLACK_BRUSH)、灰色(GRAY_BRUSH)等。通过GetStockObject(int fnObject)函数获取。它们的优势在于无需创建和销毁由系统管理在需要标准颜色且生命周期随应用的情况下使用非常方便避免了资源泄漏的风险。阴影画刷Hatch Brush用预定义的阴影图案进行填充如水平线、垂直线、交叉线、斜线等。创建函数是CreateHatchBrush(int fnStyle, COLORREF clrref)。fnStyle定义了图案样式例如HS_HORIZONTAL表示水平线。这种画刷在传统UI中常用于表示禁用状态、背景区分或者在报表、图表中用于区分不同的数据区域。它的性能介于实心和位图画刷之间。位图画刷Pattern Brush / Bitmap Brush使用一个位图Bitmap作为填充图案。创建函数是CreatePatternBrush(HBITMAP hbmp)。这是功能最强大、也最复杂的一种画刷。你可以用任何小位图如8x8, 16x16平铺开来填充一个区域从而实现纹理、渐变早期模拟、自定义图案等效果。然而它的性能开销最大因为涉及位图的解码、内存访问和平铺计算。注意GDI位图画刷使用的位图最好是8x8, 16x16这类小尺寸的并且颜色深度不宜过高。如果使用一个大尺寸的真彩色位图不仅创建速度慢在填充时也会带来巨大的性能负担。一个常见的技巧是先创建一个兼容DC和一个小位图在小位图上绘制好你的复杂图案再用它来创建画刷。2.2 GDI 与 Direct2D 中的画刷演进随着图形需求的提升原生的GDI画刷在表现力上逐渐力不从心尤其是在渐变和复杂特效方面。因此在后续的技术栈中画刷的概念得到了极大的增强。GDI 画刷GDI是GDI的一个增强版提供了更丰富的画刷类型主要通过Graphics类的方法来使用。SolidBrush对应GDI的实心画刷但颜色使用Color类支持Alpha通道可以实现半透明填充。HatchBrush提供了比GDI更丰富的阴影样式。TextureBrush类似GDI的位图画刷但功能更强可以设置平铺模式Tile, FlipX, FlipY等、变换矩阵实现更灵活的纹理填充。LinearGradientBrush和PathGradientBrush这是GDI带来的革命性特性。线性渐变画刷可以在一条直线上定义多个颜色节点实现平滑的颜色过渡。路径渐变画刷则更强大可以沿着一个路径如一个多边形的边界和中心点定义渐变用于创建非常炫目的光泽、立体按钮效果。这是用传统GDI位图画刷模拟渐变难以企及的效率和效果。Direct2D 画刷在追求高性能、硬加速的现代图形编程中Direct2D是首选。它的画刷体系设计更为严谨和高效。ID2D1SolidColorBrush实心画刷创建和使用都非常直接。ID2D1LinearGradientBrush和ID2D1RadialGradientBrush线性渐变和径向渐变画刷。需要先定义ID2D1GradientStopCollection渐变停止点集合然后创建画刷。这种设计将渐变数据与画刷对象分离有利于资源复用。ID2D1BitmapBrush位图画刷。可以设置扩展模式ExtendMode决定位图边界外的填充方式、插值模式以及变换。Direct2D的位图画刷在支持硬件加速的情况下性能远超GDI和GDI。核心机制对比与选型建议简单性与兼容性如果你的目标是开发一个简单的工具、维护一个古老的MFC项目或者对系统兼容性要求极高需要支持Windows XP等老系统那么纯GDI画刷仍然是可靠的选择。记住要管理好资源生命周期创建、选入DC、用后删除。表现力与开发效率如果你需要实现渐变、半透明、复杂的路径填充且目标系统至少是Windows XP已内置GDI那么GDI是更佳选择。它的API相对现代表现力强但需要注意GDI本身是软渲染性能在复杂场景下可能成为瓶颈。性能与现代化对于全新的、对图形性能要求高的项目如编辑器、游戏UI、数据可视化大屏强烈推荐使用Direct2D。它完全利用GPU加速画刷类型丰富且设计合理是构建现代化Windows应用图形界面的基石。当然学习曲线也相对陡峭一些。3. 实战从创建到渲染的完整流程与避坑指南理解了理论我们通过一个具体的场景来串联知识绘制一个自定义的渐变背景按钮。我们将分别用GDI和Direct2D来实现并对比其中的细节。3.1 使用GDI实现渐变按钮假设我们在一个基于Win32 API的窗口中在WM_PAINT消息处理函数中绘制按钮。#include windows.h #include gdiplus.h #pragma comment(lib, gdiplus.lib) // ... 窗口过程函数 case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hWnd, ps); // 初始化GDI绘图对象 Gdiplus::Graphics graphics(hdc); graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); // 抗锯齿 // 1. 定义按钮矩形区域 Gdiplus::Rect buttonRect(50, 50, 200, 60); // 2. 创建线性渐变画刷 // 参数矩形区域起始颜色结束颜色渐变方向 Gdiplus::LinearGradientBrush gradientBrush( buttonRect, Gdiplus::Color(255, 0, 120, 215), // 不透明的蓝色 Gdiplus::Color(255, 0, 80, 160), // 更深的蓝色 Gdiplus::LinearGradientModeVertical // 垂直渐变 ); // 3. 可选设置渐变插值颜色 // 可以添加更多颜色节点实现多色渐变 // Gdiplus::Color colors[] { ... }; // Gdiplus::REAL positions[] { ... }; // gradientBrush.SetInterpolationColors(colors, positions, 3); // 4. 使用画刷填充圆角矩形需要先创建路径 Gdiplus::GraphicsPath path; INT diameter 10; // 圆角直径 Gdiplus::Rect pathRect buttonRect; path.AddArc(pathRect.X, pathRect.Y, diameter, diameter, 180, 90); // 左上角 path.AddArc(pathRect.X pathRect.Width - diameter, pathRect.Y, diameter, diameter, 270, 90); // 右上角 path.AddArc(pathRect.X pathRect.Width - diameter, pathRect.Y pathRect.Height - diameter, diameter, diameter, 0, 90); // 右下角 path.AddArc(pathRect.X, pathRect.Y pathRect.Height - diameter, diameter, diameter, 90, 90); // 左下角 path.CloseFigure(); graphics.FillPath(gradientBrush, path); // 5. 绘制按钮边框使用实心画刷创建的Pen Gdiplus::Pen borderPen(Gdiplus::Color(255, 0, 60, 120), 2.0f); // 边框笔颜色更深2像素宽 graphics.DrawPath(borderPen, path); // 6. 绘制按钮文字 Gdiplus::SolidBrush textBrush(Gdiplus::Color(255, 255, 255, 255)); // 白色实心画刷 Gdiplus::FontFamily fontFamily(L微软雅黑); Gdiplus::Font font(fontFamily, 14, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel); Gdiplus::StringFormat stringFormat; stringFormat.SetAlignment(Gdiplus::StringAlignmentCenter); stringFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter); graphics.DrawString(L渐变按钮, -1, font, buttonRect, stringFormat, textBrush); EndPaint(hWnd, ps); } break;实操要点与避坑资源管理GDI对象如Graphics,Brush,Pen,Font是C类遵循RAII原则在栈上创建离开作用域后会自动调用析构函数释放资源。这与需要手动DeleteObject的GDI句柄不同大大降低了资源泄漏的风险。抗锯齿设置SetSmoothingMode对填充和绘制线条都有影响。对于UI绘制通常开启SmoothingModeAntiAlias可以获得更平滑的边缘但会有轻微的性能开销。渐变方向LinearGradientModeVertical是垂直渐变。你也可以使用LinearGradientModeHorizontal或者通过自定义起点和终点坐标来定义任意角度的渐变。路径绘制GDI绘制圆角矩形没有直接函数需要手动构造路径。这是一个常见的步骤可以封装成一个工具函数复用。文本渲染GDI的文本渲染质量通常比GDI的TextOut要好并且支持抗锯齿。注意DrawString的矩形参数是文本布局的矩形不是文本的起始坐标。3.2 使用Direct2D实现相同效果Direct2D的步骤更多因为它需要独立的渲染目标Render Target和工厂Factory。#include windows.h #include d2d1.h #pragma comment(lib, d2d1.lib) // 全局或类成员变量 ID2D1Factory* pD2DFactory nullptr; ID2D1HwndRenderTarget* pRenderTarget nullptr; ID2D1SolidColorBrush* pTextBrush nullptr; ID2D1LinearGradientBrush* pGradientBrush nullptr; // 在窗口创建时初始化Direct2D资源如WM_CREATE中 HRESULT hr D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, pD2DFactory); if (SUCCEEDED(hr)) { RECT rc; GetClientRect(hWnd, rc); D2D1_SIZE_U size D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); hr pD2DFactory-CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(hWnd, size), pRenderTarget ); } // 创建画刷可以放在这里也可以放在绘制时懒创建 // 在WM_PAINT或自定义绘制函数中 if (pRenderTarget) { pRenderTarget-BeginDraw(); pRenderTarget-SetTransform(D2D1::Matrix3x2F::Identity()); // 重置变换 pRenderTarget-Clear(D2D1::ColorF(D2D1::ColorF::White)); // 清空背景为白色 // 1. 定义按钮矩形 D2D1_RECT_F buttonRect D2D1::RectF(50.0f, 50.0f, 250.0f, 110.0f); D2D1_ROUNDED_RECT roundedRect D2D1::RoundedRect(buttonRect, 10.0f, 10.0f); // 圆角半径10 // 2. 创建渐变画刷如果尚未创建 if (!pGradientBrush) { ID2D1GradientStopCollection* pGradientStops nullptr; D2D1_GRADIENT_STOP gradientStops[2]; gradientStops[0].color D2D1::ColorF(D2D1::ColorF::DodgerBlue, 1.0f); gradientStops[0].position 0.0f; gradientStops[1].color D2D1::ColorF(D2D1::ColorF::DarkBlue, 1.0f); gradientStops[1].position 1.0f; pRenderTarget-CreateGradientStopCollection(gradientStops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, pGradientStops); if (pGradientStops) { pRenderTarget-CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties( D2D1::Point2F(buttonRect.left, buttonRect.top), // 渐变起点 D2D1::Point2F(buttonRect.left, buttonRect.bottom) // 渐变终点垂直 ), pGradientStops, pGradientBrush ); pGradientStops-Release(); } } // 3. 使用渐变画刷填充圆角矩形 if (pGradientBrush) { pRenderTarget-FillRoundedRectangle(roundedRect, pGradientBrush); } // 4. 绘制边框创建实心画刷的PenDirect2D中边框是Draw方法使用笔刷 ID2D1SolidColorBrush* pBorderBrush nullptr; pRenderTarget-CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Navy), pBorderBrush); if (pBorderBrush) { pRenderTarget-DrawRoundedRectangle(roundedRect, pBorderBrush, 2.0f); // 边框宽度2 pBorderBrush-Release(); // 临时使用的画刷用完立即释放 } // 5. 绘制文字创建文本格式和布局 if (!pTextBrush) { pRenderTarget-CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), pTextBrush); } IDWriteTextFormat* pTextFormat nullptr; // 需要先创建IDWriteFactory此处省略... 通常与D2D工厂一起初始化 // writeFactory-CreateTextFormat(L微软雅黑, ..., pTextFormat); // D2D1_RECT_F textRect buttonRect; // pRenderTarget-DrawTextW(L渐变按钮, ..., pTextFormat, textRect, pTextBrush); // pTextFormat-Release(); hr pRenderTarget-EndDraw(); // 如果设备丢失如窗口全屏切换需要重建渲染目标和所有依赖资源 if (hr D2DERR_RECREATE_TARGET) { SafeRelease(pRenderTarget); SafeRelease(pGradientBrush); SafeRelease(pTextBrush); // 需要在下一个绘制周期重新初始化 } } // 在窗口销毁时WM_DESTROY释放所有COM接口资源 SafeRelease(pGradientBrush); SafeRelease(pTextBrush); SafeRelease(pRenderTarget); SafeRelease(pD2DFactory);Direct2D实战心得COM对象管理Direct2D基于COM必须严格遵守引用计数规则。每个Create*调用后如果成功对象的引用计数为1。使用完毕后必须调用Release。使用类似SafeRelease的宏或智能指针如Microsoft::WRL::ComPtr可以极大避免泄漏。渲染目标ID2D1HwndRenderTarget是关联到窗口的渲染目标。所有绘制命令都通过它执行。BeginDraw和EndDraw必须成对调用。设备丢失处理EndDraw可能返回D2DERR_RECREATE_TARGET这通常发生在显示设备发生变化时如分辨率调整、休眠唤醒。此时必须释放所有依赖于该渲染目标的资源画刷、位图等然后重新创建渲染目标及其依赖资源。这是Direct2D编程中的一个关键错误处理点。资源复用像pGradientBrush、pTextBrush这类在多次绘制中使用的画刷应该创建一次并重复使用而不是每帧都创建销毁这对性能至关重要。临时使用的画刷如边框刷则可以在局部创建和释放。绘制 vs 填充Direct2D明确区分了Fill*填充内部和Draw*绘制轮廓操作。绘制轮廓需要使用画刷和笔宽参数。4. 高级技巧与性能优化实战掌握了基础绘制后我们来看看如何将画刷用到极致并规避性能陷阱。4.1 画刷的变换与复用画刷本身可以应用几何变换这能实现动态效果而无需重新定义画刷。GDI示例旋转渐变方向Gdiplus::Matrix matrix; matrix.RotateAt(45.0f, Gdiplus::PointF(buttonRect.Width/2, buttonRect.Height/2)); // 绕中心旋转45度 gradientBrush.SetTransform(matrix); graphics.FillRectangle(gradientBrush, buttonRect);Direct2D示例通过设置画刷的SetTransform方法可以实现平移、旋转、缩放渐变图案。这对于创建动态背景如流动的光效非常有用。复用技巧对于静态UI在窗口初始化时创建所有需要的画刷并缓存起来。对于动态数据如根据数值改变颜色的进度条可以预定义一个颜色到画刷的映射表或者使用一个可修改的实心画刷Direct2D的ID2D1SolidColorBrush::SetColor。4.2 使用位图画刷创建复杂纹理假设我们要为一个游戏中的地面绘制草地纹理。GDI方案传统但需谨慎// 1. 加载或创建一个小位图如32x32的草地位图 HBITMAP hBmpGrass LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_GRASS)); // 从资源加载 // 2. 创建位图画刷 HBRUSH hBrushGrass CreatePatternBrush(hBmpGrass); // 3. 选入DC并使用 HBRUSH hOldBrush (HBRUSH)SelectObject(hdc, hBrushGrass); Rectangle(hdc, 0, 0, 800, 600); // 填充一大片区域 SelectObject(hdc, hOldBrush); // 恢复原画刷 // 4. 清理 DeleteObject(hBrushGrass); DeleteObject(hBmpGrass); // 注意画刷使用位图的副本实际上画刷持有位图依赖。应先删除画刷再删除位图不文档指出当不再需要位图时可以删除它因为画刷已内部复制。安全做法是先删画刷。关键陷阱CreatePatternBrush使用的位图必须是8位色深或以下的设备相关位图DDB。如果你从文件加载了一个24位或32位的位图通常是DIB直接使用会导致创建失败或显示异常。正确的做法是将其选入一个兼容DC或者使用CreateDIBPatternBrushPt函数。这是GDI位图画刷最常遇到的坑。Direct2D方案现代且强大// 假设已从文件加载位图到 ID2D1Bitmap* pGrassBitmap ID2D1BitmapBrush* pGrassBrush nullptr; if (pGrassBitmap) { D2D1_BITMAP_BRUSH_PROPERTIES brushProperties D2D1::BitmapBrushProperties( D2D1_EXTEND_MODE_WRAP, // X方向平铺 D2D1_EXTEND_MODE_WRAP, // Y方向平铺 D2D1_BITMAP_INTERPOLATION_MODE_LINEAR // 线性插值缩放时更平滑 ); pRenderTarget-CreateBitmapBrush(pGrassBitmap, brushProperties, pGrassBrush); } // 使用时还可以设置变换 if (pGrassBrush) { pGrassBrush-SetTransform(D2D1::Matrix3x2F::Translation(offsetX, offsetY)); // 滚动纹理 pRenderTarget-FillRectangle(D2D1::RectF(0, 0, 800, 600), pGrassBrush); }Direct2D的位图画刷清晰定义了平铺模式和插值模式并且可以轻松应用变换来实现纹理动画如滚动的水面、移动的云层。4.3 性能优化要点减少画刷创建/销毁频率这是最重要的原则。在初始化时创建在整个生命周期或窗口存续期内复用。避免在绘制循环如WM_PAINT内频繁创建和销毁GDI对象或Direct2D资源。选择合适的画刷类型纯色用实心画刷简单图案用阴影画刷复杂纹理或渐变才用位图/渐变画刷。GDI的库存画刷永远是最快的。警惕GDI对象泄漏这是Windows图形编程的经典问题。确保每一个Create*、GetStockObject对于需要DeleteObject的某些对象都有对应的DeleteObject。使用RAII包装器如std::unique_ptr配合自定义删除器或MFC/ATL的封装类可以自动化这个过程。Direct2D的设备丢失处理如前所述必须妥善处理D2DERR_RECREATE_TARGET。一个健壮的结构是将渲染目标及其依赖资源的重建逻辑封装成一个函数在窗口大小改变和设备丢失时调用。批量绘制如果可能将使用相同画刷的绘制操作集中在一起减少状态切换。这在GDI中影响相对小但在Direct2D和更现代的图形API中状态切换是有成本的。测量与调试在复杂的UI中如果感到卡顿可以使用性能分析工具如Visual Studio的性能探查器来检查绘制调用中画刷创建和资源管理的耗时。5. 常见问题排查与调试实录即使经验丰富在图形编程中也会遇到各种诡异的问题。以下是一些与画刷相关的典型问题及排查思路。5.1 问题颜色显示不正确或为黑色可能原因1GDI实心画刷颜色值错误。COLORREF是0x00bbggrr格式蓝-绿-红而不是常见的RGB。使用RGB(r, g, b)宏可以避免这个问题。排查检查CreateSolidBrush(RGB(255, 0, 0))是否真的创建了红色画刷。可能原因2GDI位图画刷位图格式不支持。如前所述CreatePatternBrush对位图格式要求苛刻。排查确保位图是使用CreateBitmap、CreateCompatibleBitmap创建的设备相关位图并且色深合适。尝试使用CreateDIBPatternBrushPt配合DIB节。可能原因3所有画刷画刷未被正确选入设备上下文DC。或者在绘制完成后没有恢复先前的画刷导致后续绘制使用了错误画刷。排查检查SelectObject的返回值确保选择成功。使用SaveDC和RestoreDC来安全地管理DC状态。可能原因4GDI / Direct2D颜色通道顺序或Alpha值问题。GDI的Color构造函数是Color(alpha, red, green, blue)。Direct2D的ColorF通常也是RGBA顺序。排查检查Alpha值是否为0完全透明。确认颜色常量的值例如D2D1::ColorF::Red代表的是不透明的红色。5.2 问题绘制区域出现“花屏”或残留图案可能原因1使用了已删除或无效的画刷句柄。在GDI中将一个已删除的对象选入DC会导致未定义行为。排查确保画刷的生命周期长于其被使用的DC操作周期。对于局部创建的画刷必须在函数返回前将其从DC中选出并删除。可能原因2多线程访问冲突。GDI对象不是线程安全的。如果多个线程同时操作同一个画刷或DC会导致崩溃或图形错误。排查确保对GDI对象的访问尤其是创建、删除、选入DC都在同一线程内通常是主UI线程。使用线程锁或消息传递进行同步。可能原因3Direct2D设备丢失后未正确重建资源继续使用无效的渲染目标或画刷进行绘制。排查检查EndDraw的返回值。如果返回D2DERR_RECREATE_TARGET必须执行完整的资源释放和重建流程。5.3 问题渐变或纹理填充性能极差可能原因1在每一帧都重新创建复杂的画刷如大尺寸位图画刷、多色渐变画刷。解决将画刷创建移到初始化阶段并缓存。可能原因2GDI位图画刷使用了过大的位图。解决将纹理缩小到足够表现图案的最小尺寸如64x64或128x128。GDI的平铺算法对小位图更友好。可能原因3Direct2D渐变画刷渐变停止点Gradient Stop数量过多。解决评估是否真的需要那么多颜色节点。通常3-5个停止点就能实现很好的渐变效果。过多的停止点会增加GPU的插值计算负担。可能原因4在软件渲染模式下使用高级画刷。如果系统不支持硬件加速GDI和Direct2D都会回退到软件渲染此时复杂画刷的操作会非常消耗CPU。排查检查系统配置和DirectX支持情况。对于性能要求高的应用可以提供一个“低画质”模式禁用复杂的渐变和纹理。5.4 调试工具与技巧GDI调试工具旧版Windows SDK中的GDIView.exe或Process Explorer的GDI句柄查看功能可以查看进程创建的GDI对象数量帮助定位泄漏。如果一个进程的GDI对象数持续增长几乎可以肯定存在泄漏。Visual Studio图形调试器对于Direct2D这是神器。它可以捕获一帧的绘制调用让你看到每一步绘制命令的效果、使用的资源包括画刷、以及性能分析。你可以清楚地看到画刷是否被正确创建和绑定。简单的日志与检查在Create*和DeleteObject/Release调用前后输出日志跟踪对象的生命周期。在关键绘制代码前后检查GDI错误码GetLastError或DirectX的HRESULT。最小化复现当遇到一个诡异的图形问题时尝试创建一个最简单的、只重现该问题的新项目。剥离无关代码后往往能更快地定位到根本原因例如是否是某个特定API的调用顺序问题或者是某个特定参数组合导致的。画刷作为图形编程的基石之一其重要性贯穿了从经典Win32到现代Direct2D的整个Windows图形开发史。理解其原理掌握其用法规避其陷阱是每一个Windows客户端开发者图形功力的体现。从一把简单的“刷子”开始你实际上是在驾驭整个图形系统的填充规则与渲染状态。希望这些从实战中总结出的经验能让你在构建绚丽界面的道路上少走一些弯路多一份从容。