ReactOS 图形系统分析(38):区域子系统 — region.c

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)),覆盖以下四类职责:

  1. 区域对象管理 :分配、加锁、同步、清理、删除(REGION_AllocRgnWithHandle / REGION_LockRgn / REGION_vSyncRegion / REGION_vCleanup / REGION_Delete)。
  2. 区域布尔运算 :联合(Union)、相交(Intersect)、相减(Subtract)、异或(Xor),统一由 REGION_RegionOp 这一扫描线运算器驱动。
  3. 命中测试与查询 :点/矩形是否在区域内(REGION_PtInRegion / REGION_RectInRegion)、包围盒查询(REGION_GetRgnBox)、复杂度判定(REGION_Complexity)。
  4. 区域构造与变换 :矩形区域(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.clibX11/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 行)给出了精确定义:

  1. 所有矩形先按 y(top)排序,再按 x(left)排序。

  2. 分带约束:具有相同 top 坐标的矩形必然具有相同的 bottom 坐标(一个带 = 一条水平扫描线区间带),反之亦然。若某矩形在一带内有扫描线,则它横跨该带的整个垂直距离。

  3. 横向最大化约束:带内任意两个矩形不得相邻(不允许触碰),即带内矩形必须覆盖尽可能大的水平面积。

  4. 相邻带在满足"底边相触 + 矩形位置一致"时合并为更高矩形,以减少矩形数量。

    坐标示意(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_hInsertObjectGDIOBJ_LockObjectGDIOBJ_vDeleteObject 全部依赖该布局。
  • prgnattr / rgnattr :区域属性(RGN_ATTR)。系统区域 (如默认区域 prgnDefaultIntSysCreateRectpRgn 创建的)直接使用内嵌的 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 判断是否需要扩容。rcBoundBuffer == &rdh.rcBound兼作内嵌矩形容器 :当区域只有 1 个矩形时,该矩形直接存在 rcBound 里(零额外分配),这是文件注释强调的"> 95% 的区域只有一个矩形"的优化。

3.3 RECTL Buffer\[\]

Buffer 指向矩形序列,排序不变量(由 REGION_RegionOpREGION_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)依据矩形个数决定 XCLIPOBJiDComplexityDC_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 个矩形。

实现流程

  1. 溢出防护:若 cRects > MAXULONG / sizeof(RECTL) 直接返回 FALSE。
  2. 计算新大小 cjNewSize = cRects * sizeof(RECTL)
  3. 翻倍策略 :若 2 * prgn->rdh.nRgnSize > cjNewSize,则取 cjNewSize = 2 * prgn->rdh.nRgnSize。注释说明"避免频繁分配:旧大小永远不会大到翻倍溢出,因为内存早已耗尽"。
  4. ExAllocatePoolWithTag(PagedPool, cjNewSize, TAG_REGION) 分配分页池。
  5. COPY_RECTS 把旧矩形搬入新缓冲区;若旧缓冲区不是内嵌的 &rdh.rcBound,则 ExFreePoolWithTag 释放。
  6. 更新 prgn->Bufferprgn->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_IntersectOREGION_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 指针 → NULLREGIONnCount==0NULLREGIONnCount==1SIMPLEREGION;否则 COMPLEXREGION

使用方式 :几乎每个 NtGdi 入口在运算结束后用它包装返回值(如 NtGdiOffsetRgnNtGdiCombineRgn),使 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

实现流程

  1. dst == src 直接返回 TRUE(自复制)。
  2. dst->rdh.nRgnSize < src->rdh.nCount * sizeof(RECT),分配新缓冲区(分配失败返回 FALSE),释放旧缓冲区(若非内嵌)。
  3. 复制 nCountrcBound(四个坐标)、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_IntersectRegionREGION_SubtractRegionREGION_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 的开销。

实现流程

  1. 平凡拒绝:rect 空、或 EXTENTCHECK(rect, &rgnSrc->rdh.rcBound)==0(包围盒不相交)→ 跳转 empty
  2. 上界扫描clipa 从 0 开始,跳过所有 bottom <= rect->top 的矩形(完全在裁剪矩形上方);若全部跳过则 empty
  3. 下界扫描clipbclipa 开始,找到第一个 top >= rect->bottom 的矩形位置(其后矩形都在裁剪矩形下方),中途 bottom >= rect->top 即停止条件 if (rgnSrc->Buffer[clipb].top >= rect->bottom) break。于是 [clipa, clipb) 就是 y 方向上可能与裁剪矩形相交的矩形子区间。
  4. rgnDst != rgnSrcrgnDst 容量不足(nRgnSize < (clipb-clipa)*sizeof(RECT)),分配新缓冲区并释放旧缓冲区。
  5. 对子区间内每个矩形:若在 x 方向也与 rect 相交(lpr->left < rect->right && lpr->right > rect->left),则四边取 min/max 裁剪出一个新矩形写入 rgnDst,同时维护新的包围盒。
  6. j==0(一个矩形都没产出)→ empty
  7. 更新 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 内部被调用。

实现流程

  1. 定位前带:pPrevRect = Buffer + prevStart,前带矩形数 prevNumRects = curStart - prevStart
  2. 计算当前带矩形数 curNumRects:从 curStart 起,数出所有 top == bandtop 的矩形。注释说明必须这样做,因为 RegionOp 在"一个区域耗尽后"可能一次追加了多个带。
  3. 若追加了多个带(pCurRect != pRegEnd),把 curStart 回退到最后一个带的起始(倒序扫描相同 top),以便下轮合并从正确位置开始。
  4. 合并条件 (缺一不可):
    • curNumRects == prevNumRects 且非零;
    • 前带 pPrevRect->bottom == pCurRect->top(底边与顶边相触,开区间相接);
    • 两个带的每个矩形 left/right 逐对相等(矩形位置完全对齐)。
  5. 条件满足:nCount -= curNumRects(删掉当前带矩形),然后循环把当前带各矩形的 bottom 赋给前带对应矩形(纵向拉伸)。
  6. 收尾:若当前带是最后一个带,返回 prevStart(下轮从合并后的带开始);否则把剩余矩形整体前移覆盖被删除的矩形,返回 curStart

为何必须对齐:若两个带矩形 x 位置不同却强行合并,会产生"带内矩形纵向长度不一致"的非法表示,破坏分带不变量,导致后续布尔运算与命中测试出错。

6. 扫描线布尔运算核心:REGION_RegionOp

REGION_RegionOpregion.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 可能就是 reg1reg2,因此不能 先释放它的旧 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 大小的缓冲)。
  • 释放 oldRectsnewReg 原缓冲区)------若它曾内嵌于 rdh.rcBound(即 newReg 原本是单矩形区域)则无需释放。

