ReactOS 图形系统分析(38):区域子系统 --- region.c
1. 概述
region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c) 位于 win32ss/gdi/ntgdi/ 目录,是 ReactOS 内核态 GDI(win32k.sys)的区域(REGION)子系统 核心实现,共约 4144 行(截至 2026 年 8 月分析时点)。该文件对外暴露 12 个 NtGdi* 系统调用入口 ,对内提供几十个 REGION_* 内部 API(声明见 region.h(file:///d:/reactos/win32ss/gdi/ntgdi/region.h)),覆盖以下四类职责:
- 区域对象管理 :分配、加锁、同步、清理、删除(
REGION_AllocRgnWithHandle/REGION_LockRgn/REGION_vSyncRegion/REGION_vCleanup/REGION_Delete)。 - 区域布尔运算 :联合(Union)、相交(Intersect)、相减(Subtract)、异或(Xor),统一由
REGION_RegionOp这一扫描线运算器驱动。 - 命中测试与查询 :点/矩形是否在区域内(
REGION_PtInRegion/REGION_RectInRegion)、包围盒查询(REGION_GetRgnBox)、复杂度判定(REGION_Complexity)。 - 区域构造与变换 :矩形区域(
REGION_SetRectRgn)、多边形区域(REGION_SetPolyPolygonRgn,含扫描转换边表算法)、边框区域(REGION_bMakeFrameRegion)、坐标变换(REGION_bXformRgn/REGION_bOffsetRgn)。
区域是 GDI 裁剪体系的核心数据载体。在 ReactOS 的窗口管理(ntuser)与 GDI 引擎中,可见区域(prgnVis)、光栅操作区域(prgnRao)、API 裁剪区域(prgnAPI)三层区域通过 IntGdiCombineRgn 合成出最终的设备裁剪区(详见《分析_31》第 6 节与本分析第 16 章)。
应用层: CreateRectRgn / CombineRgn / PtInRegion / OffsetRgn / SetRectRgn / GetRegionData ...
↓ NtGdi*(region.c,12 个系统调用入口)
┌─────────────────────────────────────────────────────────────┐
│ region.c(REGION_* 内部 API) │
│ ┌─────────────┐ ┌──────────────────────┐ ┌────────────┐ │
│ │ 对象生命周期 │ │ 扫描线布尔运算 │ │ 构造/变换 │ │
│ │ Alloc/Lock │ │ RegionOp(统一调度) │ │ SetRectRgn │ │
│ │ Sync/Unlock │ │ ├ IntersectO │ │ SetPoly... │ │
│ │ Cleanup/... │ │ ├ UnionO/UnionNonO │ │ MakeFrame │ │
│ │ │ │ ├ SubtractO/NonO1 │ │ bXformRgn │ │
│ │ │ │ └ Xor(=Sub+Sub+U) │ │ bOffsetRgn │ │
│ └─────────────┘ └──────────────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ IntGdiCombineRgn / REGION_LockRgn(被 ntuser、dc、eng 复用)
win32ss 内部消费者: dclife.c(裁剪合成) / cliprgn.c(API 裁剪) / eng/clip.c(XCLIPOBJ)
region.c 的代码自述其来源:头部注释明确指出该文件直接移植自 X11 发行版 (libX11/src/PolyReg.c 与 libX11/src/Region.c),版权归属于 X Consortium、Digital Equipment Corporation 以及 Alexandre Julliard(Wine)、Huw Davies、Alex Korobka。这意味着区域布尔运算的算法骨架(y-x 分带表示 + 逐带归并)沿袭了 X11 Sample Server 的经典实现,ReactOS 在其上做了内核态适配(句柄表、RGN_ATTR 属性、ExAllocatePoolWithTag 分页池分配等)。
2. 设计动机:扫描线区域表示为何是裁剪的基础
GDI 的裁剪操作本质上是一个二维集合运算问题:绘制时要把画笔/画刷"限制"在某个多边形或矩形组合区域内,而屏幕上的每个像素要么在区域内、要么在区域外。区域(Region)就是这个"集合"的显式表示。
2.1 为什么不用位图
最直观的区域表示是位图(每像素 1 bit)。位图区域简单、命中测试快,但存在致命缺点:
- 内存爆炸:1024×768 的屏幕仅 1 bit 深度就需要 96 KB;若要表示任意多边形,内存随包围盒面积而非边界复杂度增长。
- 布尔运算慢:位图的 AND/OR/XOR 需要逐像素遍历整块位图,与区域实际形状无关。
- 精度损失:坐标空间受限,无法表达"坐标很大但形状简单"的区域。
2.2 y-x 分带矩形数组表示
region.c 采用 X11 继承来的表示:区域 = 按 y 坐标分带(band)、带内按 x 坐标排序且互不重叠的 RECTL 数组。文件头注释(第 90-114 行)给出了精确定义:
-
所有矩形先按 y(top)排序,再按 x(left)排序。
-
分带约束:具有相同 top 坐标的矩形必然具有相同的 bottom 坐标(一个带 = 一条水平扫描线区间带),反之亦然。若某矩形在一带内有扫描线,则它横跨该带的整个垂直距离。
-
横向最大化约束:带内任意两个矩形不得相邻(不允许触碰),即带内矩形必须覆盖尽可能大的水平面积。
-
相邻带在满足"底边相触 + 矩形位置一致"时合并为更高矩形,以减少矩形数量。
坐标示意(y 向下为正,bottom/right 为开区间):
y=0 ┌──────────────────────────────┐
│ │
y=5 │ ┌───┐ ┌───────────┐ │ 带 A:top=5, bottom=10
│ │ │ │ │ │ (两个矩形,x 不重叠)
y=10│ │ │ │ ├──────┐ │ 带 B:top=10, bottom=15
│ │ │ │ │ │ │ (左侧矩形延续,右侧矩形更宽)
y=15│ └───┘ └───────────┘ │ │
│ │
└─────────────────────────────┘
带 A 与带 B 不能合并:虽然左侧矩形上下对齐,
但右侧矩形宽度不同(x 位置不一致),违反归并条件。
这种表示的优点:内存与边界复杂度成正比 而非面积成正比;布尔运算可退化为两条有序链表(矩形序列)的归并,复杂度 O(n1+n2);命中测试可借助 y 分带快速二分跳过。
2.3 与裁剪流程的关系
win32k 在窗口绘制时,需要把"窗口可见部分 ∩ 被遮挡部分之外 ∩ API 裁剪"反复组合。这些组合全是区域布尔运算,因此区域子系统的性能直接决定窗口重绘、拖动、InvalidateRect 等常见操作的帧率。region.c 为此做了若干工程优化:REGION_UnionRegion 中先做平凡情形短路 (同源、空区域、包含关系),REGION_RegionOp 中预分配 2*max(n1,n2) 个矩形的缓冲区,结尾对"分配量 > 2 倍实际量"的大缓冲区收缩重分配。
3. 核心数据结构
3.1 REGION 结构(region.h(file:///d:/reactos/win32ss/gdi/ntgdi/region.h#L7-L17))
c
typedef struct _REGION
{
BASEOBJECT BaseObject; /* GDI 句柄表对象头,必须位于首部 */
_Notnull_ PRGN_ATTR prgnattr; /* 指向区域属性(通常指向 rgnattr,用户模式区域指向池中对象) */
RGN_ATTR rgnattr; /* 内嵌属性(栈上/对象内) */
RGNDATAHEADER rdh; /* 区域头:矩形数 + 包围盒 + 类型 */
RECTL *Buffer; /* 矩形数组指针(分页池或 &rdh.rcBound 内嵌) */
} REGION, *PREGION;
要点:
BASEOBJECT BaseObject:必须是第一个成员,因为 win32k 的 GDI 句柄表(GDIOBJ_*系列)通过CONTAINING_RECORD把句柄表项反向映射回对象。region.c 中的GDIOBJ_AllocateObject(GDIObjType_RGN_TYPE, ...)、GDIOBJ_hInsertObject、GDIOBJ_LockObject、GDIOBJ_vDeleteObject全部依赖该布局。prgnattr/rgnattr:区域属性(RGN_ATTR)。系统区域 (如默认区域prgnDefault、IntSysCreateRectpRgn创建的)直接使用内嵌的rgnattr;用户模式区域 (应用通过CreateRectRgn等创建)的属性存放在每进程 GDI 属性池(ppi->pPoolRgnAttr)中,prgnattr指向池内对象------这样 user32/gdi32 的用户态部分可以直接读写属性 (把复杂度/矩形缓存写回),内核在下次锁定该区域时通过REGION_vSyncRegion将其同步进rdh。这就是 ReactOS 用户态/内核态共享区域对象的关键机制。REGION_UnlockRgn在解锁时把rdh中的复杂度与包围盒写回属性 (置ATTR_RGN_VALID),下一次REGION_LockRgn先调用REGION_vSyncRegion检查ATTR_RGN_DIRTY标志,若用户态改过属性则重建rdh。详见第 12 章。
3.2 RGNDATAHEADER(定义于 win32k 公共头)
c
typedef struct _RGNDATAHEADER {
ULONG dwSize; /* sizeof(RGNDATAHEADER) = 0x20 */
ULONG iType; /* RDH_RECTANGLES = 1 */
ULONG nCount; /* 矩形个数 */
ULONG nRgnSize; /* 缓冲区字节数(= nCount * sizeof(RECT),动态增长) */
RECTL rcBound; /* 包围盒(所有矩形的并集外框) */
} RGNDATAHEADER;
注意 region.c 中 rdh.nRgnSize 的语义是"缓冲区容量 (字节)",而 Windows 的 RGNDATAHEADER 中该字段语义是"数据大小";ReactOS 借它跟踪 Buffer 的分配大小,以便 REGION_bEnsureBufferSize 判断是否需要扩容。rcBound 在 Buffer == &rdh.rcBound 时兼作内嵌矩形容器 :当区域只有 1 个矩形时,该矩形直接存在 rcBound 里(零额外分配),这是文件注释强调的"> 95% 的区域只有一个矩形"的优化。
3.3 RECTL Buffer\[\]
Buffer 指向矩形序列,排序不变量(由 REGION_RegionOp 及 REGION_Coalesce 维护):
- 按
top升序;同top的矩形聚成一个带(band)。 - 带内按
left升序;带内矩形互不重叠且不相邻 (REGION_bMergeRect保证)。 - 带与带之间由
REGION_Coalesce尝试合并(见第 5.5 节)。
矩形坐标约定:left/top 为闭、right/bottom 为开(像素包容规则),即矩形覆盖 [left, right) × [top, bottom)。INRECT 宏即按此约定实现:
c
#define INRECT(r, x, y) \
( ( ((r).right > x)) && \
( ((r).left <= x)) && \
( ((r).bottom > y)) && \
( ((r).top <= y)) )
3.4 区域复杂度(REGION 复杂度枚举)
REGION_Complexity 依据 rdh.nCount 判定复杂度,返回值对应 GDI 头文件中的枚举:
| 复杂度值 | 含义 | nCount | 典型场景 |
|---|---|---|---|
NULLREGION(=1) |
空区域 | 0 | CombineRgn(RGN_AND) 结果为空 |
SIMPLEREGION(=2) |
单矩形区域 | 1 | CreateRectRgn 产物 |
COMPLEXREGION(=3) |
复杂区域 | ≥2 | 多边形、圆角矩形、多次 OR |
ERROR(=0) |
错误 | --- | 句柄无效 |
复杂度是上层裁剪优化的关键判据:IntEngUpdateClipRegion(eng/clip.c)依据矩形个数决定 XCLIPOBJ 的 iDComplexity(DC_TRIVIAL/DC_RECT/DC_COMPLEX);驱动与引擎据此跳过无意义的裁剪检查。此外 RGN_ATTR.iComplexity 也会缓存该值,供用户态 gdi32 直接使用,避免每次调用都进内核。
3.5 关键宏
c
#define EMPTY_REGION(pReg) { \
(pReg)->rdh.nCount = 0; \
(pReg)->rdh.rcBound.left = (pReg)->rdh.rcBound.top = 0; \
(pReg)->rdh.rcBound.right = (pReg)->rdh.rcBound.bottom = 0; \
(pReg)->rdh.iType = RDH_RECTANGLES; \
}
#define REGION_NOT_EMPTY(pReg) pReg->rdh.nCount
#define EXTENTCHECK(r1, r2) \
((r1)->right > (r2)->left && (r1)->left < (r2)->right && \
(r1)->bottom > (r2)->top && (r1)->top < (r2)->bottom)
EMPTY_REGION:清空矩形数并把包围盒归零。EXTENTCHECK:两个矩形包围盒是否重叠的快速判据,用于布尔运算的"平凡拒绝"。COPY_RECTS(dst, src, n):逐元素复制矩形数组。
4. 缓冲区管理与矩形添加原语
区域在运算过程中矩形数会动态变化,因此 Buffer 必须可增长。region.c 提供了四个层次的分配/添加原语,构成自底向上的调用链:
REGION_bGrowBufferSize(prgn, cRects) ------ 真正分配新缓冲区并搬运
↑
REGION_bEnsureBufferSize(prgn, cRects) ------ 容量不够才扩容
↑
REGION_bAddRect(prgn, l,t,r,b) ------ 带容量检查的添加
↑
REGION_vAddRect(prgn, l,t,r,b) ------ 无检查、直接写(调用方保证容量)
4.1 REGION_bGrowBufferSize(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L407-L455))
c
static BOOL FASTCALL REGION_bGrowBufferSize(_Inout_ PREGION prgn, _In_ UINT cRects)
作用 :把区域矩形缓冲区扩容到至少能容纳 cRects 个矩形。
实现流程:
- 溢出防护:若
cRects > MAXULONG / sizeof(RECTL)直接返回 FALSE。 - 计算新大小
cjNewSize = cRects * sizeof(RECTL)。 - 翻倍策略 :若
2 * prgn->rdh.nRgnSize > cjNewSize,则取cjNewSize = 2 * prgn->rdh.nRgnSize。注释说明"避免频繁分配:旧大小永远不会大到翻倍溢出,因为内存早已耗尽"。 ExAllocatePoolWithTag(PagedPool, cjNewSize, TAG_REGION)分配分页池。COPY_RECTS把旧矩形搬入新缓冲区;若旧缓冲区不是内嵌的&rdh.rcBound,则ExFreePoolWithTag释放。- 更新
prgn->Buffer与prgn->rdh.nRgnSize。
注意事项 :分配失败返回 FALSE 后,区域保持原样(调用方需处理失败,通常是整次布尔运算失败)。缓冲区只增不减------收缩逻辑仅在 REGION_RegionOp 结尾单独处理。
4.2 REGION_bEnsureBufferSize(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L457-L471))
c
static __inline BOOL FASTCALL REGION_bEnsureBufferSize(_Inout_ PREGION prgn, _In_ UINT cRects)
作用 :若 cRects 超过当前容量(rdh.nRgnSize / sizeof(RECTL)),调用 REGION_bGrowBufferSize;否则直接返回 TRUE。这是所有"将要添加 cRects 个矩形"的调用点的统一入口。
4.3 REGION_vAddRect(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L473-L491))
c
FORCEINLINE VOID FASTCALL REGION_vAddRect(_Inout_ PREGION prgn, _In_ LONG left, _In_ LONG top, _In_ LONG right, _In_ LONG bottom)
作用 :把矩形追加到区域末尾(Buffer[nCount] 处,nCount++)。
注意事项 :该函数不做任何容量检查 ,仅 NT_ASSERT((nCount+1)*sizeof(RECT) <= nRgnSize)。调用方必须先用 REGION_bEnsureBufferSize 保证容量。它也不做排序/去重/合并------这些由上层算法保证添加顺序的正确性。之所以拆出"无检查"版本,是因为 REGION_RegionOp 的热路径中常常一次性预分配整个带的容量,然后批量追加。
4.4 REGION_bAddRect(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L493-L509))
c
static __inline BOOL FASTCALL REGION_bAddRect(_Inout_ PREGION prgn, _In_ LONG left, _In_ LONG top, _In_ LONG right, _In_ LONG bottom)
作用 :REGION_bEnsureBufferSize(prgn, nCount+1) 成功后再 REGION_vAddRect,返回 BOOL 表示成功/失败。是"一次只加一个矩形"场景(如 REGION_IntersectO、REGION_SubtractO)的标准入口。
4.5 辅助:IntDumpRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L526-L553))
仅在 NDEBUG 未定义(调试构建)时编译的调试工具:锁定区域后打印句柄、包围盒、类型。REGION_LockRgn / REGION_UnlockRgn 的典型配对用法范例。
5. 基础工具函数
5.1 REGION_Complexity(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L555-L572))
c
INT FASTCALL REGION_Complexity(PREGION prgn)
作用 :返回区域复杂度。NULL 指针 → NULLREGION;nCount==0 → NULLREGION;nCount==1 → SIMPLEREGION;否则 COMPLEXREGION。
使用方式 :几乎每个 NtGdi 入口在运算结束后用它包装返回值(如 NtGdiOffsetRgn、NtGdiCombineRgn),使 Win32 API 的返回值语义(NULLREGION/SIMPLEREGION/COMPLEXREGION/ERROR)与内部状态一致。同时它被 REGION_UnlockRgn 用来回写 prgnattr->iComplexity。
5.2 REGION_CopyRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L574-L615))
c
static BOOL FASTCALL REGION_CopyRegion(PREGION dst, PREGION src)
作用 :把 src 的矩形数组、包围盒、类型、计数整体复制到 dst。
实现流程:
- 若
dst == src直接返回 TRUE(自复制)。 - 若
dst->rdh.nRgnSize < src->rdh.nCount * sizeof(RECT),分配新缓冲区(分配失败返回 FALSE),释放旧缓冲区(若非内嵌)。 - 复制
nCount、rcBound(四个坐标)、iType,然后COPY_RECTS搬运矩形。
注意事项 :dst 若是用户模式区域,其 prgnattr 不会被更新------更新发生在之后 REGION_UnlockRgn 时。REGION_bCopy 是对它的薄封装(加 NULL 检查)。REGION_UnionRegion 的平凡分支、REGION_SubtractRegion 的平凡拒绝分支、IntGdiCombineRgn(RGN_COPY) 都直接调用它。
5.3 REGION_SetExtents(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L617-L660))
c
static VOID FASTCALL REGION_SetExtents(PREGION pReg)
作用 :重算 区域包围盒 rcBound。布尔运算后矩形可能被合并、删除,包围盒不能直接由输入外推,必须扫描一遍结果。
实现流程:
nCount==0→ 包围盒清零,直接返回。- 利用分带性质:
Buffer[0]的top必为最小 top,Buffer[nCount-1]的bottom必为最大 bottom(因为按 y 排序且 bottom 随带递增),据此初始化rcBound。 - 单次线性扫描求出
min(left)与max(right)(矩形按 y 排序但 x 无序,所以 left/right 必须全扫)。 - 置
iType = RDH_RECTANGLES。
调用时机 :REGION_IntersectRegion 与 REGION_SubtractRegion 在 REGION_RegionOp 返回后调用它(注释强调:不能在调用 RegionOp 之前改 extents,因为目标区域可能是源区域之一,RegionOp 依赖源区域的原始 extents 做带推进)。
5.4 REGION_CropRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L662-L792))
c
INT FASTCALL REGION_CropRegion(PREGION rgnDst, PREGION rgnSrc, const RECTL *rect)
作用 :把 rgnSrc 与矩形 rect 相交,结果写入 rgnDst,返回结果复杂度(ERROR 表示失败)。等价于"区域 ∩ 矩形"的专用快速实现,不需要 通用 REGION_RegionOp 的开销。
实现流程:
- 平凡拒绝:
rect空、或EXTENTCHECK(rect, &rgnSrc->rdh.rcBound)==0(包围盒不相交)→ 跳转empty。 - 上界扫描 :
clipa从 0 开始,跳过所有bottom <= rect->top的矩形(完全在裁剪矩形上方);若全部跳过则empty。 - 下界扫描 :
clipb从clipa开始,找到第一个top >= rect->bottom的矩形位置(其后矩形都在裁剪矩形下方),中途bottom >= rect->top即停止条件if (rgnSrc->Buffer[clipb].top >= rect->bottom) break。于是[clipa, clipb)就是 y 方向上可能与裁剪矩形相交的矩形子区间。 - 若
rgnDst != rgnSrc且rgnDst容量不足(nRgnSize < (clipb-clipa)*sizeof(RECT)),分配新缓冲区并释放旧缓冲区。 - 对子区间内每个矩形:若在 x 方向也与
rect相交(lpr->left < rect->right && lpr->right > rect->left),则四边取 min/max 裁剪出一个新矩形写入rgnDst,同时维护新的包围盒。 - 若
j==0(一个矩形都没产出)→empty。 - 更新
rgnDst的包围盒与nCount,返回REGION_Complexity(rgnDst)。
empty 标签处:若 Buffer==NULL 则把 Buffer 指向内嵌 &rdh.rcBound,然后 EMPTY_REGION,返回 NULLREGION。
使用方式 :被 REGION_bMakeFrameRegion 之外的许多内部裁剪路径使用(如 IntGdiGetClipBox 相关逻辑、DC 可见区域计算)。注意 rgnDst == rgnSrc 时原地操作是允许的(容量检查被跳过,因为裁剪只会减少矩形数)。
5.5 REGION_Coalesce(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L808-L922))
c
static INT FASTCALL REGION_Coalesce(PREGION pReg, INT prevStart, INT curStart)
作用 :尝试把 pReg 中"前一个带"(从 prevStart 开始)与"当前带"(从 curStart 开始)合并为一个更高的带 ,返回"下一个要参与合并的前带起始索引"。仅在 REGION_RegionOp 内部被调用。
实现流程:
- 定位前带:
pPrevRect = Buffer + prevStart,前带矩形数prevNumRects = curStart - prevStart。 - 计算当前带矩形数
curNumRects:从curStart起,数出所有top == bandtop的矩形。注释说明必须这样做,因为 RegionOp 在"一个区域耗尽后"可能一次追加了多个带。 - 若追加了多个带(
pCurRect != pRegEnd),把curStart回退到最后一个带的起始(倒序扫描相同 top),以便下轮合并从正确位置开始。 - 合并条件 (缺一不可):
curNumRects == prevNumRects且非零;- 前带
pPrevRect->bottom == pCurRect->top(底边与顶边相触,开区间相接); - 两个带的每个矩形
left/right逐对相等(矩形位置完全对齐)。
- 条件满足:
nCount -= curNumRects(删掉当前带矩形),然后循环把当前带各矩形的bottom赋给前带对应矩形(纵向拉伸)。 - 收尾:若当前带是最后一个带,返回
prevStart(下轮从合并后的带开始);否则把剩余矩形整体前移覆盖被删除的矩形,返回curStart。
为何必须对齐:若两个带矩形 x 位置不同却强行合并,会产生"带内矩形纵向长度不一致"的非法表示,破坏分带不变量,导致后续布尔运算与命中测试出错。
6. 扫描线布尔运算核心:REGION_RegionOp
REGION_RegionOp(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L946-L1219))是 region.c 的心脏:所有二元布尔运算(AND/OR/SUB,XOR 分解为两次 SUB + 一次 OR)最终都汇聚到这里。
6.1 函数签名与回调
c
typedef BOOL (FASTCALL *overlapProcp)(PREGION, PRECT, PRECT, PRECT, PRECT, INT, INT);
typedef BOOL (FASTCALL *nonOverlapProcp)(PREGION, PRECT, PRECT, INT, INT);
static BOOL FASTCALL REGION_RegionOp(
PREGION newReg, /* 结果区域(可能是 reg1 或 reg2 之一) */
PREGION reg1, /* 操作数 1(被减数/减数等) */
PREGION reg2, /* 操作数 2 */
overlapProcp overlapFunc, /* 两带重叠时的回调 */
nonOverlapProcp nonOverlap1Func, /* 仅 reg1 有内容的带回调 */
nonOverlapProcp nonOverlap2Func) /* 仅 reg2 有内容的带回调 */
设计哲学(见函数注释):把两个区域看作集合,它们在平面上共同覆盖一个矩形区域;该矩形被水平分割成若干带 ,每个带要么只被一个区域覆盖(非重叠带),要么被两者同时覆盖(重叠带)。非重叠带调用 nonOverlapXFunc 处理(通常是把该带的矩形直接拷入结果),重叠带调用 overlapFunc 处理(负责按运算语义裁剪 x 区间)。每个带结束后尝试 REGION_Coalesce 合并以减少矩形数。
各运算的回调映射:
| 运算 | overlapFunc | nonOverlap1Func | nonOverlap2Func |
|---|---|---|---|
| AND(相交) | REGION_IntersectO |
NULL | NULL |
| OR(联合) | REGION_UnionO |
REGION_UnionNonO |
REGION_UnionNonO |
| SUB(reg1 - reg2) | REGION_SubtractO |
REGION_SubtractNonO1 |
NULL |
注意 AND 的两个 nonOverlap 回调都是 NULL------"只被一个区域覆盖的带"在交集中必然为空,直接丢弃。
6.2 初始化阶段
c
r1 = reg1->Buffer; r2 = reg2->Buffer;
r1End = r1 + reg1->rdh.nCount; r2End = r2 + reg2->rdh.nCount;
oldRects = newReg->Buffer; /* 记住旧缓冲区,结尾释放 */
newReg->rdh.nCount = 0; /* 先把结果标记为空(不清 Buffer) */
newReg->rdh.nRgnSize = max(reg1->rdh.nCount + 1, reg2->rdh.nCount) * 2 * sizeof(RECT);
newReg->Buffer = ExAllocatePoolWithTag(PagedPool, newReg->rdh.nRgnSize, TAG_REGION);
要点:
newReg可能就是reg1或reg2,因此不能 先释放它的旧 Buffer 或清空 extents------只把nCount置 0,旧 Buffer 指针保存在oldRects,运算结束后统一释放。- 预分配
2*max(n1+1, n2)个矩形的容量,尽量让整个运算过程无需二次扩容(注释:"希望最终能删掉结尾的 Xrealloc")。 - 初始化
ybot = min(reg1->rcBound.top, reg2->rcBound.top):在当前带处理中,ybot是"最近一次交叠的底",ytop是"下一次交叠的顶",二者配合把非重叠带裁剪到正确的 y 区间。
6.3 主循环:逐带推进
c
prevBand = 0;
do {
curBand = newReg->rdh.nCount;
/* 求 reg1 当前带结束位置 r1BandEnd、reg2 当前带结束位置 r2BandEnd */
r1BandEnd = r1;
while ((r1BandEnd != r1End) && (r1BandEnd->top == r1->top)) r1BandEnd++;
r2BandEnd = r2;
while ((r2BandEnd != r2End) && (r2BandEnd->top == r2->top)) r2BandEnd++;
/* 情形 A:reg1 的当前带在 reg2 当前带之上 */
if (r1->top < r2->top) {
top = max(r1->top, ybot);
bot = min(r1->bottom, r2->top);
if ((top != bot) && (nonOverlap1Func != NULL))
if (!(*nonOverlap1Func)(newReg, r1, r1BandEnd, top, bot)) return FALSE;
ytop = r2->top;
}
/* 情形 B:reg2 的当前带在 reg1 当前带之上 */
else if (r2->top < r1->top) {
top = max(r2->top, ybot);
bot = min(r2->bottom, r1->top);
if ((top != bot) && (nonOverlap2Func != NULL))
if (!(*nonOverlap2Func)(newReg, r2, r2BandEnd, top, bot)) return FALSE;
ytop = r1->top;
}
/* 情形 C:两带 top 相同 → 重叠带 */
else {
ytop = r1->top;
}
/* 若本带添加了矩形,尝试与前带合并 */
if (newReg->rdh.nCount != curBand)
prevBand = REGION_Coalesce(newReg, prevBand, curBand);
/* 处理重叠带:仅当 ybot > ytop 时二者确实相交 */
ybot = min(r1->bottom, r2->bottom);
curBand = newReg->rdh.nCount;
if (ybot > ytop) {
if (!(*overlapFunc)(newReg, r1, r1BandEnd, r2, r2BandEnd, ytop, ybot)) return FALSE;
}
if (newReg->rdh.nCount != curBand)
prevBand = REGION_Coalesce(newReg, prevBand, curBand);
/* 推进:bottom 等于 ybot 的带已经处理完,跳过 */
if (r1->bottom == ybot) r1 = r1BandEnd;
if (r2->bottom == ybot) r2 = r2BandEnd;
} while ((r1 != r1End) && (r2 != r2End));
逐带推进的核心不变量 :ybot = min(r1->bottom, r2->bottom) 是当前两带交叠的底边界;一个区域当前带的 bottom == ybot 意味着它在本次迭代中被完全消费,跳到下一带;另一个区域若 bottom > ybot 则其带尚未结束,保留到下一轮继续与新区间比较。这样"短带"会被多次切片,"长带"分次输出,保证结果的每个输出带都有完整的 y 跨度信息。
非重叠带的 y 裁剪 :情形 A 中,reg1 的带只被 reg1 覆盖的部分是 [max(r1->top, ybot), min(r1->bottom, r2->top))------下界被"最近交叠的底"截断(防止把已处理的 y 区域重复输出),上界被 reg2 的顶截断(其上即将进入重叠带或 reg2 独有带)。top == bot 表示零高度,跳过。
6.4 收尾:处理剩余带
主循环结束于某个区域耗尽。此时另一个区域剩余的带必然不与已耗尽区域交叠(否则循环不会终止),因此只需调用其对应的 nonOverlap 回调把剩余矩形全部加入:
c
if (r1 != r1End) {
do {
r1BandEnd = r1;
while ((r1BandEnd < r1End) && (r1BandEnd->top == r1->top)) r1BandEnd++;
if (!(*nonOverlap1Func)(newReg, r1, r1BandEnd, max(r1->top, ybot), r1->bottom))
return FALSE;
r1 = r1BandEnd;
} while (r1 != r1End);
}
else if ((r2 != r2End) && (nonOverlap2Func != NULL)) { /* 对称处理 reg2 */ }
if (newReg->rdh.nCount != curBand)
(VOID)REGION_Coalesce(newReg, prevBand, curBand);
注意这里 y 区间取 [max(r->top, ybot), r->bottom):ybot 是最后一次交叠的底,剩余带完全在其下,故下界只需防重复输出。
6.5 收尾:缓冲区收缩与清理
c
if ((newReg->rdh.nRgnSize > (2 * newReg->rdh.nCount * sizeof(RECT))) &&
(newReg->rdh.nCount > 2))
{
if (REGION_NOT_EMPTY(newReg)) {
/* 分配 nCount 精确大小的缓冲区,拷贝后释放旧的 */
} else {
newReg->rdh.nRgnSize = sizeof(RECT);
/* 释放旧缓冲,分配一个最小缓冲(防止 Buffer 悬空) */
}
}
newReg->rdh.iType = RDH_RECTANGLES;
if (oldRects != &newReg->rdh.rcBound)
ExFreePoolWithTag(oldRects, TAG_REGION);
return TRUE;
- 收缩条件 :容量超过实际需要 2 倍且矩形数 > 2。空区域也要保证
Buffer有效(分配 1 个 RECT 大小的缓冲)。 - 释放
oldRects(newReg原缓冲区)------若它曾内嵌于rdh.rcBound(即newReg原本是单矩形区域)则无需释放。
6.6 复杂度分析
- 时间:O(n1 + n2)(两个矩形数组各扫描常数遍;
REGION_Coalesce与带数成线性)。 - 空间:预分配 O(max(n1, n2)),最坏收缩后 O(nResult)。
- 正确性前提:两个输入必须满足"带内 x 不重叠、带间 y 相接、按 y/x 排序"不变量------这正是
REGION_Coalesce、REGION_bMergeRect维护的东西。
7. 四种布尔运算详解
7.1 相交:REGION_IntersectO 与 REGION_IntersectRegion
REGION_IntersectO(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1236-L1287))------重叠带回调:
c
static BOOL FASTCALL REGION_IntersectO(PREGION pReg, PRECTL r1, PRECTL r1End,
PRECTL r2, PRECTL r2End, INT top, INT bottom)
双指针归并,取两矩形 x 区间交集:
c
while ((r1 != r1End) && (r2 != r2End)) {
left = max(r1->left, r2->left);
right = min(r1->right, r2->right);
if (left < right) {
if (!REGION_bAddRect(pReg, left, top, right, bottom)) return FALSE;
}
/* 推进右边界较小的那个指针(另一个仍可能与后继矩形相交) */
if (r1->right < r2->right) r1++;
else if (r2->right < r1->right) r2++;
else { r1++; r2++; }
}
注释特别说明"无需检查包含关系(subsumption)":因为带内矩形互不相邻,两个区域在同一带内都不会有相邻矩形,所以交叠时不可能出现需要合并的情形------每个输出矩形天然不重叠。
REGION_IntersectRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1292-L1325)):
c
static BOOL FASTCALL REGION_IntersectRegion(PREGION newReg, PREGION reg1, PREGION reg2)
- 平凡拒绝 :
reg1空、reg2空、或EXTENTCHECK(rcBound1, rcBound2)==0(包围盒不相交)→newReg->rdh.nCount = 0(结果为空,无需调 RegionOp)。 - 否则调
REGION_RegionOp(newReg, reg1, reg2, REGION_IntersectO, NULL, NULL)。 - 之后
REGION_SetExtents(newReg)重算包围盒。
注意调用顺序的注释:extents 必须在 RegionOp 返回后才更新,因为 newReg 可能是 reg1/reg2 之一,RegionOp 依赖源区域的原始 extents。
7.2 联合:REGION_UnionNonO、REGION_UnionO、REGION_bMergeRect、REGION_UnionRegion
REGION_UnionNonO (region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1344-L1370))------非重叠带回调:把整个带的矩形以 [top, bottom) 为 y 区间直接拷入结果。先 REGION_bEnsureBufferSize(pReg, nCount + (rEnd-r)) 预分配该带所需容量,再循环 REGION_vAddRect(无需合并检查,因为非重叠带的矩形天然不重叠)。
REGION_UnionO (region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1414-L1459))------重叠带回调:每次取两矩形中 left 较小的那个,用 REGION_bMergeRect 加入结果(允许与结果中"同一带、bottom 相同、right 相邻"的矩形合并)。循环结束后,把剩余未消费的矩形全部 REGION_bMergeRect 进去。
REGION_bMergeRect(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1372-L1399)):
c
static __inline BOOL REGION_bMergeRect(_Inout_ PREGION prgn, _In_ LONG left, _In_ LONG top,
_In_ LONG right, _In_ LONG bottom)
作用 :向区域添加矩形,但先尝试与最后一个矩形合并(横向拉伸),以维持"带内矩形不相邻"的不变量。
合并条件:nCount != 0、最后矩形 top == top && bottom == bottom(同一带)、最后矩形.right >= left(相邻或重叠);满足则 最后矩形.right = max(最后矩形.right, right)。否则退化为 REGION_bAddRect。
REGION_UnionRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1464-L1544))------短路优化最丰富的运算:
reg1 == reg2、reg1空 → 结果 = 复制reg2(若newReg != reg2)。reg2空 → 结果 = 复制reg1。reg1为单矩形且完全包含reg2包围盒 → 结果 = 复制reg1。reg2为单矩形且完全包含reg1包围盒 → 结果 = 复制reg2。- 否则
REGION_RegionOp(newReg, reg1, reg2, REGION_UnionO, REGION_UnionNonO, REGION_UnionNonO),成功后再手动更新包围盒:rcBound = 两源包围盒的并集(对 OR 运算可以直接外推,无需REGION_SetExtents全扫)。
包含检查条件示例(reg1 包含 reg2):reg1->rdh.nCount == 1 && reg1->rcBound.left <= reg2->rcBound.left && reg1->rcBound.top <= reg2->rcBound.top && reg2->rcBound.right <= reg1->rcBound.right && reg2->rcBound.bottom <= reg1->rcBound.bottom。注意这只检查包围盒包含------对单矩形 reg1 而言包围盒即区域本身,检查是精确的。
7.3 相减:REGION_SubtractNonO1、REGION_SubtractO、REGION_SubtractRegion
REGION_SubtractNonO1 (region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1561-L1587))------非重叠带回调(reg1 独有带,即"被减数独有"):与 REGION_UnionNonO 完全相同,把带内矩形直接加入结果(减数在该带没有内容,无需裁剪)。
REGION_SubtractO (region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1600-L1704))------重叠带回调,相减算法的精髓。它用栅栏(fence)变量 left 跟踪"当前被减数矩形尚未被覆盖的最左 x",与被减数 r1、减数 r2 双指针推进:
c
left = r1->left;
while ((r1 != r1End) && (r2 != r2End)) {
if (r2->right <= left) {
/* 减数整体在被减数左侧:该减数已无用,换下一个减数 */
r2++;
}
else if (r2->left <= left) {
/* 减数盖住被减数左缘:栅栏右移到减数右缘 */
left = r2->right;
if (left >= r1->right) {
/* 被减数整个被覆盖:推进被减数,栅栏重置 */
r1++;
if (r1 != r1End) left = r1->left;
} else {
r2++; /* 减数用尽(其右缘不超过被减数右缘) */
}
}
else if (r2->left < r1->right) {
/* 减数从内部切入:把 [left, r2->left) 这段未覆盖区间输出 */
if (!REGION_bAddRect(pReg, left, top, r2->left, bottom)) return FALSE;
left = r2->right;
if (left >= r1->right) { r1++; if (r1 != r1End) left = r1->left; }
else { r2++; }
}
else {
/* 减数完全在被减数右侧:输出剩余未覆盖部分并推进被减数 */
if (r1->right > left)
if (!REGION_bAddRect(pReg, left, top, r1->right, bottom)) return FALSE;
r1++;
if (r1 != r1End) left = r1->left;
}
}
/* 减数耗尽后,被减数剩余矩形整体输出(用预分配的 EnsureBufferSize 批量写) */
if (r1 != r1End) {
if (!REGION_bEnsureBufferSize(pReg, pReg->rdh.nCount + (r1End - r1))) return FALSE;
do {
REGION_vAddRect(pReg, left, top, r1->right, bottom);
r1++;
if (r1 != r1End) left = r1->left;
} while (r1 != r1End);
}
return TRUE;
四种情形的几何含义:
| 情形 | 条件 | 动作 |
|---|---|---|
| 减数在左 | r2->right <= left |
跳过该减数(不影响当前被减数) |
| 减数覆盖左缘 | r2->left <= left |
栅栏右移;若盖完整个被减数则换被减数 |
| 减数切入内部 | r2->left < r1->right |
输出 [left, r2->left) 段,栅栏右移到 r2->right |
| 减数在右 | 其余 | 输出 [left, r1->right) 剩余段,换被减数 |
由于减数栅栏 left 单调递增且只前进,每个矩形最多处理常数次,整体仍线性。输出的每个矩形 y 区间为 [top, bottom)(重叠带 y 区间),x 为未被减数覆盖的若干段------这些段在同一带内天然不相邻(被减去的部分隔开),满足不变量。
REGION_SubtractRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1717-L1748)):
c
static BOOL FASTCALL REGION_SubtractRegion(PREGION regD, PREGION regM, PREGION regS)
regM(被减数 minuend)− regS(减数 subtrahend)→ regD。
- 平凡拒绝:
regM空、regS空、或二者包围盒不相交 → 直接REGION_CopyRegion(regD, regM)。 - 否则
REGION_RegionOp(regD, regM, regS, REGION_SubtractO, REGION_SubtractNonO1, NULL)。 REGION_SetExtents(regD)重算包围盒。
7.4 异或:REGION_XorRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1753-L1792))
c
static BOOL FASTCALL REGION_XorRegion(PREGION dr, PREGION sra, PREGION srb)
{
/* 分配两个临时区域(带句柄) */
tra = REGION_AllocRgnWithHandle(sra->rdh.nCount + 1);
trb = REGION_AllocRgnWithHandle(srb->rdh.nCount + 1);
ret = REGION_SubtractRegion(tra, sra, srb) && /* A - B */
REGION_SubtractRegion(trb, srb, sra) && /* B - A */
REGION_UnionRegion(dr, tra, trb); /* (A-B) ∪ (B-A) */
/* 解锁并删除两个临时区域 */
}
实现要点 :XOR(对称差)= (A−B) ∪ (B−A)。代码用两个带句柄的临时区域 做中间量(REGION_AllocRgnWithHandle 返回的区域已插入句柄表,htra = tra->BaseObject.hHmgr),运算结束后 GreDeleteObject 删除。注释 FIXME: Don't use a handle 承认这是次优设计------理论上可用无句柄临时区域避免句柄表开销,属已知待优化点。
7.5 矩形便捷封装
REGION_UnionRectWithRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1797-L1810)):
c
BOOL FASTCALL REGION_UnionRectWithRgn(PREGION rgn, const RECTL *rect)
{
REGION region; /* 栈上临时区域 */
region.Buffer = ®ion.rdh.rcBound; /* 单矩形:内嵌缓冲 */
region.rdh.nCount = 1;
region.rdh.nRgnSize = sizeof(RECT);
region.rdh.rcBound = *rect;
return REGION_UnionRegion(rgn, rgn, ®ion); /* rgn ∪ rect */
}
用栈上临时 REGION 包装矩形,复用 REGION_UnionRegion 完成"区域并入一个矩形"。rgn 同时是源与目标(reg1 == newReg),UnionRegion 的短路逻辑会正确处理(reg1 == reg2 分支不触发,因为 reg2 是栈上区域;newReg == reg1 时复制分支被跳过)。被 NtGdiExtCreateRegion 逐矩形建区时反复调用。
REGION_SubtractRectFromRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1812-L1827)):
c
INT FASTCALL REGION_SubtractRectFromRgn(PREGION prgnDest, PREGION prgnSrc, const RECTL *prcl)
同样构造栈上单矩形临时区域,REGION_SubtractRegion(prgnDest, prgnSrc, &rgnLocal),返回 REGION_Complexity(prgnDest)。与 Union 版本的对称 API,用于"从区域中挖掉一个矩形"(如窗口无效矩形扣除)。
7.6 薄封装:REGION_bCopy、REGION_bIntersectRegion
REGION_bCopy(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1829-L1837)):对REGION_CopyRegion加 NULL 检查的公开封装(供其他模块调用)。REGION_bIntersectRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1839-L1848)):对REGION_IntersectRegion加 NULL 检查的公开封装。这两个 b 前缀封装的存在是因为内部 static 版本不带参数校验,而跨模块调用(如 dclife.c 的裁剪合成)需要防御性检查。
8. 边框区域(Frame Region)
边框区域用于 NtGdiFrameRgn(《分析_36》):给定源区域和边框宽度 cx × cy,产生"沿区域边界向外/向内扩出的边框带"。
8.1 REGION_bMakeSimpleFrameRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1850-L1939))
针对单矩形源区域 的快速路径。输入 prclSrc(源矩形)、cx/cy(边框尺寸,已断言非负)。
实现流程:
EMPTY_REGION清空结果。- 退化情形 :若源矩形高度
<= cy*2或宽度<= cx*2(边框把内部区域完全覆盖),直接输出整个矩形(单矩形区域)。 - 构造最多 4 个矩形:
- 上边框 (cy != 0 时):
[left, top, right, top+cy)。 - 左边框 (cx != 0 时):
[left, top+cy, left+cx, bottom-cy)。 - 右边框 (cx != 0 时):
[right-cx, top+cy, right, bottom-cy)。 - 下边框 (cy != 0 时):
[left, bottom-cy, right, bottom)。
- 上边框 (cy != 0 时):
- 若
i != 0(非空),nCount = i,nRgnSize = i * sizeof(RECT),分配分页池缓冲区(此刻Buffer仍是内嵌&rdh.rcBound,断言确认后替换),COPY_RECTS拷贝。包围盒rcBound保持为源矩形。
注意结果矩形数必 > 1(否则走退化分支),因此复杂度为 COMPLEXREGION。四个矩形按"上、左、右、下"顺序存储,且 y 分带自然成立(上/下带各一个矩形,中间带左右两个矩形)。
8.2 REGION_bMakeFrameRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1941-L2024))
通用路径(源区域为复杂区域),采用平移 + 相交 + 相减的几何构造法:
c
cx = abs(cx); cy = abs(cy);
/* 校验:边框尺寸不超 MAX_COORD、源区域非空、(cx,cy)!=(0,0) */
/* 简单源区域走 REGION_bMakeSimpleFrameRgn */
if (REGION_Complexity(prgnSrc) == SIMPLEREGION)
return REGION_bMakeSimpleFrameRgn(prgnDest, &prgnSrc->rdh.rcBound, cx, cy);
/* 坐标范围预检:平移后不能越界(MIN_COORD/MAX_COORD) */
REGION_CopyRegion(prgnDest, prgnSrc); /* 结果 = 源区域 */
REGION_bOffsetRgn(prgnSrc, cx, cy); /* 源 → 右下 */
REGION_IntersectRegion(prgnDest, prgnDest, prgnSrc); /* 裁掉左上边框外 */
REGION_bOffsetRgn(prgnSrc, -2*cx, 0); /* 源 → 左下 */
REGION_IntersectRegion(prgnDest, prgnDest, prgnSrc); /* 裁掉右上边框外 */
REGION_bOffsetRgn(prgnSrc, 0, -2*cy); /* 源 → 左上 */
REGION_IntersectRegion(prgnDest, prgnDest, prgnSrc); /* 裁掉右下边框外 */
REGION_bOffsetRgn(prgnSrc, 2*cx, 0); /* 源 → 右上 */
REGION_IntersectRegion(prgnDest, prgnDest, prgnSrc); /* 裁掉左下边框外 */
REGION_bOffsetRgn(prgnSrc, -cx, cy); /* 源归位 */
REGION_SubtractRegion(prgnDest, prgnSrc, prgnDest); /* 源 − 内部 = 边框 */
return TRUE;
几何原理 :把源区域分别平移 (cx,cy)、(−cx,cy)、(cx,−cy)、(−cx,−cy) 并依次与结果求交,四次相交后 prgnDest 恰好是"源区域收缩了 cx×cy 的内部区域"(每步裁掉一条边方向的突出部分,注释标注了对应裁剪的边);最后 源 − 内部 = 边框带。
副作用 :函数会修改 prgnSrc (多次偏移),因此调用方必须持有对 prgnSrc 的独占访问(GreCreateFrameRgn 中先 REGION_LockRgn 锁定)。NT_VERIFY(REGION_bOffsetRgn(...)) 断言偏移必然成功(已预检坐标范围)。
8.3 GreCreateFrameRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2026-L2065))
c
HRGN FASTCALL GreCreateFrameRgn(HRGN hrgn, INT cx, INT cy)
对外入口(被 NtGdiFrameRgn 使用):
REGION_AllocUserRgnWithHandle(1)分配结果区域;失败置EngSetLastError(ERROR_NOT_ENOUGH_MEMORY)。REGION_LockRgn(hrgn)锁定源区域(获得独占访问以便REGION_bMakeFrameRegion就地修改)。REGION_bMakeFrameRegion(prgnFrame, prgnSrc, cx, cy);成功则解锁并返回句柄;失败则REGION_Delete删除结果,返回 NULL。- 解锁源区域。
9. 区域变换
9.1 REGION_bOffsetRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2708-L2795))
c
BOOL FASTCALL REGION_bOffsetRgn(_Inout_ PREGION prgn, _In_ INT cx, _In_ INT cy)
作用 :把区域整体平移 (cx, cy)。这是 NtGdiOffsetRgn 的内部实现,也是 REGION_bXformRgn 平移分支、REGION_bMakeFrameRegion 几何构造的基础。
实现流程:
- 平凡情形:
(cx, cy) == (0,0)→ TRUE;nCount == 0(空区域)→ TRUE(忽略偏移)。 - 越界预检:
cx/cy本身超出[MIN_COORD, MAX_COORD],或平移后包围盒越界(cx > 0检查rcBound.right > MAX_COORD - cx,cx < 0检查rcBound.left < MIN_COORD - cx;cy 方向对称)→ FALSE。 - 遍历所有矩形,四个坐标各加 cx/cy。
- 更新包围盒(仅当
Buffer非内嵌时------若Buffer == &rdh.rcBound,矩形与包围盒是同一个结构体,第一步的循环已经同步更新了它,无需重复)。
平移保持 y-x 分带不变量(所有矩形整体移动,相对关系不变),因此无需重排。
9.2 REGION_bXformRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2067-L2200))
c
BOOL FASTCALL REGION_bXformRgn(_Inout_ PREGION prgn, _In_ PMATRIX pmx)
作用 :对区域施加二维坐标变换(世界变换到设备变换,如 SetWorldTransform 相关路径)。按矩阵类型分三条路径:
路径 1 --- 纯平移(XFORM_UNITY) :REGION_bOffsetRgn(prgn, (pmx->fxDx + 8) / 16, (pmx->fxDy + 8) / 16)。fxDx/fxDy 是 16.16 定点数,(+8)/16 是四舍五入到整数像素。空区域(nCount < 1)也走此分支(flAccel & XFORM_UNITY 时返回 TRUE)。
路径 2 --- 仅缩放(XFORM_SCALE,无旋转/剪切,对角元素非零):
c
XFORMOBJ_vInit(&xo, pmx);
/* 先对包围盒试变换,越界则整体失败(不破坏区域) */
if (!XFORMOBJ_bApplyXform(&xo, XF_LTOL, 2, &prgn->rdh.rcBound, &rect))
return FALSE;
/* 对全部矩形(nCount*2 个点)应用变换 */
XFORMOBJ_bApplyXform(&xo, XF_LTOL, prgn->rdh.nCount * 2, prgn->Buffer, prgn->Buffer);
/* 重置包围盒,逐矩形 RECTL_vMakeWellOrdered 归一化(缩放可能反转 left/right),RECTL_bUnionRect 重算 */
缩放可能产生负宽/负高矩形(镜像),故逐个 RECTL_vMakeWellOrdered 纠正,并用 RECTL_bUnionRect 重新累积包围盒;结尾用断言验证 top 排序不变量仍成立(纯缩放保持 y 顺序,x 轴镜像也不会破坏 y 分带)。
路径 3 --- 一般仿射(含旋转/剪切) :旋转/剪切会破坏 y-x 分带表示,因此把区域退化为多边形再重扫:
c
cjSize = prgn->rdh.nCount * (4 * sizeof(POINT) + sizeof(ULONG));
ppt = ExAllocatePoolWithTag(PagedPool, cjSize, GDITAG_REGION);
/* 每个矩形展开为 4 个顶点(左下→右下→右上→左上,逆时针) */
pcPoints = (PULONG)&ppt[4 * prgn->rdh.nCount];
pcPoints[i] = 4; /* 每个多边形 4 点 */
ppt[4*i+0] = {left, top}; ppt[4*i+1] = {right, top};
ppt[4*i+2] = {right, bottom}; ppt[4*i+3] = {left, bottom};
XFORMOBJ_vInit(&xo, pmx);
XFORMOBJ_bApplyXform(&xo, XF_LTOL, prgn->rdh.nCount * 2, ppt, ppt); /* 点变换 */
bResult = REGION_SetPolyPolygonRgn(prgn, ppt, pcPoints, prgn->rdh.nCount, WINDING);
用 WINDING(绕数)模式重扫转换后的多边形------因为单个矩形的四个顶点按逆时针给出,旋转/剪切后仍是简单多边形,绕数规则正确区分内外。这是"通用仿射变换 + 区域重构造"的标准做法,代价是 O(n) 的多边形扫描转换开销。
10. 多边形区域(扫描转换)
region.c 第二大半部分(约 1200 行)实现多边形→区域的扫描转换,用于 NtGdiCreatePolygonRgn / NtGdiCreatePolyPolygonRgn / CreateRoundRectRgn(见第 14 章)。其算法是经典的边表(Edge Table)+ 活动边表(Active Edge Table)扫描线填充,支持**奇偶(ALTERNATE)与绕数(WINDING)**两种填充规则,源自 Foley/Van Dam 的教科书算法(文件注释说明),配套 Bresenham 整数增量来跟踪边的 x 位置。
10.1 数据结构
c
typedef struct _EDGE_TABLE_ENTRY { /* ETE:一条边 */
INT ymax; /* 该边结束的 y(退出活动表) */
BRESINFO bres; /* Bresenham 参数(minor_axis 即当前 x) */
EDGE_TABLE_ENTRY *next, *back; /* AET 双向链表 */
EDGE_TABLE_ENTRY *nextWETE; /* 绕数规则下的"有效边"链 */
INT ClockWise; /* 绕数方向标志(1 顺时针 / 0 逆时针) */
} EDGE_TABLE_ENTRY;
typedef struct _SCANLINE_LIST { /* 边表桶 */
INT scanline; /* 该扫描线 y */
EDGE_TABLE_ENTRY *edgelist; /* 从此 y 进入的边链表 */
SCANLINE_LIST *next;
} SCANLINE_LIST;
typedef struct { INT ymax, ymin; SCANLINE_LIST scanlines; } EDGE_TABLE;
typedef struct _POINTBLOCK { /* 输出点缓冲块 */
POINT pts[NUMPTSTOBUFFER]; /* NUMPTSTOBUFFER = 200 */
POINTBLOCK *next;
} POINTBLOCK;
SCANLINE_LISTBLOCK 按每块 25 个 SCANLINE_LIST 批量分配,减少小对象分配次数。
10.2 辅助函数一览
| 函数 | 作用 |
|---|---|
REGION_InsertEdgeInET |
把一条边插入边表:先在扫描线桶链中找到/创建对应 y 的桶(需要时分配新 SCANLINE_LISTBLOCK),再按 minor_axis(x)升序插入桶内边链(插入排序) |
REGION_CreateETandAET |
遍历所有多边形的所有边:区分上下端点、跳过水平边、用 BRESINITPGONSTRUCT 初始化 Bresenham 参数、ymax = bottom->y - 1(-1 避免处理最后一条扫描线)、按 ClockWise 记录绕数方向,全部插入 ET;同时初始化 AET 头节点 |
REGION_loadAET |
当扫描线到达某桶的 y 时,把该桶的边链表按 x 序合并进 AET(保持排序) |
REGION_InsertionSort |
对 AET 按 minor_axis 做插入排序(边可能相交导致 x 序变化),返回是否有改动 |
REGION_computeWAET |
按绕数规则从 AET 中选出"有效边"链(nextWETE):沿 AET 累加 ClockWise(±1),在"内部/外部"状态翻转的边处打点------即仅保留改变内外状态的边 |
REGION_EVALUATEEDGEEVENODD / EVALUATEEDGEWINDING |
宏:每处理一条扫描线后更新边 x(BRESINCRPGONSTRUCT),ymax == y 时把边移出 AET(绕数版本同时置 fixWAET 要求重算 WETE) |
REGION_FreeStorage |
释放 SCANLINE_LISTBLOCK 链 |
REGION_PtsToRegion |
把扫描线输出的 x 点对(每扫描线偶数个,成对构成区间)转换回 RECTL 数组,并在可能时纵向合并 相邻扫描线矩形(pts->x == rects->left && pts->y == rects->bottom && pts[1].x == rects->right 时拉伸 bottom) |
10.3 主流程:REGION_SetPolyPolygonRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3255-L3451))
c
BOOL FASTCALL REGION_SetPolyPolygonRgn(
_Inout_ PREGION prgn, /* 结果区域 */
_In_ const POINT *ppt, /* 顶点数组(cPolygons 个多边形首尾相接) */
_In_ const ULONG *pcPoints, /* 每个多边形的顶点数 */
_In_ ULONG cPolygons, /* 多边形个数 */
_In_ INT iMode) /* ALTERNATE(奇偶)或 WINDING(绕数) */
实现流程:
- 模式校验:
iMode必须是ALTERNATE或WINDING,否则返回 FALSE。 - 矩形特判 :若"1 个多边形、4 个顶点(或 5 个顶点且首尾闭合)、且四角构成轴对齐矩形"(两组平行条件任一成立),直接
REGION_SetRectRgn返回------避免走完整扫描转换的开销。 - 统计总顶点数
total,分配pETEs = ExAllocatePoolWithTag(... sizeof(EDGE_TABLE_ENTRY) * total ...)(每条非水平边一个 ETE,最坏 = 总顶点数)。 REGION_CreateETandAET建立边表 ET 与空的活动边表 AET。- 按
iMode分两条扫描循环(见 10.4 / 10.5)。 REGION_PtsToRegion(numFullPtBlocks, iPts, &FirstPtBlock, prgn)把点缓冲转成矩形数组。Cleanup:REGION_FreeStorage释放扫描线桶块;逐个释放POINTBLOCK链;释放pETEs。
10.4 奇偶模式(ALTERNATE)扫描循环
c
for (y = ET.ymin; y < ET.ymax; y++) {
/* 到达某桶的 y:把该桶的边并入 AET(保持 x 序) */
if (pSLL != NULL && y == pSLL->scanline) {
REGION_loadAET(&AET, pSLL->edgelist);
pSLL = pSLL->next;
}
pPrevAET = &AET;
pAET = AET.next;
/* 对每条活动边:输出 (x, y) 到点缓冲,然后更新/淘汰边 */
while (pAET) {
pts->x = pAET->bres.minor_axis, pts->y = y;
pts++, iPts++;
if (iPts == NUMPTSTOBUFFER) { /* 缓冲满 → 链接新 POINTBLOCK */
curPtBlock->next = 分配新的 POINTBLOCK;
curPtBlock = 新块; pts = 新块->pts;
numFullPtBlocks++; iPts = 0;
}
EVALUATEEDGEEVENODD(pAET, pPrevAET, y); /* 更新 x / ymax==y 时出表 */
}
REGION_InsertionSort(&AET); /* 边可能交叉,按 x 重排 */
}
关键点:
- 每扫描线输出偶数个 x 坐标,第 1-2 个点构成第一个区间、第 3-4 个构成第二个......这正是奇偶规则:扫描线与多边形相交于偶数个交点,成对配对成内部区间。
EVALUATEEDGEEVENODD宏在边过期(ymax == y)时把边从 AET 双向链表中摘除,否则用 Bresenham 增量步进minor_axis(x)。- 点缓冲 200 个点满则链接新块,避免不定长内存管理。
10.5 绕数模式(WINDING)扫描循环
c
for (y = ET.ymin; y < ET.ymax; y++) {
if (pSLL != NULL && y == pSLL->scanline) {
REGION_loadAET(&AET, pSLL->edgelist);
REGION_computeWAET(&AET); /* 重算有效边链 */
pSLL = pSLL->next;
}
pPrevAET = &AET;
pAET = AET.next;
pWETE = pAET;
while (pAET) {
/* 只有"有效边"(nextWETE 链上的)才输出点 */
if (pWETE == pAET) {
pts->x = pAET->bres.minor_axis; pts->y = y;
pts++; iPts++;
...(缓冲满则链接新块)...
pWETE = pWETE->nextWETE;
}
EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET);
}
/* 重排或出边后需重算有效边链 */
if (REGION_InsertionSort(&AET) || fixWAET) {
REGION_computeWAET(&AET);
fixWAET = FALSE;
}
}
与奇偶模式的差别:
- 只输出有效边 :
REGION_computeWAET沿 AET 累加绕数(ClockWise为 +1、否则 −1),只有绕数从 0 变非 0 或从非 0 变 0 的边(内外状态翻转处)才被链入nextWETE。这样自交多边形、含孔洞多边形能按绕数正确填充(绕数非零的区域属于多边形内部)。 - 边被淘汰(
fixWAET)或 AET 被重排(REGION_InsertionSort返回 TRUE)时都要重算 WAET。
10.6 GreCreatePolyPolygonRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3453-L3487))
c
HRGN NTAPI GreCreatePolyPolygonRgn(const POINT *ppt, const ULONG *pcPoints,
ULONG cPolygons, INT iMode)
REGION_AllocUserRgnWithHandle(0) 分配结果区域 → REGION_SetPolyPolygonRgn 扫描转换 → 成功则取句柄并 REGION_UnlockRgn;失败则 REGION_Delete 并返回 NULL。这是 NtGdiCreatePolygonRgn / NtGdiCreatePolyPolygonRgn 的公共内核实现(gdi32 用户态把两个 API 都映射到它)。
11. 对象生命周期管理
区域对象在内核态 GDI 句柄表中生存,生命周期管理涉及:分配(带/不带句柄)、属性分配、加锁同步、解锁回写、清理删除。
11.1 REGION_AllocRgnWithHandle(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2202-L2260))
c
PREGION FASTCALL REGION_AllocRgnWithHandle(INT nReg)
作用 :分配一个带句柄的区域对象,预分配 nReg 个矩形的缓冲区,并插入句柄表。
实现流程:
GDIOBJ_AllocateObject(GDIObjType_RGN_TYPE, sizeof(REGION), BASEFLAG_LOOKASIDE)------从 GDI 对象池(lookaside)取一块 REGION 大小内存,初始化BaseObject。- 内嵌缓冲优化 :
nReg == 0 || nReg == 1时pReg->Buffer = &pReg->rdh.rcBound(注释:"测试表明 >95% 的区域只有 1 个矩形,内嵌可省一次分配");否则ExAllocatePoolWithTag(PagedPool, nReg * sizeof(RECT), TAG_REGION)分配独立缓冲(失败则GDIOBJ_vDeleteObject回滚)。 EMPTY_REGION(pReg)清零;rdh.dwSize = sizeof(RGNDATAHEADER);rdh.nCount = nReg(注意:分配容量即初始矩形数!);rdh.nRgnSize = nReg * sizeof(RECT)。- 属性初始化:
prgnattr = &rgnattr(先指向内嵌属性),AttrFlags = 0,iComplexity = SIMPLEREGION,Rect = rdh.rcBound。 GDIOBJ_hInsertObject(&pReg->BaseObject, GDI_OBJ_HMGR_POWNED)插入句柄表(失败则GDIOBJ_vFreeObject回滚),返回pReg。
注意事项 :nCount 被初始化为 nReg------调用方随后必须通过 REGION_SetRectRgn / 布尔运算把矩形数设为实际值(例如 REGION_AllocRgnWithHandle(1) 后 REGION_SetRectRgn 置 nCount = 1)。GDI_OBJ_HMGR_POWNED 表示句柄表项归进程所有(用户模式区域)。
11.2 REGION_bAllocRgnAttr(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2262-L2290))
c
BOOL NTAPI REGION_bAllocRgnAttr(PREGION prgn)
作用 :把区域的属性从"内嵌 rgnattr"迁移到每进程 GDI 属性池 (ppi->pPoolRgnAttr),并设置到句柄表项的属性槽(GDIOBJ_vSetObjectAttr)。属性池对象可以被用户态 gdi32 直接映射访问。
实现流程:
- 断言当前
prgn->prgnattr == &prgn->rgnattr。 ppi = PsGetCurrentProcessWin32Process();prgnattr = GdiPoolAllocate(ppi->pPoolRgnAttr)(失败返回 FALSE)。*prgnattr = prgn->rgnattr拷贝当前属性。prgn->prgnattr = prgnattr;GDIOBJ_vSetObjectAttr(&prgn->BaseObject, prgnattr)。
注意 :属性池分配是 per-process 的,因此跨进程句柄转移(IntGdiSetRegionOwner)时必须把属性迁回内嵌(见 11.8)。
11.3 REGION_AllocUserRgnWithHandle(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2295-L2314))
c
PREGION FASTCALL REGION_AllocUserRgnWithHandle(INT nRgn)
REGION_AllocRgnWithHandle(nRgn) + REGION_bAllocRgnAttr(prgn)(失败 ASSERT(FALSE)------属性池耗尽视为致命)。所有暴露给用户模式的区域 都走这个入口(NtGdiCreateRectRgn、NtGdiExtCreateRegion、GreCreateFrameRgn、GreCreatePolyPolygonRgn),保证属性在共享内存中可被 gdi32 访问。
11.4 REGION_vSyncRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2316-L2357))
c
static VOID REGION_vSyncRegion(_In_ PREGION prgn)
作用 :把用户态属性中的"脏"改动同步回 rdh。在 REGION_LockRgn 及 NtGdiCombineRgn/NtGdiEqualRgn 的多句柄锁定路径中被调用。
实现流程:
- 断言属性有效:
prgnattr非 NULL,且要么是内嵌属性、要么已置ATTR_RGN_VALID。 - 若
AttrFlags & ATTR_RGN_DIRTY(用户态改过属性):- 断言句柄归当前进程所有(
GreGetObjectOwner == GDI_OBJ_HMGR_POWNED)且属性确实在池中(非内嵌)。 iComplexity == NULLREGION→EMPTY_REGION(prgn)。iComplexity == SIMPLEREGION→REGION_SetRectRgn(prgn, attr.Rect.left, ...)(用属性里缓存的矩形重建单矩形区域)。- 其他值 → 属性损坏(
DPRINT1+ 断言)。
- 断言句柄归当前进程所有(
- 清除
ATTR_RGN_DIRTY | ATTR_RGN_VALID。
机制意义 :gdi32 用户态(如 SetRectRgn、OffsetRgn 的用户态快速路径)可以不进入内核 直接改属性并置 DIRTY;下次任何内核操作锁定该区域时,内核通过此函数把属性"落地"到 rdh。这是 ReactOS 对"用户态/内核态共享 GDI 对象"的核心实现。
11.5 REGION_LockRgn / REGION_UnlockRgn
c
PREGION FASTCALL REGION_LockRgn(_In_ HRGN hrgn) /* region.c#L2359-L2372 */
{
prgn = GDIOBJ_LockObject(hrgn, GDIObjType_RGN_TYPE);
if (prgn == NULL) return NULL;
REGION_vSyncRegion(prgn); /* 锁定时同步用户态脏属性 */
return prgn;
}
VOID FASTCALL REGION_UnlockRgn(_In_ PREGION prgn) /* region.c#L2374-L2398 */
{
prgnattr = prgn->prgnattr;
if (prgnattr != &prgn->rgnattr) { /* 用户模式区域:回写属性 */
prgnattr->iComplexity = REGION_Complexity(prgn);
prgnattr->Rect.left = prgn->rdh.rcBound.left;
prgnattr->Rect.top = prgn->rdh.rcBound.top;
prgnattr->Rect.right = prgn->rdh.rcBound.right;
prgnattr->Rect.bottom = prgn->rdh.rcBound.bottom;
prgnattr->AttrFlags |= ATTR_RGN_VALID; /* 属性现与 rdh 一致 */
}
GDIOBJ_vUnlockObject(&prgn->BaseObject);
}
- Lock :
GDIOBJ_LockObject增加引用计数并校验类型(防句柄重用攻击),随后同步用户态改动。 - Unlock :把内核运算后的
nCount(复杂度)与包围盒写回共享属性并置ATTR_RGN_VALID,这样用户态下一次读取属性无需进内核。ATTR_RGN_VALID表示"属性反映 rdh 当前状态",而 DIRTY 表示"属性比 rdh 新"------二者互斥轮转。
11.6 IntSysCreateRectpRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2408-L2432))
c
PREGION FASTCALL IntSysCreateRectpRgn(INT LeftRect, INT TopRect, INT RightRect, INT BottomRect)
系统区域专用 :不插句柄表(无句柄)、不使用属性池(prgnattr = &rgnattr,AttrFlags = ATTR_RGN_VALID),Buffer 内嵌,REGION_SetRectRgn 初始化后直接返回指针。注释明确"系统区域不使用属性段,分配走 gdiobj 级函数"。IntSysCreateRectpRgnIndirect(prc) 宏是它的 RECTL* 版本。这类区域(如默认区域 prgnDefault)由 win32k 内部长期持有,从不销毁(REGION_Delete 中 if (pRgn == prgnDefault) return; 保护)。
11.7 REGION_vCleanup / REGION_Delete
c
VOID NTAPI REGION_vCleanup(PVOID ObjectBody) /* region.c#L2434-L2448 */
{
PPROCESSINFO ppi = PsGetCurrentProcessWin32Process();
if (pRgn->prgnattr != &pRgn->rgnattr)
GdiPoolFree(ppi->pPoolRgnAttr, pRgn->prgnattr); /* 归还属性池 */
if (pRgn->Buffer && pRgn->Buffer != &pRgn->rdh.rcBound)
ExFreePoolWithTag(pRgn->Buffer, TAG_REGION); /* 归还矩形缓冲 */
}
VOID FASTCALL REGION_Delete(PREGION pRgn) /* region.c#L2450-L2458 */
{
if (pRgn == prgnDefault) return; /* 默认区域不可删 */
GDIOBJ_vDeleteObject(&pRgn->BaseObject); /* 触发句柄表删除 → vCleanup 回调 */
}
REGION_vCleanup是句柄表对象销毁时的清理回调 (通过GDIOBJ注册),负责释放属性池对象与矩形缓冲(内嵌缓冲无需释放)。REGION_Delete是主动删除入口;对prgnDefault(系统默认区域)静默忽略,防止误删内核内部持有的区域。
11.8 IntGdiSetRegionOwner(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2460-L2486))
c
BOOL FASTCALL IntGdiSetRegionOwner(HRGN hRgn, DWORD OwnerMask)
作用 :区域跨进程转移所有权时,把属性从"原进程的属性池"迁回内嵌 rgnattr(属性池是 per-process 的,不能跨进程共享),然后 GreSetObjectOwner(hRgn, OwnerMask) 更新句柄表所有权。
实现流程:
REGION_LockRgn(hRgn)锁定(无效返回 FALSE)。- 若属性在池中(
prgnattr != &rgnattr):GDIOBJ_vSetObjectAttr(..., NULL)清句柄表属性槽、prgnattr = &rgnattr、GdiPoolFree(ppi->pPoolRgnAttr, prgnattr)归还给原进程池。 REGION_UnlockRgn解锁(解锁回写逻辑:此时属性已指向内嵌rgnattr,故跳过回写)。GreSetObjectOwner(hRgn, OwnerMask)转移所有权。
用途 :窗口拖动/跨进程句柄传递(如 GetClipRgn 相关)、进程退出时句柄回收等场景。
12. 内部 API 与命中测试
除布尔运算与对象管理外,region.c 还提供一组被 win32k 内部(dc、ntuser、引擎)直接调用的"非 NtGdi"API。
12.1 REGION_SetRectRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2666-L2706))
c
VOID FASTCALL REGION_SetRectRgn(PREGION rgn, INT LeftRect, INT TopRect, INT RightRect, INT BottomRect)
作用 :把区域重置为单矩形 [LeftRect, TopRect, RightRect, BottomRect)(或空区域)。
实现流程:
- 归一化:若
LeftRect > RightRect交换(TopRect > BottomRect同理)------矩形坐标可反序传入。 - 若交换后
LeftRect != RightRect && TopRect != BottomRect(非空矩形):- 写
Buffer[0]与rcBound(此时Buffer必为内嵌&rdh.rcBound,两者是同一块内存,一次赋值两处生效------代码写了两遍赋值,语义相同); nCount = 1,iType = RDH_RECTANGLES。
- 写
- 否则
EMPTY_REGION(rgn)。
使用方式 :NtGdiCreateRectRgn、NtGdiSetRectRgn、REGION_vSyncRegion(SIMPLEREGION 分支)、REGION_SetPolyPolygonRgn(矩形特判)、IntSysCreateRectpRgn 都用它。注意它假设 Buffer 有至少 1 个矩形容量 (firstRect = rgn->Buffer; ASSERT(firstRect)),因此只适用于 nReg >= 1 分配的区域。
12.2 REGION_GetRgnBox / IntGdiGetRgnBox(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2544-L2581))
c
INT FASTCALL REGION_GetRgnBox(PREGION Rgn, PRECTL pRect) /* 内部:不做锁定 */
INT APIENTRY IntGdiGetRgnBox(HRGN hRgn, PRECTL pRect) /* 封装:锁定+解锁 */
REGION_GetRgnBox:Rgn != NULL 时 *pRect = Rgn->rdh.rcBound 并返回 REGION_Complexity(Rgn);NULL 返回 0(ERROR 语义)。IntGdiGetRgnBox 在其上套 REGION_LockRgn/REGION_UnlockRgn。NtGdiGetRgnBox 又在其上加了用户缓冲区探测(见 13.7)。
12.3 REGION_PtInRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2583-L2604))
c
BOOL FASTCALL REGION_PtInRegion(PREGION prgn, INT X, INT Y)
作用 :点 (X, Y) 是否在区域内。
实现流程 :先 INRECT(rcBound, X, Y) 快速排除(点在包围盒外必不在区域内);再线性扫描所有矩形 INRECT(Buffer[i], X, Y),命中即 TRUE。矩形按 y 分带有序,但函数并未利用有序性做二分------NtGdiPtInRegion 的场景(应用逐点查询)矩形数通常很小,线性扫描足够。
12.4 REGION_RectInRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2606-L2664))
c
BOOL FASTCALL REGION_RectInRegion(PREGION Rgn, const RECTL *rect)
作用 :矩形 rect 是否与区域相交(任一像素重叠即 TRUE)。
实现流程:
- 归一化 :
rect坐标乱序时交换(top > bottom或right < left时交换),得到规范矩形rc。 - 快速排除:
nCount > 0 && EXTENTCHECK(&rcBound, &rc)才进入扫描。 - 扫描循环利用分带有序性做提前终止 :
pCurRect->bottom <= rc.top→ 还没到,continue(矩形还在上面)。pCurRect->top >= rc.bottom→ 已过界,break(后面的矩形更靠下,不可能相交)。pCurRect->right <= rc.left→ x 上还太左,continue。pCurRect->left >= rc.right→ x 上太右;continue(注意:带内 x 有序,这里理论可 break,但代码保守地用 continue)。- 否则返回 TRUE。
IntRectInRegion(HRGN, LPRECTL)(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3489-L3507))是对它的句柄封装(锁定→调用→解锁),供 NtGdiRectInRegion 及 ntuser 内部使用。
12.5 IntGdiCombineRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L2488-L2542))
c
INT FASTCALL IntGdiCombineRgn(PREGION prgnDest, PREGION prgnSrc1, PREGION prgnSrc2, INT iCombineMode)
作用 :内部区域的 CombineRgn(假定参数已锁定且已验证)。返回复杂度或 ERROR。
实现流程:
- 参数检查:
prgnDest/prgnSrc1为 NULL →ERROR。 RGN_COPY:REGION_CopyRegion(prgnDest, prgnSrc1),返回复杂度(不需要prgnSrc2)。- 其余模式要求
prgnSrc2非 NULL(ASSERT(FALSE)+DPRINT1)。 - 按模式分发:
c
switch (iCombineMode) {
case RGN_AND: Ret = REGION_IntersectRegion(prgnDest, prgnSrc1, prgnSrc2); break;
case RGN_OR: Ret = REGION_UnionRegion(prgnDest, prgnSrc1, prgnSrc2); break;
case RGN_XOR: Ret = REGION_XorRegion(prgnDest, prgnSrc1, prgnSrc2); break;
case RGN_DIFF: Ret = REGION_SubtractRegion(prgnDest, prgnSrc1, prgnSrc2); break;
}
return Ret ? REGION_Complexity(prgnDest) : ERROR;
调用方 :NtGdiCombineRgn(用户模式)与 dclife.c 的裁剪合成(内核内部,见第 15 章)。这是区域子系统暴露给 win32k 其他模块的最重要接口。
13. NtGdi 入口(12 个系统调用)
以下 12 个 NtGdi* 函数是 region.c 的对外系统调用面,全部遵循统一模式:参数校验 → 句柄锁定(REGION_LockRgn 或 GDIOBJ_bLockMultipleObjects)→ 同步用户态属性(REGION_vSyncRegion)→ 执行内部运算 → 解锁(REGION_UnlockRgn,触发属性回写)→ 返回。
13.1 NtGdiCreateRectRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3586-L3613))
REGION_AllocUserRgnWithHandle(1)(内嵌缓冲)→ REGION_SetRectRgn(pRgn, l, t, r, b) → REGION_UnlockRgn → 返回 pRgn->BaseObject.hHmgr。失败置 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY)。这是 CreateRectRgn 的内核实现。
13.2 NtGdiCreateRoundRectRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3615-L3718))
作用 :创建圆角矩形区域(CreateRoundRectRgn)。
实现流程:
- 归一化 left/right、top/bottom;
right--; bottom--(注释:"区域是矩形内部,但因某种原因右/下边界减一"------与 Win32 的圆角矩形包含语义对齐)。 ellipse_width = min(right - left, abs(ellipse_width));ellipse_height同理。- 若
ellipse_width < 2 || ellipse_height < 2(几乎无圆角)→ 退化为NtGdiCreateRectRgn。 REGION_AllocUserRgnWithHandle(ellipse_height)(每扫描线一个矩形,预分配椭圆高度个)。- 基于 Alois Zingl 的算法 逐扫描线求椭圆内左右 x 边界:初始化
a = ellipse_width-1, b = ellipse_height-1,asq = 8a², bsq = 8b²,dx/dy/err为 64 位误差项;从(0, ellipse_height/2)沿椭圆第一象限弧推进,每步更新err,rects[y].left = left+x; rects[y].right = right-x。 - 把上半弧镜像到下半弧,补全每个扫描线矩形的 top/bottom(每带高 1),中间扫描线拉伸到椭圆中心。
REGION_UnlockRgn,返回句柄。
NtGdiCreateEllipticRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3571-L3584))是它的退化封装:CreateEllipticRgn(l,t,r,b) = CreateRoundRectRgn(l, t, r, b, r-l, b-t)(椭圆 = 100% 圆角)。
13.3 NtGdiCombineRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3512-L3569))
c
INT APIENTRY NtGdiCombineRgn(IN HRGN hrgnDst, IN HRGN hrgnSrc1, IN HRGN hrgnSrc2, IN INT iMode)
CombineRgn 的内核实现:
- 模式校验:
iMode < RGN_AND || iMode > RGN_COPY→ ERROR。 - 参数校验:
hrgnDst/hrgnSrc1非 NULL;iMode != RGN_COPY时hrgnSrc2也非 NULL(否则EngSetLastError(ERROR_INVALID_HANDLE))。 - 多句柄原子锁定 :
GDIOBJ_bLockMultipleObjects(3, ahrgn, aprgn, GDIObjType_RGN_TYPE)一次性锁定三个区域(防死锁:按句柄排序锁定),失败返回 ERROR。 - 同步用户态属性 :
REGION_vSyncRegion依次处理三个区域(跳过重复句柄)------HACK注释表明这是为了处理"用户态属性可能较新"的竞态。 IntGdiCombineRgn(aprgn[0], aprgn[1], aprgn[2], iMode)执行运算。- 依次
REGION_UnlockRgn解锁(结果区域回写属性)。
13.4 NtGdiExtCreateRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3805-L3918))
c
HRGN APIENTRY NtGdiExtCreateRegion(OPTIONAL LPXFORM Xform, DWORD Count, LPRGNDATA RgnData)
ExtCreateRegion 的内核实现------从 RGNDATA(矩形数组 + 可选变换)创建区域:
- SEH 探测 :
_SEH2_TRY中ProbeForRead(RgnData, Count, 1)并读取nCount/iType/dwSize/Buffer(用户缓冲区不可读则返回 NULL)。 - 参数校验(不置 LastError):
Count < sizeof(RGNDATAHEADER) + nCount * sizeof(RECT)、iType != RDH_RECTANGLES、dwSize != sizeof(RGNDATAHEADER)任一成立 → NULL。 REGION_AllocUserRgnWithHandle(nCount)。- SEH 内逐矩形
REGION_UnionRectWithRgn(Region, &rects[i])(仅接受left < right && top < bottom的有效矩形)------注意用 Union 而非直接拷贝,因为输入矩形可能任意顺序/重叠。 - 若提供
Xform:XFORMOBJ_iSetXform初始化,XFORMOBJ_bApplyXform(XF_LTOL, nCount*2, Buffer, Buffer)就地变换矩形。 - 失败路径统一
REGION_UnlockRgn+GreDeleteObject(hRgn)+ 返回 NULL。
13.5 NtGdiEqualRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3720-L3803))
c
BOOL APIENTRY NtGdiEqualRgn(HRGN hSrcRgn1, HRGN hSrcRgn2)
- NULL 检查 → FALSE。
hSrcRgn1 == hSrcRgn2特例:仅当句柄类型是GDILoObjType_LO_REGION_TYPE且GreIsHandleValid时返回 TRUE。GDIOBJ_bLockMultipleObjects(2, ...)锁定两个区域,REGION_vSyncRegion同步。- 依次比较:
nCount不等 → 不等;都为空(nCount==0)→ 相等;包围盒四坐标不等 → 不等;逐矩形四坐标不等 → 不等;全部相同 → TRUE。
比较利用分带正则性:两个正规表示的区域相等当且仅当矩形序列逐项相同,无需归一化。
13.6 NtGdiOffsetRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3962-L3995))
REGION_LockRgn → REGION_bOffsetRgn(prgn, cx, cy)(失败置 iResult = ERROR)→ 成功则 iResult = REGION_Complexity(prgn) → REGION_UnlockRgn → 返回复杂度或 ERROR。
13.7 NtGdiGetRgnBox(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3920-L3960))
REGION_LockRgn → REGION_GetRgnBox(Rgn, &SafeRect)(先写入内核栈上 SafeRect )→ REGION_UnlockRgn → ProbeForWrite(pRect, sizeof(RECT), 1) 探测后拷贝 *pRect = SafeRect。返回复杂度(ERROR 表失败)。
13.8 NtGdiPtInRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3997-L4021))
REGION_LockRgn → REGION_PtInRegion(prgn, x, y) → REGION_UnlockRgn → 返回 BOOL。失败返回 FALSE(并打印 DPRINT1)。
13.9 NtGdiRectInRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L4023-L4047))
__kernel_entry 入口:SEH 中 ProbeForRead + 拷贝用户矩形到栈上 rcTemp(异常返回 FALSE)→ IntRectInRegion(hrgn, &rcTemp)(内部含锁定)。
13.10 NtGdiSetRectRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L4049-L4073))
REGION_LockRgn → REGION_SetRectRgn(prgn, xLeft, yTop, xRight, yBottom) → REGION_UnlockRgn → TRUE。失败返回 FALSE。
13.11 NtGdiGetRegionData(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L4085-L4142))
c
ULONG APIENTRY NtGdiGetRegionData(_In_ HRGN hrgn, _In_ ULONG cjBuffer,
_Out_writes_bytes_to_opt_(cjBuffer, return) LPRGNDATA lpRgnData)
GetRegionData 的内核实现(MSDN 语义:成功返回所需字节数;lpRgnData == NULL 时返回所需大小;缓冲不足返回 0):
REGION_LockRgn(失败EngSetLastError(ERROR_INVALID_HANDLE),返回 0)。cjRects = nCount * sizeof(RECT);cjSize = cjRects + sizeof(RGNDATAHEADER)。- 若
lpRgnData非空且cjBuffer >= cjSize:SEH 内ProbeForWrite+ 拷贝rdh头 + 拷贝矩形数组,iType = RDH_RECTANGLES、nRgnSize = cjRects(对用户呈现"数据大小"语义);缓冲不足置ERROR_INVALID_PARAMETER且cjSize = 0。 REGION_UnlockRgn,返回cjSize(0 表示失败)。
13.12 NtGdi 入口汇总表
| 入口 | 内部实现 | 对应 Win32 API |
|---|---|---|
NtGdiCreateRectRgn |
REGION_AllocUserRgnWithHandle + REGION_SetRectRgn |
CreateRectRgn |
NtGdiCreateRoundRectRgn |
Zingl 椭圆扫描算法 | CreateRoundRectRgn |
NtGdiCreateEllipticRgn |
委托给 NtGdiCreateRoundRectRgn |
CreateEllipticRgn |
NtGdiCombineRgn |
IntGdiCombineRgn(AND/OR/XOR/DIFF/COPY) |
CombineRgn |
NtGdiExtCreateRegion |
逐矩形 REGION_UnionRectWithRgn + 可选 XFORMOBJ |
ExtCreateRegion |
NtGdiEqualRgn |
序列逐项比较 | EqualRgn |
NtGdiOffsetRgn |
REGION_bOffsetRgn |
OffsetRgn |
NtGdiGetRgnBox |
REGION_GetRgnBox |
GetRgnBox |
NtGdiPtInRegion |
REGION_PtInRegion |
PtInRegion |
NtGdiRectInRegion |
IntRectInRegion → REGION_RectInRegion |
RectInRegion |
NtGdiSetRectRgn |
REGION_SetRectRgn |
SetRectRgn |
NtGdiGetRegionData |
头+矩形数组直拷 | GetRegionData |
NtGdiCreatePolygonRgn / NtGdiCreatePolyPolygonRgn / NtGdiFrameRgn 不在 region.c 中定义(分别经由 GreCreatePolyPolygonRgn 与 GreCreateFrameRgn 被同文件/他处消费),故 region.c 的 NtGdi 面正好是上表 12 个。
14. 扫描线布尔运算详解:逐带推进与归并规则
本节把第 6 章的机制用更直观的方式总结,作为理解 REGION_RegionOp 的速查。
14.1 带(Band)的切分模型
把两个区域画在同一坐标系,所有矩形的 top/bottom 边界把平面切成若干水平条带。每一条带内,两个区域各自贡献 0 或 1 个"带片段"(该带内属于该区域的矩形序列)。RegionOp 的推进顺序是按 y 从小到大的事件序列:
reg1 带: ████████ ████████
reg2 带: ████████████
y 事件线: 0───5───10───15───20───25
- 区间 [5,10):仅 reg1(→ nonOverlap1Func)。
- 区间 [10,15):两者都有(→ overlapFunc)。
- 区间 [15,20):仅 reg1(→ nonOverlap1Func)。
- [20,25):仅 reg2(→ nonOverlap2Func,若回调非 NULL)。
RegionOp 每次迭代处理一个"源带",并维护 ybot(最近交叠底)与 ytop(本带交叠顶)两个游标,把非重叠部分裁剪到 [max(top,ybot), min(bottom, 另一区域top)) 输出,把重叠部分交给 overlapFunc 在 [ytop, ybot) 内裁剪 x。
14.2 归并规则(维护不变量)
结果区域在带推进过程中必须始终保持 y-x 分带正则性,靠两条规则保证:
- 带内合并(
REGION_bMergeRect) :同一带内新加入的矩形若与前一个矩形top/bottom相同且right相接(last.right >= new.left),则横向拉伸合并。用于 OR/UNION 的重叠带输出,防止带内出现相邻矩形。 - 带间合并(
REGION_Coalesce) :相邻两带若bottom == top(垂直相接)、矩形数相同、每个矩形left/right逐对相同,则纵向拉伸合并(删除后带、拉长前带)。用于所有运算的每个带输出后。
这两条规则的组合效果是:结果区域的矩形数始终是给定形状的最小表示(在"仅做纵向合并"这一层意义上),同时为下一次布尔运算提供规范输入。
14.3 每种运算的带语义速查
| 运算 | 非重叠带(reg1 独有) | 非重叠带(reg2 独有) | 重叠带 |
|---|---|---|---|
| AND | 丢弃 | 丢弃 | 保留 x 交集 [max(l1,l2), min(r1,r2)) |
| OR | 保留整个带 | 保留整个带 | 保留 x 并集(MergeRect 合并相邻) |
| SUB | 保留整个带 | 丢弃(回调为 NULL) | 保留 r1 − r2 的 x 差集(栅栏算法) |
| XOR | 保留整个带 | 保留整个带 | 保留 (r1−r2) ∪ (r2−r1)(两次 SUB + OR 实现) |
14.4 关键正确性论证
- 重叠带判定 :
ybot > ytop时两带 y 区间[ytop, ybot)非空,才有必要调用 overlapFunc;否则(如一个带完全在另一个带之上)跳过。 - 指针推进的唯一性 :每次迭代
ybot = min(r1->bottom, r2->bottom),凡bottom == ybot的带必然被本轮的 nonOverlap/overlap 处理完全消费,可安全跳到下一带。这保证每个 y 事件恰好处理一次,不会漏带也不会重复输出。 - 空结果与零矩形 :运算可能产生空结果(
nCount == 0),此时 RegionOp 收缩逻辑仍保证Buffer非空(分配 1 个 RECT 的缓冲),REGION_SetExtents随后把包围盒清零,区域进入合法的 NULLREGION 状态。
15. 裁剪合成中的应用(prgnVis ∩ prgnRao ∩ prgnAPI)
区域子系统不是孤立模块,它是 win32k 绘制裁剪链的底层引擎。三层区域的概念(源自 Windows 的"three regions"裁剪模型)如下:
- prgnVis(可见区域):窗口在桌面上真正可见的部分(受遮挡、最小化影响),由 ntuser 的窗口管理维护。
- prgnRao(光栅操作区域):与 ROP/更新区域相关的辅助裁剪(如仅重画无效矩形)。
- prgnAPI(API 裁剪区域) :应用通过
SelectClipRgn等 API 显式设置的裁剪区。
在 DC 的"裁剪合成"路径(dclife.c 中的 IntGdiGetClipBox 相关逻辑与 IntUpdateClipRegion)中,最终裁剪区按:
最终裁剪区 = prgnVis ∩ prgnRao ∩ prgnAPI
合成实现正是通过 IntGdiCombineRgn(region.c#L2488)或 REGION_bIntersectRegion 连续做两次相交完成的。合成结果随后交给 IntEngUpdateClipRegion(eng/clip.c#L173-L215)转换为引擎侧的 XCLIPOBJ:
- 矩形数 > 1 → 分配
ENUMRECTS缓冲,iDComplexity = DC_COMPLEX,iFComplexity = FC_RECT4/FC_COMPLEX; - 矩形数 ≤ 1 → 直接使用
rclBounds内嵌矩形,iDComplexity = DC_TRIVIAL/DC_RECT(DC_TRIVIAL表示空裁剪或全屏,驱动可完全跳过裁剪)。
XCLIPOBJ 的 iDComplexity 直接来自 REGION_Complexity 判定的矩形数,而 REGION_RectInRegion/REGION_PtInRegion 又被引擎的命中测试路径复用------这就是"区域子系统是裁剪的基础"的完整含义:上层只持有区域(REGION),下层引擎只消费矩形流(XCLIPOBJ),region.c 是两者之间的转换与运算枢纽。
16. 调用链(mermaid)
#mermaid-svg-Bet1k3SnIzEfONrx{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-Bet1k3SnIzEfONrx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bet1k3SnIzEfONrx .error-icon{fill:#552222;}#mermaid-svg-Bet1k3SnIzEfONrx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bet1k3SnIzEfONrx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bet1k3SnIzEfONrx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bet1k3SnIzEfONrx .marker.cross{stroke:#333333;}#mermaid-svg-Bet1k3SnIzEfONrx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bet1k3SnIzEfONrx p{margin:0;}#mermaid-svg-Bet1k3SnIzEfONrx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bet1k3SnIzEfONrx .cluster-label text{fill:#333;}#mermaid-svg-Bet1k3SnIzEfONrx .cluster-label span{color:#333;}#mermaid-svg-Bet1k3SnIzEfONrx .cluster-label span p{background-color:transparent;}#mermaid-svg-Bet1k3SnIzEfONrx .label text,#mermaid-svg-Bet1k3SnIzEfONrx span{fill:#333;color:#333;}#mermaid-svg-Bet1k3SnIzEfONrx .node rect,#mermaid-svg-Bet1k3SnIzEfONrx .node circle,#mermaid-svg-Bet1k3SnIzEfONrx .node ellipse,#mermaid-svg-Bet1k3SnIzEfONrx .node polygon,#mermaid-svg-Bet1k3SnIzEfONrx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bet1k3SnIzEfONrx .rough-node .label text,#mermaid-svg-Bet1k3SnIzEfONrx .node .label text,#mermaid-svg-Bet1k3SnIzEfONrx .image-shape .label,#mermaid-svg-Bet1k3SnIzEfONrx .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bet1k3SnIzEfONrx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bet1k3SnIzEfONrx .rough-node .label,#mermaid-svg-Bet1k3SnIzEfONrx .node .label,#mermaid-svg-Bet1k3SnIzEfONrx .image-shape .label,#mermaid-svg-Bet1k3SnIzEfONrx .icon-shape .label{text-align:center;}#mermaid-svg-Bet1k3SnIzEfONrx .node.clickable{cursor:pointer;}#mermaid-svg-Bet1k3SnIzEfONrx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bet1k3SnIzEfONrx .arrowheadPath{fill:#333333;}#mermaid-svg-Bet1k3SnIzEfONrx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bet1k3SnIzEfONrx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bet1k3SnIzEfONrx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bet1k3SnIzEfONrx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bet1k3SnIzEfONrx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bet1k3SnIzEfONrx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bet1k3SnIzEfONrx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bet1k3SnIzEfONrx .cluster text{fill:#333;}#mermaid-svg-Bet1k3SnIzEfONrx .cluster span{color:#333;}#mermaid-svg-Bet1k3SnIzEfONrx 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-Bet1k3SnIzEfONrx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bet1k3SnIzEfONrx rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bet1k3SnIzEfONrx .icon-shape,#mermaid-svg-Bet1k3SnIzEfONrx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bet1k3SnIzEfONrx .icon-shape p,#mermaid-svg-Bet1k3SnIzEfONrx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bet1k3SnIzEfONrx .icon-shape .label rect,#mermaid-svg-Bet1k3SnIzEfONrx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bet1k3SnIzEfONrx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bet1k3SnIzEfONrx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bet1k3SnIzEfONrx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} win32ss 内部
win32k.sys --- region.c
gdi32/user32
CreateRectRgn
NtGdiCreateRectRgn
CombineRgn
NtGdiCombineRgn
PtInRegion/RectInRegion
NtGdiPtInRegion
GetRegionData/ExtCreateRegion
NtGdiGetRegionData
REGION_AllocUserRgnWithHandle
REGION_SetRectRgn
GDIOBJ_bLockMultipleObjects
REGION_vSyncRegion
IntGdiCombineRgn
REGION_UnionRegion / IntersectRegion / SubtractRegion / XorRegion
REGION_RegionOp
REGION_Coalesce
REGION_bAddRect / REGION_bMergeRect
REGION_SetExtents
REGION_PtInRegion
REGION_LockRgn
dclife.c 裁剪合成
ntuser 可见区域
REGION_LockRgn
REGION_bIntersectRegion
IntGdiGetRgnBox
REGION_GetRgnBox
IntEngUpdateClipRegion --- eng/clip.c
XCLIPOBJ → 驱动
REGION_bAllocRgnAttr 属性池
REGION_UnlockRgn 回写属性
主要路径说明:
- 创建 :
CreateRectRgn→NtGdiCreateRectRgn→ 分配(含属性池)→ 置矩形 → 解锁回写。 - 组合 :
CombineRgn→ 多句柄锁定 → 同步属性 →IntGdiCombineRgn分发 →REGION_RegionOp扫描线运算 → 每带Coalesce→SetExtents重算包围盒 → 解锁回写。 - 内部合成 :dclife.c 直接调
IntGdiCombineRgn做prgnVis ∩ prgnRao ∩ prgnAPI→IntEngUpdateClipRegion生成XCLIPOBJ交给显示驱动。 - 命中测试 :
PtInRegion/RectInRegion→ 锁定 →REGION_PtInRegion/REGION_RectInRegion(利用分带有序性提前终止)。
17. 源码索引
| 文件 | 关键内容 |
|---|---|
| region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c)(约 4144 行) | 全部区域实现:REGION_RegionOp 扫描线布尔运算器(#L946)、四种运算(#L1236-L1792)、缓冲区原语(#L407-L509)、REGION_Coalesce(#L808)、REGION_CropRegion(#L662)、多边形扫描转换(#L2806-L3451)、对象生命周期(#L2202-L2486)、命中测试(#L2544-L2664)、12 个 NtGdi 入口(#L3512-L4142) |
| region.h(file:///d:/reactos/win32ss/gdi/ntgdi/region.h) | REGION 结构(BaseObject/prgnattr/rdh/Buffer)、prgnDefault/hrgnDefault 全局、全部 REGION_* 函数声明、IntSysCreateRectpRgnIndirect/GreCreateRectRgnIndirect/GreSetRectRgnIndirect 宏 |
| ntgdihdl.h(file:///d:/reactos/win32ss/include/ntgdihdl.h) | RGN_ATTR 结构(AttrFlags/iComplexity/Rect,#L253)、ATTR_RGN_VALID/ATTR_RGN_DIRTY 标志(#L195-196) |
| eng/clip.c(file:///d:/reactos/win32ss/gdi/eng/clip.c) | IntEngUpdateClipRegion(#L173)消费区域矩形流生成 XCLIPOBJ(DC_TRIVIAL/DC_RECT/DC_COMPLEX) |
| gdi32/objects/region.c(file:///d:/reactos/win32ss/gdi/gdi32/objects/region.c) | 用户态 gdi32 区域实现(读写 RGN_ATTR、置 ATTR_RGN_DIRTY 的快速路径) |
关键行号速查(region.c):
| 函数 | 行号 | 函数 | 行号 |
|---|---|---|---|
REGION_bGrowBufferSize |
#L407 | REGION_IntersectRegion |
#L1292 |
REGION_bEnsureBufferSize |
#L457 | REGION_UnionNonO |
#L1344 |
REGION_vAddRect |
#L473 | REGION_bMergeRect |
#L1372 |
REGION_bAddRect |
#L493 | REGION_UnionO |
#L1414 |
REGION_Complexity |
#L555 | REGION_UnionRegion |
#L1464 |
REGION_CopyRegion |
#L574 | REGION_SubtractNonO1 |
#L1561 |
REGION_SetExtents |
#L617 | REGION_SubtractO |
#L1600 |
REGION_CropRegion |
#L662 | REGION_SubtractRegion |
#L1717 |
REGION_Coalesce |
#L808 | REGION_XorRegion |
#L1753 |
REGION_RegionOp |
#L946 | REGION_UnionRectWithRgn |
#L1797 |
REGION_IntersectO |
#L1236 | REGION_SubtractRectFromRgn |
#L1812 |
REGION_bCopy / REGION_bIntersectRegion |
#L1829 / #L1839 | REGION_bMakeSimpleFrameRgn |
#L1850 |
REGION_bMakeFrameRegion |
#L1941 | GreCreateFrameRgn |
#L2026 |
REGION_bXformRgn |
#L2067 | REGION_bOffsetRgn |
#L2708 |
REGION_AllocRgnWithHandle |
#L2202 | REGION_bAllocRgnAttr |
#L2262 |
REGION_AllocUserRgnWithHandle |
#L2295 | REGION_vSyncRegion |
#L2316 |
REGION_LockRgn / REGION_UnlockRgn |
#L2359 / #L2374 | IntSysCreateRectpRgn |
#L2408 |
REGION_vCleanup / REGION_Delete |
#L2434 / #L2450 | IntGdiSetRegionOwner |
#L2460 |
IntGdiCombineRgn |
#L2488 | REGION_GetRgnBox / IntGdiGetRgnBox |
#L2544 / #L2562 |
REGION_PtInRegion / REGION_RectInRegion |
#L2583 / #L2606 | REGION_SetRectRgn |
#L2666 |
REGION_SetPolyPolygonRgn |
#L3255 | GreCreatePolyPolygonRgn |
#L3453 |
NtGdiCombineRgn |
#L3512 | NtGdiCreateEllipticRgn |
#L3571 |
NtGdiCreateRectRgn |
#L3586 | NtGdiCreateRoundRectRgn |
#L3615 |
NtGdiEqualRgn |
#L3720 | NtGdiExtCreateRegion |
#L3805 |
NtGdiGetRgnBox |
#L3920 | NtGdiOffsetRgn |
#L3962 |
NtGdiPtInRegion |
#L3997 | NtGdiRectInRegion |
#L4023 |
NtGdiSetRectRgn |
#L4049 | NtGdiGetRegionData |
#L4085 |
关联模块 :cliprgn.c(API 裁剪,另册分析)、dclife.c(裁剪合成,IntGdiCombineRgn 消费方)、rect.c(RECTL_vMakeWellOrdered/RECTL_bUnionRect 工具)、eng/clip.c(XCLIPOBJ 枚举,《分析_4》3.2)。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/region.c(约 4144 行,2026 年 8 月分析时点)及关联模块(region.h、eng/clip.c、ntgdihdl.h、gdi32/objects/region.c)撰写,所有函数签名、行号与行为描述均以源码为准。