ReactOS 图形系统分析(19):颜色转换对象 --- XLATEOBJ(xlateobj.c/h)
1. 概述
xlateobj.c / xlateobj.h 位于 win32ss/gdi/eng/,实现 XLATEOBJ(颜色转换对象) ------Windows GDI/DDI 中描述"源表面颜色 → 目标表面颜色"转换的核心对象。它解决一个本质问题:不同位深/格式/调色板的表面之间复制像素时,每个像素值如何映射。
与《分析_16/17/18》不同,xlateobj 不是预留服务层,而是引擎最核心、被高频调用的模块 :DIB 引擎每个位深(1/4/8/16/24/32bpp)的 BitBlt、拉伸、Alpha 混合、文本字形上屏、画刷填充,全部经过 XLATEOBJ_iXlate 做像素级颜色转换。
┌─────────────────────────────────────────────────────────────┐
│ 消费方 │
│ DIB 引擎 (dib*.c / BitBlt 内联 XLATEOBJ_iXlate) │
│ eng/bitblt.c (画刷/前景背景色) dib/alphablend.c │
│ dib/stretchblt.c ntgdi(bitblt/fillshap/freetype/palette) │
├─────────────────────────────────────────────────────────────┤
│ gdi/eng/xlateobj.c(本分析对象) │
│ 核心: EXLATEOBJ 结构 + pfnXlate 函数指针分发 │
│ ├── ~25 个专用转换函数 (FN_XLATE) │
│ ├── EXLATEOBJ_vInitialize: 按源/目标调色板类型选择函数 │
│ └── DDI 公开: XLATEOBJ_iXlate / cGetPalette / │
│ hGetColorTransform / piVector │
├─────────────────────────────────────────────────────────────┤
│ ntgdi/palette.c (调色板对象/位掩码) │
│ 提供 gpalRGB/gpalRGB555/gpalRGB565/gppalMono 等全局调色板 │
└─────────────────────────────────────────────────────────────┘
2. 核心数据结构
2.1 XLATEOBJ --- DDI 公开部分(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1254-L1261))
c
typedef struct _XLATEOBJ {
ULONG iUniq; // 唯一编号(每次初始化递增,用于缓存命中判断)
FLONG flXlate; // XO_* 标志
USHORT iSrcType; // 源调色板类型
USHORT iDstType; // 目标调色板类型
ULONG cEntries; // 转换表项数
ULONG *pulXlate; // 转换表指针(可指向内嵌数组或堆分配)
} XLATEOBJ;
2.2 EXLATEOBJ --- 引擎扩展(xlateobj.h(file:///d:/reactos/win32ss/gdi/eng/xlateobj.h#L19-L51))
c
typedef struct _EXLATEOBJ
{
XLATEOBJ xlo; // 公开头
PFN_XLATE pfnXlate; // ← 函数指针分发核心(FASTCALL ULONG(pexlo, iColor))
PPALETTE ppalSrc; // 源调色板
PPALETTE ppalDst; // 目标调色板
PPALETTE ppalDstDc; // 目标 DC 调色板(DC_PALETTE 用)
HANDLE hColorTransform; // 颜色变换句柄(ICM)
union // 6 个 ULONG 的复用池:
{
ULONG aulXlate[6]; // ① 内嵌小转换表(≤6 项,省去堆分配)
struct { // ② 位域转换参数(掩码 + 旋转偏移)
ULONG ulRedMask; ULONG ulGreenMask; ULONG ulBlueMask;
ULONG ulRedShift; ULONG ulGreenShift; ULONG ulBlueShift;
};
struct { // ③ 彩色→单色转换参数(点积判别常数)
LONG lDeltaR; LONG lDeltaG; LONG lDeltaB; LONG lHalfDist;
} ToMono;
};
} EXLATEOBJ, *PEXLATEOBJ;
设计亮点: pfnXlate 函数指针 + 6 个 ULONG 的联合池------同一块内嵌存储按转换类型复用作"小表 / 位域掩码偏移 / 单色点积常数",避免为小转换表堆分配,且转换本身是一条快路径调用。
2.3 标志位
*XO_ 转换类型标志(winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L1247-L1249)):**
| 标志 | 含义 |
|---|---|
XO_TRIVIAL |
恒等转换(源值即目标值) |
XO_TABLE |
查表转换 |
XO_TO_MONO |
转换到单色 |
XO_ 调色板编号(XLATEOBJ_cGetPalette 用,winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h#L3151-L3155)): * XO_SRCPALETTE=1、XO_DESTPALETTE=2、XO_DESTDCPALETTE=3、XO_SRCBITFIELDS=4、XO_DESTBITFIELDS=5。
*PAL_ 调色板类型(palette.h(file:///d:/reactos/win32ss/gdi/ntgdi/palette.h#L10-L28),节选):**
| 标志 | 含义 |
|---|---|
PAL_INDEXED |
索引色(查表,含颜色表) |
PAL_BITFIELDS |
位域(RGB 掩码 + 偏移) |
PAL_RGB / PAL_BGR |
24/32bpp 直接 RGB / BGR 顺序 |
PAL_RGB16_555 / PAL_RGB16_565 |
16bpp 高/低 5 位格式 |
PAL_MONOCHROME |
单色(仅 2 色) |
PAL_DIBSECTION |
DIB Section 专用(转换方向特殊处理) |
2.4 全局单例 gexloTrivial
c
EXLATEOBJ gexloTrivial = {{0, XO_TRIVIAL, 0, 0, 0, 0}, EXLATEOBJ_iXlateTrivial};
恒等转换的全局实例(flXlate=XO_TRIVIAL + pfnXlate=iXlateTrivial),当无颜色转换需求时直接引用(bitblt_new.c(file:///d:/reactos/win32ss/gdi/eng/bitblt_new.c#L141)、mouse.c(file:///d:/reactos/win32ss/gdi/eng/mouse.c#L357)),避免每次分配对象。
3. 转换函数族(~25 个 FN_XLATE)
全部签名为 FASTCALL ULONG(PEXLATEOBJ, ULONG iColor),按用途分五类:
3.1 单色快速通道
| 函数 | 行为 |
|---|---|
iXlateTrivial |
恒等(返回 iColor) |
iXlateMonoInvert |
iColor ^ 1(黑白互换) |
iXlateMonoTo0 / iXlateMonoTo1 |
全部映射为 0 / 1 |
iXlateToMono |
查 1 项表:(iColor == pulXlate[0]) |
iXlateRGBToBW / iXlateRGBToWB |
亮度阈值(r+g+b >= 383 判黑/白),仅用于纯黑/纯白目标 |
3.2 彩色→单色(点积判别)
c
EXLATEOBJ_iXlateRGBToMono(pexlo, rgbColor)
{
LONG lDist = r * pexlo->ToMono.lDeltaR +
g * pexlo->ToMono.lDeltaG +
b * pexlo->ToMono.lDeltaB;
return (lDist > pexlo->ToMono.lHalfDist);
}
把前景/背景色看成 RGB 空间两点,预计算前景减背景的方向向量 (lDelta*)与判别阈值 (lHalfDist = (|F|²−|B|²)/2),每像素用点积符号判归属------几何意义即"像素点落在前背景连线哪一侧"。iXlateBGRToMono 仅是字节序反转版本(GetRValue 取蓝)。
3.3 位格式转换(16bpp 家族)
| 转换 | 要点 |
|---|---|
RGBtoBGR / 反向 |
仅交换 R/B 字节(`(v&0x00ff00ff)<<16 |
RGBto555 / BGRto555 |
24bpp → 5-5-5(右移+掩码,含 BGR 序) |
RGBto565 / BGRto565 |
24bpp → 5-6-5 |
555toRGB / 555toBGR |
5-5-5 → 24bpp,用 gajXlate5to8 扩位表(5bit→8bit 按 8 间隔扩展) |
565toRGB / 565toBGR |
5-6-5 → 24bpp,绿用 gajXlate6to8(6bit→8bit,4 间隔) |
555to565 / 565to555 |
16bpp 互转(复制最高绿位等位操作) |
扩位查表: gajXlate5to8[32] / gajXlate6to8[64] 把 5/6 位分量均匀映射到 8 位(如 5bit 值 v → v*255/31 取整),比移位+或操作精度更高。
3.4 位域转换
c
EXLATEOBJ_iXlateShiftAndMask(pexlo, iColor)
{
iNewColor = _rotl(iColor, ulRedShift) & ulRedMask;
iNewColor |= _rotl(iColor, ulGreenShift) & ulGreenMask;
iNewColor |= _rotl(iColor, ulBlueShift) & ulBlueMask;
return iNewColor;
}
通用位域重排:用循环左移对齐各通道后按掩码提取,源/目标的 555/565/掩码位域一律走此函数。
3.5 调色板索引转换
| 函数 | 行为 |
|---|---|
RGBtoPal / 555toPal / 565toPal |
转 RGB 后 PALETTE_ulGetNearestPaletteIndex 求最近色索引 |
BitfieldsToPal |
先 ShiftAndMask 转 RGB 再查最近索引 |
Table |
通用查表:iColor >= cEntries ? 0 : pulXlate[iColor](越界保护) |
4. EXLATEOBJ_vInitialize --- 转换器选择器
这是模块的总调度器 ,根据源/目标调色板类型(flFlags)组合,从 ~25 个函数中挑出最优实现:
#mermaid-svg-lerTbOjwFIwfCSnj{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-lerTbOjwFIwfCSnj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lerTbOjwFIwfCSnj .error-icon{fill:#552222;}#mermaid-svg-lerTbOjwFIwfCSnj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lerTbOjwFIwfCSnj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lerTbOjwFIwfCSnj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lerTbOjwFIwfCSnj .marker.cross{stroke:#333333;}#mermaid-svg-lerTbOjwFIwfCSnj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lerTbOjwFIwfCSnj p{margin:0;}#mermaid-svg-lerTbOjwFIwfCSnj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lerTbOjwFIwfCSnj .cluster-label text{fill:#333;}#mermaid-svg-lerTbOjwFIwfCSnj .cluster-label span{color:#333;}#mermaid-svg-lerTbOjwFIwfCSnj .cluster-label span p{background-color:transparent;}#mermaid-svg-lerTbOjwFIwfCSnj .label text,#mermaid-svg-lerTbOjwFIwfCSnj span{fill:#333;color:#333;}#mermaid-svg-lerTbOjwFIwfCSnj .node rect,#mermaid-svg-lerTbOjwFIwfCSnj .node circle,#mermaid-svg-lerTbOjwFIwfCSnj .node ellipse,#mermaid-svg-lerTbOjwFIwfCSnj .node polygon,#mermaid-svg-lerTbOjwFIwfCSnj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lerTbOjwFIwfCSnj .rough-node .label text,#mermaid-svg-lerTbOjwFIwfCSnj .node .label text,#mermaid-svg-lerTbOjwFIwfCSnj .image-shape .label,#mermaid-svg-lerTbOjwFIwfCSnj .icon-shape .label{text-anchor:middle;}#mermaid-svg-lerTbOjwFIwfCSnj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lerTbOjwFIwfCSnj .rough-node .label,#mermaid-svg-lerTbOjwFIwfCSnj .node .label,#mermaid-svg-lerTbOjwFIwfCSnj .image-shape .label,#mermaid-svg-lerTbOjwFIwfCSnj .icon-shape .label{text-align:center;}#mermaid-svg-lerTbOjwFIwfCSnj .node.clickable{cursor:pointer;}#mermaid-svg-lerTbOjwFIwfCSnj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lerTbOjwFIwfCSnj .arrowheadPath{fill:#333333;}#mermaid-svg-lerTbOjwFIwfCSnj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lerTbOjwFIwfCSnj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lerTbOjwFIwfCSnj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lerTbOjwFIwfCSnj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lerTbOjwFIwfCSnj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lerTbOjwFIwfCSnj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lerTbOjwFIwfCSnj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lerTbOjwFIwfCSnj .cluster text{fill:#333;}#mermaid-svg-lerTbOjwFIwfCSnj .cluster span{color:#333;}#mermaid-svg-lerTbOjwFIwfCSnj 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-lerTbOjwFIwfCSnj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lerTbOjwFIwfCSnj rect.text{fill:none;stroke-width:0;}#mermaid-svg-lerTbOjwFIwfCSnj .icon-shape,#mermaid-svg-lerTbOjwFIwfCSnj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lerTbOjwFIwfCSnj .icon-shape p,#mermaid-svg-lerTbOjwFIwfCSnj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lerTbOjwFIwfCSnj .icon-shape .label rect,#mermaid-svg-lerTbOjwFIwfCSnj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lerTbOjwFIwfCSnj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lerTbOjwFIwfCSnj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lerTbOjwFIwfCSnj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
MONO → MONO
MONO → 彩色
彩色 → MONO
INDEXED → INDEXED
RGB/BGR/555/565/BITFIELDS
→ 各目标
ShiftAndMask 且偏移全 0
pfnXlate == Trivial
EXLATEOBJ_vInitialize(ppalSrc, ppalDst,
crSrcBack, crDstBack, crDstFore)
默认 RGB; iUniq++ 递增; 清空状态
ppalDst == ppalSrc?
pfnXlate = Trivial, XO_TRIVIAL
非索引调色板 → 预计算
PALETTE_vGetBitMasks + CalculateShift
源/目标类型组合
4 情形: trivial / invert / to0 / to1
2 项查表 (DIB/直接 或 前背景色)
iXlateToMono + 点积常数
(CLR_INVALID 时 vInitRGBToMono)
建全量表; cDiff==0 → 退化 Trivial
(>6 项才堆分配)
按 5×5 组合表选专用函数
尾优化
退化 Trivial
XO_TRIVIAL
关键分支细节:
- MONO→MONO 四情形 (xlateobj.c(file:///d:/reactos/win32ss/gdi/eng/xlateobj.c#L541-L589)):先按"DIB/DDB 方向 + 前背景色"算出
iColors[0]/[1],再匹配 4 种简化:(0,1)→Trivial、(1,0)→Invert、(0,0)→To0、(1,1)→To1。 - 彩色→MONO 的 CLR_INVALID 特例 :调用方(如
NtGdiSetPixel)传CLR_INVALID表示"直接用 DIB 颜色表做点积判别",此时EXLATEOBJ_vInitRGBToMono用目标单色调色板的 0/1 号色初始化点积参数。 - INDEXED→INDEXED 退化优化 :构建全量表后若所有项
pulXlate[i]==i(恒等映射)则释放堆内存、退回 Trivial ;表 ≤6 项时直接复用联合池内嵌aulXlate[6],>6才EngAllocMem(GDITAG_PXLATE)。 - 位域偏移预计算
CalculateShift:由源/目标掩码算旋转偏移,使运行时仅_rotl + and。
三个初始化包装:
| 函数 | 用途 |
|---|---|
EXLATEOBJ_vInitXlateFromDCs |
由源/目标 DC 表面调色板初始化(BitBlt 用) |
EXLATEOBJ_vInitXlateFromDCsEx |
同前,但可指定源背景色(透明色/掩码 blt 用) |
EXLATEOBJ_vInitSrcMonoXlate |
强制源为单色调色板(gppalMono,单色源 blt 用) |
EXLATEOBJ_vCleanup: 若 pulXlate 指向堆内存(≠内嵌 aulXlate)则 EngFreeMem 并复位。
5. DDI 公开函数与导出
| 函数 | 行为 | win32k 导出 | UMPD(gdi32→win32u→umpdstubs) |
|---|---|---|---|
XLATEOBJ_iXlate |
转换入口 :空指针→原值;否则 pfnXlate(pexlo, iColor) 分发 |
---(内部+UMPD) | NtGdiXLATEOBJ_iXlate(umpdstubs(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L718),桩) |
XLATEOBJ_cGetPalette |
按 iPal(XO_* 编号)返回源/目标/DC 调色板颜色(pPalOut==NULL 时返回项数);校验 PAL_INDEXED/PAL_BITFIELDS 匹配 |
✓(win32k.spec(file:///d:/reactos/win32ss/win32k.spec#L228)) | NtGdiXLATEOBJ_cGetPalette(桩) |
XLATEOBJ_hGetColorTransform |
返回 ICM 颜色变换句柄 | ✓(L229) | NtGdiXLATEOBJ_hGetColorTransform(桩) |
XLATEOBJ_piVector |
源为索引色时返回转换表指针,否则 NULL | ✓(L231) | --- |
注意: XLATEOBJ_iXlate 在 xlateobj.c 中定义前先 #undef XLATEOBJ_iXlate(防御早期 winddi.h 曾把它定义成宏);内核态驱动直接调用,用户态 UMPD 经 NtGdiXLATEOBJ_iXlate。
6. 消费方与调用链(核心活跃证据)
ntgdi/bitblt.c IntGdiBitBlt → EXLATEOBJ_vInitXlateFromDCs
→ XLATEOBJ_iXlate(每像素)
↓ BLTINFO.XlateSourceToDest
DIB 引擎 (dib1bpp~dib32bpp, stretchblt, alphablend)
内联宏: XLATEOBJ_iXlate(ColorTranslation, Source) ← 每像素调用
eng/bitblt.c 画刷颜色 → XLATEOBJ_iXlate(pxloBrush, iSolidColor)
前/背景色 → pxloRGB2Dest
ntgdi/freetype.c 文本 → exloRGB2Dst / exloDst2RGB (字形掩码与画刷)
ntgdi/fillshap.c 椭圆/洪水填充 → 目标色转换
ntgdi/dcutil.c GetPixel/SetPixel → gpalRGB → 表面调色板
ntgdi/palette.c GetNearestColor → EXLATEOBJ_vInitialize + iXlate
ntgdi/dibobj.c DIB 位图/调色板互转
dib/alphablend.c Alpha 混合 → 3 个 exlo 来回转换
c
/* DIB 引擎内联转换宏(dib.h),所有位深 BitBlt 的像素必经之路 */
#define XLATEOBJ_iXlate(ColorTranslation, Source) \
((ColorTranslation) ? \
XLATEOBJ_pfnXlate(ColorTranslation)((ColorTranslation), (Source)) : (Source))
性能设计: 转换以函数指针单跳实现(无分支判断);Trivial 情形(最普遍,如 32bpp→32bpp 同格式)退化为直接返回原值,DIB 引擎甚至可走无转换快路径。
7. 与《分析_4》标注对照
| 标注 | 核对 |
|---|---|
| 《_4》第 39 行:xlateobj.c/h =「颜色转换对象」 | 准确。补充:核心活跃模块(非预留层);由 EXLATEOBJ.pfnXlate 函数指针分发 ~25 个专用转换函数,EXLATEOBJ_vInitialize 按调色板类型选择实现 |
8. 源码索引
| 文件 | 关键内容 |
|---|---|
| xlateobj.c(file:///d:/reactos/win32ss/gdi/eng/xlateobj.c) | 转换函数族、EXLATEOBJ_vInitialize/vInitXlateFromDCs(Ex)/vInitSrcMonoXlate/vCleanup、XLATEOBJ_iXlate/cGetPalette/hGetColorTransform/piVector |
| xlateobj.h(file:///d:/reactos/win32ss/gdi/eng/xlateobj.h) | EXLATEOBJ 结构(联合池)、PFN_XLATE、初始化声明 |
| winddi.h(file:///d:/reactos/sdk/include/psdk/winddi.h) | XLATEOBJ 结构、XO_* 标志、ENGAPI 声明 |
| palette.h(file:///d:/reactos/win32ss/gdi/ntgdi/palette.h) | PAL_* 标志、CalculateShift、gpalRGB 等全局 |
| palette.c(file:///d:/reactos/win32ss/gdi/ntgdi/palette.c) | 调色板对象、全局调色板初始化 |
| dib.h(file:///d:/reactos/win32ss/gdi/dib/dib.h) | XLATEOBJ_iXlate 内联宏(BLTINFO 中调用) |
| bitblt.c(file:///d:/reactos/win32ss/gdi/eng/bitblt.c) | 画刷/前景背景色转换消费 |
| ntgdi/bitblt.c(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c) | IntGdiBitBlt 初始化 exlo |
| umpdstubs.c(file:///d:/reactos/win32ss/gdi/eng/umpdstubs.c#L705-L740) | NtGdiXLATEOBJ_*(UMPD 桩) |
关联模块:
- 上层调用者:ntgdi/(file:///d:/reactos/win32ss/gdi/ntgdi/bitblt.c)(BitBlt/GetPixel/填充/文本)
- 后端消费:gdi/dib/(file:///d:/reactos/win32ss/gdi/dib/)(各位深 BitBlt 内联转换)
- 调色板来源:ntgdi/palette.c(file:///d:/reactos/win32ss/gdi/ntgdi/palette.c)
- 兄弟对象:BRUSHOBJ/CLIPOBJ/SURFOBJ(同属 DDI 对象族)
本文档基于 ReactOS 源代码
win32ss/gdi/eng/xlateobj.c/h及关联模块分析(2026 年 8 月)