6.6 复杂度分析

  • 时间:O(n1 + n2)(两个矩形数组各扫描常数遍;REGION_Coalesce 与带数成线性)。
  • 空间:预分配 O(max(n1, n2)),最坏收缩后 O(nResult)。
  • 正确性前提:两个输入必须满足"带内 x 不重叠、带间 y 相接、按 y/x 排序"不变量------这正是 REGION_CoalesceREGION_bMergeRect 维护的东西。

7. 四种布尔运算详解

7.1 相交:REGION_IntersectO 与 REGION_IntersectRegion

REGION_IntersectOregion.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_IntersectRegionregion.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1292-L1325)):

c 复制代码
static BOOL FASTCALL REGION_IntersectRegion(PREGION newReg, PREGION reg1, PREGION reg2)
  1. 平凡拒绝reg1 空、reg2 空、或 EXTENTCHECK(rcBound1, rcBound2)==0(包围盒不相交)→ newReg->rdh.nCount = 0(结果为空,无需调 RegionOp)。
  2. 否则调 REGION_RegionOp(newReg, reg1, reg2, REGION_IntersectO, NULL, NULL)
  3. 之后 REGION_SetExtents(newReg) 重算包围盒。

注意调用顺序的注释:extents 必须在 RegionOp 返回后才更新,因为 newReg 可能是 reg1/reg2 之一,RegionOp 依赖源区域的原始 extents。

