ReactOS GDI DIB 边界计算 Bug 修复记录
概述
本文档记录了在 ReactOS GDI 子系统中发现并修复的多个与设备无关位图(DIB)边界计算相关的 Bug。这些问题导致 gdi32:dib 测试用例中的边界断言失败,具体表现为裁剪区域边界错误、RTL(从右到左)布局边界错误、负尺寸 StretchDIBits 边界错误以及 DIBSECTION 位字段错误。
问题 1:DIBSECTION 位字段在 BI_RGB 格式下不为 0
文件
d:\\reactos\\win32ss\\gdi\\ntgdi\\bitmaps.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitmaps.c)
问题描述
测试用例 dib.c:3179-3181 断言失败:
got 0x00ff0000, 0x0000ff00, 0x000000ff
当 DIBSECTION 使用 BI_RGB 压缩格式时,dsBitfields 数组应全部为 0。但原始代码无条件地填充了位字段值,导致断言失败。
修复内容
在 EngCreateDIBSECTION 函数中,仅在 BI_BITFIELDS 压缩格式时填充 dsBitfields 字段:
c
if (pds->dsBmih.biCompression == BI_BITFIELDS)
{
pds->dsBitfields[0] = psurf->ppal->RedMask;
pds->dsBitfields[1] = psurf->ppal->GreenMask;
pds->dsBitfields[2] = psurf->ppal->BlueMask;
}
else
{
pds->dsBitfields[0] = 0;
pds->dsBitfields[1] = 0;
pds->dsBitfields[2] = 0;
}
改动要点
| 变更 | 说明 |
|---|---|
| 条件判断 | 添加 BI_BITFIELDS 格式检查 |
| 非 BITFIELDS 格式 | 将位字段清零 |
问题 2:裁剪边界计算错误
文件
d:\\reactos\\win32ss\\gdi\\ntgdi\\dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c)
问题描述
测试用例断言失败:
expected (10,99)-(300,200) got (10,99)-(290,190)
右边界和下边界各少 10 像素。
根因分析
原始代码错误地使用区域宽度/高度来裁剪边界:
c
rc.right = min( rc.right, rcRgn.right - rcRgn.left );
rc.bottom = min( rc.bottom, rcRgn.bottom - rcRgn.top );
rcRgn.right - rcRgn.left 计算的是区域宽度,但正确做法应该直接使用区域边界值。
修复内容
修改边界裁剪逻辑,直接使用区域边界值:
c
rc.right = min( rc.right, rcRgn.right );
rc.bottom = min( rc.bottom, rcRgn.bottom );
问题 3:RTL 布局边界错误(右边界差 1 像素)
文件
d:\\reactos\\win32ss\\gdi\\ntgdi\\dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c)
问题描述
测试用例断言失败:
expected bounds (300,10)-(9,260) got (300,10)-(10,260)
在 RTL(从右到左)布局模式下,右边界差 1 像素。
根因分析
- ReactOS 的 RTL 实现缺陷 :
DC_vSetLayout没有实际镜像 x 轴坐标(viewport_extent_x未设为 -1),坐标映射是恒等映射。 - 排他性矩形语义 :Win32 矩形使用排他性边界(exclusive bounds),即
right表示最后一个像素 + 1。交换 left/right 后,原始 left(已成为 right)需要减 1 才能保持排他性语义。
修复内容
在 NtGdiGetBoundsRect 函数的 RTL 交换代码后添加边界调整:
c
if (pdc->pdcattr->dwLayout & LAYOUT_RTL)
{
LONG temp = rc.left;
rc.left = rc.right;
rc.right = temp;
rc.right--;
}
改动要点
| 变更 | 说明 |
|---|---|
| 坐标交换 | 交换 left 和 right 以保持 RTL 方向 |
| 边界调整 | rc.right-- 处理排他性矩形语义 |
问题 4:LPtoDP 后反转矩形导致边界计算错误
文件
d:\\reactos\\win32ss\\gdi\\ntgdi\\bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c)
d:\\reactos\\win32ss\\gdi\\ntgdi\\fillshap.c(file:///d:/reactos/win32ss/gdi/ntgdi/fillshap.c)
问题描述
在 RTL 模式下,IntLPtoDP 转换后矩形坐标会反转(left > right),但 RECTL_bUnionRect 不会规范化矩形,导致边界计算错误。
修复内容
在以下函数的 IntLPtoDP 转换后添加矩形规范化:
修复 1:IntPatBlt(bitblt.c)
c
IntLPtoDP(pdc, (LPPOINT)&DestRect, 2);
DestRect.left += pdc->ptlDCOrig.x;
DestRect.top += pdc->ptlDCOrig.y;
DestRect.right += pdc->ptlDCOrig.x;
DestRect.bottom += pdc->ptlDCOrig.y;
RECTL_vMakeWellOrdered(&DestRect);
if (pdc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
IntUpdateBoundsRect(pdc, &DestRect);
}
修复 2:Rectangle(fillshap.c)
c
IntLPtoDP(dc, (LPPOINT)&DestRect, 2);
DestRect.left += dc->ptlDCOrig.x;
DestRect.right += dc->ptlDCOrig.x;
DestRect.top += dc->ptlDCOrig.y;
DestRect.bottom += dc->ptlDCOrig.y;
RECTL_vMakeWellOrdered(&DestRect);
if (dc->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
IntUpdateBoundsRect(dc, &DestRect);
}
修复 3:GradientFill(fillshap.c)
c
IntLPtoDP(pdc, (LPPOINT)&rclExtent, 2);
rclExtent.left += pdc->ptlDCOrig.x;
rclExtent.right += pdc->ptlDCOrig.x;
rclExtent.top += pdc->ptlDCOrig.y;
rclExtent.bottom += pdc->ptlDCOrig.y;
RECTL_vMakeWellOrdered(&rclExtent);
if (RECTL_bIsEmptyRect(&rclExtent))
改动要点
| 位置 | 函数 | 说明 |
|---|---|---|
| bitblt.c | IntPatBlt |
LPtoDP 后规范化矩形 |
| fillshap.c | Rectangle | LPtoDP 后规范化矩形 |
| fillshap.c | GradientFill | LPtoDP 后规范化矩形 |
问题 5:负尺寸 StretchDIBits 边界错误
文件
d:\\reactos\\win32ss\\gdi\\ntgdi\\dibobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/dibobj.c)
d:\\reactos\\win32ss\\gdi\\ntgdi\\bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c)
问题描述
测试用例断言失败:
expected (10,8)-(60,104) got (10,10)-(43,104)
使用负尺寸参数调用 StretchDIBits 时边界计算错误。
根因分析
dibobj.c中的负尺寸处理 :直接使用rcDest.left + Width计算右边界,当 Width 为负数时产生反转矩形。bitblt.c中的 MaskBlt 路径 :当cxSrc == cxDst && cySrc == cyDst时,SRCCOPY 走NtGdiMaskBlt而非NtGdiStretchBlt。NtGdiMaskBlt创建反转矩形后,RECTL_bIsEmptyRect返回 TRUE,导致IntUpdateBoundsRect跳过边界更新。
修复内容
修复 1:dibobj.c - 负尺寸矩形规范化
c
if (WidthDest < 0)
{
DestRect.left = XOriginDest + WidthDest;
DestRect.right = XOriginDest;
}
else
{
DestRect.left = XOriginDest;
DestRect.right = XOriginDest + WidthDest;
}
if (HeightDest < 0)
{
DestRect.top = YOriginDest + HeightDest;
DestRect.bottom = YOriginDest;
}
else
{
DestRect.top = YOriginDest;
DestRect.bottom = YOriginDest + HeightDest;
}
修复 2:bitblt.c - NtGdiMaskBlt 边界规范化
c
DestRect.left = nXDest;
DestRect.top = nYDest;
DestRect.right = nXDest + nWidth;
DestRect.bottom = nYDest + nHeight;
IntLPtoDP(DCDest, (LPPOINT)&DestRect, 2);
DestRect.left += DCDest->ptlDCOrig.x;
DestRect.top += DCDest->ptlDCOrig.y;
DestRect.right += DCDest->ptlDCOrig.x;
DestRect.bottom += DCDest->ptlDCOrig.y;
if (DestRect.left > DestRect.right)
{
LONG temp = DestRect.left;
DestRect.left = DestRect.right;
DestRect.right = temp;
}
if (DestRect.top > DestRect.bottom)
{
LONG temp = DestRect.top;
DestRect.top = DestRect.bottom;
DestRect.bottom = temp;
}
if (DCDest->fs & (DC_ACCUM_APP|DC_ACCUM_WMGR))
{
IntUpdateBoundsRect(DCDest, &DestRect);
}
问题 6:rosautotest journal 目录创建失败
文件
d:\\reactos\\modules\\rostests\\rosautotest\\CJournaledTestList.cpp(file:///d:/reactos/modules/rostests/rosautotest/CJournaledTestList.cpp)
问题描述
rosautotest 因 journal 目录不存在而崩溃:
CreateFileW failed (Error 3: PATH_NOT_FOUND)
修复内容
在目录创建失败时禁用 journal 功能:
cpp
if (!CreateDirectory(journalPath.c_str(), NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
{
DPRINT("Failed to create journal directory %s, disabling journal\n", journalPath.c_str());
return;
}
编译验证
powershell
# 设置环境变量
[Environment]::SetEnvironmentVariable("M4", "C:\RosBE\bin\m4.exe", "Process")
[Environment]::SetEnvironmentVariable("BISON_PKGDATADIR", "C:\RosBE\share\bison", "Process")
$env:PATH = "C:\RosBE\bin;" + $env:PATH
# 编译 win32k(问题 2-5)
cd d:\reactos\output-MinGW-i386
C:\RosBE\bin\ninja.exe win32k
# 编译 rosautotest(问题 6)
C:\RosBE\bin\ninja.exe rosautotest
# 生成 bootcd
C:\RosBE\bin\ninja.exe bootcd
测试结果
修复前
| 错误类型 | 数量 |
|---|---|
| dsBitfields 断言失败 | 3 |
| 裁剪边界错误 | 1 |
| RTL 边界错误 | 1 |
| stretchdibits 边界错误 | 1 |
| 总计边界错误 | 6 |
修复后
| 错误类型 | 数量 |
|---|---|
| expected bounds 错误 | 0 |
所有 GDI DIB 边界断言全部通过。
涉及的文件列表
| 文件 | 修复内容 |
|---|---|
| bitmaps.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitmaps.c) | BI_RGB 格式位字段清零 |
| dcutil.c(file:///d:/reactos/win32ss/gdi/ntgdi/dcutil.c) | 裁剪边界计算修复、RTL 边界调整 |
| bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) | IntPatBlt 矩形规范化、MaskBlt 边界规范化 |
| fillshap.c(file:///d:/reactos/win32ss/gdi/ntgdi/fillshap.c) | Rectangle、GradientFill 矩形规范化 |
| dibobj.c(file:///d:/reactos/win32ss/gdi/ntgdi/dibobj.c) | 负尺寸矩形规范化 |
| CJournaledTestList.cpp(file:///d:/reactos/modules/rostests/rosautotest/CJournaledTestList.cpp) | journal 目录创建失败处理 |
技术要点总结
- 矩形语义 :Win32 矩形使用排他性边界(exclusive bounds),
right = last_pixel_x + 1,bottom = last_pixel_y + 1。 - RTL 布局 :ReactOS 的 RTL 实现仅设置
dwLayout标志,未实际镜像坐标(viewport_extent_x 未设为 -1),需通过交换坐标模拟。 - 负尺寸处理:负宽度/高度表示从右向左或从下向上绘制,需规范化矩形后再进行边界计算。
- 矩形规范化 :使用
RECTL_vMakeWellOrdered确保left <= right且top <= bottom,避免RECTL_bIsEmptyRect误判。