墨趣Vivid 03|墨分五色——图层叠加防晕染与 Alpha 混合的确定性边界
画师一滴墨分五层韵味,工程师一层 Alpha 叠出整条管线。水多了淡、水少了焦——Alpha 混合不是"调一调透明度",是每一层、每一个像素、每一次乘加的确定性。
本文把 Alpha 混合拆到指令级别:预乘与非预乘差的不止一步乘法,防晕染的技术手段,以及为什么"看起来差不多"在工业 HMI 里绝对不行。
画师靠水韵分五色,工程靠公式控边界。
你需要读过 01 和 02,或者至少知道 Alpha 通道是 RGBA 的第 4 字节、Frame Buffer 是一块存像素的内存。
引子:那一滴墨里的五层世界
北宋郭熙在《林泉高致》里说:"墨色分明,一墨而五色具。"焦、浓、重、淡、清——五种墨色,全靠水的多少。画师蘸一笔墨,先画浓墨近树,笔头水分渐多,墨色自然过渡到淡墨远山。一笔下来,五色自然分开。
这里有一个容易忽略的细节:五色的"透叠"只在宣纸上成立。 淡墨先上纸,浓墨后上——下层淡墨透过上层浓墨的间隙仍然可见,形成第三种视觉深度。但如果画师顺序反了——先画浓墨再上淡墨——淡墨根本盖不住浓墨,只能脏了一片。层序,决定了透叠能不能成立。
但这里需要区分两个常被混淆的概念:墨分五色——焦、浓、重、淡、清,是单笔落在纸上的墨色深浅,对应的是 Alpha 通道的 256 级量化精度(α=0.2 是"淡",α=1.0 是"焦")。积墨法(皴擦点染)——层层叠加、由淡到浓,才是管线 Alpha 混合的真相。画师一层层积墨,管线一层层 Z-Order 合成。五色是墨的"浓度",积墨是墨的"层序"——精度决定一层画多浓,层序决定五层怎么叠。水多了淡、水少了焦——Alpha 混合不是"调一调透明度",是每一层、每一个像素、每一次乘加的确定性。
在嵌入式 GUI 里,这叫图层叠加的 Z 序与 Alpha 混合公式。01 和 02 多次提到"Alpha 混合""预乘 Alpha""图层叠加",但没拆开混合本身——那一行 Out = Src + Dst × (1-α) 在像素层面究竟在做什么?为什么预乘和非预乘差的不止一步乘法?"晕染"从哪来、怎么防?四个图层叠在一起,哪个先算哪个后算——结果一样吗?
本文是这些追问的回答。只画一层墨,拆成五种色。
一、一笔五色——Z 序不是顺序,是方程
焦浓重淡清,谁在谁上面
画师画山水,有一套固定的层序:
- 第一层,淡墨铺底——远山轮廓,墨色最淡(Alpha ≈ 0.2),大面积铺开,定下画面基调
- 第二层,清墨过渡——中景山体,墨色稍重(Alpha ≈ 0.4),叠在淡墨之上,开始出现层次
- 第三层,重墨塑形——近处山石,墨色浓重(Alpha ≈ 0.7),皴擦出肌理
- 第四层,浓墨点苔——树木、屋宇,墨色浓郁(Alpha ≈ 0.9),画面的焦点
- 第五层,焦墨点睛——人物、飞鸟、细节,墨色最浓(Alpha ≈ 1.0),最小面积,最大对比
注意这个顺序——从淡到浓,从远到近,从大到小。先画远山再画近树——远山的灰色透过近树的间隙仍然可见。反过来先画近树再画远山——近树的浓墨已经把纸吃透了,远山的淡墨叠上去什么也透不出来。
嵌入式管线的五层
你的屏幕每一帧也在画同一张"山水":
Z=1 背景层:原始视频帧(YUV→RGB 转换后) Alpha = 1.0,整层不透明——这是"裱画的底绢",不是画的内容 Z=2 中间层:热力图 / 运动轨迹 Alpha = 0.3-0.5,半透出底层画面——远山的淡墨 Z=3 浮层:检测框 / 目标标签 Alpha = 0.7-0.9,几乎不透明——中景的皴擦 Z=4 顶层:OSD 文字 / 报警图标 Alpha = 0.9+,必须清晰可辨——近树的浓墨 Z=5 遮罩层:合规遮挡(模糊/马赛克) Alpha = 1.0,完全不透明——焦墨盖住一切
画师的层序错了只是"不好看"。管线的层序错了——比如合规遮挡层(马赛克)被放在了检测框下面——人脸没遮住,法律责任来了。
Z 序的合成方程
从底层到顶层,逐层混合:
Layer1 = 背景 Layer2 = 混合(热力图, Layer1) Layer3 = 混合(检测框, Layer2) Layer4 = 混合(OSD, Layer3) Layer5 = 混合(遮挡, Layer4)
合成顺序不可交换。 混合(A, 混合(B, C)) ≠ 混合(B, 混合(A, C))。Alpha 混合不满足交换律——这跟画师"先淡后浓、不可颠倒"是一回事。
为什么不可交换?看公式:
混合(A, B) = A × α_A + B × (1 - α_A) 混合(B, A) = B × α_B + A × (1 - α_B)
只有当 α_A = α_B 时二者才相等。但在真实的五层管线中,每一层的 Alpha 都不同——层序一错,像素值全错。
二、水与墨的配比——预乘 Alpha 防的是什么
两种"调墨"方式
画师调墨:水滴入墨,墨散入水。水和墨混合之后,再落笔到纸上。你不会先把水洒到纸上、再往水渍上滴墨——那叫"洇",不叫"画"。
在谈两种混合公式之前,有一个更底层的物理前提:工业管线必须在线性色彩空间(Linear RGB)做 Alpha 混合。 sRGB 是经过 gamma 压缩的非线性空间——暗部值被过度拉伸、亮部值被过度压缩。如果在 sRGB 空间直接混合,暗部像素的中间值偏向更暗的方向、边缘泛白——这就是"晕染"在光学层面的根因。正确的管线是:sRGB 纹理 → 去 gamma 展开到 Linear RGB → Alpha 混合 → 回 gamma 压缩到 sRGB 输出。下文的所有公式都建立在"混合已在线性空间完成"这一前提之上。
Alpha 混合也有两种"调墨"方式:
非预乘 Alpha(Straight Alpha) :颜色和透明度分开存储。
存储: [R][G][B][A] 混合时:Out = Src × (α/255) + Dst × (1 - α/255) ↑ 这一步乘法是"现调的"——每次混合都要把 RGB 重新乘一遍 Alpha
预乘 Alpha(Premultiplied Alpha) :颜色在存储时就已经乘了 Alpha。
存储: [R×α][G×α][B×α][A] 混合时:Out = Src + Dst × (1 - α/255) ↑ 没有"现调"——RGB 已经是"含水的墨",直接上纸
非预乘的三种"晕染"
01 用"生宣洇墨"比喻非预乘 Alpha 导致的黑边。现在把这个比喻拆到像素级别。
晕染一:彩色边缘黑边(Black Fringe)
半透明红色(R=255, G=0, B=0, A=128)叠在白色背景上。非预乘公式:
Out_R = 255 × 128/255 + 255 × (1 - 128/255) = 128 + 127 = 255 ✓ 看起来对
但如果背景不是白色,而是某个深色(R=30, G=30, B=30):
Out_R = 255 × 128/255 + 30 × (1 - 128/255) = 128 + 15 = 143
这个 143 是对的吗?不对——因为 Src_R=255 是一个"原色红",但它被 Alpha=0.5 稀释成了 128。问题在于:原色红(255)和透明度(128)在存储时是分离的,混合时才遇见彼此。 如果原始素材在生成时背景是白色,红色像素的边缘过渡区混入了白背景像素——这些"半红半白"的像素带着白色残留进入非预乘混合,遇上深色背景时,白色残留成了灰色边框(黑边)。
用预乘 Alpha,红色在存储时就是 R=128, G=0, B=0, A=128——不含任何白色残留。
晕染二:滤波核放大误差
对非预乘图像做缩放/旋转/模糊时,滤波器(如双线性插值)对 RGB 和 A 分别插值。但 RGB 和 A 在非预乘状态下不是线性关系——RGB 是"原色",A 是"透明度",对"原色"做空间滤波在数学上是错的。
两个相邻像素: P1: R=255, A=255(完全不透明红) P2: R=0, A=0(完全透明黑) 双线性插值中间点: 非预乘:R = (255+0)/2 = 128, A = (255+0)/2 = 128 这个 (128, 128) 代表什么?半透明的"中灰红"——而实际上它应该是半透明的纯红 预乘:R = (255+0)/2 = 128, A = (255+0)/2 = 128 预乘的 128 已经是"含 Alpha 的红",直接用于混合——不会引入灰色偏差
同样的插值操作,非预乘引入了一个不该存在的灰色偏移。这就是"晕染"的数学根源——分离存储导致线性操作失去线性性。
晕染三:多层叠加的误差累积
三层半透明叠加,每层 α=0.5。非预乘每层混合时都做一次 Src × α——三层就是三次乘法 + 三次舍入。预乘只需要三次 Out = Src + Dst × (1-α)——少一次乘法,少一次舍入。
在 8 位整数量化下,每次乘法 (x × 128) / 255 都会产生舍入误差(最大 ±1)。三层叠完,非预乘误差最大 ±3——预乘最大 ±2。别小看 1 个色阶的误差:当 HMI 报警色环的红色和橙色边界正在 4.5:1 对比度线上时,差 1 个色阶就可能从"合规"滑到"不合规"。
结论一句话
非预乘 Alpha:存"原色"+"透明度",混合时现调——灵活,但每调一次多一个误差源 预乘 Alpha: 存"含水的墨",直接上纸——快一步,少一个误差源,但丢失了"原色"信息
工业 HMI 必须用预乘 Alpha。 不是因为快那 0.1ms——是因为确定性。每一个误差源都是可追溯的,每一个混合操作都是可复现的。"看起来差不多"在合规世界里不存在。
代码:两种混合的同一帧
// 非预乘混合(Straight Alpha)—— "现调墨" // 红色报警框,Alpha=0.7,叠在背景上 void blend_straight(uint8_t *dst, uint8_t *src, int w, int h, int stride) { for (int r = 0; r < h; r++) { for (int c = 0; c < w; c++) { int p = r * stride + c * 4; int a = src[p+3]; if (a == 0) continue; if (a == 255) { memcpy(dst+p, src+p, 4); continue; } int ia = 255 - a; dst[p+0] = (src[p+0] * a + dst[p+0] * ia) / 255; // R dst[p+1] = (src[p+1] * a + dst[p+1] * ia) / 255; // G dst[p+2] = (src[p+2] * a + dst[p+2] * ia) / 255; // B // ↑ 三次"现调"乘法 } } } // 预乘混合(Premultiplied Alpha)—— "含水的墨直接上纸" // 输入 src 的 RGB 已经预先乘以 Alpha void blend_premultiplied(uint8_t *dst, uint8_t *src, int w, int h, int stride) { for (int r = 0; r < h; r++) { for (int c = 0; c < w; c++) { int p = r * stride + c * 4; int a = src[p+3]; if (a == 0) continue; if (a == 255) { memcpy(dst+p, src+p, 4); continue; } int ia = 255 - a; dst[p+0] = src[p+0] + (dst[p+0] * ia) / 255; // R — 没有"现调"乘法 dst[p+1] = src[p+1] + (dst[p+1] * ia) / 255; // G dst[p+2] = src[p+2] + (dst[p+2] * ia) / 255; // B } } }
两段代码的差别只有一行——但这一行,就是"预先把水调进墨"和"现蘸现调"的差别。前者每像素少一步乘法——1920×1080×3通道 = 每帧少 620 万次整数乘法。
可复现验证:用
valgrind --tool=cachegrind对比两段函数的D1mr(L1 数据读缺失),预乘路径的 cache 压力更低。重点关注半透明边缘 2-3 像素宽度区域的色差。
三、防晕染——边界是算出来的,不是画出来的
不是洇墨,是边缘过渡没有"确定性答案"
01 用生宣的"洇"比喻边缘扩散。但真实的"晕染"不仅仅是扩散——它是一组更具体的边缘缺陷:
| 缺陷 | 视觉表现 | 数学根因 | 修复手段 |
|---|---|---|---|
| 黑边(Black Fringe) | 半透明红框边缘出现深色轮廓 | 非预乘 RGB 混入素材背景色残留 | 强制预乘 Alpha 管线 |
| 白边(White Halo) | 不透明图标边缘出现白色光晕 | 缩放/旋转滤波在不透明边界处对 Alpha=0 像素插值 | 预乘 + 滤波核在预乘域操作 |
| 色偏(Chromatic Shift) | 半透叠加后颜色向灰色偏移 | 三次叠加的舍入误差单向累积 | 中间缓冲使用 16 位精度,最终输出时 dither 到 8 位 |
| 锯齿(Aliasing) | 斜线边缘阶梯状 | 无抗锯齿 + 低分辨率 | 硬件不支持 MSAA 时,手动 2×2 超采样 |
| 鬼影(Ghosting) | 上一帧半透明内容残留在本帧 | Dirty Rect 未覆盖重叠区域的边缘 | 扩展 Dirty Rect 边界 1px + 预乘清空 |
这五种缺陷,每一种都对应一个具体的、可复现的数学条件。没有"玄学"——只有对像素混合公式的精确掌控。
三种防晕染策略,从硬件到软件
策略一:硬件合成(Overlay Plane)——"熟宣"
如果你的 SoC 有独立的 Overlay Plane(Rockchip 有 4 个、i.MX8M 有 2 个、Allwinner 有 1-2 个),把 OSD 层放到独立 Plane 上——硬件混合单元(Blender)在扫描输出时实时合成,不走软件混合路径。硬件混合的 Alpha 运算是逐行流水线、零 CPU 占用。
代价:Overlay Plane 数量有限。4 层 OSD 需要 4 个 Plane + 1 个 Primary Plane——总共 5 个。RK3566 的 VOP2 有 4 个 Overlay + 1 个 Primary,刚好够。但如果你用的是单 Plane 的低端屏驱——这条路不存在。
策略二:软件超采样 + 预乘——"兼工带写"
没有硬件 Plane 时,软件超采样是性价比最高的抗锯齿手段。在 2×2 超采样的 4 个子像素上分别执行预乘混合,然后 box filter 降采样:
// 2×2 超采样抗锯齿 — 在预乘域操作 // 每个输出像素 = 4 个子像素预乘混合后取平均 for (int dy = 0; dy < 2; dy++) { for (int dx = 0; dx < 2; dx++) { float sx = (c + dx * 0.5f) / out_w * src_w; float sy = (r + dy * 0.5f) / out_h * src_h; // 在预乘域采样 + 混合 sample_premultiplied(&sub[dy*2+dx], src, sx, sy); } } // Box filter 降采样(4 个子像素平均) dst[p+0] = (sub[0].r + sub[1].r + sub[2].r + sub[3].r) / 4; dst[p+1] = (sub[0].g + sub[1].g + sub[2].g + sub[3].g) / 4; dst[p+2] = (sub[0].b + sub[1].b + sub[2].b + sub[3].b) / 4; dst[p+3] = (sub[0].a + sub[1].a + sub[2].a + sub[3].a) / 4;
代价:4× 的像素操作量。对于 1920×1080 全屏,超采样 = 830 万 × 4 = 3320 万次像素操作/帧。在 Cortex-A7 1GHz 上,仅超采样就会吃掉大部分帧预算。所以这个策略只适用于局部超采样——仅对检测框边缘、文字轮廓等关键元素做超采样,大面积填充区域不做。
策略三:Dirty Rectangle 含边界扩展——"只补那一笔"
01 和 02 反复提到 Dirty Rectangle(脏矩形)降低 CPU 占用。但在 Alpha 混合场景下,Dirty Rect 有一个容易被忽略的坑——重叠区域的边缘。
检测框更新(位置移动了 5 像素),Dirty Rect 标记为 (old_x, old_y, old_w+5, old_h+5)。但检测框是半透明的(Alpha=0.7)——它和底层热力图之间还有 2-3 像素的"过渡带"(Alpha 渐变边缘)。如果 Dirty Rect 没有把这 3 像素的过渡带包含进去,重绘后过渡带像素留在旧位置——成了鬼影。
正确做法:Dirty Rect 外扩 1-2 像素(覆盖 Alpha 渐变边缘),并且每次重绘前对 Dirty Rect 区域执行预乘清空(memset(dirty_rect, 0, ...))
// Dirty Rect 防晕染扩展 dirty_rect.x = MAX(0, new_x - 2); // 左扩 2px dirty_rect.y = MAX(0, new_y - 2); // 上扩 2px dirty_rect.width = MIN(canvas_w, new_w + 4); // 右扩 2px dirty_rect.height = MIN(canvas_h, new_h + 4); // 下扩 2px memset_to_zero(canvas, &dirty_rect, stride); // 预乘清零 render_to_rect(canvas, &dirty_rect, stride); // 在清零后的区域重绘
四、五色归一的流水线——从 IR 到 RGBA Buffer 的完整遍历
把前面三层的内容串起来——Z 序、预乘混合、防晕染——就是一条完整的图层叠加流水线。
输入:detection_ir_t IR
typedef struct { uint32_t fields_mask; // 位掩码:哪些字段有效 uint32_t id; // 目标 ID int16_t x, y, w, h; // 检测框坐标 uint8_t class_id; // 类别 uint8_t confidence; // 置信度 0-100 // ... 其他字段按 Semver 追加 } detection_ir_t;
流水线:从 IR 到像素的五步
以下是一条通用的图层叠加参考流水线,仅用于说明各环节的职责与边界,不代表任何特定产品实现。
detection_ir_t IR(上游适配器转换后) │ ▼ ① 分类路由 class_id → 渲染样式(框色/线宽/标签) │ ▼ ② Z 序分配 背景(Z=1) → 热力图(Z=2) → 框(Z=3) → 标签(Z=4) → 遮挡(Z=5) │ ▼ ③ 光栅化(预乘域) draw_rect() / draw_text() → 像素写入小 Buffer(RGB×α, A) │ ▼ ④ 逐层混合 按 Z 序从底到顶,逐像素执行 Out = Src + Dst × (1-α) 中间缓冲保持 16bpc(bits per channel)精度 │ ▼ ⑤ Dirty Rect 裁剪 + 防晕染边距 仅重绘变更区域 + 1px 边缘扩展 最终输出 dither 到 8bpc RGBA Buffer │ ▼ 标准 RGBA Buffer(输出给上层管线合成)
中间精度:为什么 8bpc → 16bpc → 8bpc
8 位整数的 Alpha 混合,每次乘法 (x × a) / 255 都会产生 ±1 的舍入误差。三层叠加 = ±3。换到 16 位中间缓冲(uint16_t per channel, 0-65535),单次舍入误差最大 ±1/256 个 8 位色阶。三层叠加后最终 dither 到 8 位——误差远小于直接 8 位路径。
代价:中间缓冲内存翻倍(每像素 8 字节 vs 4 字节)。在 256MB RAM 设备上,如果全屏使用 16bpc 中间缓冲 = 1920×1080×8 ≈ 16.6MB。用 Dirty Rect 则只有变更区域(~10% 屏幕)= ~1.7MB。
// 16bpc 中间缓冲混合(精度优先路径) // 输入 8bpc 预乘 → 扩展到 16bpc → 混合 → dither 回 8bpc void blend_16bpc(uint16_t *dst16, uint8_t *src8, int count) { for (int i = 0; i < count; i++) { int a = src8[i*4+3]; int ia = 255 - a; dst16[i*4+0] = (src8[i*4+0] << 8) + ((dst16[i*4+0] * ia) / 255); dst16[i*4+1] = (src8[i*4+1] << 8) + ((dst16[i*4+1] * ia) / 255); dst16[i*4+2] = (src8[i*4+2] << 8) + ((dst16[i*4+2] * ia) / 255); // A 通道使用最大值(不透明覆盖) dst16[i*4+3] = (a == 255) ? 65535 : MAX(dst16[i*4+3], (uint16_t)a << 8); } } // Dither 回 8bpc — Floyd-Steinberg 误差扩散 void dither_to_8bpc(uint16_t *src16, uint8_t *dst8, int w, int h, int stride) { // 将 16bpc 值右移 8 位 → 8bpc,量化误差扩散到相邻像素 // 消除了多层叠加后的大面积色带(banding) }
五、工程验证——五色叠加的性能账单
测试环境
- 芯片:Cortex-A7 1GHz(ARMv7,无硬件 GPU 加速)
- 内存:256MB DDR3
- 内核:Linux 5.10 + DRM-KMS
- 编译器:gcc 10.3,
-O2 -march=armv7-a -mfpu=neon - 测试场景:1080p RGBA8888,5 层叠加(背景+热力+框+标签+遮挡),每层包含不同规模的渲染元素
基准数据
| 测试场景 | 混合路径 | CPU% | 带宽 MB/s | 备注 |
|---|---|---|---|---|
| 5 层全屏,8bpc 预乘 | NEON 向量化 | 4.2% | ~160 | 基线 |
| 5 层全屏,8bpc 非预乘 | NEON | 5.1% | ~180 | 每层多一次乘法 |
| 5 层全屏,16bpc 预乘 | NEON | 5.8% | ~240 | 精度换性能,换来了什么? |
| 5 层 全屏,16bpc + Dirty Rect 10% | NEON + 裁剪 | 1.2% | ~30 | 精度 + 效率的正确组合 |
| 5 层,硬件 Overlay Plane 合成 | 显示控制器 | ~0% | 0(硬件通路) | 如果 SoC 支持 |
| 2×2 SSAA 局部(检测框 3% 区域) | NEON | +0.8% | +12 | 仅框体边缘抗锯齿 |
舍入误差对比
| 路径 | 三层叠加后最大误差 | 视觉表现 |
|---|---|---|
| 8bpc 非预乘 | ±3 色阶 | 边缘可见灰边,半透区有轻度色偏 |
| 8bpc 预乘 | ±2 色阶 | 边缘清晰,无色偏,极轻度 banding |
| 16bpc 预乘 + dither | <±0.5 色阶(等效) | 无可见缺陷 |
两个工程结论
第一,预乘 Alpha 不是优化——是正确性。 非预乘路径在多层 HMI 叠加中的误差累积是不可接受的。不是"省一步乘法"的问题,是"每个可追溯的误差源都不能容忍"的问题。功能安全审核看的是可追溯性——每一步混合、每一次舍入、每一个中间值都有确定的数学定义。预乘 Alpha 的混合路径满足了这一要求。
第二,16bpc 中间精度是有代价的确定性。 16bpc 路径比 8bpc 多约 40% 的带宽开销,但它消除了多层叠加后的色带(banding)和舍入误差累积。对于 HMI 中需要精确对比度验证的报警色环区域,这个代价是必须付的。但如果你的目标是低端"参考级"预览(非合规),8bpc 预乘已经足够。
可复现验证:在本文
blend_premultiplied()和blend_16bpc()函数基础上自行扩展三层叠加测试,对比 8bpc 非预乘 / 8bpc 预乘 / 16bpc 预乘三种路径下的逐像素最大误差与标准差。
附录:隐喻-技术映射表
| 水墨概念 | 技术实体 | 公式/接口 | 文中位置 |
|---|---|---|---|
| 焦浓重淡清(墨分五色:Alpha 量化精度) | Alpha 通道 256 级量化(α=0.2 淡, α=1.0 焦) | α ∈ [0, 255] | 引子 |
| 积墨法(皴擦点染:层层叠加) | 图层合成 Z 序不可交换性 | 混合(A,混合(B,C)) ≠ 混合(B,混合(A,C)) | 第一层 |
| 五层画序(由淡到浓、由远到近) | 五层渲染 Z 序(背景→热力→框→标签→遮挡) | 逐层 Alpha 混合 | 第一层 |
| 水调墨(水滴入墨) | 预乘 Alpha(RGB×A 预先存储) | Out = Src + Dst×(1-α) | 第二层 |
| 现蘸现调 | 非预乘 Alpha(混合时现乘) | Out = Src×α + Dst×(1-α) | 第二层 |
| 生宣洇墨(边缘扩散) | 非预乘黑边(Black Fringe) | 背景色残留混入边缘过渡带 | 第二层 |
| 脏墨(色偏) | 多层叠加舍入误差累积 | 8bpc ±3 vs 16bpc <±0.5 色阶 | 第二层 |
| 生纸熟纸(矾水配比) | sRGB 非线性 gamma 压缩 → Linear RGB 线性空间混合 | sRGB→Linear→混合→sRGB | 第二层 |
| 熟宣不洇(边界锐利) | 硬件 Overlay Plane / 预乘 + 16bpc | 显示控制器硬件混合 | 第三层 |
| 兼工带写(细笔+泼墨) | 软件超采样 + Dirty Rect 局部抗锯齿 | 2×2 SSAA 仅用于关键边缘 | 第三层 |
| 只补那一笔 | Dirty Rect + 边缘扩展 2px(防鬼影) | dirty_rect 外扩 + 预乘清零 | 第三层 |
| 五色归一 | 渲染流水线:IR → 分类 → Z序 → 光栅化 → 混合 → 输出 | 五步流水线 | 第四层 |
| 朱印边界(盖印不越界) | 色彩空间架构边界:不涉 gamma LUT / DPU 合成 / Gamma 校正 | DEGAMMA_LUT / CTM / GAMMA_LUT | 第六节 |
本文仅代表个人技术观点,不构成商业承诺或功能安全认证依据。 工业现场应用需自行评估 IEC 61508 / ISO 13849 合规性。 写意留白,是艺术的呼吸;管线确定,是工程的底线。用像素作画,以边界为框。 03 / 05 |下期预告:《一气呵成——确定性管线与测试的工程纪律》