7.2 联合:REGION_UnionNonO、REGION_UnionO、REGION_bMergeRect、REGION_UnionRegion

REGION_UnionNonOregion.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1344-L1370))------非重叠带回调:把整个带的矩形以 [top, bottom) 为 y 区间直接拷入结果。先 REGION_bEnsureBufferSize(pReg, nCount + (rEnd-r)) 预分配该带所需容量,再循环 REGION_vAddRect(无需合并检查,因为非重叠带的矩形天然不重叠)。

REGION_UnionOregion.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1414-L1459))------重叠带回调:每次取两矩形中 left 较小的那个,用 REGION_bMergeRect 加入结果(允许与结果中"同一带、bottom 相同、right 相邻"的矩形合并)。循环结束后,把剩余未消费的矩形全部 REGION_bMergeRect 进去。

REGION_bMergeRectregion.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_UnionRegionregion.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1464-L1544))------短路优化最丰富的运算:

  1. reg1 == reg2reg1 空 → 结果 = 复制 reg2(若 newReg != reg2)。
  2. reg2 空 → 结果 = 复制 reg1
  3. reg1 为单矩形且完全包含 reg2 包围盒 → 结果 = 复制 reg1
  4. reg2 为单矩形且完全包含 reg1 包围盒 → 结果 = 复制 reg2
  5. 否则 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_SubtractNonO1region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1561-L1587))------非重叠带回调(reg1 独有带,即"被减数独有"):与 REGION_UnionNonO 完全相同,把带内矩形直接加入结果(减数在该带没有内容,无需裁剪)。

REGION_SubtractOregion.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_SubtractRegionregion.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

  1. 平凡拒绝:regM 空、regS 空、或二者包围盒不相交 → 直接 REGION_CopyRegion(regD, regM)
  2. 否则 REGION_RegionOp(regD, regM, regS, REGION_SubtractO, REGION_SubtractNonO1, NULL)
  3. 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_UnionRectWithRgnregion.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 = &region.rdh.rcBound; /* 单矩形:内嵌缓冲 */
    region.rdh.nCount = 1;
    region.rdh.nRgnSize = sizeof(RECT);
    region.rdh.rcBound = *rect;
    return REGION_UnionRegion(rgn, rgn, &region);  /* rgn ∪ rect */
}

栈上临时 REGION 包装矩形,复用 REGION_UnionRegion 完成"区域并入一个矩形"。rgn 同时是源与目标(reg1 == newReg),UnionRegion 的短路逻辑会正确处理(reg1 == reg2 分支不触发,因为 reg2 是栈上区域;newReg == reg1 时复制分支被跳过)。被 NtGdiExtCreateRegion 逐矩形建区时反复调用。

REGION_SubtractRectFromRgnregion.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_bCopyregion.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L1829-L1837)):对 REGION_CopyRegion 加 NULL 检查的公开封装(供其他模块调用)。
  • REGION_bIntersectRegionregion.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(边框尺寸,已断言非负)。

实现流程

  1. EMPTY_REGION 清空结果。
  2. 退化情形 :若源矩形高度 <= cy*2 或宽度 <= cx*2(边框把内部区域完全覆盖),直接输出整个矩形(单矩形区域)。
  3. 构造最多 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)
  4. i != 0(非空),nCount = inRgnSize = 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 使用):

  1. REGION_AllocUserRgnWithHandle(1) 分配结果区域;失败置 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY)
  2. REGION_LockRgn(hrgn) 锁定源区域(获得独占访问以便 REGION_bMakeFrameRegion 就地修改)。
  3. REGION_bMakeFrameRegion(prgnFrame, prgnSrc, cx, cy);成功则解锁并返回句柄;失败则 REGION_Delete 删除结果,返回 NULL。
  4. 解锁源区域。

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 几何构造的基础。

