ReactOS 图形系统分析(41):画刷子系统 --- brush.cpp
1. 概述
brush.cpp(file:///d:/reactos/win32ss/gdi/ntgdi/brush.cpp) 实现 win32k 内核态(ntgdi)的画刷(BRUSH)子系统 。它负责逻辑画刷对象的创建(纯色 / 影线 / 图案 / DIB 图案)、颜色修改、对象查询与销毁,并把逻辑画刷交给引擎(engbrush.c)实现为已实现画刷(realized brush),最终供填充、描边等绘制操作使用。
本文分析的文件及范围:
| 文件 | 作用 |
|---|---|
win32ss/gdi/ntgdi/brush.cpp |
BRUSH 类实现、内部工厂、7 个 NtGdi* 入口、Int* 内部接口(共约 580 行) |
win32ss/gdi/ntgdi/brush.h |
BRUSHBODY / EBRUSHOBJ 结构、BR_* 属性标志、EBRUSHOBJ_* 引擎接口声明 |
win32ss/gdi/ntgdi/brush.hpp |
BRUSH C++ 类定义(内存分配、Lock 系列、成员函数声明) |
win32ss/gdi/eng/engbrush.c |
引擎侧实现:EBRUSHOBJ 初始化/更新/实现、影线位图、BRUSHOBJ_* DDI 导出 |
win32ss/include/ntgdihdl.h |
BRUSH_ATTR、SC_BB_STOCKOBJ 等句柄表相关定义 |
1.1 架构图
应用层 (gdi32.dll / user32.dll)
CreateSolidBrush / CreateHatchBrush / CreatePatternBrush / CreateDIBPatternBrush
SetDCBrushColor / SetBrushAttributes / GetObject / DeleteObject ...
│ (用户态,gdi32/objects/brush.c 等)
▼
系统调用层
NtGdiCreateSolidBrush NtGdiCreateHatchBrushInternal
NtGdiCreatePatternBrushInternal NtGdiCreateDIBBrush
NtGdiGetObjectBitmapHandle NtGdiSetBrushAttributes / NtGdiClearBrushAttributes
│
▼
brush.cpp (逻辑画刷层)
CreateBrushInternal(flAttrs, crColor, iHatch, hbmPattern, pvClient)
│ new BRUSH(...) → BASEOBJECT + BRUSHBODY
│ bAllocateBrushAttr() (非全局画刷:分配进程私有 BRUSH_ATTR)
│ hInsertObject(owner) (PUBLIC / POWNED,插入句柄表)
▼
BRUSH 逻辑画刷对象(句柄表,GDILoObjType_LO_BRUSH_TYPE)
│ 绘制时(DC 级联)
▼
engbrush.c (引擎实现层)
EBRUSHOBJ_vInit / EBRUSHOBJ_vInitFromDC / EBRUSHOBJ_vUpdateFromDC
EBRUSHOBJ_vSetSolidRGBColor (颜色 → iSolidColor)
EBRUSHOBJ_bRealizeBrush (DrvRealizeBrush / EngRealizeBrush)
│
▼
EBRUSHOBJ(已实现画刷:pengbrush 位图 / pvRbrush 驱动私有数据 / psoMask 掩码)
│
▼
绘制引擎:bitblt.c(PatBlt)、fill.c(填充)、line.c(描边)...
1.2 设计动机
-
逻辑画刷与已实现画刷分离。 逻辑画刷(BRUSH)只描述"画刷长什么样"(颜色、影线样式、图案位图句柄),与具体输出表面(surface)、调色板(palette)、设备无关。真正可被绘制引擎使用的颜色值(iSolidColor)、图案表面(pengbrush)、掩码(psoMask)必须针对目标表面的像素格式做转换(realization),这部分由 EBRUSHOBJ 承担。这样同一个逻辑画刷可以被多个 DC、多种格式的表面复用,转换只在实际绘制时按需进行(lazy realization)。
-
BRUSHBODY 与 PEN 共用布局。 brush.h 中明确注释:
The layout of this structure is taken from "Windows Graphics Programming" book written by Feng Yuan,且严禁随意修改 。该布局中后半部分(lWidth、eWidth、ulPenStyle、pStyle、jJoin、jEndCap、iBrushStyle、ulStyleSize、ListHead)仅画笔使用,画刷不使用。之所以共用,是为了让 win32k 的句柄表对象管理器(GDIOBJ)以统一方式管理 BRUSH 与 PEN 两种对象------两者的类型 ID 也同属GDILoObjType_LO_BRUSH_TYPE/GDILoObjType_LO_PEN_TYPE/GDILoObjType_LO_EXTPEN_TYPE,共用同一个释放回调BRUSH_vDeleteObject(见其断言:三种类型皆允许)。 -
全局画刷与进程私有属性分离(BRUSH_ATTR)。 库存对象(stock objects)与系统画刷(syscolor brushes)是全局共享的(
BR_IS_GLOBAL),它们的BrushAttr内嵌在对象体内;而用户进程通过 NtGdi* 创建的画刷需要每进程私有 的属性,因此bAllocateBrushAttr()会从 GDI 池分配一块BRUSH_ATTR,并通过vSetObjectAttr()登记到句柄表,使 gdi32 的GdiGetHandleUserData可以按进程访问。NtGdiSetBrushAttributes/NtGdiClearBrushAttributes的语义正是"把画刷在全局(stock)与私有(user)属性之间切换"。 -
影线(hatch)位图预生成。 六种标准影线样式是固定 8×8 的 1bpp 图案,
InitBrushImpl()在系统初始化阶段用EngCreateBitmap一次性生成并缓存到gahsurfHatch[HS_DDI_MAX],之后创建影线画刷时零成本引用。这套做法与 Windows NT 一致(HS_DDI_MAX=6,对应 HS_HORIZONTAL...HS_DIAGCROSS)。 -
C++ 与 C 双接口。 内核对象层使用 C++ 类(BRUSH 继承 BASEOBJECT),但 win32k 的其余模块(以及 hook 回调)都是 C 代码,因此提供
BRUSH_vDeleteObject、BRUSH_GetObject等薄 C 包装,把 C 调用映射到 C++ 成员函数。
2. 核心数据结构
2.1 BRUSHBODY ------ 画刷对象体(brush.h)
_BRUSHBODY 是 BRUSH 的"对象体",C++ 侧由 class BRUSH : public BASEOBJECT, protected _BRUSHBODY 继承,C 侧则内嵌为 BRUSH 结构的第二个成员。逐字段说明如下:
c
typedef struct _BRUSHBODY
{
ULONG iHatch; // 注意:不是画刷样式,而是"影线样式"!
HBITMAP hbmPattern; // 图案位图(内核持有的真正图案,如 BITMAP_CopyBitmap 的副本)
HBITMAP hbmClient; // 客户端位图句柄(CreatePatternBrush 传入的原始位图 / DIB 的 pvClient)
ULONG flAttrs; // 画刷属性标志(BR_IS_SOLID / BR_IS_HATCH / BR_IS_BITMAP / ...)
ULONG ulBrushUnique; // 全局唯一序号(InterlockedIncrement 自增,供引擎缓存判断)
BRUSH_ATTR *pBrushAttr; // 指向"当前激活"的画刷属性(可能指向对象体内或进程池)
BRUSH_ATTR BrushAttr; // 内嵌属性(全局画刷使用;私有画刷的"模板")
POINT ptOrigin; // 画刷原点(SetBrushOrgEx 设置,图案对齐基准)
ULONG bCacheGrabbed; // 缓存是否已被占用(引擎缓存标志)
COLORREF crBack; // 背景色
COLORREF crFore; // 前景色
ULONG ulPalTime; // 调色板时间戳(调色板变化后强制重新实现)
ULONG ulSurfTime; // 表面时间戳(表面格式变化后强制重新实现)
PVOID pvRBrush; // 驱动私有"已实现画刷"指针(DrvRealizeBrush 返回)
HDEV hdev; // 关联的设备句柄(所属 PDEV)
//DWORD unk054;
/* 以下字段仅供 PEN 使用 */
LONG lWidth; // 画笔宽度
FLOATOBJ eWidth; // 几何画笔宽度(浮点)
ULONG ulPenStyle; // 画笔样式(PS_SOLID / PS_DASH ...)
DWORD *pStyle; // 用户自定义虚线样式数组
ULONG dwStyleCount; // 样式数组元素个数
BYTE jJoin; // 0x06c 几何宽线的连接样式
BYTE jEndCap; // 几何宽线的端点样式
INT iBrushStyle; // 0x070 画刷样式
ULONG ulStyleSize; // 样式数组大小(字节)
LIST_ENTRY ListHead; // 0x080 链表节点(缓存链表挂接)
} BRUSHBODY;
要点:
- iHatch 语义 :注释特别强调
This is not the brush style, but the hatch style!。即它存的是 HS_HORIZONTAL(0)...HS_DIAGCROSS(5) 影线样式编号,而不是 BS_HATCHED(2) 这类画刷样式。BS_*样式由 flAttrs 派生(见cjGetObject的映射),iHatch 只作为索引使用(ppdev->ahsurf[pbr->iHatch]、gahsurfHatch[i])。 - hbmPattern 与 hbmClient :对于
NtGdiCreatePatternBrushInternal,内核BITMAP_CopyBitmap拷贝一份作为 hbmPattern(画刷拥有,析构时删除),而调用者的原始位图作为 hbmClient 保存,供GetObject返回lbHatch = (ULONG_PTR)hbmClient。这是 Win32 语义:GetObject(BS_PATTERN)的lbHatch必须返回创建时传入的位图句柄。 - ulPalTime / ulSurfTime :调色板或目标表面变化(时间戳更新)后,缓存的已实现画刷失效,需要重新实现。
vSetSolidColor把两者置为-1强制失效。 - pvRBrush :驱动实现画刷的私有数据(
BRUSHOBJ_pvAllocRbrush分配、EngFreeMem释放)。注意它同时也出现在 EBRUSHOBJ.BrushObject.pvRbrush 中,这里 BRUSHBODY.pvRBrush 是逻辑画刷层面的缓存。
2.2 BRUSH ------ C/C++ 双视图
C 视图(C 模块使用):
c
typedef struct _BRUSH
{
/* 句柄表中所有 GDI 对象的公共头,不可移动 */
BASEOBJECT BaseObject;
BRUSHBODY;
} BRUSH, *PBRUSH;
C++ 视图(brush.hpp):
cpp
class BRUSH : public BASEOBJECT, protected _BRUSHBODY
BASEOBJECT 提供句柄(hHmgr)、锁(Lock/Unlock/Reference)、句柄表插入(hInsertObject)、对象属性(vSetObjectAttr)等公共设施。_BRUSHBODY 以 protected 继承,保证只有 BRUSH 成员函数能直接访问对象体字段,外部代码通过公开成员函数访问。
2.3 BS_* 画刷样式(wingdi.h,公共 API 层面)
| 常量 | 值 | 含义 | cjGetObject 映射来源 |
|---|---|---|---|
| BS_SOLID | 0 | 纯色画刷 | BR_IS_SOLID |
| BS_NULL / BS_HOLLOW | 1 | 空(中空)画刷 | BR_IS_NULL |
| BS_HATCHED | 2 | 影线画刷 | BR_IS_HATCH |
| BS_PATTERN | 3 | 图案画刷(DDB) | BR_IS_BITMAP |
| BS_INDEXED | 4 | 索引色画刷 | --- |
| BS_DIBPATTERN | 5 | DIB 图案画刷(句柄形式) | BR_IS_DIB |
| BS_DIBPATTERNPT | 6 | DIB 图案画刷(指针形式) | --- |
| BS_PATTERN8X8 | 7 | 8×8 图案画刷 | --- |
| BS_DIBPATTERN8X8 | 8 | 8×8 DIB 图案画刷 | --- |
| BS_MONOPATTERN | 9 | 单色图案画刷 | --- |
BS_* 值并不显式存储,而是由 flAttrs 的 BR_IS_* 位推导(见 2.6 与 BRUSH::cjGetObject)。
2.4 BR_* 画刷属性标志(brush.h)
c
#define BR_NEED_FG_CLR 0x00000001 /* 需要前景色 */
#define BR_NEED_BK_CLR 0x00000002 /* 需要背景色 */
#define BR_DITHER_OK 0x00000004 /* 允许颜色抖动(dithering) */
#define BR_IS_SOLID 0x00000010 /* 纯色画刷 */
#define BR_IS_HATCH 0x00000020 /* 影线画刷 */
#define BR_IS_BITMAP 0x00000040 /* DDB 图案画刷 */
#define BR_IS_DIB 0x00000080 /* DIB 图案画刷 */
#define BR_IS_NULL 0x00000100 /* 空/中空画刷 */
#define BR_IS_GLOBAL 0x00000200 /* 库存对象(全局共享) */
#define BR_IS_PEN 0x00000400 /* 画笔 */
#define BR_IS_OLDSTYLEPEN 0x00000800 /* 几何画笔 */
#define BR_IS_DIBPALCOLORS 0x00001000 /* DIB 颜色表为调色板索引(DIB_PAL_COLORS) */
#define BR_IS_DIBPALINDICES 0x00002000 /* DIB 位图直接存设备调色板索引 */
#define BR_IS_DEFAULTSTYLE 0x00004000 /* 默认样式(样式数组无需释放) */
#define BR_IS_MASKING 0x00008000 /* 图案位图用作透明掩码 */
#define BR_IS_INSIDEFRAME 0x00010000 /* 内框画刷 */
#define BR_CACHED_ENGINE 0x00040000 /* 引擎已实现画刷已缓存 */
#define BR_CACHED_IS_SOLID 0x80000000 /* 缓存标记:纯色 */
语义要点:
- 互斥的"种类"位 :BR_IS_SOLID / BR_IS_HATCH / BR_IS_BITMAP / BR_IS_DIB / BR_IS_NULL 五者构成画刷种类,
cjGetObject按固定顺序判断(SOLID → HATCH → DIB → BITMAP → NULL)。 - 组合位 :
BR_IS_BITMAP | BR_IS_DIB表示"来自 DIB 的图案画刷"(NtGdiCreateDIBBrush 设置);BR_IS_DIBPALCOLORS/BR_IS_DIBPALINDICES是 DIB 颜色表用法的补充标记;BR_IS_NULL在 DIB_PAL_INDICES 分支被"hack"式附加(源码注释FIXME: This makes tests pass)。 - BR_IS_GLOBAL:决定画刷是否内嵌属性即可(全局)还是必须分配进程私有 BRUSH_ATTR(非全局),也决定句柄表 owner 是 PUBLIC 还是 POWNED。
2.5 BRUSH_ATTR ------ 画刷属性(ntgdihdl.h)
c
typedef struct _BRUSH_ATTR /* Used with pen too. */
{
FLONG AttrFlags;
COLORREF lbColor;
} BRUSH_ATTR, *PBRUSH_ATTR;
只有两个字段:属性标志(AttrFlags)和颜色(lbColor,注意名称与 LOGBRUSH 的 lbColor 一致,因为它正是 GetObject 要回填的颜色来源)。BRUSH 通过 pBrushAttr 指针解引用当前激活的属性:
- 全局画刷:
pBrushAttr = &BrushAttr(对象体内)。 - 进程私有画刷:
bAllocateBrushAttr()从 GDI 池分配一块,拷贝内嵌属性内容,pBrushAttr指向池中块,并vSetObjectAttr(pBrushAttr)挂到句柄表(gdi32 可通过GdiGetHandleUserData直接读写)。 - 释放时:析构函数 /
vReleaseAttribute()用GdiPoolFree(GetBrushAttrPool(), ...)归还池。
GetBrushAttrPool()声明在 brush.hpp 末尾,标有/* HACK! */------它返回进程 GDI 池(PGDI_POOL),用于分配/释放用户模式可见的画刷属性。
2.6 EBRUSHOBJ ------ 已实现画刷对象(brush.h,eng 侧使用)
c
typedef struct _EBRUSHOBJ
{
BRUSHOBJ BrushObject; // 对外暴露的 DDI 画刷对象(驱动可见)
COLORREF crRealize; // 本次实现的 RGB 颜色
ULONG ulRGBColor; // 当前 RGB 颜色(BRUSHOBJ_ulGetBrushColor 返回)
PVOID pengbrush; // 引擎实现的图案表面(SURFACE*,1bpp/目标格式)
ULONG ulSurfPalTime; // 表面调色板时间戳
ULONG ulDCPalTime; // DC 调色板时间戳
COLORREF crCurrentText; // 当前前景色(影线画刷前景)
COLORREF crCurrentBack; // 当前背景色
COLORADJUSTMENT *pca; // 颜色调整(ColorAdjustment)
SURFACE * psurfTrg; // 目标表面(可为默认位图表面)
PALETTE * ppalSurf; // 目标表面调色板(引用计数 +1)
PALETTE * ppalDC; // DC 调色板(引用计数 +1,缺省 gppalDefault)
PALETTE * ppalDIB; // DIB 画刷专用调色板(FixupDIBBrushPalette 产物)
PBRUSH pbrush; // 回指逻辑画刷
FLONG flattrs; // 逻辑画刷 flAttrs 的快照(BR_IS_* 位)
DWORD ulUnique; // 逻辑画刷 ulBrushUnique 快照
SURFOBJ * psoMask; // 掩码表面(影线画刷 = 影线位图;1bpp)
} EBRUSHOBJ, *PEBRUSHOBJ;
设计要点:
- EBRUSHOBJ 是"逻辑画刷 × DC × 目标表面"三者的交汇 。每个 DC 内嵌两个 EBRUSHOBJ(
pdc->eboFill填充画刷、pdc->eboLine线条画刷,见 dcobjs.c),当SelectObject换画刷或颜色变化时通过EBRUSHOBJ_vUpdateFromDC重新初始化。 - BRUSHOBJ 是 DDI 契约 :
BrushObject成员即驱动看到的BRUSHOBJ(含 iSolidColor、pvRbrush 等),CONTAINING_RECORD(pbo, EBRUSHOBJ, BrushObject)宏(如 BRUSHOBJ_pvGetRbrush、BRUSHOBJ_ulGetBrushColor 中)用于从驱动传回的 BRUSHOBJ* 反推出 EBRUSHOBJ*。 - pengbrush 与 pvRbrush 双轨 :
EBRUSHOBJ_bRealizeBrush(pebo, bCallDriver)中,bCallDriver=TRUE 时调用显示驱动DrvRealizeBrush(产物在BrushObject.pvRbrush,走BRUSHOBJ_pvGetRbrush惰性触发);bCallDriver=FALSE 时调用引擎自带的EngRealizeBrush(产物在pengbrush,走EBRUSHOBJ_pvGetEngBrush/EBRUSHOBJ_psoPattern惰性触发)。 - iSolidColor 三态 :
EBRUSHOBJ_vInit中------BR_IS_NULL → 0;BR_IS_SOLID → 经EBRUSHOBJ_vSetSolidRGBColor转换为目标格式索引;图案画刷 →0xFFFFFFFF(表示"非纯色,需要图案表面")。
2.7 BRUSH 类(brush.hpp)------ 内存分配与锁
cpp
class BRUSH : public BASEOBJECT, protected _BRUSHBODY
{
public:
void* operator new(size_t cjSize) throw()
{
return ExAllocatePoolWithTag(PagedPool, cjSize, GDITAG_HMGR_BRUSH_TYPE);
//return BASEOBJECT::pvAllocate(GDIObjType_BRUSH_TYPE, cjSize);
}
void operator delete(void *pvObject)
{
/// HACK! better would be to extract the exact object type's tag
ExFreePool(pvObject);
}
...
};
- operator new :从**分页池(PagedPool)**以
GDITAG_HMGR_BRUSH_TYPE标签分配对象内存(注释掉的旧方案是走 BASEOBJECT 分配器)。 - operator delete :
ExFreePool直接释放(注释承认"无法精确提取对象类型的标签"是个 HACK)。 - 三种锁 (全部基于
BASEOBJECT::LockShared,类型强制为GDILoObjType_LO_BRUSH_TYPE):LockForRead(hbr):OWNER::PUBLIC------要求对象为公共(全局)所有,常用于只读查询。LockForWrite(hbr):OWNER::POWNED------要求对象为进程私有,用于修改。LockAny(hbr):OWNER::NONE------不限制所有权,用于无法预先判断归属的场合(如IntGdiSetSolidBrushColor、NtGdiSetBrushAttributes)。
- 锁函数失败返回 NULL(句柄无效或类型不符),调用方必须判空。
3. 构造函数与析构函数
3.1 BRUSH::BRUSH(构造函数)
cpp
BRUSH::BRUSH(
_In_ FLONG flAttrs,
_In_ COLORREF crColor,
_In_ ULONG iHatch,
_In_opt_ HBITMAP hbmPattern,
_In_opt_ PVOID pvClient,
_In_ GDILOOBJTYPE loobjtype = GDILoObjType_LO_BRUSH_TYPE)
: BASEOBJECT(loobjtype)
{
static ULONG ulGlobalBrushUnique = 0;
this->ulBrushUnique = InterlockedIncrementUL(&ulGlobalBrushUnique);
...
}
实现流程:
InterlockedIncrementUL从静态计数器取全局唯一序号存入ulBrushUnique(无锁原子自增,供引擎侧缓存与 EBRUSHOBJ.ulUnique 比对)。pBrushAttr = &this->BrushAttr------默认使用内嵌属性(后续bAllocateBrushAttr可改指向池中块)。- 直接赋值参数:
flAttrs、iHatch、hbmPattern、hbmClient = (HBITMAP)pvClient、pBrushAttr->lbColor = crColor。 - 零初始化其余字段:
ptOrigin=(0,0)、bCacheGrabbed=FALSE、crBack=0、crFore=0、ulPalTime=0、ulSurfTime=0、pvRBrush=NULL、hdev=NULL。 - 顺带初始化画笔字段
dwStyleCount=0、pStyle=NULL------注释说明"理论上只在 PEN 构造函数做,但析构函数需要",保证析构时pStyle判定安全。
注意:构造函数不 对 hbmPattern 增加引用计数------约定"画刷接管位图所有权"(CreateBrushInternal 注释:brush takes ownership of the bitmap),失败路径由调用方负责清理。
3.2 BRUSH::~BRUSH(析构函数)
cpp
BRUSH::~BRUSH(VOID)
{
if (this->pBrushAttr != &this->BrushAttr)
{
GdiPoolFree(GetBrushAttrPool(), this->pBrushAttr); // 释放进程私有属性
}
if (this->hbmPattern != NULL && GreIsHandleValid(this->hbmPattern))
{
GreSetBitmapOwner(this->hbmPattern, BASEOBJECT::OWNER::POWNED);
GreDeleteObject(this->hbmPattern); // 删除图案位图
}
if ((this->pStyle != NULL) && !(this->flAttrs & BR_IS_DEFAULTSTYLE))
{
ExFreePoolWithTag(this->pStyle, GDITAG_PENSTYLE); // 释放画笔样式数组
}
}
清理三件事:
- 进程私有 BRUSH_ATTR :若
pBrushAttr指向池中块(而非内嵌),归还给进程 GDI 池。 - 图案位图 :若
hbmPattern有效,先GreSetBitmapOwner(..., POWNED)再GreDeleteObject。注释提示:位图可能已在 GDI 清理流程中先行删除,所以必须GreIsHandleValid校验。GreDeleteObject会真正释放位图(此时画刷是最后一个引用者)。 - 画笔样式数组 :仅当 pStyle 非空且非默认样式时释放(
BR_IS_DEFAULTSTYLE表示样式是静态/共享的,不能释放)。
3.3 BRUSH::vDeleteObject(静态)与 C 包装 BRUSH_vDeleteObject
cpp
VOID BRUSH::vDeleteObject(_In_ PVOID pvObject)
{
PBRUSH pbr = static_cast<PBRUSH>(pvObject);
NT_ASSERT((GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_BRUSH_TYPE) ||
(GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_PEN_TYPE) ||
(GDI_HANDLE_GET_TYPE(pbr->hHmgr()) == GDILoObjType_LO_EXTPEN_TYPE));
delete pbr;
}
- 断言句柄表中对象类型必须是 BRUSH / PEN / EXTPEN 三者之一------这正是"BRUSHBODY 与 PEN 共用布局、共用释放回调"的体现。
delete pbr触发~BRUSH()(虚析构经由 BASEOBJECT 释放机制)。- C 包装:
cpp
VOID NTAPI BRUSH_vDeleteObject(PVOID pvObject)
{
BRUSH::vDeleteObject(pvObject);
}
该函数注册为 GDI 对象类型 BRUSH 的析构回调(GDIOBJ 句柄表释放对象时调用)。
3.4 BRUSH::bAllocateBrushAttr ------ 分配进程私有属性
cpp
BOOL BRUSH::bAllocateBrushAttr(VOID)
{
NT_ASSERT(this->pBrushAttr == &this->BrushAttr);
pBrushAttr = static_cast<PBRUSH_ATTR>(GdiPoolAllocate(GetBrushAttrPool()));
if (pBrushAttr == NULL) { ERR("Could not allocate brush attr\n"); return FALSE; }
this->pBrushAttr = pBrushAttr;
*this->pBrushAttr = this->BrushAttr; // 拷贝内嵌属性作为初始值
vSetObjectAttr(pBrushAttr); // 登记到句柄表(gdi32 可见)
return TRUE;
}
要点:
- 前置断言要求当前仍使用内嵌属性(防止重复分配泄漏)。
- 从进程 GDI 池分配后整体拷贝内嵌属性(AttrFlags + lbColor),保证私有属性初始值与全局一致。
vSetObjectAttr(pBrushAttr)把池地址写入句柄表条目,使 gdi32 的GdiGetHandleUserData能拿到它(这就是 win32k 与 user32 共享画刷属性的通道)。- 返回 FALSE 时调用方(CreateBrushInternal)会删除画刷对象。
3.5 BRUSH::vReleaseAttribute ------ 释放私有属性回到内嵌
cpp
VOID BRUSH::vReleaseAttribute(VOID)
{
if (this->pBrushAttr != &this->BrushAttr)
{
this->BrushAttr = *this->pBrushAttr; // 回写内嵌
GdiPoolFree(GetBrushAttrPool(), this->pBrushAttr);
this->pBrushAttr = &this->BrushAttr;
}
}
与 bAllocateBrushAttr 相反:把私有属性当前值回写到内嵌 BrushAttr,归还池块,指针复位。这是 NtGdiSetBrushAttributes(hbr, SC_BB_STOCKOBJ) 的核心动作------画刷转为"库存/全局"语义时放弃私有属性。
4. 成员函数展开
4.1 BRUSH::vSetSolidColor
cpp
VOID BRUSH::vSetSolidColor(_In_ COLORREF crColor)
{
NT_ASSERT(this->flAttrs & BR_IS_SOLID); // 只能用于纯色画刷
this->pBrushAttr->lbColor = crColor & 0xFFFFFF; // 屏蔽高位(COLORREF 低 24 位为 RGB)
this->ulPalTime = -1; // 调色板时间戳失效
this->ulSurfTime = -1; // 表面时间戳失效
}
- 断言画刷必须是 BR_IS_SOLID(否则修改颜色无意义)。
- 颜色写入当前激活的属性 (
pBrushAttr,可能是进程私有块)。 crColor & 0xFFFFFF确保只保留 RGB 分量。ulPalTime = ulSurfTime = -1强制下一次实现(realize)时忽略调色板/表面缓存------颜色变了,旧的已实现画刷作废。
调用者:IntGdiSetSolidBrushColor(见 6.4)、IntSetSysColors(系统颜色变化时批量刷新系统画刷)。
4.2 BRUSH::hbmGetBitmapHandle
cpp
HBITMAP BRUSH::hbmGetBitmapHandle(_Out_ PUINT puUsage) const
{
*puUsage = (this->flAttrs & BR_IS_DIBPALCOLORS) ? DIB_PAL_COLORS :
(this->flAttrs & BR_IS_DIBPALINDICES) ? DIB_PAL_INDICES :
DIB_RGB_COLORS;
return this->hbmPattern;
}
- 按属性位推导 DIB 颜色用法:
BR_IS_DIBPALCOLORS → DIB_PAL_COLORS(1)、BR_IS_DIBPALINDICES → DIB_PAL_INDICES(2)、否则DIB_RGB_COLORS(0)。 - 返回
hbmPattern(内核持有的图案位图)。注意常量定义位置:DIB_PAL_COLORS 在 wingdi.h(值 0),DIB_PAL_INDICES 在 ntgdityp.h(值 2,ReactOS 扩展),DIB_PAL_BRUSHHACK 在 dib.h(值 3,内部使用)。 - 供
NtGdiGetObjectBitmapHandle(见 6.10)调用,该 Nt 服务是 gdi32 实现GetObject对 BS_PATTERN/BS_DIBPATTERN 画刷返回位图句柄的底层。
4.3 BRUSH::cjGetObject ------ 填充 LOGBRUSH
cpp
UINT BRUSH::cjGetObject(_In_ UINT cjSize, _Out_bytecap_(cjSize) PLOGBRUSH plb) const
{
if (plb == NULL) return sizeof(LOGBRUSH); // 只询问大小
if (cjSize == 0) return 0; // 缓冲区非法
plb->lbColor = this->BrushAttr.lbColor; // 注意:总是内嵌属性的颜色
if (this->flAttrs & BR_IS_SOLID) { plb->lbStyle = BS_SOLID; plb->lbHatch = 0; }
else if (this->flAttrs & BR_IS_HATCH) { plb->lbStyle = BS_HATCHED; plb->lbHatch = this->iHatch; }
else if (this->flAttrs & BR_IS_DIB) { plb->lbStyle = BS_DIBPATTERN; plb->lbHatch = (ULONG_PTR)this->hbmClient; }
else if (this->flAttrs & BR_IS_BITMAP){ plb->lbStyle = BS_PATTERN; plb->lbHatch = (ULONG_PTR)this->hbmClient; }
else if (this->flAttrs & BR_IS_NULL) { plb->lbStyle = BS_NULL; plb->lbHatch = 0; }
else { NT_ASSERT(FALSE); }
return sizeof(LOGBRUSH);
}
规则与细节:
- 两段式查询 :plbNULL 时只返回所需字节数(GetObject 常见用法:先查大小再填数据);cjSize0 视为非法返回 0;否则必须至少有 LOGBRUSH 大小。
- 样式映射:严格按 SOLID → HATCH → DIB → BITMAP → NULL 优先级。注意 DIB 在 BITMAP 之前判断------因为 DIB 画刷同时带 BR_IS_BITMAP | BR_IS_DIB 两个位,必须先判 DIB 才能返回 BS_DIBPATTERN。
- lbHatch 语义随样式不同 :
- BS_HATCHED:填影线样式号 iHatch(0~5)。
- BS_PATTERN / BS_DIBPATTERN:填客户端位图句柄 (hbmClient)------这正是 Win32
GetObject对图案画刷返回"创建时传入的位图"的语义,而内核副本 hbmPattern 对用户不可见。
- lbColor 来源 :取
this->BrushAttr.lbColor(内嵌属性),而非pBrushAttr------对 stock 对象两者一致;对私有画刷,内嵌属性在 bAllocateBrushAttr 时已拷贝过初始值,但注意修改颜色走的是 pBrushAttr(vSetSolidColor),因此对私有画刷 GetObject 可能返回旧颜色------这是当前实现的已知行为(以源码为准)。 - C 包装
BRUSH_GetObject(FASTCALL)直接转发:
cpp
INT FASTCALL BRUSH_GetObject(PBRUSH pbr, INT cjBuffer, LPLOGBRUSH plbBuffer)
{
return pbr->cjGetObject(cjBuffer, plbBuffer);
}
gdi32 GetObject 对画刷的调用链:NtGdiGetObject → win32k 按对象类型分发 → BRUSH_GetObject。
5. 内部工厂 CreateBrushInternal(static)
cpp
static HBRUSH
CreateBrushInternal(
_In_ ULONG flAttrs,
_In_ COLORREF crColor,
_In_ ULONG iHatch,
_In_opt_ HBITMAP hbmPattern,
_In_opt_ PVOID pvClient)
{
BASEOBJECT::OWNER owner;
PBRUSH pbr;
HBRUSH hbr;
NT_ASSERT(((flAttrs & BR_IS_BITMAP) == 0) || (hbmPattern != NULL));
...
}
流程(所有创建入口的公共路径):
- 参数断言:带 BR_IS_BITMAP 标志必须同时提供非空 hbmPattern。
- 分配对象 :
pbr = new BRUSH(flAttrs, crColor, iHatch, hbmPattern, pvClient)。若失败,画刷未接管位图所有权,由本函数负责归还:GreSetBitmapOwner(hbmPattern, POWNED); GreDeleteObject(hbmPattern);返回 NULL。(注释:brush takes ownership of the bitmap。) - 私有属性 :若非
BR_IS_GLOBAL,调用pbr->bAllocateBrushAttr();失败则delete pbr返回 NULL(此时析构函数会清理位图------所有权已转移)。 - owner 决策 :
BR_IS_GLOBAL ? PUBLIC : POWNED。PUBLIC 对象所有进程可见、不随进程退出销毁(库存对象);POWNED 对象归创建进程所有,进程退出时自动清理。 - 插入句柄表 :
hbr = pbr->hInsertObject(owner);失败delete pbr返回 NULL。 - 解锁 :
pbr->vUnlock()(hInsertObject 内部已加锁,插入完成解锁)返回 hbr。
所有权规则汇总:
| 路径 | 位图所有权 |
|---|---|
| 成功 | 画刷持有(析构时 GreDeleteObject) |
| new 失败 | CreateBrushInternal 直接删除(此时所有权尚未移交) |
| bAllocateBrushAttr / hInsertObject 失败 | delete pbr → 析构函数删除(所有权已移交) |
| NtGdiCreatePatternBrushInternal 中 BITMAP_CopyBitmap 失败 | 返回 NULL,不影响原图 |
6. 全部导出函数逐一展开
6.1 BRUSH_vDeleteObject(C 释放回调)
- 签名 :
VOID NTAPI BRUSH_vDeleteObject(PVOID pvObject) - 作用:GDI 句柄表释放 BRUSH 类型对象时的回调。
- 实现 :转发给
BRUSH::vDeleteObject(见 3.3),即delete pbr。 - 使用方式 :注册于 GDI 对象类型表(GDIOBJ),
DeleteObject/进程清理/句柄表回收时由内核调用;外部模块一般不直接调用。 - 注意:被删对象类型须为 BRUSH/PEN/EXTPEN 之一(断言);图案位图与私有属性的清理都在析构函数内完成。
6.2 BRUSH_GetObject
- 签名 :
INT FASTCALL BRUSH_GetObject(PBRUSH GdiObject, INT Count, LPLOGBRUSH Buffer) - 作用:GetObject 的画刷分支------把逻辑画刷导出为 LOGBRUSH。
- 实现 :一行转发
pbr->cjGetObject(cjBuffer, plbBuffer)(见 4.3)。 - 使用方式 :由
NtGdiGetObject按类型分发调用;gdi32 的GetObject(hbr, ...)最终落到这里。 - 注意:Count 语义是"缓冲区字节数";返回值为实际写入字节数或所需字节数(Buffer==NULL 时)。
6.3 IntGdiCreateNullBrush
- 签名 :
HBRUSH NTAPI IntGdiCreateNullBrush(VOID) - 作用:创建空(中空)画刷------填充时不产生任何像素。
- 实现 :
return CreateBrushInternal(BR_IS_NULL | BR_IS_GLOBAL, 0, 0, NULL, NULL);------种类位 BR_IS_NULL,全局对象,无颜色无影线无图案。 - 使用方式 :win32k 内部(如 stockobj.c 创建库存 NULL_BRUSH:
StockObjects[NULL_BRUSH] = IntGdiCreateNullBrush();)。 - 注意 :由于带 BR_IS_GLOBAL,不分配私有属性,owner=PUBLIC;EBRUSHOBJ_vInit 遇到 BR_IS_NULL 直接
iSolidColor = 0且不做任何颜色转换。
6.4 IntGdiCreateSolidBrush
- 签名 :
HBRUSH APIENTRY IntGdiCreateSolidBrush(COLORREF crColor) - 作用:创建全局纯色画刷。
- 实现 :
CreateBrushInternal(BR_IS_SOLID | BR_IS_GLOBAL, crColor, 0, NULL, NULL)。 - 使用方式 :win32k 内部高频接口------库存对象(stockobj.c 的 WHITE_BRUSH/LTGRAY_BRUSH/GRAY_BRUSH/DKGRAY_BRUSH/BLACK_BRUSH/DC_BRUSH)、系统颜色画刷(
CreateSysColorObjects:gpsi->ahbrSystem[i] = IntGdiCreateSolidBrush(SysColors[i]))、ntuser 菜单(menu.c:IntGdiCreateSolidBrush(color))等。 - 注意:与 NtGdiCreateSolidBrush 的差异仅在 BR_IS_GLOBAL------内部接口创建"全局共享"画刷,系统调用入口创建"进程私有"画刷。
6.5 IntGdiCreatePatternBrush
- 签名 :
HBRUSH NTAPI IntGdiCreatePatternBrush(HBITMAP hbmPattern) - 作用:用已有位图创建全局图案画刷(BS_PATTERN)。
- 实现 :
NT_ASSERT(hbmPattern != NULL)。GreSetBitmapOwner(hbmPattern, BASEOBJECT::OWNER::PUBLIC)------把位图改为公共所有(因为画刷是全局的)。CreateBrushInternal(BR_IS_BITMAP | BR_IS_GLOBAL, 0, 0, hbmPattern, NULL)。
- 使用方式 :ntuser.c 创建灰色画刷:
gpsi->hbrGray = IntGdiCreatePatternBrush(hPattern55AABitmap)。 - 注意:位图句柄直接引用(不拷贝);pvClient=NULL 所以 GetObject 的 lbHatch 返回 0。
6.6 IntGdiSetSolidBrushColor
- 签名 :
VOID NTAPI IntGdiSetSolidBrushColor(_In_ HBRUSH hbr, _In_ COLORREF crColor) - 作用:修改纯色画刷颜色(内核内部接口)。
- 实现 :
pbr = BRUSH::LockAny(hbr)(不限制 owner,兼容全局/私有画刷);失败 ERR 返回。pbr->vSetSolidColor(crColor)(断言 BR_IS_SOLID,写 pBrushAttr->lbColor,失效时间戳)。pbr->vUnlock()。
- 使用方式 :
IntSetSysColors(stockobj.c)------系统颜色变更时刷新gpsi->ahbrSystem[Element]的颜色。 - 注意 :与 gdi32 的
SetDCBrushColor(DC 层颜色)不同------这是"改画刷对象本身颜色"的接口。DC 的 DC_BRUSH 颜色走IntSetDCBrushColor(见第 9 节链路)。
6.7 NtGdiCreateSolidBrush
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiCreateSolidBrush(_In_ COLORREF crColor, _In_opt_ HBRUSH hbr) - 作用:CreateSolidBrush 的系统调用入口(gdi32 转发)。
- 实现 :
- 若 hbr 非空:
WARN("hbr is not supported, ignoring\n")------第二参数(Windows 用于缓存/复用)未实现,忽略。 return CreateBrushInternal(BR_IS_SOLID, crColor, 0, NULL, NULL);------不带 BR_IS_GLOBAL,即进程私有画刷(POWNED)。
- 若 hbr 非空:
- 使用方式 :应用
CreateSolidBrush→ gdi32 → NtGdiCreateSolidBrush。 - 注意:与 IntGdiCreateSolidBrush 的关键区别就是 BR_IS_GLOBAL 位;私有画刷会走 bAllocateBrushAttr 分配进程私有属性,GetObject 可见。
6.8 NtGdiCreateHatchBrushInternal
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiCreateHatchBrushInternal(_In_ ULONG iHatch, _In_ COLORREF crColor, _In_ BOOL bPen) - 作用:CreateHatchBrush 的系统调用入口。
- 实现 :
- bPen 非 0:
WARN("bPen is not supported, ignoring\n")。 - 影线号归一化 :
iHatch < HS_DDI_MAX(6)→flAttr = BR_IS_HATCH(真正的影线画刷,0...5 对应六种标准样式)。iHatch < HS_API_MAX(12)→flAttr = BR_IS_SOLID(6...11 是"伪影线"------Windows 把 HS_API 范围内的编号按纯色处理,以兼容旧应用)。- 其余 →
ERR("Invalid iHatch: %lu\n"),返回 NULL。
CreateBrushInternal(flAttr, crColor, iHatch, NULL, NULL)------注意 iHatch 原样传入并保存在 BRUSHBODY.iHatch。
- bPen 非 0:
- 使用方式 :
CreateHatchBrush(HS_BDIAGONAL, RGB(...))。 - 注意 :HS_DDI_MAX=6 定义在 winddi.h,HS_API_MAX=12 定义在 wingdi.h;影线画刷的图案不在此处创建------绘制时 EBRUSHOBJ 层从
ppdev->ahsurf[iHatch](每 PDEV 的影线位图缓存,源自 InitBrushImpl 的 gahsurfHatch)取用。
6.9 NtGdiCreatePatternBrushInternal
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiCreatePatternBrushInternal(_In_ HBITMAP hbmClient, _In_ BOOL bPen, _In_ BOOL b8X8) - 作用:CreatePatternBrush 的系统调用入口。
- 实现 :
- b8X8 / bPen 非 0:WARN 忽略(8×8 优化与画笔变体未实现)。
hbmPattern = BITMAP_CopyBitmap(hbmClient)------深拷贝用户位图,内核持有副本(防止用户随后修改/删除原图影响画刷)。- 失败:ERR 返回 NULL。
CreateBrushInternal(BR_IS_BITMAP, 0, 0, hbmPattern, hbmClient)------hbmClient 作为 pvClient 保存(GetObject 返回原句柄),失败时内部会删除 hbmPattern 副本。
- 使用方式 :
CreatePatternBrush(hbm)。 - 注意 :与 IntGdiCreatePatternBrush 的区别:系统调用入口拷贝 位图且画刷为进程私有(无 BR_IS_GLOBAL);内部接口直接引用公共位图。两处 hbmClient/hbmPattern 的区分体现了"用户句柄 ↔ 内核句柄"隔离。
6.10 NtGdiCreateDIBBrush
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiCreateDIBBrush(_In_reads_bytes_(cj) PVOID pv, _In_ FLONG uUsage, _In_ UINT cj, _In_ BOOL b8X8, _In_ BOOL bPen, _In_ PVOID pvClient) - 作用:CreateDIBPatternBrushPt / CreateDIBPatternBrush 的系统调用入口------由打包 DIB(BITMAPINFO + 像素)创建图案画刷。
- 实现流程 :
- b8X8 / bPen:WARN 忽略。
- 参数校验 :
uUsage > DIB_PAL_INDICES(2)→ERR+EngSetLastError(ERROR_INVALID_PARAMETER)返回 NULL(uUsage 只允许 DIB_RGB_COLORS=0 / DIB_PAL_COLORS=1 / DIB_PAL_INDICES=2)。 - 安全拷贝用户内存 :
ExAllocatePoolWithTag(PagedPool, cj, GDITAG_TEMP)分配临时缓冲;_SEH2_TRY { ProbeForRead(pv, cj, 1); RtlCopyMemory(...); }------对用户指针做探测读取,异常转入 cleanup。 - 属性组合 :
flAttrs = BR_IS_BITMAP | BR_IS_DIB。uUsage == DIB_PAL_COLORS:追加BR_IS_DIBPALCOLORS,并把 uUsage 改成内部专用值DIB_PAL_BRUSHHACK(3)(dib.h)------见注释:用 BRUSHHACK 创建"特殊"调色板(后续 FixupDIBBrushPalette 依赖)。uUsage == DIB_PAL_INDICES:追加BR_IS_DIBPALINDICES,并追加 BR_IS_NULL (源码注释FIXME: This makes tests pass, but needs investigation.------使 GetObject 表现为 BS_NULL 的兼容性 hack)。
hbm = GreCreateDIBitmapFromPackedDIB(pvPackedDIB, cj, uUsage)从打包 DIB 构造内核位图;失败 goto cleanup。CreateBrushInternal(flAttrs, 0, 0, hbm, pvClient)(失败时内部删除 hbm)。- cleanup:
ExFreePoolWithTag(pvPackedDIB, GDITAG_TEMP)释放临时缓冲,返回 hbr。
- 使用方式 :
CreateDIBPatternBrushPt(pvPackedDIB, DIB_RGB_COLORS)。 - 注意:pvClient 是"客户端"指针/句柄原样保存(不探测其内容,仅作为 GetObject 回填值);DIB 位图在 EBRUSHOBJ_bRealizeBrush 实现时才结合 DC 调色板做最终转换。
6.11 NtGdiGetObjectBitmapHandle
- 签名 :
__kernel_entry HBITMAP APIENTRY NtGdiGetObjectBitmapHandle(_In_ HBRUSH hbr, _Out_ UINT *piUsage) - 作用:取图案画刷的位图句柄与颜色用法。
- 实现 :
pbr = BRUSH::LockForRead(hbr)(要求 PUBLIC 所有权);失败 ERR 返回 NULL。hbm = pbr->hbmGetBitmapHandle(&uUsage)。pbr->vUnlock()。_SEH2_TRY { ProbeForWrite(piUsage, sizeof(*piUsage), 1); *piUsage = uUsage; }------写回用户缓冲,异常时置 hbm=NULL。- 返回 hbm。
- 使用方式:gdi32 GetObject 对图案画刷(BS_PATTERN / BS_DIBPATTERN)取 lbHatch 位图句柄的底层服务(gdi32 侧无法直接读内核句柄,需要此 Nt 服务)。
- 注意 :返回的是内核持有的图案位图(hbmPattern),而 GetObject 最终回填给应用的是 hbmClient------语义转换发生在 gdi32 层。
6.12 NtGdiSetBrushAttributes
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiSetBrushAttributes(_In_ HBRUSH hbr, _In_ DWORD dwFlags) - 作用:把画刷转为库存(stock)语义。
- 实现 :
- 仅支持
SC_BB_STOCKOBJ(1)标志(ntgdihdl.h);无该位直接返回 NULL。 GDIOBJ_ConvertToStockObj((HGDIOBJ*)&hbr)------句柄表层面把画刷条目转换为 stock 对象(成功后 hbr 被改写)。失败返回 NULL。- 成功:
pbr = BRUSH::LockAny(hbr);pbr->vReleaseAttribute()(放弃进程私有属性,回写内嵌,见 3.5);vUnlock();返回 hbr。
- 仅支持
- 使用方式 :gdi32
SetBrushAttributes(hbr, SC_BB_STOCKOBJ)(brush.c,校验dwFlags & ~SC_BB_STOCKOBJ后调用)。应用层直接调用者为数极少,主要供 COM/OLE 与兼容性场景。 - 注意:转换后画刷变为全局共享,进程退出不再销毁它;属性回写内嵌后 GetObject 仍能读到最新颜色(vReleaseAttribute 先回写再释放)。
6.13 NtGdiClearBrushAttributes
- 签名 :
__kernel_entry HBRUSH APIENTRY NtGdiClearBrushAttributes(_In_ HBRUSH hbr, _In_ DWORD dwFlags) - 作用:把库存画刷转回进程私有语义(SetBrushAttributes 的逆操作)。
- 实现 :
- 仅支持 SC_BB_STOCKOBJ;否则返回 NULL。
GDIOBJ_ConvertFromStockObj((HGDIOBJ*)&hbr)------句柄表条目从 stock 转普通(成功后 hbr 被改写)。失败返回 NULL。- 成功:
LockAny;bAllocateBrushAttr()(重新分配进程私有属性并拷贝内嵌值);失败仅 ERR(画刷仍可用);vUnlock();返回 hbr。
- 使用方式 :gdi32
ClearBrushAttributes(hbr, SC_BB_STOCKOBJ)。 - 注意:与 Set 对称------Set 是"私有 → 全局",Clear 是"全局 → 私有";Clear 后画刷随进程退出销毁。
7. 各画刷类型创建流程详解
7.1 纯色画刷(BS_SOLID)
CreateSolidBrush(RGB) ── gdi32 ──> NtGdiCreateSolidBrush(crColor, hbr=NULL)
└─ CreateBrushInternal(BR_IS_SOLID, crColor, 0, NULL, NULL)
├─ new BRUSH:flAttrs=BR_IS_SOLID, BrushAttr.lbColor=crColor
├─ 非全局 → bAllocateBrushAttr()(进程私有属性)
└─ hInsertObject(POWNED)
关键点:颜色在创建时存入 pBrushAttr->lbColor;绘制时 EBRUSHOBJ_vInit 检测 BR_IS_SOLID 调 EBRUSHOBJ_vSetSolidRGBColor,把 RGB 经 XLATEOBJ 转成目标表面的颜色索引(iSolidColor),纯色画刷不需要 图案表面。修改颜色用 vSetSolidColor(置时间戳 -1)。
7.2 影线画刷(BS_HATCHED)
CreateHatchBrush(iHatch, crColor) ── gdi32 ──> NtGdiCreateHatchBrushInternal(iHatch, crColor, bPen)
├─ iHatch<6 → BR_IS_HATCH(真影线)
├─ iHatch<12 → BR_IS_SOLID(兼容纯色)
└─ 否则 → NULL
└─ CreateBrushInternal(flAttr, crColor, iHatch, NULL, NULL)
关键点:
- 影线图案不在创建时生成 。系统初始化时
InitBrushImpl()已把六种 8×8 1bpp 图案(gaulHatchBrushes 表)用 EngCreateBitmap 缓存到全局gahsurfHatch[6];每个 PDEV 的ahsurf[]持有自己的副本(源自全局表)。 - 绘制时
EBRUSHOBJ_bRealizeBrush:BR_IS_HATCH→hbmPattern = ppdev->ahsurf[pbr->iHatch]、iHatch = pbr->iHatch,并把pbr->BrushAttr.lbColor作为影线前景色(EBRUSHOBJ_vInit:pebo->crCurrentText = pbrush->BrushAttr.lbColor)。 - 掩码:
EBRUSHOBJ_psoMask对影线画刷直接取ppdev->ahsurf[iHatch](1bpp),供 DrvRealizeBrush 使用。
7.3 图案画刷(BS_PATTERN,DDB)
CreatePatternBrush(hbm) ── gdi32 ──> NtGdiCreatePatternBrushInternal(hbm, bPen, b8X8)
├─ hbmPattern = BITMAP_CopyBitmap(hbm) ← 深拷贝用户位图
└─ CreateBrushInternal(BR_IS_BITMAP, 0, 0, hbmPattern, hbmClient=hbm)
关键点:
- 双句柄:内核持有副本 hbmPattern(析构时删除),用户原图作为 hbmClient 仅用于 GetObject 回填。
- 绘制时
EBRUSHOBJ_bRealizeBrush:非影线分支hbmPattern = pbr->hbmPattern、iHatch = -1;SURFACE_ShareLockSurface锁定后交给 DrvRealizeBrush 或 EngRealizeBrush 实现为目标格式图案表面。 IntGdiCreatePatternBrush(内部接口)则不拷贝,直接引用公共位图(ntuser 的 hbrGray 场景)。
7.4 DIB 图案画刷(BS_DIBPATTERN)
CreateDIBPatternBrushPt(pvPackedDIB, uUsage) ── gdi32 ──> NtGdiCreateDIBBrush(pv, uUsage, cj, ...)
├─ ProbeForRead + RtlCopyMemory(SEH 保护拷贝)
├─ uUsage=DIB_PAL_COLORS → BR_IS_DIBPALCOLORS, uUsage=DIB_PAL_BRUSHHACK
├─ uUsage=DIB_PAL_INDICES → BR_IS_DIBPALINDICES (+BR_IS_NULL hack)
├─ hbm = GreCreateDIBitmapFromPackedDIB(pvPackedDIB, cj, uUsage)
└─ CreateBrushInternal(BR_IS_BITMAP|BR_IS_DIB, 0, 0, hbm, pvClient)
关键点:
- BRUSHHACK 调色板 :DIB_PAL_COLORS 的 DIB 颜色表是"16 位调色板索引",内核用
DIB_PAL_BRUSHHACK值创建位图时,调色板索引被以peRed=LOWORD, peGreen=HIWORD方式存进特殊调色板。 - 实现时颜色修复 :
EBRUSHOBJ_bRealizeBrush检测 BR_IS_DIBPALCOLORS →FixupDIBBrushPalette(psurfPattern->ppal, pebo->ppalDC):为每个颜色读回 WORD 索引(peRed | peGreen<<8)、%= ppalDC->NumColors防越界、再取 DC 调色板对应 RGB 存入新调色板pebo->ppalDIB(引用计数管理,vCleanup 释放)。 - DIB_PAL_INDICES:位图像素本身是设备调色板索引,无需颜色表;BR_IS_NULL hack 使其 GetObject 表现为 BS_NULL。
8. 与 EBRUSHOBJ 的实现关系(engbrush.c)
8.1 InitBrushImpl ------ 影线位图预生成
c
static const ULONG gaulHatchBrushes[HS_DDI_MAX][8] =
{
{0xFF,0xFF,0xFF,0xFF,0x00,0xFF,0xFF,0xFF}, /* HS_HORIZONTAL */
{0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,0xF7,0xF7}, /* HS_VERTICAL */
{0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F}, /* HS_FDIAGONAL */
{0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE}, /* HS_BDIAGONAL */
{0xF7,0xF7,0xF7,0xF7,0x00,0xF7,0xF7,0xF7}, /* HS_CROSS */
{0x7E,0xBD,0xDB,0xE7,0xE7,0xDB,0xBD,0x7E} /* HS_DIAGCROSS */
};
HSURF gahsurfHatch[HS_DDI_MAX];
CODE_SEG("INIT")
NTSTATUS NTAPI InitBrushImpl(VOID)
{
for (i = 0; i < HS_DDI_MAX; i++)
gahsurfHatch[i] = (HSURF)EngCreateBitmap(sizl={8,8}, 0, BMF_1BPP, 0,
(PVOID)gaulHatchBrushes[i]);
return STATUS_SUCCESS;
}
- 六组 8 字节即 8×8 的 1bpp 位图(每字节一行,0 位为影线像素)。
- 代码段标
CODE_SEG("INIT"):仅初始化阶段使用,之后可被分页/回收。
8.2 EBRUSHOBJ_vInit ------ 初始化
c
VOID NTAPI EBRUSHOBJ_vInit(EBRUSHOBJ *pebo, PBRUSH pbrush, PSURFACE psurf,
COLORREF crBackgroundClr, COLORREF crForegroundClr,
PPALETTE ppalDC)
流程:
- 清零
BrushObject.flColorType、pvRbrush;记录pbrush、pengbrush=NULL、flattrs = pbrush->flAttrs、psoMask=NULL。 - 记录前景/背景色:
crCurrentBack、crCurrentText。 - 目标表面:
psurfTrg = psurf;为空(新内存 DC)则用psurfDefaultBitmap;断言ppal存在。 - 调色板引用:
ppalSurf = psurfTrg->ppal(GDIOBJ_vReferenceObjectByPointer引用 +1);ppalDC = ppalDC ?: gppalDefault(引用 +1);ppalDIB = NULL。 - 按种类初始化 iSolidColor :
- BR_IS_NULL →
iSolidColor = 0(无需颜色)。 - BR_IS_SOLID →
EBRUSHOBJ_vSetSolidRGBColor(pebo, pbrush->BrushAttr.lbColor)。 - 其它(图案)→
iSolidColor = 0xFFFFFFFF(非纯色标记);若 BR_IS_HATCH,crCurrentText = pbrush->BrushAttr.lbColor(影线前景 = 画刷颜色)。
- BR_IS_NULL →
注意 EBRUSHOBJ_vInit 是 NTAPI 且声明在 brush.h------brush.cpp 只负责逻辑画刷,实现(realize)全部在 engbrush.c。
8.3 EBRUSHOBJ_vInitFromDC / vUpdateFromDC
c
EBRUSHOBJ_vInitFromDC(pebo, pbrush, pdc)
→ EBRUSHOBJ_vInit(pebo, pbrush, pdc->dclevel.pSurface,
pdc->pdcattr->crBackgroundClr, pdc->pdcattr->crForegroundClr,
pdc->dclevel.ppal);
EBRUSHOBJ_vUpdateFromDC(pebo, pbrush, pdc)
→ EBRUSHOBJ_vCleanup(pebo); EBRUSHOBJ_vInitFromDC(pebo, pbrush, pdc);
- 从 DC 取表面、前景/背景色、DC 调色板。
- vUpdateFromDC = 先清理旧实现再重建(SelectObject 换画刷、SetBkColor/SetTextColor 后调用------见 dcobjs.c
DC_vUpdateFillBrush)。
8.4 EBRUSHOBJ_vSetSolidRGBColor ------ RGB → iSolidColor
c
VOID FASTCALL EBRUSHOBJ_vSetSolidRGBColor(EBRUSHOBJ *pebo, COLORREF crColor)
{
ASSERT(pebo->flattrs & BR_IS_SOLID);
crColor &= 0xFFFFFF;
pebo->crRealize = crColor;
pebo->ulRGBColor = crColor;
if (pebo->ppalSurf->flFlags & PAL_MONOCHROME)
{
/* 单色表面:最近色索引 + 反转位 */
iBackIndex = PALETTE_ulGetNearestPaletteIndex(ppalSurf, crCurrentBack);
iForeIndex = iBackIndex ^ 1;
rgbBack = PALETTE_ulGetRGBColorFromIndex(ppalSurf, iBackIndex);
iSolidColor = (crColor == rgbBack || crColor == crCurrentBack) ? iBackIndex : iForeIndex;
flattrs |= BR_NEED_BK_CLR;
}
else
{
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, ppalSurf, crCurrentBack, 0, 0);
iSolidColor = XLATEOBJ_iXlate(&exlo.xlo, crColor); // RGB → 表面格式
EXLATEOBJ_vCleanup(&exlo);
}
pebo->BrushObject.iSolidColor = iSolidColor;
}
- 单色表面(PAL_MONOCHROME):背景索引取最近色,前景索引=背景索引异或 1(黑白对),再比对画刷颜色与背景色决定用哪个;并置 BR_NEED_BK_CLR 提示需要背景色。
- 彩色表面:借助 XLATEOBJ(
gpalRGB→ 表面调色板)做颜色翻译,iSolidColor即目标表面的颜色索引/像素值。
8.5 EBRUSHOBJ_bRealizeBrush ------ 核心实现函数
c
BOOL NTAPI EBRUSHOBJ_bRealizeBrush(EBRUSHOBJ *pebo, BOOL bCallDriver)
流程:
ppdev取自psurfTrg->SurfObj.hdev,空则用gpmdev->ppdevGlobal。- 选择实现者:bCallDriver →
ppdev->DriverFunctions.RealizeBrush(无则 ERR 返回 FALSE),掩码psoMask = EBRUSHOBJ_psoMask(pebo);否则用引擎自带EngRealizeBrush,掩码置 NULL(引擎实现不处理掩码,按需再取)。 - 取图案 :
- 影线画刷:
hbmPattern = (HBITMAP)ppdev->ahsurf[pbr->iHatch]、iHatch = pbr->iHatch(PDEV 的影线位图缓存)。 - 其它画刷:
hbmPattern = pbr->hbmPattern、iHatch = -1。
- 影线画刷:
psurfPattern = SURFACE_ShareLockSurface(hbmPattern)(断言非空且带调色板)。- DIB_PAL_COLORS 特殊处理 :
BR_IS_DIBPALCOLORS→ppalPattern = FixupDIBBrushPalette(psurfPattern->ppal, pebo->ppalDC)生成新调色板存入pebo->ppalDIB(失败 ERR 返回 FALSE);否则直接用psurfPattern->ppal。 EXLATEOBJ_vInitialize(&exlo, ppalPattern, psurfTrg->ppal, 0, crCurrentBack, crCurrentText)------建立图案调色板 → 目标表面调色板的翻译对象。- 调用
pfnRealizeBrush(&pebo->BrushObject, &psurfTrg->SurfObj, &psurfPattern->SurfObj, psoMask, &exlo.xlo, iHatch)。 - 清理:
EXLATEOBJ_vCleanup、SURFACE_ShareUnlockSurface(psurfPattern),返回结果。
注意:影线画刷的 ppdev->ahsurf 与全局 gahsurfHatch 的关系------PDEV 初始化时把全局影线表面登记进 ahsurf[](按 HS 索引),这里直接按 pbr->iHatch 取用;非影线画刷的图案来自逻辑画刷的 hbmPattern。
8.6 EngRealizeBrush ------ 引擎实现(非导出)
c
BOOL APIENTRY EngRealizeBrush(BRUSHOBJ *pbo, SURFOBJ *psoDst, SURFOBJ *psoPattern,
SURFOBJ *psoMask, XLATEOBJ *pxlo, ULONG iHatch)
- 注释:不导出,因为驱动没必要"把实现打回给引擎"。
- 实现:按目标格式计算每行字节数
WIDTH_BYTES_ALIGN32→EngCreateBitmap(psoPattern->sizlBitmap, lWidth, psoDst->iBitmapFormat, BMF_NOZEROINIT, NULL)建目标格式位图 →SURFACE_ShareLockSurface锁定 →EngDeleteSurface(句柄先行删除,靠引用计数存活)→EngCopyBits把图案按 XLATEOBJ 转换拷贝到新位图 →pebo->pengbrush = psurfRealize。 - 即:引擎实现 = "把图案位图转换成目标表面格式的一份拷贝",供 blt/填充引擎直接使用(如 1bpp 影线图案 → 32bpp 目标格式)。
8.7 FixupDIBBrushPalette ------ DIB_PAL_COLORS 调色板修复(static)
c
static PPALETTE FixupDIBBrushPalette(_In_ PPALETTE ppalDIB, _In_ PPALETTE ppalDC)
PALETTE_AllocPalette(PAL_INDEXED, ppalDIB->NumColors, NULL, 0, 0, 0)分配新调色板。- 逐颜色处理:
iPalIndex = IndexedColors[i].peRed | (IndexedColors[i].peGreen << 8)(BRUSHHACK 把 WORD 索引拆存于 peRed=LOWORD / peGreen=HIWORD)→iPalIndex %= ppalDC->NumColors(越界回绕)→crColor = PALETTE_ulGetRGBColorFromIndex(ppalDC, iPalIndex)(取 DC 调色板的实际 RGB)→PALETTE_vSetRGBColorForIndex(ppalNew, i, crColor)。 - 结果:DIB 中的"16 位调色板索引"被解析成 DC 调色板对应的真实 RGB,存进新调色板(pebo->ppalDIB),供 XLATEOBJ 翻译使用。
8.8 惰性取用:pvGetEngBrush / psoPattern / psoMask / vCleanup
c
PVOID NTAPI EBRUSHOBJ_pvGetEngBrush(EBRUSHOBJ *pebo)
{
if (!pebo->pengbrush)
{
bResult = EBRUSHOBJ_bRealizeBrush(pebo, FALSE); // 引擎实现
if (!bResult) { 失败时清理 pengbrush 并置 NULL; }
}
return pebo->pengbrush;
}
- 惰性实现:第一次需要图案表面时才调用 bRealizeBrush(bCallDriver=FALSE);失败清理后下次重试。
EBRUSHOBJ_psoPattern(pebo):pvGetEngBrush后返回&psurfPattern->SurfObj,供填充引擎(如 PatBlt)取图案 SURFOBJ;宏BRUSHOBJ_psoPattern(pbo)用CONTAINING_RECORD从 BRUSHOBJ* 反推 EBRUSHOBJ*。EBRUSHOBJ_psoMask(pebo):掩码未取过时,对影线画刷从ppdev->ahsurf[iHatch]锁定(断言 1bpp)存入psoMask;非影线画刷返回 NULL。掩码供 DrvRealizeBrush 区分前景/背景(ROP 处理)。EBRUSHOBJ_vCleanup(pebo):对称释放------SURFACE_ShareUnlockSurface(pengbrush)、EngFreeMem(pvRbrush)、SURFACE_ShareUnlockSurface(psoMask)、PALETTE_ShareUnlockPalette(ppalSurf/ppalDC/ppalDIB),全部复位。
8.9 驱动接口:BRUSHOBJ_pvAllocRbrush / pvGetRbrush / ulGetBrushColor
c
PVOID APIENTRY BRUSHOBJ_pvAllocRbrush(IN BRUSHOBJ *pbo, IN ULONG cj)
{ pbo->pvRbrush = EngAllocMem(0, cj, GDITAG_RBRUSH); return pbo->pvRbrush; }
- BRUSHOBJ_pvAllocRbrush :驱动在 DrvRealizeBrush 中分配私有已实现画刷内存(带 GDITAG_RBRUSH 标签,便于审计),返回后存于
pbo->pvRbrush。 - BRUSHOBJ_pvGetRbrush :惰性调用
EBRUSHOBJ_bRealizeBrush(pebo, TRUE)(驱动实现);失败时EngFreeMem释放残留并返回 NULL。驱动每次需要已实现画刷时调用。 - BRUSHOBJ_ulGetBrushColor :返回
pebo->ulRGBColor(当前纯色 RGB),驱动可查画刷颜色。 - 三者均为
@implemented的 DDI 导出(winddi 接口),BRUSHOBJ_*是驱动可见的"只读"视角(BRUSHOBJ 前缀),内部操作通过 CONTAINING_RECORD 回到 EBRUSHOBJ。
9. SetDCBrushColor 链路(DC 画刷颜色)
SetDCBrushColor 修改的是 DC 的画刷颜色(配合 DC_BRUSH 库存画刷使用),与 6.6 节"修改画刷对象颜色"(IntGdiSetSolidBrushColor)是两条不同的链路。完整链路如下:
应用: SetDCBrushColor(hdc, crColor) (gdi32/objects/dc.c)
│
├─ HANDLE_EMETAFDC → EMFDRV_SetDCBrushColor (增强元文件 DC 走 EMF 记录)
│
▼
NtGdiSetDCBrushColor → IntSetDCBrushColor(hdc, crColor) (ntgdi/dcutil.c)
│
├─ dc->pdcattr->ulBrushClr = crColor ("最近一次 SetDCBrushColor"缓存)
├─ if (crBrushClr != crColor):
│ ulDirty_ |= DIRTY_FILL (标记填充画刷脏)
│ pdcattr->crBrushClr = crColor (实际生效的 DC 画刷颜色)
│
▼
(下次 DC 被使用时,如 GrePatBlt / DC_vUpdateFillBrush) (ntgdi/dcobjs.c)
DC_vUpdateFillBrush(pdc):
├─ 若 hbrush 变化 → BRUSH_ShareLockBrush/UnlockBrush 换 pbrFill,置 DIRTY_FILL
├─ 若 DIRTY_FILL → EBRUSHOBJ_vUpdateFromDC(&pdc->eboFill, pbrFill, pdc)
│ └─ EBRUSHOBJ_vCleanup + EBRUSHOBJ_vInit(重新按 DC 状态初始化)
└─ 若 hbrush == StockObjects[DC_BRUSH]:
EBRUSHOBJ_vSetSolidRGBColor(&pdc->eboFill, pdcattr->crBrushClr)
└─ XLATEOBJ 翻译 → eboFill.BrushObject.iSolidColor
└─ 清除 ulDirty_ 的 DIRTY_FILL / DC_BRUSH_DIRTY
要点:
- DC_BRUSH 是共享库存画刷 (stockobj.c:
StockObjects[DC_BRUSH] = IntGdiCreateSolidBrush(RGB(255,255,255))),它本身是白色纯色画刷。每个 DC 通过eboFill(EBRUSHOBJ)持有自己的实现状态 ,SetDCBrushColor之后并不修改 DC_BRUSH 对象本身,而是更新pdcattr->crBrushClr并把DIRTY_FILL置位,待DC_vUpdateFillBrush时对eboFill单独调用EBRUSHOBJ_vSetSolidRGBColor覆盖其 iSolidColor。这样所有 DC 共用同一个 DC_BRUSH 句柄,却各有各的填充颜色。 - DIRTY_FILL / DC_BRUSH_DIRTY :
SetDCBrushColor置 DIRTY_FILL;SelectObject(DC_BRUSH)置 DC_BRUSH_DIRTY;两者任一置位都会在 DC 使用时触发DC_vUpdateFillBrush重建 eboFill。 - 该链路与第 6.6 节的
IntGdiSetSolidBrushColor(改画刷对象本身,供SetSysColors刷新系统画刷)互补:前者是"DC 局部颜色",后者是"对象全局颜色"。
10. 调用链总览(Mermaid)
10.1 画刷创建与查询
#mermaid-svg-EjVPwpm27nIZSa4p{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EjVPwpm27nIZSa4p .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EjVPwpm27nIZSa4p .error-icon{fill:#552222;}#mermaid-svg-EjVPwpm27nIZSa4p .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EjVPwpm27nIZSa4p .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EjVPwpm27nIZSa4p .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EjVPwpm27nIZSa4p .marker.cross{stroke:#333333;}#mermaid-svg-EjVPwpm27nIZSa4p svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EjVPwpm27nIZSa4p p{margin:0;}#mermaid-svg-EjVPwpm27nIZSa4p .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EjVPwpm27nIZSa4p .cluster-label text{fill:#333;}#mermaid-svg-EjVPwpm27nIZSa4p .cluster-label span{color:#333;}#mermaid-svg-EjVPwpm27nIZSa4p .cluster-label span p{background-color:transparent;}#mermaid-svg-EjVPwpm27nIZSa4p .label text,#mermaid-svg-EjVPwpm27nIZSa4p span{fill:#333;color:#333;}#mermaid-svg-EjVPwpm27nIZSa4p .node rect,#mermaid-svg-EjVPwpm27nIZSa4p .node circle,#mermaid-svg-EjVPwpm27nIZSa4p .node ellipse,#mermaid-svg-EjVPwpm27nIZSa4p .node polygon,#mermaid-svg-EjVPwpm27nIZSa4p .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EjVPwpm27nIZSa4p .rough-node .label text,#mermaid-svg-EjVPwpm27nIZSa4p .node .label text,#mermaid-svg-EjVPwpm27nIZSa4p .image-shape .label,#mermaid-svg-EjVPwpm27nIZSa4p .icon-shape .label{text-anchor:middle;}#mermaid-svg-EjVPwpm27nIZSa4p .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EjVPwpm27nIZSa4p .rough-node .label,#mermaid-svg-EjVPwpm27nIZSa4p .node .label,#mermaid-svg-EjVPwpm27nIZSa4p .image-shape .label,#mermaid-svg-EjVPwpm27nIZSa4p .icon-shape .label{text-align:center;}#mermaid-svg-EjVPwpm27nIZSa4p .node.clickable{cursor:pointer;}#mermaid-svg-EjVPwpm27nIZSa4p .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EjVPwpm27nIZSa4p .arrowheadPath{fill:#333333;}#mermaid-svg-EjVPwpm27nIZSa4p .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EjVPwpm27nIZSa4p .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EjVPwpm27nIZSa4p .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EjVPwpm27nIZSa4p .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EjVPwpm27nIZSa4p .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EjVPwpm27nIZSa4p .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EjVPwpm27nIZSa4p .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EjVPwpm27nIZSa4p .cluster text{fill:#333;}#mermaid-svg-EjVPwpm27nIZSa4p .cluster span{color:#333;}#mermaid-svg-EjVPwpm27nIZSa4p div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EjVPwpm27nIZSa4p .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EjVPwpm27nIZSa4p rect.text{fill:none;stroke-width:0;}#mermaid-svg-EjVPwpm27nIZSa4p .icon-shape,#mermaid-svg-EjVPwpm27nIZSa4p .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EjVPwpm27nIZSa4p .icon-shape p,#mermaid-svg-EjVPwpm27nIZSa4p .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EjVPwpm27nIZSa4p .icon-shape .label rect,#mermaid-svg-EjVPwpm27nIZSa4p .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EjVPwpm27nIZSa4p .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EjVPwpm27nIZSa4p .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EjVPwpm27nIZSa4p :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} gdi32
gdi32
gdi32
gdi32
gdi32
类型分发
gdi32
gdi32
gdi32
类型分发
CreateSolidBrush
NtGdiCreateSolidBrush
CreateHatchBrush
NtGdiCreateHatchBrushInternal
CreatePatternBrush
NtGdiCreatePatternBrushInternal
CreateDIBPatternBrushPt
NtGdiCreateDIBBrush
CreateBrushInternal
new BRUSH
bAllocateBrushAttr / hInsertObject
BRUSH 对象入句柄表
GetObject
NtGdiGetObject
BRUSH_GetObject
cjGetObject → LOGBRUSH
GetObject 图案画刷
NtGdiGetObjectBitmapHandle
hbmGetBitmapHandle
SetBrushAttributes / ClearBrushAttributes
NtGdiSetBrushAttributes / NtGdiClearBrushAttributes
GDIOBJ_ConvertTo/FromStockObj + vReleaseAttribute / bAllocateBrushAttr
DeleteObject
NtGdiDeleteObject
BRUSH_vDeleteObject → ~BRUSH
10.2 绘制时的实现(realize)
#mermaid-svg-I9jgqdbWsjMV3f6L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-I9jgqdbWsjMV3f6L .error-icon{fill:#552222;}#mermaid-svg-I9jgqdbWsjMV3f6L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-I9jgqdbWsjMV3f6L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-I9jgqdbWsjMV3f6L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-I9jgqdbWsjMV3f6L .marker.cross{stroke:#333333;}#mermaid-svg-I9jgqdbWsjMV3f6L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-I9jgqdbWsjMV3f6L p{margin:0;}#mermaid-svg-I9jgqdbWsjMV3f6L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster-label text{fill:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster-label span{color:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster-label span p{background-color:transparent;}#mermaid-svg-I9jgqdbWsjMV3f6L .label text,#mermaid-svg-I9jgqdbWsjMV3f6L span{fill:#333;color:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L .node rect,#mermaid-svg-I9jgqdbWsjMV3f6L .node circle,#mermaid-svg-I9jgqdbWsjMV3f6L .node ellipse,#mermaid-svg-I9jgqdbWsjMV3f6L .node polygon,#mermaid-svg-I9jgqdbWsjMV3f6L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-I9jgqdbWsjMV3f6L .rough-node .label text,#mermaid-svg-I9jgqdbWsjMV3f6L .node .label text,#mermaid-svg-I9jgqdbWsjMV3f6L .image-shape .label,#mermaid-svg-I9jgqdbWsjMV3f6L .icon-shape .label{text-anchor:middle;}#mermaid-svg-I9jgqdbWsjMV3f6L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-I9jgqdbWsjMV3f6L .rough-node .label,#mermaid-svg-I9jgqdbWsjMV3f6L .node .label,#mermaid-svg-I9jgqdbWsjMV3f6L .image-shape .label,#mermaid-svg-I9jgqdbWsjMV3f6L .icon-shape .label{text-align:center;}#mermaid-svg-I9jgqdbWsjMV3f6L .node.clickable{cursor:pointer;}#mermaid-svg-I9jgqdbWsjMV3f6L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-I9jgqdbWsjMV3f6L .arrowheadPath{fill:#333333;}#mermaid-svg-I9jgqdbWsjMV3f6L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-I9jgqdbWsjMV3f6L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-I9jgqdbWsjMV3f6L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I9jgqdbWsjMV3f6L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-I9jgqdbWsjMV3f6L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I9jgqdbWsjMV3f6L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster text{fill:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L .cluster span{color:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-I9jgqdbWsjMV3f6L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-I9jgqdbWsjMV3f6L rect.text{fill:none;stroke-width:0;}#mermaid-svg-I9jgqdbWsjMV3f6L .icon-shape,#mermaid-svg-I9jgqdbWsjMV3f6L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-I9jgqdbWsjMV3f6L .icon-shape p,#mermaid-svg-I9jgqdbWsjMV3f6L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-I9jgqdbWsjMV3f6L .icon-shape .label rect,#mermaid-svg-I9jgqdbWsjMV3f6L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-I9jgqdbWsjMV3f6L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-I9jgqdbWsjMV3f6L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-I9jgqdbWsjMV3f6L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
BR_IS_SOLID
BR_IS_NULL
图案/影线
bCallDriver=TRUE
bCallDriver=FALSE
DC 绘制 / SelectObject / SetDCBrushColor
DC_vUpdateFillBrush / DC_vUpdateLineBrush
画刷句柄或颜色变化?
EBRUSHOBJ_vUpdateFromDC
EBRUSHOBJ_vCleanup
EBRUSHOBJ_vInit / vInitFromDC
画刷种类
EBRUSHOBJ_vSetSolidRGBColor
iSolidColor = 0
iSolidColor = 0xFFFFFFFF
iSolidColor = XLATEOBJ_iXlate
EBRUSHOBJ_bRealizeBrush
DrvRealizeBrush → pvRbrush
EngRealizeBrush → pengbrush
EBRUSHOBJ_psoMask 影线掩码
FixupDIBBrushPalette DIB_PAL_COLORS
BRUSHOBJ_pvGetRbrush 惰性取用
EBRUSHOBJ_pvGetEngBrush / psoPattern 惰性取用
填充/描边引擎使用
10.3 系统初始化
#mermaid-svg-NExp08y7GVryMHne{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NExp08y7GVryMHne .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NExp08y7GVryMHne .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NExp08y7GVryMHne .error-icon{fill:#552222;}#mermaid-svg-NExp08y7GVryMHne .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NExp08y7GVryMHne .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NExp08y7GVryMHne .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NExp08y7GVryMHne .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NExp08y7GVryMHne .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NExp08y7GVryMHne .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NExp08y7GVryMHne .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NExp08y7GVryMHne .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NExp08y7GVryMHne .marker.cross{stroke:#333333;}#mermaid-svg-NExp08y7GVryMHne svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NExp08y7GVryMHne p{margin:0;}#mermaid-svg-NExp08y7GVryMHne .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NExp08y7GVryMHne .cluster-label text{fill:#333;}#mermaid-svg-NExp08y7GVryMHne .cluster-label span{color:#333;}#mermaid-svg-NExp08y7GVryMHne .cluster-label span p{background-color:transparent;}#mermaid-svg-NExp08y7GVryMHne .label text,#mermaid-svg-NExp08y7GVryMHne span{fill:#333;color:#333;}#mermaid-svg-NExp08y7GVryMHne .node rect,#mermaid-svg-NExp08y7GVryMHne .node circle,#mermaid-svg-NExp08y7GVryMHne .node ellipse,#mermaid-svg-NExp08y7GVryMHne .node polygon,#mermaid-svg-NExp08y7GVryMHne .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NExp08y7GVryMHne .rough-node .label text,#mermaid-svg-NExp08y7GVryMHne .node .label text,#mermaid-svg-NExp08y7GVryMHne .image-shape .label,#mermaid-svg-NExp08y7GVryMHne .icon-shape .label{text-anchor:middle;}#mermaid-svg-NExp08y7GVryMHne .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NExp08y7GVryMHne .rough-node .label,#mermaid-svg-NExp08y7GVryMHne .node .label,#mermaid-svg-NExp08y7GVryMHne .image-shape .label,#mermaid-svg-NExp08y7GVryMHne .icon-shape .label{text-align:center;}#mermaid-svg-NExp08y7GVryMHne .node.clickable{cursor:pointer;}#mermaid-svg-NExp08y7GVryMHne .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NExp08y7GVryMHne .arrowheadPath{fill:#333333;}#mermaid-svg-NExp08y7GVryMHne .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NExp08y7GVryMHne .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NExp08y7GVryMHne .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NExp08y7GVryMHne .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NExp08y7GVryMHne .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NExp08y7GVryMHne .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NExp08y7GVryMHne .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NExp08y7GVryMHne .cluster text{fill:#333;}#mermaid-svg-NExp08y7GVryMHne .cluster span{color:#333;}#mermaid-svg-NExp08y7GVryMHne div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NExp08y7GVryMHne .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NExp08y7GVryMHne rect.text{fill:none;stroke-width:0;}#mermaid-svg-NExp08y7GVryMHne .icon-shape,#mermaid-svg-NExp08y7GVryMHne .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NExp08y7GVryMHne .icon-shape p,#mermaid-svg-NExp08y7GVryMHne .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NExp08y7GVryMHne .icon-shape .label rect,#mermaid-svg-NExp08y7GVryMHne .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NExp08y7GVryMHne .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NExp08y7GVryMHne .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NExp08y7GVryMHne :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 系统启动
InitBrushImpl
gaulHatchBrushes 表 → EngCreateBitmap ×6
gahsurfHatch 缓存
CreateSysColorObjects
IntGdiCreateSolidBrush ×NUM_SYSCOLORS
gpsi->ahbrSystem + GDIOBJ_ConvertToStockObj
stockobj.c 库存对象
IntGdiCreateSolidBrush: WHITE/LTGRAY/GRAY/DKGRAY/BLACK/DC_BRUSH
IntGdiCreateNullBrush: NULL_BRUSH
ntuser: IntGdiCreatePatternBrush → hbrGray
11. 源码索引
| 文件 | 关键内容 |
|---|---|
| brush.cpp(file:///d:/reactos/win32ss/gdi/ntgdi/brush.cpp) | BRUSH 构造/析构、vDeleteObject、bAllocateBrushAttr、vReleaseAttribute、vSetSolidColor、hbmGetBitmapHandle、cjGetObject、CreateBrushInternal、BRUSH_vDeleteObject、BRUSH_GetObject、IntGdiCreateNullBrush/SolidBrush/PatternBrush、IntGdiSetSolidBrushColor、NtGdiCreateSolidBrush/CreateHatchBrushInternal/CreatePatternBrushInternal/CreateDIBBrush/GetObjectBitmapHandle/SetBrushAttributes/ClearBrushAttributes |
| brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) | BRUSHBODY、BRUSH、EBRUSHOBJ 结构;BR* 标志;BRUSH_ShareLockBrush 宏;EBRUSHOBJ_* 声明 |
| brush.hpp(file:///d:/reactos/win32ss/gdi/ntgdi/brush.hpp) | BRUSH 类(operator new/delete、LockForRead/Write/Any、成员函数声明)、GetBrushAttrPool |
| engbrush.c(file:///d:/reactos/win32ss/gdi/eng/engbrush.c) | gaulHatchBrushes、gahsurfHatch、InitBrushImpl、EBRUSHOBJ_vInit/vInitFromDC/vSetSolidRGBColor/vUpdateFromDC/vCleanup、EngRealizeBrush、FixupDIBBrushPalette、EBRUSHOBJ_bRealizeBrush/pvGetEngBrush/psoPattern/psoMask、BRUSHOBJ_pvAllocRbrush/pvGetRbrush/ulGetBrushColor |
| ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) | BRUSH_ATTR、SC_BB_STOCKOBJ |
| ntgdityp.h(file:///d:/reactos/win32ss/include/ntgdityp.h) | DIB_PAL_INDICES |
| dib.h(file:///d:/reactos/win32ss/gdi/ntgdi/dib.h) | DIB_PAL_BRUSHHACK |
| dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) | DC_vUpdateFillBrush(eboFill 更新、DC_BRUSH 颜色覆盖) |
| dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) | IntSetDCBrushColor(ulBrushClr/crBrushClr/DIRTY_FILL) |
| stockobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/stockobj.c) | 库存画刷创建、CreateSysColorObjects、IntSetSysColors(→ IntGdiSetSolidBrushColor) |
| brush.c (gdi32)(file:///d:/reactos/win32ss/gdi/gdi32/objects/brush.c) | SetBrushAttributes / ClearBrushAttributes 用户态包装 |
| wingdi.h(file:///d:/reactos/sdk/include/psdk/wingdi.h) | BS_*(1086 行起)、HS_HORIZONTAL...HS_DIAGCROSS、HS_API_MAX=12、DIB_RGB_COLORS |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h) | HS_DDI_MAX=6、BRUSHOBJ 接口 |
关键调用关系速查
IntGdiCreateSolidBrush/IntGdiCreateNullBrush/IntGdiCreatePatternBrush→ 内部全局画刷(BR_IS_GLOBAL,owner=PUBLIC),供 stockobj/ntuser 使用。NtGdiCreateSolidBrush/NtGdiCreateHatchBrushInternal/NtGdiCreatePatternBrushInternal/NtGdiCreateDIBBrush→ 进程私有画刷(POWNED + bAllocateBrushAttr),供 gdi32 API 使用。BRUSH::cjGetObject→ BS_* 样式由 flAttrs 推导;lbHatch 对图案画刷返回 hbmClient。EBRUSHOBJ_bRealizeBrush→ 影线取ppdev->ahsurf[iHatch]、图案取pbr->hbmPattern;DIB_PAL_COLORS 走 FixupDIBBrushPalette;双轨实现(DrvRealizeBrush / EngRealizeBrush)。DC_vUpdateFillBrush→ DC_BRUSH 颜色经EBRUSHOBJ_vSetSolidRGBColor覆盖 eboFill.iSolidColor。
12. 已知限制与 FIXME(源码为准)
NtGdiCreateSolidBrush的hbr参数未实现(WARN 忽略)------Windows 用它做句柄缓存优化,ReactOS 每次新建。NtGdiCreateHatchBrushInternal/NtGdiCreatePatternBrushInternal/NtGdiCreateDIBBrush的bPen、b8X8参数均未实现(WARN 忽略)------几何画笔与 8×8 优化变体不受支持。NtGdiCreateDIBBrush在DIB_PAL_INDICES分支附加BR_IS_NULL:源码注释FIXME: This makes tests pass, but needs investigation.------疑似为通过 winetest 的权宜之计。BRUSH::cjGetObject的 lbColor 取内嵌BrushAttr.lbColor而非pBrushAttr:对进程私有画刷,修改颜色(vSetSolidColor 写 pBrushAttr)后 GetObject 可能读到旧颜色。operator new用GDITAG_HMGR_BRUSH_TYPE分配、operator delete用无标签ExFreePool释放------注释HACK! better would be to extract the exact object type's tag,标签不一致影响池审计精度。BRUSHBODY布局冻结(Feng Yuan 书),新增字段必须谨慎,尤其不可移动 BASEOBJECT 头与既有偏移。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/brush.cpp、brush.h、brush.hpp、win32ss/gdi/eng/engbrush.c及关联模块分析(2026 年 8 月)。所有代码引用均来自上述真实源码,未做任何虚构。