ReactOS 图形系统分析(36):位块传输 --- bitblt.c
1. 概述
bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) 位于 win32ss/gdi/ntgdi/,是内核 GDI(ntgdi/)的位块传输(BitBlt)子系统 。文件共 1655 行,实现 13 个 NtGdi* 系统调用入口 ,是引擎 BitBlt 管线(《分析_4》3.1)的 ntgdi 侧发起者 ------负责 DC 锁定与解锁、源/目标表面定位、逻辑坐标到设备坐标换算、裁剪区域的准备、颜色转换对象(EXLATEOBJ)的构建,以及 ROP/画刷参数的装配,最终把统一格式的请求转发给 IntEng* 系列引擎函数。
应用: BitBlt / StretchBlt / TransparentBlt / AlphaBlend / MaskBlt / PlgBlt /
PatBlt / PolyPatBlt / FillRgn / PaintRgn / FrameRgn / InvertRgn /
SetPixel / GetPixel
↓ gdi32.dll → win32u.dll(NtGdi* 系统调用)
bitblt.c(本分析对象)
├─ GDIOBJ_bLockMultipleObjects / DC_LockDc:按序锁定 1~3 个 DC
├─ IntLPtoDP + ptlDCOrig:逻辑坐标 → 设备坐标(含负尺寸归一化)
├─ DC_vPrepareDCsForBlit:更新画刷、按 hsemDevLock 序加锁、鼠标安全
├─ 裁剪合成(prgnClip = Vis∩Rao + API 裁剪 + DC 原点,见《分析_17/31》)
├─ EXLATEOBJ_vInitXlateFromDCs(Ex):源调色板 → 目标调色板颜色转换
└─ IntEngAlphaBlend / IntEngTransparentBlt / IntEngStretchBlt /
IntEngPaint / IntEngBitBlt / EngCopyBits
↓
gdi/eng/(DDI 分发 + 软件渲染,见《分析_4》)
↓
gdi/dib/ + gdi/diblib/(像素实现,见《分析_29/30》)
本文件把"应用要画什么 "翻译成"引擎怎么画 ",本身几乎不做像素级工作(仅 NtGdiGetPixel 借助 EngCopyBits 完成一次 1×1 拷贝)。全部 13 个入口最终都汇聚到 5 个引擎函数上,体现了 ntgdi 层"薄壳 + 装配"的设计思想。
1.1 入口一览
| 函数 | API | 关键逻辑 | 引擎对接 |
|---|---|---|---|
NtGdiBitBlt |
BitBlt | CAPTUREBLT → StretchBlt;否则转 MaskBlt(无掩码) | IntEngBitBlt(《分析_4》3.1) |
NtGdiStretchBlt |
StretchBlt | 转发 GreStretchBltMask(无掩码) |
IntEngStretchBlt(《分析_27》) |
NtGdiTransparentBlt |
TransparentBlt | 透明色先经源调色板转换 | IntEngTransparentBlt(《分析_27》) |
NtGdiAlphaBlend |
AlphaBlend | 负尺寸/空句柄参数校验 | IntEngAlphaBlend(《分析_26》) |
NtGdiMaskBlt |
MaskBlt | 掩码位图须 1BPP;ROP4 分解 | IntEngBitBlt(带掩码参数) |
NtGdiPlgBlt |
PlgBlt | 未实现(FIXME 桩) | --- |
NtGdiPatBlt |
PatBlt | 无源图案填充;ROP4 禁止源 | IntPatBlt → IntEngBitBlt |
NtGdiPolyPatBlt |
PolyPatBlt | 用户缓冲区探测 + 逐矩形填充 | IntGdiPolyPatBlt |
NtGdiFillRgn |
FillRgn | 锁 DC+区域+画刷 | IntGdiFillRgn(《分析_17》) |
NtGdiFrameRgn |
FrameRgn | 先 GreCreateFrameRgn 生成边框 |
NtGdiFillRgn |
NtGdiInvertRgn |
InvertRgn | ROP4_DSTINVERT 反色区域 | IntGdiBitBltRgn |
NtGdiSetPixel |
SetPixel | 文本画刷临时换色 + 1×1 PatBlt | IntPatBlt |
NtGdiGetPixel |
GetPixel | 1×1 EngCopyBits + 颜色转回 |
EngCopyBits(eng/copybits.c) |
2. 设计动机:为什么有这么多变体
2.1 位块传输的本质
位块传输(Bit Block Transfer)把"源表面一块矩形区域"按光栅操作(ROP)与"目标表面一块矩形区域"合并,必要时再混入 图案(Pattern)和掩码(Mask)。Windows GDI 把这一操作抽象为四个操作数的布尔代数问题:
- 源 S(Source):源 DC 上的位图区域;
- 目标 D(Dest):目标 DC 上将被覆盖的区域(也参与运算);
- 图案 P(Pattern):画刷(纯色或图案位图);
- 掩码 M(Mask):1BPP(或 8BPP 灰度)位图,逐位/逐字节决定使用"前景 ROP"还是"背景 ROP"。
ROP3 是三元布尔函数 f(S, P, D),共 256 种(索引 0x00~0xFF,其中 0xCC 为 SRCCOPY、0xAA 为 PATCOPY、0x55 为 DSTINVERT 等)。ROP4 把前景/背景各用一个 ROP3 编码为 (背景 << 8) | 前景,共 65536 种;掩码位为 1 用前景,为 0 用背景。引擎中的 ROP4_USES_SOURCE / ROP4_USES_PATTERN / ROP4_USES_DEST / ROP4_USES_MASK 宏(eng/inteng.h:44-47)通过比较 ROP4 高低字节的位模式,静态判定某次操作是否"用得上"某个操作数,从而裁剪掉无用的表面访问。
2.2 统一管线:一切皆 BitBlt
bitblt.c 的核心设计动机是:把形态各异的绘制请求全部归约到少数几个引擎原语上。
PatBlt / PolyPatBlt / FillRgn / PaintRgn / FrameRgn / InvertRgn / SetPixel
└─ 无源操作 → IntEngBitBlt(pso, NULL, NULL, ...) 或 IntEngPaint
BitBlt / MaskBlt / TransparentBlt / AlphaBlend / StretchBlt
└─ 有源操作 → IntEngBitBlt / IntEngStretchBlt / IntEngAlphaBlend / ...
这样引擎只需维护一套裁剪(CLIPOBJ)、颜色转换(XLATEOBJ)、画刷(BRUSHOBJ)与 ROP 求值管线,硬件驱动也只需实现 DrvBitBlt 等少数回调。区域填充 (FillRgn 等)甚至不走专用填充函数,而是构造一个"纯色画刷 + MIX"交给 IntEngPaint(《分析_17》),后者再转换成一次无源的 BitBlt------这就是《分析_17》所称"区域填充完整链路"的上游。
2.3 三层职责划分
| 层 | 文件 | 职责 |
|---|---|---|
| 用户态 | gdi32.dll / win32u.dll | API 包装、参数打包、NtGdi* 调用 |
| 内核 GDI | ntgdi/bitblt.c | 句柄→对象、锁 DC、坐标变换、裁剪准备、EXLATEOBJ、ROP4 装配 |
| 引擎 | gdi/eng/bitblt.c 等 | 裁剪枚举、驱动挂钩(Drv*)决策、软件回退 |
| 像素 | gdi/dib/、gdi/diblib/ | DIB 引擎逐行/逐块写像素 |
3. 公共设施:bitblt.c 的"三板斧"
3.1 坐标变换套路:IntLPtoDP + ptlDCOrig
除 PatBlt 外的所有入口都遵循同一套坐标换算(以 NtGdiAlphaBlend 70-95 行为模板):
c
DestRect.left = XOriginDest; /* 逻辑坐标 */
DestRect.right = XOriginDest + WidthDest;
IntLPtoDP(DCDest, (LPPOINT)&DestRect, 2); /* 页面→设备变换 */
DestRect.left += DCDest->ptlDCOrig.x; /* 加上 DC 原点偏移 */
DestRect.right += DCDest->ptlDCOrig.x;
IntLPtoDP施加世界变换(DC_pmxWorldToDevice)与页面变换(视口/窗口映射),见 ntgdi/coord.c;ptlDCOrig是 DC 原点(窗口 DC 的客户区偏移),区域与表面坐标都以设备坐标为准,所以矩形必须平移 DC 原点才落到表面像素坐标;- 矩形的
right/bottom是开区间(半开半闭),面积 =(right-left) × (bottom-top)。
3.2 负尺寸与翻转(RTL / 镜像)
Windows 允许 Width/Height 为负,表示翻转的拷贝(镜像、上下颠倒)。bitblt.c 在换算后用两种手段处理:
- 归一化 :
RECTL_vMakeWellOrdered(&DestRect)(bitblt.c:525、930)把left>right或top>bottom的"反向矩形"交换端点,恢复 left≤right 且 top≤bottom 的不变式; - 源点补偿 :
NtGdiMaskBlt中(bitblt.c:510-522):
c
/* Fix BitBlt so that it will not flip left to right */
if ((DestRect.left > DestRect.right) && (nWidth < 0))
{
SourcePoint.x += nWidth;
nWidth = -nWidth;
}
负宽度意味着源矩形应从 XSrc + nWidth 处向右读,而不是从 XSrc 向左读。补偿后把源点平移到等效位置、宽度取绝对值,交给引擎时就不会意外产生镜像。真正的镜像/翻转由引擎层(EngBitBlt 中的 bLeftToRight/bTopToBottom 标志)根据未归一化的原始矩形方向还原(eng/bitblt.c:365-381、783-795)。
3.3 累积边界:IntUpdateBoundsRect
凡目标 DC 带有 DC_ACCUM_APP 或 DC_ACCUM_WMGR 标志(NtGdiBeginPath/BeginDeferWindowPos 累积矩形边界,见 dcutil.c:862-875 附近的 NtGdiSetBoundsRect),绘制前都要调用 IntUpdateBoundsRect(pdc, &rc)(dcutil.c:694),把本次绘制的设备矩形并入 DC 的累积边界矩形(erclBounds),供后续 NtGdiGetBoundsRect 查询。bitblt.c 的 13 个入口中,除 NtGdiPlgBlt(桩)外几乎全部做了这一检查。
3.4 DC 准备:DC_vPrepareDCsForBlit / DC_vFinishBlit
这两个函数在 dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c)(500-636 行),是所有 blit 类操作必须成对调用的包裹:
DC_vPrepareDCsForBlit(pdcDest, rcDest, pdcSrc, rcSrc)(dclife.c:503-609):
- 刷新画刷 :若
ulDirty_含DIRTY_FILL|DC_BRUSH_DIRTY→DC_vUpdateFillBrush;含DIRTY_LINE|DC_PEN_DIRTY→DC_vUpdateLineBrush;含DIRTY_TEXT→DC_vUpdateTextBrush(dclife.c:515-520)。DC_vUpdateFillBrush(dcobjs.c:15-57)核对句柄pdcattr->hbrush与dclevel.pbrFill是否一致、必要时重锁新画刷、EBRUSHOBJ_vUpdateFromDC重建引擎画刷、对 DC_BRUSH 用crBrushClr刷新纯色; - 按序加锁 :源/目标 DC 可能属于不同 PDEV。为避免死锁,按
hsemDevLock(设备锁信号量句柄)的数值排序,先锁大者(dclife.c:525-539),保证两个 PDEV 的锁总是以相同顺序获取; - 直写 DC(DCTYPE_DIRECT)更新表面 :若
ppdev->pSurface != dclevel.pSurface(显示模式切换/表面重建后),调DC_vUpdateDC刷新表面指针与尺寸(dclife.c:558-561、589-592); - 鼠标安全 :对直写 DC 调
MouseSafetyOnDrawStart(ppdev, rc)(dclife.c:569-573),在硬件鼠标(光标)所在区域即将被覆盖时隐藏光标,见《分析_24》; - 调试构建置
DC_PREPARED标志。
DC_vFinishBlit(pdc1, pdc2) (dclife.c:612-636):对称地调用 MouseSafetyOnDrawEnd + EngReleaseSemaphore 释放设备锁,清除 DC_PREPARED。若中途提前返回(异常路径),也必须先 finish 再解锁 DC ------bitblt.c 大量使用 goto done/failed/leave 保证成对性。
注意:
DC_vPrepareDCsForBlit与DC_vFinishBlit管理的是 PDEV 设备锁 ;GDIOBJ_vUnlockObject/DC_UnlockDc管理的是 DC 对象引用锁。两者正交,必须分别成对。
3.5 颜色转换:EXLATEOBJ
源表面与目标表面的像素格式可能不同(如 8BPP 调色板源 → 32BPP 目标),需要逐像素颜色转换 。ntgdi 用 EXLATEOBJ(eng/xlateobj.c,详见《分析_19》)封装:
EXLATEOBJ_vInitXlateFromDCs(&exlo, pdcSrc, pdcDst)(xlateobj.c:824-845):以源表面调色板(无表面用gppalMono)、目标表面调色板、源/目标的背景色与目标前景色初始化,并记录目标 DC 调色板ppalDstDc;EXLATEOBJ_vInitXlateFromDCsEx(..., crBackColor)(xlateobj.c:847-874):多一个可指定的背景色(crBackColor == CLR_INVALID时回退到源 DC 背景色),NtGdiMaskBlt/GreStretchBltMask用它处理背景色参与 ROP 的转换;EXLATEOBJ_vCleanup(xlateobj.c:892-902):若转换表是动态分配的则释放,复位为内嵌的aulXlate。
引擎侧通过 XLATEOBJ_iXlate(pxlo, color) 逐像素调用(xlateobj.c:906+)。bitblt.c 中 EXLATEOBJ 的生命周期严格包裹在 blit 调用前后(见第 4 节各函数)。
3.6 多对象锁定:GDIOBJ_bLockMultipleObjects
NtGdiAlphaBlend/NtGdiTransparentBlt/NtGdiMaskBlt/GreStretchBltMask 用 GDIOBJ_bLockMultipleObjects(2 或 3, ahDC, apObj, GDIObjType_DC_TYPE) 一次锁定多个 DC,防止句柄失效。注意掩码相关函数把不用的槽位填 NULL(如 ahDC[1] = UsesSource ? hdcSrc : NULL),锁定时跳过空槽。锁定失败返回 ERROR_INVALID_HANDLE。
3.7 调用机制:从 gdi32.dll 到 NtGdi*
以 BitBlt 为例,用户态调用链为(详见《分析_31》2.1):
gdi32.dll: BitBlt(hdc, x, y, cx, cy, hdcSrc, x1, y1, rop)
└─ (内联/转发) win32u.dll: NtGdiBitBlt(...) /* 系统调用指令 */
└─ KiSystemService → win32k.sys w32ksvc 服务表
└─ NtGdiBitBlt(本文件,bitblt.c:149)
要点:
- 参数打包 :
BLENDFUNCTION、ROP4等小结构体按值传递;PATRECT等大结构体传指针,内核侧必须ProbeForRead+RtlCopyMemory(如NtGdiPolyPatBlt); - 句柄有效性 :HDC/HRGN/HBRUSH/HBITMAP 都是内核句柄表项,用户态不可直接解引用,必须经
DC_LockDc/REGION_LockRgn/BRUSH_ShareLockBrush/SURFACE_ShareLockSurface转换并加引用; - 返回约定 :
NtGdiSetPixel/NtGdiGetPixel返回COLORREF(CLR_INVALID = -1表示失败),其余返回BOOL;错误码经EngSetLastError/SetLastNtError写回用户态GetLastError; - 调试通道 :本文件第 10 行
DBG_DEFAULT_CHANNEL(GdiBlt)------开启GdiBlt调试通道后,串口会输出Locking DCs、Preparing DCs for blit、Performing the alpha blend等 TRACE 以及 DestRect/SourcePoint 的坐标打印(见 9.1 节)。
4. NtGdi* 入口逐一展开
4.1 NtGdiAlphaBlend(bitblt.c:12-147)--- AlphaBlend
c
BOOL NtGdiAlphaBlend(HDC hDCDest, LONG XOriginDest, LONG YOriginDest,
LONG WidthDest, LONG HeightDest, HDC hDCSrc, LONG XOriginSrc, LONG YOriginSrc,
LONG WidthSrc, LONG HeightSrc, BLENDFUNCTION BlendFunc, HANDLE hcmXform)
作用 :带 Alpha 通道(或源/目标常量 Alpha)的合成拷贝,即 Windows AlphaBlend。
实现流程:
BlendFunc原样装入BlendObj.BlendFunction(hcmXform是颜色矩阵句柄,本实现未使用);- 参数校验 :任一宽/高为负 →
ERROR_INVALID_PARAMETER返回 FALSE;任一 DC 句柄为 NULL → 同样失败; - 锁 DC :
GDIOBJ_bLockMultipleObjects(2, ...),失败 →ERROR_INVALID_HANDLE; - 信息 DC 短路 :
dctype == DCTYPE_INFO→ 直接解锁并返回 TRUE(注释:"Yes, Windows really returns TRUE in this case"------信息 DC 没有表面,但 Win32 语义上 AlphaBlend 到信息 DC 是"成功"的); - 坐标变换 :目标/源矩形
IntLPtoDP+ptlDCOrig(见 3.1); - 零尺寸短路:若目标或源矩形的 right/bottom 为 0(退化矩形)→ 返回 TRUE;
IntUpdateBoundsRect(累积边界);DC_vPrepareDCsForBlit(DCDest, &DestRect, DCSrc, &SourceRect);- 取
dclevel.pSurface,任一为 NULL → 失败跳leave; EXLATEOBJ_vInitXlateFromDCs(&exlo, DCSrc, DCDest);IntEngAlphaBlend(&BitmapDest->SurfObj, &BitmapSrc->SurfObj, (CLIPOBJ*)&DCDest->co, &exlo.xlo, &DestRect, &SourceRect, &BlendObj)------裁剪直接用 DC 的合成裁剪对象DCDest->co(已在 DC 状态机中维护,见《分析_31》裁剪合成);- 清理:
EXLATEOBJ_vCleanup→DC_vFinishBlit→ 解锁两个 DC。
注意事项:
- 引擎侧
IntEngAlphaBlend(eng/alphablend.c,详见《分析_26》)支持源常量 Alpha、每像素 Alpha、目标 Alpha 三种模式的组合; - 源与目标不要求同格式 ,颜色转换交给
exlo; - 与 BitBlt 不同,AlphaBlend 的结果与 ROP 无关(永远是合成覆盖),因此不需要 ROP4。
4.2 NtGdiBitBlt(bitblt.c:149-197)--- BitBlt
c
BOOL NtGdiBitBlt(HDC hDCDest, INT XDest, INT YDest, INT Width, INT Height,
HDC hDCSrc, INT XSrc, INT YSrc, DWORD dwRop, DWORD crBackColor, FLONG fl)
作用 :最经典的位块传输。本身不干活,只做路由分派:
- 若
dwRop & CAPTUREBLT(0x40000000,捕获被遮挡窗口内容)→ 直接转发NtGdiStretchBlt(等宽高等),因为 CAPTUREBLT 需要走带窗口内容合成的路径; - 否则清除
NOMIRRORBITMAP|CAPTUREBLT标志; - 转发
NtGdiMaskBlt(hDCDest, ..., NULL, 0, 0, MAKEROP4(dwRop, dwRop), crBackColor)------无掩码位图、源点(0,0),前景=背景=dwRop 的 ROP4。注释调侃:"TODO: What's fl for? LOL not to send this to MaskBit!" (fl参数未使用)。
设计要点 :BitBlt 的 256 种 ROP3 天然是 MaskBlt 的 ROP4 特例(前景=背景),复用 MaskBlt 的完整管线(源点补偿、负尺寸归一化、EXLATEOBJ 带背景色)零成本获得全部 BitBlt 语义。crBackColor 在单色→彩色转换时作为背景色参与 XLATE。
4.3 NtGdiTransparentBlt(bitblt.c:199-309)--- TransparentBlt
c
BOOL NtGdiTransparentBlt(HDC hdcDst, INT xDst, INT yDst, INT cxDst, INT cyDst,
HDC hdcSrc, INT xSrc, INT ySrc, INT cxSrc, INT cySrc, COLORREF TransColor)
作用 :把源中等于 TransColor 的像素"抠掉"(不写目标),其余像素按 1:1 拷贝。
实现流程:
- NULL 句柄校验 →
ERROR_INVALID_PARAMETER;锁 2 DC;DCTYPE_INFO → TRUE; - 目标/源矩形
IntLPtoDP+ptlDCOrig;IntUpdateBoundsRect; DC_vPrepareDCsForBlit;取源/目标表面;- 透明色转换 (bitblt.c:290-293)------这是本函数与 MaskBlt 的关键区别:先把
TransColor这个 RGB 值 转换到源表面调色板的索引:
c
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, BitmapSrc->ppal, 0, 0, 0);
TransparentColor = XLATEOBJ_iXlate(&exlo.xlo, (ULONG)TransColor);
即 gpalRGB → 源调色板 的正向转换,得到"源中哪个索引算透明";
-
再建
EXLATEOBJ_vInitXlateFromDCs(源调色板 → 目标调色板)用于普通像素拷贝; -
IntEngTransparentBlt(&BitmapDest->SurfObj, &BitmapSrc->SurfObj, (CLIPOBJ*)&DCDest->co, &exlo.xlo, &rcDest, &rcSrc, TransparentColor, 0); -
清理(
done标签处统一DC_vFinishBlit+ 解锁)。
注意事项 :透明比较发生在源像素空间(比较源索引是否等于 TransparentColor),因此正确性依赖第 4 步把 RGB 精确转换为源调色板索引(对于 32BPP 源即 RGB 值本身)。引擎侧实现见《分析_27》(eng/transblt.c)。
4.4 NtGdiMaskBlt(bitblt.c:311-555)--- MaskBlt
c
BOOL NtGdiMaskBlt(HDC hdcDest, INT nXDest, INT nYDest, INT nWidth, INT nHeight,
HDC hdcSrc, INT nXSrc, INT nYSrc, HBITMAP hbmMask, INT xMask, INT yMask,
DWORD dwRop4, DWORD crBackColor)
作用 :带 1BPP 掩码位图的三输入(源+图案+掩码)位块传输,是 BitBlt 的底层承载者。
实现流程:
rop4 = WIN32_ROP4_TO_ENG_ROP4(dwRop4)(=dwRop4 >> 16,intgdi.h:5------Win32 ROP4 高 16 位才是引擎 ROP4);UsesSource = ROP4_USES_SOURCE(rop4)、UsesPattern = ROP4_USES_PATTERN(rop4);若用到源或图案但没有源 DC → 返回 FALSE;- 掩码处理 (bitblt.c:354-377):若
ROP4_USES_MASK(rop4)且给了hbmMask:SURFACE_ShareLockSurface(hbmMask)引用锁定位图;- 校验位图格式
gajBitsPerFormat[...] == 1(必须 1BPP),否则ERROR_INVALID_HANDLE并解锁; - 若 ROP 用掩码但没给位图 → 置 NULL,注释 :"the Eng function will take care of that and use the brushobject to get a mask"(引擎可用画刷的掩码面,见 5.1 节 eng/bitblt.c:507-517);
- 锁 DC:
ahDC[1] = UsesSource ? hdcSrc : NULL;DCTYPE_INFO 短路(目标/源都查); - 目标矩形变换 + 归一化(为边界计算);
IntUpdateBoundsRect; - 源矩形推导:源只给起点
SourcePoint,尺寸取目标矩形尺寸(bitblt.c:469-472):
c
SourceRect.right = SourcePoint.x + DestRect.right - DestRect.left;
SourceRect.bottom = SourcePoint.y + DestRect.bottom - DestRect.top;
DC_vPrepareDCsForBlit;若画刷脏 →DC_vUpdateFillBrush;UsesSource时EXLATEOBJ_vInitXlateFromDCsEx(&exlo, DCSrc, DCDest, crBackColor)(背景色参与);- 翻转补偿 (见 3.2)与
RECTL_vMakeWellOrdered; IntEngBitBlt(&BitmapDest->SurfObj, BitmapSrc?&BitmapSrc->SurfObj:NULL, psurfMask?&psurfMask->SurfObj:NULL, (CLIPOBJ*)&DCDest->co, XlateObj, &DestRect, &SourcePoint, &MaskPoint, &DCDest->eboFill.BrushObject, &DCDest->dclevel.pbrFill->ptOrigin, rop4)------画刷原点取自填充画刷,供图案/掩码定位;- 清理:解锁 EXLATE、
DC_vFinishBlit、解锁 DC、SURFACE_ShareUnlockSurface;失败 →ERROR_INVALID_PARAMETER。
与 BitBlt 的关系 :NtGdiBitBlt → 本函数(无掩码、前景=背景),见 4.2。
4.5 NtGdiPlgBlt(bitblt.c:557-574)--- PlgBlt
c
BOOL NtGdiPlgBlt(HDC hdcTrg, LPPOINT pptlTrg, HDC hdcSrc, INT xSrc, INT ySrc,
INT cxSrc, INT cySrc, HBITMAP hbmMask, INT xMask, INT yMask, DWORD crBackColor)
作用 :把源矩形平行投影到目标平行四边形 (由 pptlTrg 三个顶点定义),可实现旋转/斜切。当前未实现:
c
FIXME("NtGdiPlgBlt: unimplemented.\n");
return FALSE;
引擎层 EngPlgBlt 在 eng/stubs.c 中同样是桩(见《分析_28》)。当应用调用 PlgBlt 时,会直接返回 FALSE 并输出 FIXME 日志。
4.6 NtGdiStretchBlt(bitblt.c:838-871)与 GreStretchBltMask(bitblt.c:576-835)
4.6.1 NtGdiStretchBlt
c
BOOL NtGdiStretchBlt(HDC hDCDest, INT XOriginDest, INT YOriginDest,
INT WidthDest, INT HeightDest, HDC hDCSrc, INT XOriginSrc, INT YOriginSrc,
INT WidthSrc, INT HeightSrc, DWORD dwRop3, DWORD dwBackColor)
作用 :缩放拷贝。实现为对 GreStretchBltMask 的薄封装:
dwRop3 = dwRop3 & ~(NOMIRRORBITMAP|CAPTUREBLT);- 转发
GreStretchBltMask(hDCDest, ..., MAKEROP4(dwRop3 & 0xFF0000, dwRop3), dwBackColor, NULL, 0, 0)------掩码 DC 为 NULL (无掩码),ROP4 前景取dwRop3 & 0xFF0000(ROP3 索引位)、背景取整个 dwRop3。注意 StretchBlt 的 ROP3 位布局是0x00CC0000形式,dwRop3 & 0xFF0000取出 ROP 索引供引擎R3_OPINDEX_*使用。
4.6.2 GreStretchBltMask(内部函数,非 NtGdi 入口)
作用 :带可选掩码的拉伸传输,是 StretchBlt/MaskBlt(缩放掩码) 的通用实现,同时被 NtGdiStretchBlt 和 NtGdiMaskBlt(CAPTUREBLT 时经 StretchBlt)间接使用。
签名 :GreStretchBltMask(HDC hDCDest, INT XOriginDest, INT YOriginDest, INT WidthDest, INT HeightDest, HDC hDCSrc, INT XOriginSrc, INT YOriginSrc, INT WidthSrc, INT HeightSrc, DWORD dwRop4, DWORD dwBackColor, HDC hDCMask, INT XOriginMask, INT YOriginMask)------注意掩码是 DC (而非 HBITMAP),因为它要支持把任意 DC 表面当掩码用,并参与 IntLPtoDP 坐标变换。
完整流程:
rop4 = WIN32_ROP4_TO_ENG_ROP4(dwRop4);UsesSource/UsesMask判定;- 零尺寸 :任一宽或高为 0 →
ERROR_INVALID_PARAMETER但返回 TRUE(Windows 语义:空操作算成功); - 缺 DC:
!hDCDest || (UsesSource && !hDCSrc) || (UsesMask && !hDCMask)→ 失败; - 锁 3 个 DC :
ahDC[2] = UsesMask ? hDCMask : NULL; - DCTYPE_INFO 短路(目标、源分别查)→ TRUE;
- 负尺寸目标矩形 (bitblt.c:669-689):
WidthDest<0时left = XOriginDest + WidthDest; right = XOriginDest(先把逻辑矩形定义对,再进行 LPtoDP------与 MaskBlt 先变换后归一化不同,这里在变换前就修正方向); - 目标:
IntLPtoDP+ptlDCOrig+ 归一化 +IntUpdateBoundsRect; - 源矩形 (bitblt.c:718-748):同样处理负尺寸;
UsesSource时IntLPtoDP(DCSrc)+ptlDCOrig; BrushOrigin = {0,0};DC_vPrepareDCsForBlit(DCDest, &DestRect, DCSrc, &SourceRect)------注释说明 "Only prepare Source and Dest, hdcMask represents a DIB"(掩码是独立 DIB,不需要设备锁/鼠标安全);- 画刷脏 →
DC_vUpdateFillBrush; UsesSource时EXLATEOBJ_vInitXlateFromDCsEx(&exlo, DCSrc, DCDest, dwBackColor);BrushOrigin += DCDest->ptlDCOrig(画刷原点平移到设备坐标);- 掩码表面检查 (bitblt.c:779-797):
BitmapMask->SurfObj.sizlBitmap.cx < WidthSrc || .cy < HeightSrc→ WARN "mask is smaller than bitmap" 并失败;掩码起点MaskPoint = (XOriginMask, YOriginMask)经IntLPtoDP(DCMask)+ptlDCOrig变换; IntEngStretchBlt(&BitmapDest->SurfObj, BitmapSrc?..., BitmapMask?..., (CLIPOBJ*)&DCDest->co, XlateObj, &DCDest->dclevel.ca /* COLORADJUSTMENT */, &DestRect, &SourceRect, BitmapMask?&MaskPoint:NULL, &DCDest->eboFill.BrushObject, &BrushOrigin, rop4)------颜色调整结构dclevel.ca来自 DC(影响拉伸时的颜色校正),见《分析_27》;- 清理(
failed:标签统一处理)。
与《分析_27》的关系 :IntEngStretchBlt(eng/stretchblt.c)内部按 WidthDest/WidthSrc 比例关系选择 StretchCopy / StretchMaskCopy / 逐点复制 路径,并实现 COLORONCOLOR / HALFTONE 模式,详见该分册。
4.7 NtGdiPatBlt(bitblt.c:1024-1073)与 IntPatBlt(bitblt.c:874-964)
4.7.1 NtGdiPatBlt
c
BOOL NtGdiPatBlt(HDC hdcDest, INT x, INT y, INT cx, INT cy, DWORD dwRop)
作用:用当前画刷按 ROP 填充矩形(无源)。流程:
dwRop = MAKEROP4(dwRop & 0xFF0000, dwRop)(ROP3 → ROP4);- 禁止源 :
WIN32_ROP4_USES_SOURCE(dwRop)为真 → 返回 FALSE(PatBlt 语义上不允许源参与); - 锁 DC(
DC_LockDc);无表面 → TRUE(Windows 语义); - 画刷脏 →
DC_vUpdateFillBrush; IntPatBlt(pdc, x, y, cx, cy, dwRop, &pdc->eboFill)。
4.7.2 IntPatBlt(内部核心)
签名 :IntPatBlt(PDC pdc, INT XLeft, INT YLeft, INT Width, INT Height, DWORD dwRop3, PEBRUSHOBJ pebo)------注意这里 dwRop3 实际是已包装的 ROP4 (NtGdiPatBlt 传入 MAKEROP4 结果;IntGdiPolyPatBlt 直接传 API 的 dwRop)。
流程:
pbrush = pebo->pbrush;若pbrush->flAttrs & BR_IS_NULL(空画刷)→ 直接 TRUE(无填充);- 负尺寸矩形修正(同 4.6.2 方式);
IntLPtoDP+ptlDCOrig; RECTL_vMakeWellOrdered------注释指出 "RTL layout can produce a rect with left > right (backwards rect)"(从右到左布局下 LPtoDP 可能产生反向矩形);IntUpdateBoundsRect;- 画刷原点(bitblt.c:937-943):
c
#ifdef _USE_DIBLIB_
BrushOrigin.x = pbrush->ptOrigin.x + pdc->ptlDCOrig.x + XLeft;
BrushOrigin.y = pbrush->ptOrigin.y + pdc->ptlDCOrig.y + YLeft;
#else
BrushOrigin.x = pbrush->ptOrigin.x + pdc->ptlDCOrig.x;
#endif
DIBLib 构建下原点含矩形左上角偏移(让图案从矩形内对齐),非 DIBLib 下仅画刷原点 + DC 原点;
-
DC_vPrepareDCsForBlit(pdc, &DestRect, NULL, NULL)(单 DC); -
IntEngBitBlt(&psurf->SurfObj, NULL, NULL, (CLIPOBJ*)&pdc->co, NULL, &DestRect, NULL, NULL, &pebo->BrushObject, &BrushOrigin, WIN32_ROP3_TO_ENG_ROP4(dwRop3))------无源、无掩码 ,图案由画刷对象提供; -
DC_vFinishBlit。
注意 :此处裁剪用 pdc->co(DC 合成裁剪,含可见区/RAO/API 裁剪,见《分析_31》),与区域填充函数另行构造 XCLIPOBJ 的路线不同。
4.8 NtGdiPolyPatBlt(bitblt.c:1075-1125)与 IntGdiPolyPatBlt(bitblt.c:966-1022)
4.8.1 NtGdiPolyPatBlt(系统调用入口)
作用 :一次调用填充多个矩形(每个矩形可带不同画刷),对应 PolyPatBlt。
流程:
cRects > 0时以GDITAG_PLGBLT_DATA标签从 PagedPool 分配sizeof(PATRECT) × cRects的临时缓冲;_SEH2_TRY中ProbeForRead(pRects, cRects * sizeof(PATRECT), 1)探测用户缓冲区可读性,再RtlCopyMemory拷贝------用户态指针不能直接解引用;- 异常时以
SetLastError(STATUS_*)返回 FALSE(SetLastNtError); IntGdiPolyPatBlt(hDC, dwRop, rb, cRects, Mode);最后释放缓冲。
4.8.2 IntGdiPolyPatBlt(内部)
流程:
DC_LockDc;DCTYPE_INFO → TRUE;- 逐矩形循环(bitblt.c:993-1017):
c
for (i = 0; i < cRects; i++)
{
pbrush = BRUSH_ShareLockBrush(pRects->hBrush); /* 锁该矩形的画刷 */
if (pbrush != NULL)
{
EBRUSHOBJ_vInitFromDC(&eboFill, pbrush, pdc); /* 从 DC 上下文初始化画刷对象 */
IntPatBlt(pdc, pRects->r.left, pRects->r.top,
pRects->r.right, pRects->r.bottom, /* 注意:right/bottom 当宽/高用 */
dwRop, &eboFill);
EBRUSHOBJ_vCleanup(&eboFill);
BRUSH_ShareUnlockBrush(pbrush);
}
pRects++;
}
注意事项:
PATRECT.r是RECTL,其right/bottom在此被当作宽度/高度 传给IntPatBlt(IntPatBlt内部再XLeft + Width计算右边界)------这是 ntgdi 与用户态PolyPatBlt约定好的"伪矩形"语义;- 每个矩形独立锁/解锁画刷、独立初始化
EBRUSHOBJ(EBRUSHOBJ_vInitFromDC会按 DC 的表面格式实现画刷,见 eng/engbrush.c 与《分析_17》); - 单个矩形失败只跳过该矩形,函数仍返回 TRUE(容忍性策略);
- 返回值恒为 TRUE(只要 DC 有效且非信息 DC)。
4.9 NtGdiFillRgn(bitblt.c:1335-1390)--- FillRgn
c
BOOL NtGdiFillRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush)
作用:用指定画刷填充区域(任意形状,经区域裁剪)。
流程:
DC_LockDc(hdc),失败 → ERR 日志 + FALSE;- 无表面(空内存 DC / 信息 DC)→ 解锁返回 TRUE;
REGION_LockRgn(hrgn)锁区域,失败 → ERR + FALSE;BRUSH_ShareLockBrush(hbrush)引用锁画刷,失败 → ERR + FALSE;IntGdiFillRgn(pdc, prgn, pbrFill)(内部核心,见 5.4 节);- 依次
BRUSH_ShareUnlockBrush/REGION_UnlockRgn/DC_UnlockDc。
要点:三个 GDI 对象(DC/区域/画刷)分别用三种锁(DC_LockDc、REGION_LockRgn、BRUSH_ShareLockBrush),解锁顺序与加锁相反。
4.10 NtGdiFrameRgn(bitblt.c:1392-1414)--- FrameRgn
c
BOOL NtGdiFrameRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush, INT xWidth, INT yHeight)
作用 :绘制区域的边框(厚度 xWidth×yHeight)。实现非常简洁:
c
hrgnFrame = GreCreateFrameRgn(hrgn, xWidth, yHeight); /* 由原区域生成边框区域 */
if (hrgnFrame == NULL) return FALSE;
bResult = NtGdiFillRgn(hdc, hrgnFrame, hbrush); /* 用画刷填充边框区域 */
GreDeleteObject(hrgnFrame);
GreCreateFrameRgn(region.c:2028)先复制原区域,再向内/向外扩张生成边框区域(内边框:原区域缩小 xWidth/yHeight 后的剩余部分;外边框:扩张后的区域减去原区域)。边框绘制完全复用 FillRgn 管线,没有任何特殊绘制代码。
4.11 NtGdiInvertRgn(bitblt.c:1416-1461)--- InvertRgn
作用:把区域内的像素颜色取反。
流程:
DC_LockDc,失败 →ERROR_INVALID_HANDLE;- 无表面 → TRUE;
REGION_LockRgn;IntGdiBitBltRgn(pdc, prgn, NULL, NULL, ROP4_DSTINVERT)------见 5.3 节。ROP4_DSTINVERT(dib/dib.h:7)是"目标取反"的 ROP4,无源无图案,纯目标反转;- 解锁区域与 DC。
4.12 NtGdiSetPixel(bitblt.c:1463-1549)--- SetPixel
c
COLORREF NtGdiSetPixel(HDC hdc, INT x, INT y, COLORREF crColor)
作用 :设置单像素颜色,返回实际设置后的 RGB 颜色 (若无法精确表示则返回最近似色,失败返回 CLR_INVALID,即 0xFFFFFFFF)。
实现流程------把"单像素"归约为"1×1 的 PatBlt":
- 锁 DC;无表面 →
CLR_INVALID; IntUpdateBoundsRect(1×1 矩形);- 颜色转换入 :
iSolidColor = TranslateCOLORREF(pdc, crColor)(dcutil.c:882-962,见第 8 节)------把 RGB / PALETTEINDEX / PALETTERGB / DIBINDEX 统一转换为目标表面的颜色空间; - 复用文本画刷 :
pebo = &pdc->eboText------注释:"Use the DC's text brush, which is always a solid brush"(文本画刷恒为纯色,改色代价最小); - 保存旧纯色与脏标志,
EBRUSHOBJ_iSetSolidColor(pebo, iSolidColor)换色,清DIRTY_TEXT(防止随后 PatBlt 触发文本画刷重建覆盖新色); IntPatBlt(pdc, x, y, 1, 1, PATCOPY, pebo)------以 PATCOPY(图案=结果)画 1×1;- 恢复现场 :
EBRUSHOBJ_iSetSolidColor(pebo, iOldColor)、ulDirty_ = ulDirty; - 颜色转换出(bitblt.c:1530-1542):从目标表面调色板转回 RGB:
c
EXLATEOBJ_vInitialize(&exlo, pdc->dclevel.pSurface->ppal, &gpalRGB,
CLR_INVALID, CLR_INVALID, CLR_INVALID);
crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor);
三个 CLR_INVALID 表示不使用背景/前景色替换(无调色板映射特殊处理),纯数值转换;
- 成功返回
crColor(新 RGB),失败CLR_INVALID。
注意 :FIXME 注释提示第 1530 行解引用 pSurface 时 PDEV 未加锁(潜在竞态,当前可接受)。
4.13 NtGdiGetPixel(bitblt.c:1551-1655)--- GetPixel
c
COLORREF NtGdiGetPixel(HDC hdc, INT x, INT y)
作用:读取单像素颜色并转为 RGB。
实现流程------把"读 1 个像素"归约为"1×1 的 CopyBits":
- 锁 DC(失败 →
ERROR_INVALID_HANDLE);无表面 →goto leave; - 坐标:
IntLPtoDP+ptlDCOrig,构造 1×1 目标矩形rcDest; DC_vPrepareDCsForBlit(pdc, &rcDest, NULL, NULL)(只准备目标 DC);- 越界检查 (bitblt.c:1597-1605):
ptlSrc.x/y >= sizlBitmap.cx/cy或< 0→ 失败(GetPixel在越界时返回 CLR_INVALID,与 SetPixel 的"裁剪"语义不同------SetPixel 通过 PatBlt 的裁剪静默丢弃越界像素,而 GetPixel 必须显式检查); - 分配 1×1 目标表面(bitblt.c:1608-1615):
c
psurfDest = SURFACE_AllocSurface(STYPE_BITMAP, 1, 1, BMF_32BPP, 0, 0, 0, &ulRGBColor);
32BPP 表面,初始值即 ulRGBColor;
-
EXLATEOBJ_vInitialize(&exlo, psurfSrc->ppal, &gpalRGB, CLR_INVALID, CLR_INVALID, CLR_INVALID)------源调色板 → RGB; -
EngCopyBits(&psurfDest->SurfObj, &psurfSrc->SurfObj, NULL, &exlo.xlo, &rclDest/*(0,0,1,1)*/, &ptlSrc)------从源(x,y)拷 1 像素到临时表面(0,0),逐像素颜色转换在此完成 ; -
GDIOBJ_vDeleteObject删除临时表面;ulRGBColor &= 0x00FFFFFF(去掉 Alpha 高位字节); -
leave:→DC_vFinishBlit+ 解锁,返回ulRGBColor(失败为 CLR_INVALID)。
要点 :GetPixel 是 bitblt.c 中唯一真正触碰像素 的入口(通过 EngCopyBits,见 eng/copybits.c:229),其余入口都只是装配参数。
4.14 入口公共行为对照
| 入口 | 失败错误码 | 空/无效对象行为 | 信息 DC | 关键短路 |
|---|---|---|---|---|
| NtGdiAlphaBlend | ERROR_INVALID_PARAMETER / _INVALID_HANDLE | 负尺寸→FALSE;空 DC→FALSE | 返回 TRUE | 目标/源矩形 right/bottom 为 0 |
| NtGdiBitBlt | (透传 MaskBlt/StretchBlt) | --- | 透传 | CAPTUREBLT 走 StretchBlt |
| NtGdiTransparentBlt | ERROR_INVALID_PARAMETER / _INVALID_HANDLE | 空 DC→FALSE | 返回 TRUE | --- |
| NtGdiMaskBlt | ERROR_INVALID_HANDLE / _INVALID_PARAMETER | 掩码非 1BPP→失败;缺源 DC→FALSE | 返回 TRUE | ROP 不用源则不锁源 DC |
| NtGdiPlgBlt | --- | --- | --- | FIXME 桩,恒 FALSE |
| NtGdiStretchBlt | 透传 GreStretchBltMask | 零尺寸→返回 TRUE | 透传 | NOMIRRORBITMAP/CAPTUREBLT 清除 |
| NtGdiPatBlt | ERROR_INVALID_HANDLE | 无表面→TRUE | --- | ROP4 用源→FALSE |
| NtGdiPolyPatBlt | ERROR_NOT_ENOUGH_MEMORY / 异常状态码 | cRects=0 直接过 | 返回 TRUE | --- |
| NtGdiFillRgn | (内部返回 FALSE) | 无表面→TRUE;区域/画刷锁失败→FALSE | --- | --- |
| NtGdiFrameRgn | (透传 FillRgn) | hrgnFrame 建失败→FALSE | 透传 | --- |
| NtGdiInvertRgn | ERROR_INVALID_HANDLE | 无表面→TRUE;区域锁失败→FALSE | --- | --- |
| NtGdiSetPixel | (返回 CLR_INVALID) | 无表面→CLR_INVALID | --- | --- |
| NtGdiGetPixel | (返回 CLR_INVALID) | 无表面/越界→CLR_INVALID | --- | 越界显式检查 |
共性规律:
- "无表面返回 TRUE / 信息 DC 返回 TRUE" 贯穿所有入口------空操作在 Windows 语义下算成功;
- 负尺寸只在 AlphaBlend 被直接拒绝(ERROR_INVALID_PARAMETER),其余入口把负尺寸当翻转处理;
- 错误码优先级:句柄无效(ERROR_INVALID_HANDLE)> 参数非法(ERROR_INVALID_PARAMETER)> 内存不足(ERROR_NOT_ENOUGH_MEMORY);
- 失败路径必须先清理已获取的资源 (锁、EXLATEOBJ、临时表面)再返回,
goto done/failed/leave标签保证这一点。
5. 核心内部函数详解
5.1 裁剪合成范式(IntGdiFillRgn / IntGdiBitBltRgn 共用)
两个区域类内部函数遵循同一套裁剪合成范式 ,值得先总结(以 IntGdiBitBltRgn 1141-1225 行为模板):
c
1. 无表面 → 返回 TRUE;
2. prgnClip = IntSysCreateRectpRgn(0, 0, 0, 0); /* 空区域 */
3. REGION_LPTODP(pdc, prgnClip, prgn); /* 拷贝 + 世界→设备变换 */
4. 与 prgnRao 或 prgnVis 做 RGN_AND; /* 系统裁剪:RAO/可见区 */
5. REGION_bOffsetRgn(prgnClip, ptlDCOrig.x, ptlDCOrig.y); /* DC 原点平移 */
6. IntUpdateBoundsRect(累积边界);
7. DC_vPrepareDCsForBlit(pdc, &prgnClip->rdh.rcBound, NULL, NULL);
8. IntEngInitClipObj(&xcoClip);
IntEngUpdateClipRegion(&xcoClip, rdh.nCount, Buffer, &rdh.rcBound); /* 区域→XCLIPOBJ */
9. IntEngBitBlt / IntEngPaint(用 xcoClip 作 CLIPOBJ,矩形取 rdh.rcBound);
10. DC_vFinishBlit → REGION_Delete → IntEngFreeClipResources;
其中 REGION_LPTODP(bitblt.c:1127-1139)是静态辅助:
c
static BOOL FASTCALL REGION_LPTODP(PDC pdc, PREGION prgnDest, PREGION prgnSrc)
{
if (IntGdiCombineRgn(prgnDest, prgnSrc, NULL, RGN_COPY) == ERROR) return FALSE;
return REGION_bXformRgn(prgnDest, DC_pmxWorldToDevice(pdc));
}
IntGdiCombineRgn(..., RGN_COPY)深拷贝区域(region.c:2490);REGION_bXformRgn用世界→设备矩阵变换区域路径(region.c:2710 附近的REGION_bOffsetRgn是纯平移特例);- 裁剪优先级 :
prgnRao(矩形加速优化区,可空)优先于prgnVis(可见区);两者都是"不含 DC 原点"的系统坐标,所以第 5 步必须平移 DC 原点------这是《分析_31》第 6 节裁剪合成的区域版。
5.2 IntGdiBitBltRgn(bitblt.c:1141-1225)--- 区域版无源 BitBlt
签名 :IntGdiBitBltRgn(PDC pdc, PREGION prgn, BRUSHOBJ *pbo, POINTL *pptlBrush, ROP4 rop4)------pbo/pptlBrush 可空。
完整流程(即 5.1 范式):
- 表面检查 → TRUE;
- 建空
prgnClip; REGION_LPTODP变换到设备坐标;prgnRao ? prgnRao : prgnVis求交(RGN_AND)------注释:"these are (atm) without DC-origin";REGION_bOffsetRgn(+ptlDCOrig);- 累积边界(
REGION_GetRgnBox取外接矩形); DC_vPrepareDCsForBlit(pdc, &rcBound, NULL, NULL);IntEngInitClipObj+IntEngUpdateClipRegion(把区域的rdh.nCount个矩形 +rcBound装入 XCLIPOBJ);IntEngBitBlt(&pdc->dclevel.pSurface->SurfObj, NULL, NULL, (CLIPOBJ*)&xcoClip, NULL, &prgnClip->rdh.rcBound, NULL, NULL, pbo, pptlBrush, rop4)------无源无掩码;- 清理:
DC_vFinishBlit、REGION_Delete、IntEngFreeClipResources。
使用方 :NtGdiInvertRgn(ROP4_DSTINVERT,pbo=NULL)。引擎侧 IntEngBitBlt 会用 xcoClip.rclBounds 裁剪目标矩形(eng/bitblt.c:701-712),DC_COMPLEX 时枚举矩形逐块绘制。
5.3 IntGdiFillRgn(bitblt.c:1227-1324)--- 区域填充核心(重点)
签名 :IntGdiFillRgn(PDC pdc, PREGION prgn, PBRUSH pbrFill)------pbrFill 可空(空则用 DC 当前画刷)。
完整流程:
Step 1:表面与区域准备
c
if (pdc->dclevel.pSurface == NULL) return TRUE; /* 无表面无操作 */
prgnClip = IntSysCreateRectpRgn(0, 0, 0, 0); /* 空裁剪区域 */
if (prgnClip == NULL) return FALSE;
Step 2:裁剪合成(同 5.1)
c
if (!REGION_LPTODP(pdc, prgnClip, prgn)) { REGION_Delete(prgnClip); return FALSE; }
if (pdc->prgnRao)
IntGdiCombineRgn(prgnClip, prgnClip, pdc->prgnRao, RGN_AND); /* RAO 优先 */
else
IntGdiCombineRgn(prgnClip, prgnClip, pdc->prgnVis, RGN_AND); /* 否则可见区 */
if (!REGION_bOffsetRgn(prgnClip, pdc->ptlDCOrig.x, pdc->ptlDCOrig.y)) { ... return FALSE; }
Step 3:累积边界 + 裁剪对象
c
if (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
REGION_GetRgnBox(prgnClip, &rcrgn);
IntUpdateBoundsRect(pdc, &rcrgn);
}
IntEngInitClipObj(&xcoClip);
IntEngUpdateClipRegion(&xcoClip, prgnClip->rdh.nCount,
prgnClip->Buffer, &prgnClip->rdh.rcBound);
Step 4:MIX 构造 (bitblt.c:1286-1288)------区域填充的核心语义:
c
rop2Fg = FIXUP_ROP2(pdc->pdcattr->jROP2); /* ROP2: 1~16 归一化 */
mix = rop2Fg | (pdc->pdcattr->jBkMode == OPAQUE ? rop2Fg : R2_NOP) << 8;
FIXUP_ROP2(rop2) = (((rop2 - 1) & 0xF) + 1)(intgdi.h:10),把 ROP2 从 1~16 归一到 0~15 索引(引擎 ROP2 常量 R2_BLACK 等以 0 为基);- MIX 高字节 = 背景 ROP2,低字节 = 前景 ROP2;
- 背景色处理 :
BkMode == OPAQUE时背景也用rop2Fg(背景=前景色),否则背景用R2_NOP(不动)------即 TRANSPARENT 背景模式下,区域外(背景)像素不被画刷覆盖。这正是 GDISetBkMode的填充语义; MIX_TO_ROP4(eng/eng.h:5)在引擎侧把 MIX 转成 ROP4(背景<<8 | 前景)。
Step 5:DC 准备 + 画刷对象
c
DC_vPrepareDCsForBlit(pdc, &prgnClip->rdh.rcBound, NULL, NULL);
if (pbrFill != NULL)
{
EBRUSHOBJ_vInit(&eboFill, pbrFill, pdc->dclevel.pSurface,
0x00FFFFFF, 0, NULL); /* 用指定画刷 */
pbo = &eboFill.BrushObject;
}
else
{
if (pdc->pdcattr->ulDirty_ & (DIRTY_FILL | DC_BRUSH_DIRTY))
DC_vUpdateFillBrush(pdc); /* 刷新 DC 填充画刷 */
pbo = &pdc->eboFill.BrushObject; /* 用 DC 画刷 */
}
EBRUSHOBJ_vInit(pebo, pbrush, psurf, 0x00FFFFFF, 0, NULL)(brush.h:145):以目标表面为基准"实现"画刷(纯色索引化/图案 DIB 化),参数0x00FFFFFF是实现的掩码上限、NULL调色板表示无特殊调色板;- 与
NtGdiFillRgn传显式画刷不同,PaintRgn(IntGdiPaintRgn)传 NULL 走 DC 画刷分支。
Step 6:IntEngPaint(bitblt.c:1312-1316)
c
bRet = IntEngPaint(&pdc->dclevel.pSurface->SurfObj,
(CLIPOBJ *)&xcoClip,
pbo,
&pdc->ptlFillOrigin, /* 画刷原点 = DC 填充原点 */
mix);
DC_vFinishBlit(pdc, NULL);
REGION_Delete(prgnClip);
IntEngFreeClipResources(&xcoClip);
return bRet;
引擎侧 IntEngPaint(eng/paint.c:85-102)先检查目标表面是否挂钩 DrvPaint(非 STYPE_BITMAP 且 HOOK_PAINT),有则调驱动,否则 EngPaint 用 MIX_TO_ROP4(mix) 转 ROP4 再转发 IntEngBitBlt(无源)------详见《分析_17》。
调用链全貌:
#mermaid-svg-8BqC98xSBbSVPO97{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-8BqC98xSBbSVPO97 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8BqC98xSBbSVPO97 .error-icon{fill:#552222;}#mermaid-svg-8BqC98xSBbSVPO97 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8BqC98xSBbSVPO97 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8BqC98xSBbSVPO97 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .marker.cross{stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8BqC98xSBbSVPO97 p{margin:0;}#mermaid-svg-8BqC98xSBbSVPO97 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label text{fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label span{color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster-label span p{background-color:transparent;}#mermaid-svg-8BqC98xSBbSVPO97 .label text,#mermaid-svg-8BqC98xSBbSVPO97 span{fill:#333;color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .node rect,#mermaid-svg-8BqC98xSBbSVPO97 .node circle,#mermaid-svg-8BqC98xSBbSVPO97 .node ellipse,#mermaid-svg-8BqC98xSBbSVPO97 .node polygon,#mermaid-svg-8BqC98xSBbSVPO97 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .rough-node .label text,#mermaid-svg-8BqC98xSBbSVPO97 .node .label text,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label,#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8BqC98xSBbSVPO97 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .rough-node .label,#mermaid-svg-8BqC98xSBbSVPO97 .node .label,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label,#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label{text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .node.clickable{cursor:pointer;}#mermaid-svg-8BqC98xSBbSVPO97 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .arrowheadPath{fill:#333333;}#mermaid-svg-8BqC98xSBbSVPO97 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8BqC98xSBbSVPO97 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8BqC98xSBbSVPO97 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster text{fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 .cluster span{color:#333;}#mermaid-svg-8BqC98xSBbSVPO97 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-8BqC98xSBbSVPO97 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8BqC98xSBbSVPO97 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape p,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8BqC98xSBbSVPO97 .icon-shape .label rect,#mermaid-svg-8BqC98xSBbSVPO97 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8BqC98xSBbSVPO97 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8BqC98xSBbSVPO97 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8BqC98xSBbSVPO97 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
FillRgn(hdc,hrgn,hbrush)
NtGdiFillRgn
锁 DC / REGION_LockRgn / BRUSH_ShareLockBrush
IntGdiFillRgn
REGION_LPTODP: 世界→设备变换
prgnClip ∩ prgnRao/prgnVis (RGN_AND)
REGION_bOffsetRgn(+ptlDCOrig)
IntEngInitClipObj / IntEngUpdateClipRegion → XCLIPOBJ
MIX = rop2Fg | (BkMode==OPAQUE ? rop2Fg : R2_NOP)<<8
EBRUSHOBJ_vInit / DC_vUpdateFillBrush → BRUSHOBJ
IntEngPaint(SurfObj, xcoClip, pbo, ptlFillOrigin, mix)
表面挂钩 DrvPaint?
GDIDEVFUNCS.Paint (硬件驱动)
EngPaint: MIX_TO_ROP4 → IntEngBitBlt(无源)
EngBitBlt → BltPatCopy / 纯色 / DIB 引擎
5.4 IntGdiPaintRgn(bitblt.c:1326-1333)--- PaintRgn
c
BOOL FASTCALL IntGdiPaintRgn(PDC pdc, PREGION prgn)
{
return IntGdiFillRgn(pdc, prgn, NULL); /* 无显式画刷 → 用 DC 画刷 */
}
作用 :用 DC 当前画刷(pdc->eboFill,由 SelectObject 选择)填充区域。整个实现就是一行转发------pbrFill = NULL 触发 5.3 的"DC 画刷分支"。该函数不是系统调用入口,由 NtGdiPaintRgn(在 win32k.sys 服务表中映射到本函数)使用。
5.5 IntPatBlt 总结
IntPatBlt 已在 4.7.2 详述。它是 PatBlt / PolyPatBlt / SetPixel 三个入口共用的"无源矩形填充"核心:矩形变换 → 画刷原点 → DC_vPrepareDCsForBlit → IntEngBitBlt(无源)。注意它不使用 IntGdiFillRgn 的"区域合成 + XCLIPOBJ"路线,而是直接利用 DC 的合成裁剪 pdc->co(矩形裁剪更轻量)。
5.6 GreStretchBltMask 小结
见 4.6.2,要点是:负尺寸先行修正 → 3 DC 按序锁定 → 掩码尺寸校验 → 带 COLORADJUSTMENT 的 IntEngStretchBlt。它是本文件中最复杂的内部函数(260 行),承担了 StretchBlt 的全部参数装配。
5.7 两种裁剪路线对比:pdc->co 与 XCLIPOBJ
bitblt.c 内部存在两条裁剪路线,选择标准是"绘制形状是矩形还是任意区域":
| 路线 | 裁剪来源 | 使用方 | 特点 |
|---|---|---|---|
| 矩形路线 | (CLIPOBJ *)&pdc->co(DC 合成裁剪对象,含可见区/RAO/API 裁剪) |
AlphaBlend、TransparentBlt、MaskBlt、GreStretchBltMask、IntPatBlt | 零额外开销,直接复用 DC 状态机已合成的裁剪;矩形外由引擎按 iDComplexity 分块 |
| 区域路线 | 临时 XCLIPOBJ xcoClip(IntEngInitClipObj + IntEngUpdateClipRegion 现场构造) |
IntGdiFillRgn、IntGdiBitBltRgn | 需要把任意 REGION 转换为 CLIPOBJ(nCount 矩形 + rcBound);绘制矩形取 rdh.rcBound |
为什么区域必须单独合成 :pdc->co 是"矩形裁剪"(CLIPOBJ 语义),而区域(REGION)是"矩形集合",两者对象类型不同。区域类函数把 REGION 展开为 XCLIPOBJ(其 iDComplexity 在 IntEngUpdateClipRegion 中按矩形数量判定为 DC_RECT/DC_COMPLEX),引擎据此决定单块还是分块绘制。
裁剪合成顺序(区域路线独有):
用户区域(逻辑坐标)
→ REGION_LPTODP:世界→设备变换(REGION_bXformRgn)
→ ∩ prgnRao/prgnVis(RGN_AND):系统可见性
→ REGION_bOffsetRgn(+ptlDCOrig):DC 原点对齐
→ IntEngUpdateClipRegion:REGION → XCLIPOBJ
而矩形路线把"用户矩形 → IntLPtoDP → +ptlDCOrig"后直接丢给 pdc->co(DC 原点已在合成裁剪时处理),少一次区域运算,因此矩形绘制(PatBlt 等)比区域绘制(FillRgn 等)更轻。
6. 内存位图暂存机制详解
6.1 问题域
位块传输有三类"表面访问风险":
- 设备管理表面(device-managed) :非
STYPE_BITMAP的表面(如显示表面、驱动管理表面)可能没有可直接读写的pvScan0/lDelta,或需要驱动协作才能访问像素; - 源/目标重叠 :源与目标是同一表面(同 DC 的 BitBlt)且区域重叠时,直接拷贝会产生"串扰"(拷贝过的像素被再次当作源);
- 跨设备拷贝:源与目标属于不同 PDEV(不同显示设备/打印机),驱动回调可能不支持异构拷贝。
bitblt.c 层面解决这些问题的方式是分层配合:
- 锁序保证 (ntgdi 层):
DC_vPrepareDCsForBlit按hsemDevLock排序加锁,杜绝死锁;MouseSafetyOnDrawStart/End保护硬件光标; - 临时表面暂存(引擎层):真正把"不能直接访问的表面"转成"可访问的 DIB 位图"。
6.2 引擎层暂存:IntEngEnter / IntEngLeave
引擎提供通用进入/离开机制(eng/engmisc.c:14-159):
IntEngEnter(EnterLeave, psoDest, DestRect, ReadOnly, Translate, &ppsoOutput)
├─ 若 psoDest 是设备管理表面(iType != STYPE_BITMAP 且无 pvScan0/lDelta):
│ ├─ 要求驱动支持 DrvCopyBits(HOOK_COPYBITS),否则失败
│ ├─ EngCreateBitmap(宽,高,格式,TOPDOWN) 分配临时 DIB
│ ├─ EngLockSurface 锁定 → ppsoOutput = 临时表面
│ ├─ GDIDEVFUNCS(psoDest).CopyBits(临时表面 ← 设备表面, ClippedDestRect)
│ │ ------ 把设备表面目标区域"暂存"进内存位图
│ └─ Translate = -DestRect.left/top(坐标平移:临时表面从 (0,0) 起算)
└─ 否则:ppsoOutput = psoDest,Translate = 0(普通 DIB 直接操作)
IntEngLeave(EnterLeave)
├─ 若 OutputObj != DestObj(曾暂存)且非 ReadOnly:
│ └─ GDIDEVFUNCS(DestObj).CopyBits(设备表面 ← 临时表面) /* 结果拷回 */
└─ 解锁/删除临时表面
具体实例------IntEngMaskBlt(eng/bitblt.c:1167-1223):当目标表面是设备管理表面时:
c
/* Allocate a temporary surface */
psurfTemp = SURFACE_AllocSurface(STYPE_BITMAP, rcTemp.right, rcTemp.bottom,
psoDest->iBitmapFormat, 0, 0, 0, NULL);
/* Copy the current target surface bits to the temp surface */
ret = EngCopyBits(&psurfTemp->SurfObj, psoDest, NULL, NULL, &rcTemp, (PPOINTL)&rcDest);
/* Do the operation on the temp surface */
ret = EngMaskBitBlt(&psurfTemp->SurfObj, psoMask, NULL, pxloDest, pxloSource, ...);
/* Copy the result back to the dest surface */
ret = EngCopyBits(psoDest, &psurfTemp->SurfObj, pco, NULL, &rcDest, (PPOINTL)&rcTemp);
GDIOBJ_vDeleteObject(&psurfTemp->BaseObject);
即"拷出 → 内存操作 → 拷回 "三步,保证设备表面只发生可驱动的批量拷贝,逐像素逻辑全部在内存 DIB 上进行。IntEngStretchBlt(eng/stretchblt.c,见《分析_27》)对设备管理源 表面也做同样的暂存(先 EngCopyBits 拷到临时位图,再以临时位图为源拉伸),从而避免设备源表面被反复 DrvCopyBits 逐行读取。
6.3 重叠问题的处理
同表面重叠时,方向选择由引擎的裁剪枚举决定(eng/bitblt.c:594-612):
c
case DC_COMPLEX:
if (OutputObj == InputObj) /* 源 == 目标表面 */
{
if (OutputRect.top < InputPoint.y) /* 目标在源上方 */
Direction = OutputRect.left < InputPoint.x ? CD_RIGHTDOWN : CD_LEFTDOWN;
else
Direction = OutputRect.left < InputPoint.x ? CD_RIGHTUP : CD_LEFTUP;
}
else
Direction = CD_ANY;
CLIPOBJ_cEnumStart(pco, FALSE, CT_RECTANGLES, Direction, 0);
CLIPOBJ_cEnumStart 的 Direction 参数(CD_LEFTUP/CD_RIGHTUP/CD_LEFTDOWN/CD_RIGHTDOWN/CD_ANY)指示枚举器从远离源区的角落开始分块 :例如目标矩形在源矩形的右下方时,从左上角开始逐块拷贝,保证"刚写入的目标像素"永远不会被后续块当源读取------这就是扫描方向规避重叠 (scissoring + direction)。引擎侧(eng/clip.c 的 CLIPOBJ_bEnum)据此切分矩形序列。
6.4 源/目标不同 DC 时的处理
当源、目标是不同 DC (可能不同表面、不同设备)时,ntgdi 层不暂存------直接把两个表面的 SurfObj 交给引擎;引擎只在设备管理表面 或跨设备 (psoSrc->hdev != psoTrg->hdev,eng/bitblt.c:763)时介入。异 DC 的"暂存"需求实际上由 EXLATEOBJ(格式转换)与 IntEngEnter(设备→内存)共同消化;若跨设备且两者都要求 HOOK_BITBLT,会触发 DPRINT1("Need to copy to standard bitmap format!") 断言(eng/bitblt.c:765-766)------当前实现假定目标可回退到软件路径。
6.5 暂存触发条件汇总
综合 ntgdi 与引擎两层,一次 blit 是否发生"内存位图暂存"取决于:
| 条件 | 处理 | 所在层 |
|---|---|---|
| 源/目标为普通 DIB(STYPE_BITMAP,有 pvScan0/lDelta) | 直接操作,零拷贝 | 引擎 |
| 目标为设备管理表面(无 pvScan0) | IntEngEnter/IntEngMaskBlt:拷出→内存操作→拷回 |
引擎 |
| 源为设备管理表面且需逐像素读 | 先 EngCopyBits 到临时 DIB 再操作 |
引擎 |
| 跨 PDEV 且源/目标都挂钩 DrvBitBlt | 断言/降级(当前不支持) | 引擎 |
| 源与目标同表面且重叠 | 方向化裁剪枚举(CD_LEFTUP 等)规避,不暂存 | 引擎 |
| 源/目标格式不同 | EXLATEOBJ 逐像素转换,不暂存 | ntgdi |
| 多 DC 并发 | hsemDevLock 排序加锁 + 鼠标安全,不暂存 |
ntgdi |
结论 :bitblt.c(ntgdi)层从不主动分配临时位图;所有"内存位图暂存"都发生在引擎层(eng/),ntgdi 只负责提供正确的表面、裁剪、颜色与 ROP 参数,以及保证多 DC 访问的锁序。这也是"薄壳"架构的直接推论------把最复杂的表面管理问题留在持有 SURFOBJ 语义的引擎层解决。
7. 区域填充完整链路
区域类五个 API(FillRgn / PaintRgn / FrameRgn / InvertRgn)共享同一条从"区域"到"像素"的链路:
FillRgn(hdc, hrgn, hbrush)
└─ NtGdiFillRgn(bitblt.c:1335)
├─ DC_LockDc → REGION_LockRgn → BRUSH_ShareLockBrush
└─ IntGdiFillRgn(bitblt.c:1227)★ 核心
├─ REGION_LPTODP(世界→设备)
├─ ∩ prgnRao/prgnVis + REGION_bOffsetRgn(ptlDCOrig)
├─ IntEngInitClipObj/IntEngUpdateClipRegion → XCLIPOBJ
├─ MIX = rop2Fg | (BkMode==OPAQUE ? rop2Fg : R2_NOP) << 8
├─ EBRUSHOBJ_vInit(pbrFill) 或 DC_vUpdateFillBrush
└─ IntEngPaint(pso, &xcoClip, pbo, &ptlFillOrigin, mix)
├─ 挂钩 DrvPaint → 驱动
└─ EngPaint: MIX_TO_ROP4 → IntEngBitBlt(pso, NULL, NULL, pco, ...)
└─ EngBitBlt: clippingType 分派
├─ DC_TRIVIAL → 一次 BltRectFunc
├─ DC_RECT → 与 rclBounds 求交后一次 BltRectFunc
└─ DC_COMPLEX → 方向化枚举逐块绘制
PaintRgn(hdc, hrgn)
└─ NtGdiPaintRgn → IntGdiPaintRgn(pdc, prgn)
└─ IntGdiFillRgn(pdc, prgn, NULL) /* 画刷 = DC 当前画刷 */
FrameRgn(hdc, hrgn, hbrush, xWidth, yHeight)
└─ NtGdiFrameRgn
├─ GreCreateFrameRgn(hrgn, xWidth, yHeight) /* 生成边框区域 */
├─ NtGdiFillRgn(hdc, hrgnFrame, hbrush) /* 复用填充管线 */
└─ GreDeleteObject(hrgnFrame)
InvertRgn(hdc, hrgn)
└─ NtGdiInvertRgn
└─ IntGdiBitBltRgn(pdc, prgn, NULL, NULL, ROP4_DSTINVERT)
└─(裁剪合成范式)→ IntEngBitBlt(无源, rop4=DSTINVERT)
└─ EngBitBlt → 逐像素 DIB_DoRop(目标取反)
三个分支的差别只在"入口装配":FillRgn 带显式画刷、PaintRgn 用 DC 画刷、FrameRgn 先造边框区域、InvertRgn 用 DSTINVERT 的 ROP4 且无画刷(pbo=NULL 时引擎把图案当 0 处理,DSTINVERT 不读图案)。绘制内核(IntGdiFillRgn 的裁剪合成 + MIX/ROP 驱动)完全一致,这正是"一切皆 BitBlt"设计的体现。
7.1 边界情况与语义对齐
区域填充涉及的边界情况(行为均以 Windows 为准):
| 场景 | 行为 | 原因 |
|---|---|---|
| 空区域(零矩形) | REGION_LPTODP 后 ∩ prgnVis 得空,IntEngUpdateClipRegion 装入空矩形集,引擎判 OutputRect 退化后直接返回 TRUE |
空区域是合法 GDI 对象,填充是空操作 |
| 区域完全在可见区外 | 求交后为空,同上返回 TRUE | 裁剪合成自然淘汰 |
| 信息 DC / 无表面 DC | NtGdiFillRgn/IntGdiFillRgn 在锁表面前短路返回 TRUE |
Windows 语义 |
| 画刷为空画刷(BR_IS_NULL) | IntPatBlt 返回 TRUE;FillRgn 路径经 EBRUSHOBJ 实现后同样无像素写出 |
空画刷 = 不画 |
| 世界变换含缩放/旋转 | REGION_bXformRgn 变换区域路径,ptlFillOrigin 画刷原点随 DC 平移 |
区域变换而非矩形变换 |
| TRANSPARENT 背景模式 | MIX 高字节 = R2_NOP,区域外不覆盖 | SetBkMode 语义 |
| OPAQUE 背景模式 | MIX 高字节 = rop2Fg,区域外被前景色覆盖 | 同上 |
一个容易忽略的点 :IntGdiFillRgn 中 ∩ prgnRao/prgnVis 与 REGION_bOffsetRgn 的先后顺序不能颠倒------RAO/可见区是不含 DC 原点的屏幕坐标,用户区域经 LPTODP 后也是屏幕坐标,两者求交后再统一平移 DC 原点;若先平移再求交,会把两个坐标系混在一起导致填充位置偏移。
8. Get/SetPixel 的颜色转换(EXLATEOBJ 应用)
8.1 TranslateCOLORREF:入方向转换
NtGdiSetPixel 调用 TranslateCOLORREF(pdc, crColor)(dcutil.c:882-962),把四种 COLORREF 编码统一翻译为目标表面颜色空间的索引/数值:
| COLORREF 类型 | 判定(高位字节) | 处理 |
|---|---|---|
RGB(r,g,b) |
0x00------ |
直接作为 RGB 参与转换 |
PALETTEINDEX(i) |
0x01------ |
用 DC 调色板 PALETTE_ulGetRGBColorFromIndex 取 RGB |
PALETTERGB(r,g,b) |
0x02------ |
非默认调色板时先 PALETTE_ulGetNearestIndex 取最近索引再转 RGB |
DIBINDEX(i) |
0x10FF---- |
表面调色板是索引型时低 8 位直接作索引返回;否则按 RGB 处理 |
| 其他 | --- | DPRINT 警告,按 0 处理 |
统一得到 RGB 后:
c
EXLATEOBJ_vInitialize(&exlo, &gpalRGB, psurfDC->ppal, CLR_INVALID, CLR_INVALID, CLR_INVALID);
ulColor = XLATEOBJ_iXlate(&exlo.xlo, crColor); /* gpalRGB → 目标表面调色板 */
gpalRGB 是系统内建的 32BPP RGB 调色板;XLATEOBJ_iXlate 对 8BPP 调色板表面做"最近色查表",对 16/24/32BPP 表面做位深裁剪(掩码)。无表面时用 psurfDefaultBitmap 兜底。
8.2 回读转换:出方向转换
NtGdiSetPixel 的返回值与 NtGdiGetPixel 的读取都要做反向转换(目标表面 → RGB),两者共用同一构造:
c
EXLATEOBJ_vInitialize(&exlo, psurf->ppal, &gpalRGB, /* 表面调色板 → RGB */
CLR_INVALID, CLR_INVALID, CLR_INVALID);
crColor = XLATEOBJ_iXlate(&exlo.xlo, iSolidColor);
三个 CLR_INVALID 参数分别对应 crBackgroundClr / crForegroundClr / crForegroundClr,置 CLR_INVALID 表示不做背景/前景色替换 (单色表面转 RGB 时保留原始 0/1 映射,见 xlateobj.c 的 EXLATEOBJ_vInitialize),保证 GetPixel 读到的是像素真实值而非调色板替换后的值。
8.3 为什么 SetPixel 用文本画刷
pdc->eboText 由 DC_vInitDc 时以 pbrDefaultBrush 初始化(dclife.c:292),其 EBRUSHOBJ 恒为纯色画刷。SetPixel 通过 EBRUSHOBJ_iSetSolidColor(brush.h:185-190)直接改 BrushObject.iSolidColor 实现"换色",避免走 SelectObject 的完整画刷替换流程 ;绘制后立即恢复旧色,对 DC 状态无副作用(保存/恢复 ulDirty_ 防止 DIRTY_TEXT 标志泄漏)。这一技巧把 SetPixel 的代价压缩到"一次 1×1 的 PatBlt"。
9. 调用链总览
#mermaid-svg-YvCTomyrn7Yrs0Fv{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-YvCTomyrn7Yrs0Fv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YvCTomyrn7Yrs0Fv .error-icon{fill:#552222;}#mermaid-svg-YvCTomyrn7Yrs0Fv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YvCTomyrn7Yrs0Fv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .marker.cross{stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YvCTomyrn7Yrs0Fv p{margin:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label text{fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label span{color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster-label span p{background-color:transparent;}#mermaid-svg-YvCTomyrn7Yrs0Fv .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv span{fill:#333;color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node rect,#mermaid-svg-YvCTomyrn7Yrs0Fv .node circle,#mermaid-svg-YvCTomyrn7Yrs0Fv .node ellipse,#mermaid-svg-YvCTomyrn7Yrs0Fv .node polygon,#mermaid-svg-YvCTomyrn7Yrs0Fv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .rough-node .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label text,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label{text-anchor:middle;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .rough-node .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label,#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label{text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node.clickable{cursor:pointer;}#mermaid-svg-YvCTomyrn7Yrs0Fv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .arrowheadPath{fill:#333333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster text{fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv .cluster span{color:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv 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-YvCTomyrn7Yrs0Fv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YvCTomyrn7Yrs0Fv rect.text{fill:none;stroke-width:0;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape p,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YvCTomyrn7Yrs0Fv .icon-shape .label rect,#mermaid-svg-YvCTomyrn7Yrs0Fv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YvCTomyrn7Yrs0Fv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YvCTomyrn7Yrs0Fv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YvCTomyrn7Yrs0Fv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 引擎 gdi/eng/
内核 ntgdi/bitblt.c
用户态 gdi32.dll
BitBlt
StretchBlt
TransparentBlt
AlphaBlend
MaskBlt
PatBlt
PolyPatBlt
FillRgn/PaintRgn/FrameRgn/InvertRgn
SetPixel/GetPixel
NtGdiBitBlt
NtGdiStretchBlt(CAPTUREBLT) 或 NtGdiMaskBlt
NtGdiStretchBlt
GreStretchBltMask
NtGdiMaskBlt
IntEngBitBlt
NtGdiTransparentBlt
IntEngTransparentBlt
NtGdiAlphaBlend
IntEngAlphaBlend
NtGdiPatBlt
IntPatBlt
NtGdiPolyPatBlt
IntGdiPolyPatBlt
NtGdiFillRgn
IntGdiFillRgn
IntEngPaint
NtGdiInvertRgn
IntGdiBitBltRgn
NtGdiSetPixel
NtGdiGetPixel
EngCopyBits
IntEngStretchBlt
EngBitBlt/BltMask/BlitRectFunc
IntEngTransparentBlt
IntEngAlphaBlend
EngPaint → IntEngBitBlt
IntEngStretchBlt
EngCopyBits → DrvCopyBits
10. 与 eng/bitblt.c 及《分析_4/17/26/27》的关联
10.1 与 eng/bitblt.c 的分工
| 阶段 | ntgdi/bitblt.c | eng/bitblt.c |
|---|---|---|
| 句柄/对象 | 锁 DC、锁定掩码位图(SURFACE_ShareLockSurface) | 不涉及 |
| 坐标 | IntLPtoDP + ptlDCOrig + 负尺寸补偿 | RECTL_vMakeWellOrdered、rclSrc 从目标矩形推导、bLeftToRight/bTopToBottom 还原翻转 |
| 裁剪 | 直接用 pdc->co 或构造 XCLIPOBJ |
按 pco->iDComplexity 分派 DC_TRIVIAL/DC_RECT/DC_COMPLEX |
| 颜色 | EXLATEOBJ 构造/清理 | XLATEOBJ_iXlate 逐像素 |
| ROP | ROP4 装配(MAKEROP4/WIN32_ROP4_TO_ENG_ROP4) | ROP4_USES_* 静态分析、Rop4 == ROP4_NOOP 短路 |
| 画刷 | EBRUSHOBJ_vInit/vInitFromDC/vSetSolidColor | BRUSHOBJ_psoPattern/psoMask 取图案/掩码面 |
| 执行 | --- | BltMask(逐像素掩码 ROP)、BltPatCopy(纯色填充)、CallDibBitBlt(DIB 引擎)、IntEngEnter/Leave(暂存) |
IntEngBitBlt(eng/bitblt.c:656-815)是衔接点:先裁剪目标矩形到表面尺寸与 pco->rclBounds,再按 psurfTrg->flags & HOOK_BITBLT 决定调 GDIDEVFUNCS(psoTrg).BitBlt(驱动)还是 EngBitBlt(软件)。本文件的 ROP4 与坐标约定必须与引擎完全一致,否则会出现镜像/错位/颜色错误。
10.2 与各分册的对应
| 分册 | 对应内容 |
|---|---|
| 《分析_4》3.1 | BitBlt 引擎管线:EngBitBlt / IntEngBitBlt 双层(本文 10.1) |
| 《分析_17》 | 区域填充:IntEngPaint/EngPaint、EBRUSHOBJ 实现、MIX→ROP4(本文 5.3/5.4) |
| 《分析_26》 | AlphaBlend:IntEngAlphaBlend 三种 Alpha 模式(本文 4.1 的引擎侧) |
| 《分析_27》 | 拉伸与透明:IntEngStretchBlt / IntEngTransparentBlt(本文 4.3/4.6) |
| 《分析_19》 | 颜色转换:EXLATEOBJ/XLATEOBJ(本文 3.5/8) |
| 《分析_24》 | MouseSafetyOnDrawStart/End 鼠标安全(本文 3.4) |
| 《分析_28》 | PlgBlt 桩:EngPlgBlt(本文 4.5) |
| 《分析_30》 | DibLib:USE_DIBLIB 下 IntPatBlt 画刷原点差异(本文 4.7.2) |
| 《分析_31》 | ntgdi 总览:13 入口归属、裁剪合成、DC 状态机(本文开头) |
10.3 设计经验小结
- 薄壳架构:13 个入口平均每个 60 行,几乎全是"校验→锁→坐标→装配→转发→清理",像素逻辑全部下沉到 eng/dib;
- 复用优先:BitBlt→MaskBlt、PatBlt/PolyPatBlt/SetPixel→IntPatBlt、FrameRgn/InvertRgn→FillRgn 式内核,用"特例归约"消灭重复代码;
- 成对纪律 :DC 锁 / 设备锁 / 区域锁 / 画刷引用锁 / EXLATEOBJ / 临时表面,每类资源都必须成对获取释放,
goto标签(done/failed/leave)是保证异常路径正确性的惯用手法; - Windows 语义对齐:信息 DC 返回 TRUE、空操作返回 TRUE、越界 GetPixel 返回 CLR_INVALID、负尺寸翻转------这些行为都刻意与 Windows 保持一致,不能按"直觉"优化。
11. 源码索引
| 文件 | 关键内容 |
|---|---|
| bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) | 13 个 NtGdi* 入口 + IntGdiFillRgn/IntGdiPaintRgn/IntGdiBitBltRgn/IntPatBlt/IntGdiPolyPatBlt/GreStretchBltMask/REGION_LPTODP |
| dclife.c(file:///d:/reactos/win32ss/gdi/ntgdi/dclife.c) | DC_vPrepareDCsForBlit(503)/DC_vFinishBlit(612)、DC_vInitDc、画刷/表面更新 |
| dcobjs.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcobjs.c) | DC_vUpdateFillBrush/LineBrush/TextBrush |
| dc.h(file:///d:/reactos/win32ss/gdi/ntgdi/dc.h) | DC 结构:dclevel.pSurface、ptlDCOrig、prgnVis/prgnRao、eboFill、co |
| dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) | TranslateCOLORREF(882)、IntUpdateBoundsRect(694) |
| eng/bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c) | IntEngBitBlt(656)、EngBitBlt(327)、BltMask、BltPatCopy、IntEngMaskBlt(1104)、gxcoTrivial |
| eng/paint.c(file:///d:/reactos/win32ss/gdi/eng/paint.c) | IntEngPaint(85)/EngPaint(MIX→ROP4) |
| eng/copybits.c(file:///d:/reactos/win32ss/gdi/eng/copybits.c) | IntEngCopyBits(229) |
| eng/xlateobj.c(file:///d:/reactos/win32ss/gdi/eng/xlateobj.c) | EXLATEOBJ_vInitXlateFromDCs(826)/Ex(849)/vInitialize/vCleanup |
| eng/engmisc.c(file:///d:/reactos/win32ss/gdi/eng/engmisc.c) | IntEngEnter(15)/IntEngLeave(162)------内存位图暂存 |
| eng/inteng.h(file:///d:/reactos/win32ss/gdi/eng/inteng.h) | ROP4_USES_* 宏、IntEng* 签名 |
| ntgdi/intgdi.h(file:///d:/reactos/win32ss/gdi/ntgdi/intgdi.h) | WIN32_ROP4_TO_ENG_ROP4、FIXUP_ROP2 |
| ntgdi/region.c(file:///d:/reactos/win32ss/gdi/ntgdi/region.c) | GreCreateFrameRgn(2028)、REGION_LockRgn、IntGdiCombineRgn(2490)、REGION_bOffsetRgn(2710) |
| ntgdi/brush.h(file:///d:/reactos/win32ss/gdi/ntgdi/brush.h) | EBRUSHOBJ_vInit/vInitFromDC/iSetSolidColor 声明 |
| ntgdi/region.h(file:///d:/reactos/win32ss/gdi/ntgdi/region.h) | REGION_LockRgn/UnlockRgn/GetRgnBox/Delete 声明 |
关联模块: eng/bitblt_new.c(DibLib BitBlt,《分析_30》)、eng/alphablend.c / transblt.c / stretchblt.c(《分析_26/27》)、eng/engbrush.c(BRUSHOBJ 实现)、gdi/dib(DIB_DoRop、DIB_Get/PutPixel)
12. 总结
bitblt.c 是 ntgdi 层绘制命令的"装配车间":
- 13 个 NtGdi 入口*覆盖了 14 个 Win32 位块/填充 API(BitBlt 与 MaskBlt 共用 MaskBlt 实现),其中 12 个可用、1 个(PlgBlt)为 FIXME 桩;
- 每个入口的核心工作可归纳为四步:校验与锁定 (参数、句柄、多 DC 按序加锁)→ 坐标变换 (IntLPtoDP + ptlDCOrig + 负尺寸/翻转处理 + 累积边界)→ 参数装配 (EXLATEOBJ、ROP4、BRUSHOBJ、CLIPOBJ)→ 转发引擎(IntEngBitBlt / IntEngStretchBlt / IntEngAlphaBlend / IntEngTransparentBlt / IntEngPaint / EngCopyBits);
- 区域类操作 (FillRgn/PaintRgn/FrameRgn/InvertRgn)通过
IntGdiFillRgn的裁剪合成范式(LPTODP → ∩RAO/Vis → DC 原点 → XCLIPOBJ)与 MIX/ROP 驱动,把任意形状区域归约为无源 BitBlt; - Get/SetPixel 通过"1×1 归约"(PatBlt / EngCopyBits)+ EXLATEOBJ 双向颜色转换,把单像素操作纳入同一管线;
- 表面安全(设备管理表面、重叠、跨设备)由
DC_vPrepareDCsForBlit的锁序/鼠标安全与引擎层IntEngEnter/Leave及临时位图暂存共同保证。
理解 bitblt.c 的钥匙是"归约 "二字:BitBlt 归约到 MaskBlt,填充归约到 Paint,像素归约到位块,位块归约到引擎的 ROP 求值。沿着这条归约链,就能把任意 GDI 绘制请求追溯到最终的 DIB_DoRop 像素运算。
本文档基于 ReactOS 源代码
win32ss/gdi/ntgdi/bitblt.c(1655 行,2026-08 版本)及关联模块(eng/bitblt.c、dclife.c、dc.h、paint.c、xlateobj.c、engmisc.c、dcutil.c、region.c 等)分析。文中行号均指当前源码。关联分册:《分析_4/17/19/24/26/27/28/30/31》。