实现流程

  1. 平凡情形:(cx, cy) == (0,0) → TRUE;nCount == 0(空区域)→ TRUE(忽略偏移)。
  2. 越界预检:cx/cy 本身超出 [MIN_COORD, MAX_COORD],或平移后包围盒越界(cx > 0 检查 rcBound.right > MAX_COORD - cxcx < 0 检查 rcBound.left < MIN_COORD - cx;cy 方向对称)→ FALSE。
  3. 遍历所有矩形,四个坐标各加 cx/cy。
  4. 更新包围盒(仅当 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_UNITYREGION_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(绕数) */

实现流程

  1. 模式校验:iMode 必须是 ALTERNATEWINDING,否则返回 FALSE。
  2. 矩形特判 :若"1 个多边形、4 个顶点(或 5 个顶点且首尾闭合)、且四角构成轴对齐矩形"(两组平行条件任一成立),直接 REGION_SetRectRgn 返回------避免走完整扫描转换的开销。
  3. 统计总顶点数 total,分配 pETEs = ExAllocatePoolWithTag(... sizeof(EDGE_TABLE_ENTRY) * total ...)(每条非水平边一个 ETE,最坏 = 总顶点数)。
  4. REGION_CreateETandAET 建立边表 ET 与空的活动边表 AET。
  5. iMode 分两条扫描循环(见 10.4 / 10.5)。
  6. REGION_PtsToRegion(numFullPtBlocks, iPts, &FirstPtBlock, prgn) 把点缓冲转成矩形数组。
  7. CleanupREGION_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 个矩形的缓冲区,并插入句柄表。

实现流程

  1. GDIOBJ_AllocateObject(GDIObjType_RGN_TYPE, sizeof(REGION), BASEFLAG_LOOKASIDE)------从 GDI 对象池(lookaside)取一块 REGION 大小内存,初始化 BaseObject
  2. 内嵌缓冲优化nReg == 0 || nReg == 1pReg->Buffer = &pReg->rdh.rcBound(注释:"测试表明 >95% 的区域只有 1 个矩形,内嵌可省一次分配");否则 ExAllocatePoolWithTag(PagedPool, nReg * sizeof(RECT), TAG_REGION) 分配独立缓冲(失败则 GDIOBJ_vDeleteObject 回滚)。
  3. EMPTY_REGION(pReg) 清零;rdh.dwSize = sizeof(RGNDATAHEADER)rdh.nCount = nReg(注意:分配容量即初始矩形数!);rdh.nRgnSize = nReg * sizeof(RECT)
  4. 属性初始化:prgnattr = &rgnattr(先指向内嵌属性),AttrFlags = 0iComplexity = SIMPLEREGIONRect = rdh.rcBound
  5. GDIOBJ_hInsertObject(&pReg->BaseObject, GDI_OBJ_HMGR_POWNED) 插入句柄表(失败则 GDIOBJ_vFreeObject 回滚),返回 pReg

注意事项nCount 被初始化为 nReg------调用方随后必须通过 REGION_SetRectRgn / 布尔运算把矩形数设为实际值(例如 REGION_AllocRgnWithHandle(1)REGION_SetRectRgnnCount = 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 直接映射访问。

实现流程

  1. 断言当前 prgn->prgnattr == &prgn->rgnattr
  2. ppi = PsGetCurrentProcessWin32Process()prgnattr = GdiPoolAllocate(ppi->pPoolRgnAttr)(失败返回 FALSE)。
  3. *prgnattr = prgn->rgnattr 拷贝当前属性。
  4. prgn->prgnattr = prgnattrGDIOBJ_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)------属性池耗尽视为致命)。所有暴露给用户模式的区域 都走这个入口(NtGdiCreateRectRgnNtGdiExtCreateRegionGreCreateFrameRgnGreCreatePolyPolygonRgn),保证属性在共享内存中可被 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_LockRgnNtGdiCombineRgn/NtGdiEqualRgn 的多句柄锁定路径中被调用。

