Linux图形三部曲(1):Linux 图形基础理论与图像表示核心原理
本系列三部曲每篇链接Linux图形三部曲1Linux 图形基础理论与图像表示核心原理 - 知乎Linux图形三部曲2Linux 图形硬件架构与显示管线全解析 - 知乎Linux图形三部曲3Linux 图形软件栈全架构内核→用户态→渲染引擎 - 知乎在整个 Linux 图形栈的学习路线里绝大多数人一上来就扎进 DRM、Wayland、Mesa 这些名词里结果越学越乱遇到花屏、颜色偏差、画面错位、格式不兼容这类最常见问题时依然完全找不到方向。根本原因只有一个没有先把最底层的图像表示理论真正弄懂。像素、分辨率、色彩空间、采样、量化、内存排布、Stride、FourCC、Alpha 混合、卷积滤波……这些听起来基础的概念不是可学可不学的“常识”而是整个 Linux 图形世界的第一性原理。不管是嵌入式开机 Logo、桌面窗口、3D 游戏、视频播放还是车载仪表、无人机图传、机顶盒输出所有图像行为最终都可以追溯到这一套理论。本文参考Bootlin官方资料将阐述图像从物理世界到数字世界、从数学描述到内存存储、从单个像素到完整画面的全部逻辑。如有专业词汇翻译不当、内容理解有误的地方还望指正。一、先回到起点图像到底是什么我们每天都在看屏幕但很少有人认真想过屏幕上的画面本质到底是什么在现实世界里光是连续的一片树叶、一面墙、一张纸它们反射的光在空间上是不间断的。如果用胶片去记录它可以保留无限精细的过渡不存在“最小单元”。这种记录方式叫做模拟图像。它的特点是空间连续、信息连续、没有颗粒感但无法被计算机直接存储、计算、传输。计算机和所有数字系统都无法处理无限连续的信息。我们必须做一件事把连续的光切成一份份有限、可计数、可存储的小单元。这个“切割”过程就叫量化Quantization也叫采样Sampling。切割出来的最小单元就是我们最熟悉的词像素Pixel。所以数字图像的本质可以用一句最精准的话概括数字图像 对连续光信号进行空间量化后形成的二维像素阵列。一个像素就是一个位置上的“光信息小包”它包含这个点的亮度、颜色信息。无数像素按照行列整齐排列就成了我们看到的图。宽有多少像素、高有多少像素就是分辨率比如 1920×1080。宽和高的比例就是宽高比比如 16:9。单位面积里有多少像素决定画面细腻度我们用DPIDots Per Inch表示。DPI 越高像素越小人眼越看不出颗粒感。绝大多数系统默认像素是正方形所以不用分别计算横向和纵向密度。这看起来是个小细节但在图像计算、内存布局、硬件缩放时是一切规则的基础。把连续信号切成离散像素必然带来一个无法避免的问题信息损失。这种损失不是 bug而是物理规律。最直观的表现就是混叠Aliasing比如斜线出现锯齿、密集纹理产生莫尔条纹、小字边缘粗糙。这些问题不是软件写错了而是采样本身带来的天然现象。这里必须提到奈奎斯特采样定理它是所有数字信号处理的基石想要正确还原一个信号采样频率必须大于信号最高频率的两倍。放到图像里就是如果图像里有非常细密的纹理、小字、网格这些“高频信息”而你的像素分辨率不够就一定无法正确还原最终呈现出锯齿、模糊、波纹。这也是为什么高分辨率屏幕更清晰、为什么图像放大后会失真、为什么抗锯齿是图形系统必须做的事。所有这些现象都能从“连续→离散”这个第一步里找到根源。二、颜色的本质人眼、光、与色彩模型像素解决了“位置”问题下一个问题就是颜色怎么用数字表示这不是一个纯技术问题而是一个生物学问题。人类视网膜上有三种感知颜色的细胞分别对应红、绿、蓝三个波段。自然界中所有颜色进入人眼后都会被大脑翻译成这三种刺激的组合。这就是三色视觉。几乎所有数字图像系统都以RGB为基础不是因为工程师喜欢 RGB而是因为RGB 最贴合人的眼睛。但 RGB 只是一个模型不是一套标准。同样一组 RGB 数值在不同屏幕上可能显示出完全不同的颜色。因为它们用的色彩空间Colorspace不同。Color gamut of agiven colorspace色彩空间可以理解成一套“翻译字典”它规定红、绿、蓝三原色的确切波长它规定什么是“标准白光”白点它规定能表示的颜色范围也就是色域Gamut。我们最常见的 sRGB是为网页、图片、显示器设计的标准色域。而 Display P3、Adobe RGB 这类广色域能表示更多绿色和红色适合摄影、影视。超出色域的颜色设备无法显示只能被“切掉”或“映射”成最近的可表示颜色。这就是为什么同一张图在手机和电脑上看起来不一样——不是坏了是颜色范围不一样。在 Linux 图形栈、视频系统、嵌入式显示里我们很少只使用 RGB。真正大量出现的是另外两套模型HSV/HSB和YUV/YCbCr。HSV 用色相、饱和度、明度来描述颜色更贴近人类直觉绘图软件里的调色盘基本都用它。但在渲染、传输、存储里真正统治世界的是YUV。HSV diagramYUV 把图像拆成亮度Y和色度U/V两个独立部分。它的设计逻辑非常简单粗暴人眼对亮度极度敏感对颜色细节不敏感。你可以大幅度压缩颜色信息人眼几乎看不出来但不能压缩亮度。这就是视频能被压得很小、能流畅在线播放的根本原因。从 RGB 转到 YUV 是固定的数学变换以最经典的 BT.601 为例Y 0.299 × R 0.587 × G 0.114 × B U -0.147 × R - 0.289 × G 0.436 × B V 0.615 × R - 0.515 × G - 0.100 × BTranslation between BT.601 in YUV and sRGB in RGB反向变换也有对应公式R Y 1.140 × V G Y - 0.395 × U − 0.581 × V B Y 2.032 × ULinux 内核 DRM、显示驱动、视频解码器、硬件加速器底层全是这套计算。如果你不理解 YUV几乎不可能搞定任何视频或图像格式问题。接下来是色深Color Depth也就是用多少位二进制表示一个像素。8 bit256 色早期系统、简单界面16 bit65536 色很多嵌入式设备24 bit1600 万色真彩色RGB 各 8 位32 bit带 Alpha 透明通道现代桌面标准24Bit VS 16Bit每个像素色深本质就是颜色的量化精度。位数越少颜色过渡越生硬容易出现色带位数越高颜色越平滑但占用内存越大。Linux 驱动、缓冲区大小计算、硬件格式配置第一步永远是色深。很多人遇到偏色、色带、画面发灰、透明度异常第一反应去查驱动其实 90% 的情况都是色深、色域、色彩空间不匹配导致的。三、图像大小、数据量与色度亚采样我们来算一笔最现实的账一张数字图像到底占多大内存公式非常直白大小字节 宽 × 高 × 每像素比特数 ÷ 8比如一张 4000×3000、32 位的图片 4000 × 3000 × 32 / 8 48,000,000 字节 ≈ 46 MB。这只是一张图。视频每秒 30 帧一分钟就是 46×30×60 ≈ 82 GB。显然不可能直接传输和存储。在 Linux 图形与视频系统里最核心、最通用、最无处不在的压缩手段不是复杂算法而是色度亚采样Chroma Subsampling。sub-sampling它的原理依然是人眼看亮度很敏锐看颜色很迟钝。所以我们可以让多个像素共用一组颜色数据只保留完整的亮度。行业用三个数字表示J:a:b。4:4:4无压缩每个像素都有完整 YUV4:2:2横向 2 个像素共用一组色度4:2:0横向 2 个、纵向 2 个像素共用一组色度4:2:0 是 H.264、H.265、VP9、所有在线视频、所有摄像头、所有电视面板、绝大多数嵌入式系统的默认格式。它把数据量几乎减半而观感几乎不变。在 DRM、V4L2、显示驱动、接口配置里我们会反复看到这些格式。不理解 4:2:0就不可能真正理解 Linux 图形系统。很多工程师调试视频输出画面绿色紫色错乱、花屏、分块其实就是亚采样格式不匹配发送端是 4:2:0显示端按 4:2:2 解析自然一塌糊涂。四、像素在内存里的真实排布格式、Stride、FourCC到这里我们终于进入最实用、最容易踩坑、最决定显示成败的部分像素在内存里到底怎么放同样的图、同样的像素、同样的分辨率在内存里可以有几十种不同排布。一旦排布错了屏幕立刻花屏、偏色、错位、撕裂、拉伸。这是图形开发者最常见、最头疼的问题。首先要理解三种存储结构Packed打包所有通道混在一起RGBRGBRGB……Semi-Planar半平面Y 单独一块UV 合并一块。Planar平面Y、U、V 各占一块完全独立的内存。Linux 里的 DRM、帧缓冲、视频渲染同时支持这三种结构驱动必须明确告诉硬件用哪一种。接下来是最容易被忽略的关键Stride / Pitch。很多新手以为一行像素的字节数 宽 × 位深 / 8。这是错的。 绝大多数硬件 DMA 有强制要求每一行的起始地址必须按 16/32/64 字节对齐。不足的地方要填充空白字节这部分空白就是 Stride。比如一行 100 个像素32 位深理论 400 字节。如果硬件要求 64 字节对齐400 向上取 64 的倍数是 448那么 Stride 448。最后 48 字节是空的不显示但必须预留。计算缓冲区大小如果不带 Stride一定会内存越界、画面错位、内核崩溃。这是嵌入式图形调试里排名前三的坑。为了统一标识各种格式行业用FourCC4 个字符的编码代表一种像素格式。RGB888 → RGB3ARGB8888 → ARGBYUV420 → YU12、YV12DRM 内部用 XR24 表示 XRGB8888FourCC 不是完美标准但它是 Linux 图形栈里最通用的格式语言。libdrm、DRM 驱动、应用、工具、编码器全部依赖它。我们遇到的绝大多数“花屏但背光正常”的问题基本都可以归为三类FourCC 格式不匹配Stride 计算错误平面/打包/半平面结构搞错五、把数学形状变成像素光栅化与基础绘制有了像素、格式、内存排布下一步就是画图。raster order把数学上的点、线、矩形、圆、曲线变成屏幕上的实际像素这个过程叫光栅化Rasterization。它的本质就是把连续的数学形状映射到离散的像素网格上。最基础的图形都遵循简单逻辑 矩形就是限定 xmin/xmax、ymin/ymax把范围内所有像素填色。 直线是根据两点坐标计算斜率逐点绘制。嵌入式系统最常用 Bresenham 算法速度快、不用浮点运算。 圆和椭圆用距离判断到中心点的距离小于等于半径就是圆内像素。 渐变则是在起点色和终点色之间做线性插值每一点算出对应的 RGB。这些看似简单却是 Linux 底层、无 GPU 环境、开机 Logo、串口终端、嵌入式菜单的唯一绘制方式。很多人以为画图全是 GPU 做的其实在系统启动早期、无驱动环境下全是 CPU 直接写像素。这里再次回到采样问题斜线、圆、小字直接光栅化一定会出现锯齿。解决办法是亚像素渲染Sub-pixel Rendering和抗锯齿Anti-aliasing。原理是给边缘像素一个半透明值让硬边缘变柔和。Linux 桌面字体清晰、矢量图平滑核心就是这套逻辑。不理解光栅化和采样我们就无法理解为什么字体需要渲染引擎为什么线条会锯齿为什么小图标要做高分辨率预处理为什么缩放会模糊这些都是最底层的理论决定的。六、像素的操作混合、拷贝、缩放、滤波、抖动图像不是画完就结束而是要不断处理、叠加、变换。Linux 图形栈里所有渲染、合成、窗口、动画本质都是下面这几种像素操作。第一个最基础的操作是区域拷贝Bit Blit。把一块矩形像素从一个地方复制到另一个地方。窗口移动、截图、图层叠加、界面刷新本质全是 Blit。硬件 2D 加速器存在的意义就是加速 Blit。第二个是Alpha 混合Alpha Blending。每个像素多一个 Alpha 通道表示透明度。当一个图层叠在另一个上面时最终颜色 前景×透明度 背景×(1−透明度)。窗口半透明、菜单、弹窗、图标抗锯齿全都靠它。这套规则叫 Porter-Duff 混合模型是计算机图形学最经典的理论之一。Alpha Blending第三个是色度键控Color Keying。把指定颜色变成透明也就是影视里的绿幕。在视频叠加、字幕、嵌入式 UI 里非常常用。Color Keying第四个是缩放与插值。放大缩小图像时必须重新采样。最近邻采样最快但锯齿严重双线性采样更平滑双三次采样质量最高但计算量大。缩小图像时如果不移除高频信息会出现严重混叠所以通常先做模糊再缩小。第五个是线性滤波与卷积。用一个小矩阵卷积核遍历图像每个像素由自己和周围像素加权计算。高斯模糊就是低通滤波去掉细节锐化是高通滤波增强边缘边缘检测是提取轮廓。图像处理库、视频特效、图形引擎底层全是卷积。Linear filtering in application第六个是抖动Dithering。当色深不够时用噪点模拟更多颜色避免色带。GIF 图片、低色深屏幕、硬件显示引擎都会用 Floyd-Steinberg 抖动。Dithering七、总结数字图像是连续光的量化采样最小单位是像素。 颜色基于人眼三色视觉用 RGB/YUV 表示受色域、色深、量化规则约束。 图像大小由分辨率、色深、色度亚采样决定4:2:0 是视频的通用语言。 像素在内存里的格式、对齐、排布直接决定画面是否正常显示。 画图靠光栅化图像处理靠混合、缩放、滤波、拷贝。 所有失真、锯齿、花屏、偏色、错位几乎都来自采样、格式、同步、内存规则。只要和像素打交道就离不开这套理论。到这里我们第一篇图形基础理论就完整收尾。当我们真正理解这些再去看 DRM、Wayland、Mesa、驱动代码就会发现所有设计、所有结构体、所有参数、所有流程全都能对应上。