实现流程

  1. 断言属性有效:prgnattr 非 NULL,且要么是内嵌属性、要么已置 ATTR_RGN_VALID
  2. AttrFlags & ATTR_RGN_DIRTY(用户态改过属性):
    • 断言句柄归当前进程所有(GreGetObjectOwner == GDI_OBJ_HMGR_POWNED)且属性确实在池中(非内嵌)。
    • iComplexity == NULLREGIONEMPTY_REGION(prgn)
    • iComplexity == SIMPLEREGIONREGION_SetRectRgn(prgn, attr.Rect.left, ...)(用属性里缓存的矩形重建单矩形区域)。
    • 其他值 → 属性损坏(DPRINT1 + 断言)。
  3. 清除 ATTR_RGN_DIRTY | ATTR_RGN_VALID

机制意义 :gdi32 用户态(如 SetRectRgnOffsetRgn 的用户态快速路径)可以不进入内核 直接改属性并置 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);
}
  • LockGDIOBJ_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 = &rgnattrAttrFlags = ATTR_RGN_VALID),Buffer 内嵌,REGION_SetRectRgn 初始化后直接返回指针。注释明确"系统区域不使用属性段,分配走 gdiobj 级函数"。IntSysCreateRectpRgnIndirect(prc) 宏是它的 RECTL* 版本。这类区域(如默认区域 prgnDefault)由 win32k 内部长期持有,从不销毁(REGION_Deleteif (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) 更新句柄表所有权。

实现流程

  1. REGION_LockRgn(hRgn) 锁定(无效返回 FALSE)。
  2. 若属性在池中(prgnattr != &rgnattr):GDIOBJ_vSetObjectAttr(..., NULL) 清句柄表属性槽、prgnattr = &rgnattrGdiPoolFree(ppi->pPoolRgnAttr, prgnattr) 归还给原进程池。
  3. REGION_UnlockRgn 解锁(解锁回写逻辑:此时属性已指向内嵌 rgnattr,故跳过回写)。
  4. 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)(或空区域)。

实现流程

  1. 归一化:若 LeftRect > RightRect 交换(TopRect > BottomRect 同理)------矩形坐标可反序传入。
  2. 若交换后 LeftRect != RightRect && TopRect != BottomRect(非空矩形):
    • Buffer[0]rcBound(此时 Buffer 必为内嵌 &rdh.rcBound,两者是同一块内存,一次赋值两处生效------代码写了两遍赋值,语义相同);
    • nCount = 1iType = RDH_RECTANGLES
  3. 否则 EMPTY_REGION(rgn)

使用方式NtGdiCreateRectRgnNtGdiSetRectRgnREGION_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_GetRgnBoxRgn != NULL*pRect = Rgn->rdh.rcBound 并返回 REGION_Complexity(Rgn)NULL 返回 0(ERROR 语义)。IntGdiGetRgnBox 在其上套 REGION_LockRgn/REGION_UnlockRgnNtGdiGetRgnBox 又在其上加了用户缓冲区探测(见 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)。

实现流程

  1. 归一化rect 坐标乱序时交换(top > bottomright < left 时交换),得到规范矩形 rc
  2. 快速排除:nCount > 0 && EXTENTCHECK(&rcBound, &rc) 才进入扫描。
  3. 扫描循环利用分带有序性做提前终止
    • 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

实现流程

  1. 参数检查:prgnDest/prgnSrc1 为 NULL → ERROR
  2. RGN_COPYREGION_CopyRegion(prgnDest, prgnSrc1),返回复杂度(不需要 prgnSrc2)。
  3. 其余模式要求 prgnSrc2 非 NULL(ASSERT(FALSE) + DPRINT1)。
  4. 按模式分发:
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_LockRgnGDIOBJ_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)。

实现流程

  1. 归一化 left/right、top/bottom;right--; bottom--(注释:"区域是矩形内部,但因某种原因右/下边界减一"------与 Win32 的圆角矩形包含语义对齐)。
  2. ellipse_width = min(right - left, abs(ellipse_width))ellipse_height 同理。
  3. ellipse_width < 2 || ellipse_height < 2(几乎无圆角)→ 退化为 NtGdiCreateRectRgn
  4. REGION_AllocUserRgnWithHandle(ellipse_height)(每扫描线一个矩形,预分配椭圆高度个)。
  5. 基于 Alois Zingl 的算法 逐扫描线求椭圆内左右 x 边界:初始化 a = ellipse_width-1, b = ellipse_height-1asq = 8a², bsq = 8b²dx/dy/err 为 64 位误差项;从 (0, ellipse_height/2) 沿椭圆第一象限弧推进,每步更新 errrects[y].left = left+x; rects[y].right = right-x
  6. 把上半弧镜像到下半弧,补全每个扫描线矩形的 top/bottom(每带高 1),中间扫描线拉伸到椭圆中心。
  7. REGION_UnlockRgn,返回句柄。

NtGdiCreateEllipticRgnregion.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 的内核实现:

  1. 模式校验:iMode < RGN_AND || iMode > RGN_COPY → ERROR。
  2. 参数校验:hrgnDst/hrgnSrc1 非 NULL;iMode != RGN_COPYhrgnSrc2 也非 NULL(否则 EngSetLastError(ERROR_INVALID_HANDLE))。
  3. 多句柄原子锁定GDIOBJ_bLockMultipleObjects(3, ahrgn, aprgn, GDIObjType_RGN_TYPE) 一次性锁定三个区域(防死锁:按句柄排序锁定),失败返回 ERROR。
  4. 同步用户态属性REGION_vSyncRegion 依次处理三个区域(跳过重复句柄)------HACK 注释表明这是为了处理"用户态属性可能较新"的竞态。
  5. IntGdiCombineRgn(aprgn[0], aprgn[1], aprgn[2], iMode) 执行运算。
  6. 依次 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(矩形数组 + 可选变换)创建区域:

  1. SEH 探测_SEH2_TRYProbeForRead(RgnData, Count, 1) 并读取 nCount/iType/dwSize/Buffer(用户缓冲区不可读则返回 NULL)。
  2. 参数校验(不置 LastError):Count < sizeof(RGNDATAHEADER) + nCount * sizeof(RECT)iType != RDH_RECTANGLESdwSize != sizeof(RGNDATAHEADER) 任一成立 → NULL。
  3. REGION_AllocUserRgnWithHandle(nCount)
  4. SEH 内逐矩形 REGION_UnionRectWithRgn(Region, &rects[i])(仅接受 left < right && top < bottom 的有效矩形)------注意用 Union 而非直接拷贝,因为输入矩形可能任意顺序/重叠。
  5. 若提供 XformXFORMOBJ_iSetXform 初始化,XFORMOBJ_bApplyXform(XF_LTOL, nCount*2, Buffer, Buffer) 就地变换矩形。
  6. 失败路径统一 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)
  1. NULL 检查 → FALSE。
  2. hSrcRgn1 == hSrcRgn2 特例:仅当句柄类型是 GDILoObjType_LO_REGION_TYPEGreIsHandleValid 时返回 TRUE。
  3. GDIOBJ_bLockMultipleObjects(2, ...) 锁定两个区域,REGION_vSyncRegion 同步。
  4. 依次比较:nCount 不等 → 不等;都为空(nCount==0)→ 相等;包围盒四坐标不等 → 不等;逐矩形四坐标不等 → 不等;全部相同 → TRUE。

比较利用分带正则性:两个正规表示的区域相等当且仅当矩形序列逐项相同,无需归一化。

13.6 NtGdiOffsetRgn(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3962-L3995))

REGION_LockRgnREGION_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_LockRgnREGION_GetRgnBox(Rgn, &SafeRect)先写入内核栈上 SafeRect )→ REGION_UnlockRgnProbeForWrite(pRect, sizeof(RECT), 1) 探测后拷贝 *pRect = SafeRect。返回复杂度(ERROR 表失败)。

13.8 NtGdiPtInRegion(region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c#L3997-L4021))

REGION_LockRgnREGION_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_LockRgnREGION_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):

  1. REGION_LockRgn(失败 EngSetLastError(ERROR_INVALID_HANDLE),返回 0)。
  2. cjRects = nCount * sizeof(RECT)cjSize = cjRects + sizeof(RGNDATAHEADER)
  3. lpRgnData 非空且 cjBuffer >= cjSize:SEH 内 ProbeForWrite + 拷贝 rdh 头 + 拷贝矩形数组,iType = RDH_RECTANGLESnRgnSize = cjRects(对用户呈现"数据大小"语义);缓冲不足置 ERROR_INVALID_PARAMETERcjSize = 0
  4. 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 IntRectInRegionREGION_RectInRegion RectInRegion
NtGdiSetRectRgn REGION_SetRectRgn SetRectRgn
NtGdiGetRegionData 头+矩形数组直拷 GetRegionData

NtGdiCreatePolygonRgn / NtGdiCreatePolyPolygonRgn / NtGdiFrameRgn 不在 region.c 中定义(分别经由 GreCreatePolyPolygonRgnGreCreateFrameRgn 被同文件/他处消费),故 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 分带正则性,靠两条规则保证:

  1. 带内合并(REGION_bMergeRect :同一带内新加入的矩形若与前一个矩形 top/bottom 相同且 right 相接(last.right >= new.left),则横向拉伸合并。用于 OR/UNION 的重叠带输出,防止带内出现相邻矩形。
  2. 带间合并(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_COMPLEXiFComplexity = FC_RECT4/FC_COMPLEX
  • 矩形数 ≤ 1 → 直接使用 rclBounds 内嵌矩形,iDComplexity = DC_TRIVIAL/DC_RECTDC_TRIVIAL 表示空裁剪或全屏,驱动可完全跳过裁剪)。

XCLIPOBJiDComplexity 直接来自 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 回写属性

主要路径说明:

  1. 创建CreateRectRgnNtGdiCreateRectRgn → 分配(含属性池)→ 置矩形 → 解锁回写。
  2. 组合CombineRgn → 多句柄锁定 → 同步属性 → IntGdiCombineRgn 分发 → REGION_RegionOp 扫描线运算 → 每带 CoalesceSetExtents 重算包围盒 → 解锁回写。
  3. 内部合成 :dclife.c 直接调 IntGdiCombineRgnprgnVis ∩ prgnRao ∩ prgnAPIIntEngUpdateClipRegion 生成 XCLIPOBJ 交给显示驱动。
  4. 命中测试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)撰写,所有函数签名、行号与行为描述均以源码为准。

相关推荐
嵌入式阿蔡1 小时前
STM32时钟树详解:配错一个全乱套
c语言·stm32·单片机·嵌入式硬件
caimouse2 小时前
ReactOS 图形系统分析(37):路径子系统 — path.c
c语言·reactos
CarIise2 小时前
C语言基础课程笔记:硬件原理、数据类型、结构体与格式化输入输出详解
c语言·开发语言·笔记
GeekZHR2 小时前
C语言指针2:数组名、二级指针、指针数组,一次把“指针和数组“讲透
c语言·数据结构·算法·指针
Dr.kangder3 小时前
嵌入式面试总结(二十一)——C语言关键字
c语言·开发语言·面试·职场和发展·架构·虚拟化
caimouse4 小时前
ReactOS 图形系统分析(33):字体子系统 — font.c
c语言·开发语言·reactos
不悔哥4 小时前
Tinyftp 源码学习笔记:3000 行 C 实现的 FTP 服务器
服务器·c语言·网络·tcp/ip
灵晔君4 小时前
【Linux】进程控制(一)——进程创建、进程终止与进程等待
linux·c语言·算法
caimouse4 小时前
ReactOS 图形系统分析(39):DC 对象 — dcobjs.c
c语言·开发语言