找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1739

积分

0

好友

225

主题
发表于 12 小时前 | 查看: 2| 回复: 0

不知道大家是否还记得之前分享过的一篇文章:如何在STM32C0旋钮屏上设计并制作TouchGFX GUI? 当时那个演示版的流畅度只能说是差强人意,拿给家里人体验时,那种明显的卡顿感立刻就被吐槽了。

说实话,即便界面布局和色彩搭配得再精致,上手操作时一卡一顿的感觉,立刻就暴露了资源有限硬件的短板,满满的廉价感扑面而来。

不过好在,我从 TouchGFX 官方文档里找到了不少实用的优化思路,又结合了原厂工程师在原版演示中的设计巧思做了针对性调整。现在新版演示的流畅度有了肉眼可见的提升,在体验上已经能和 STM32H7 搭配 DMA2D 渲染的同款 UI 不相上下了!

接下来,我将通过三篇文章分享这些优化思路,并提供可以落地的实操步骤:

  • 上篇:介绍图像的 L8 格式以及 RLE 压缩方式,以及如何将经过压缩的图像部署到 STM32C0 的 TouchGFX 应用层。
  • 中篇:带领大家使用设计好的 L8 图像重构过往文章中设计的旋转菜单,并添加必要的编译插件实现图像自动压缩,让 GUI 变得更流畅。
  • 下篇:介绍在 TouchGFX 中设计两种流畅的遮罩效果,为大家带来一些新的思考和启示。

01 图像的 L8 格式及其压缩

1.1 RGB色彩模式

在认识 L8 色彩格式前,我们需要先了解什么是 RGB 色彩模式。

RGB 色彩模式是工业界的一种颜色标准,通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色。这个标准几乎涵盖了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。常见的模式包括 RGB16(如 RGB555、RGB565)、RGB24(RGB888)、RGB32(ARGB8888),分别代表使用 16 位、24 位、32 位来表示一个像素。在 TouchGFX 中,我们常接触的是 RGB565ARGB8888

  • RGB565:常见于不透明图像,使用 16 位表示一个像素。这 16 位中,5 位用于红色(R),6 位用于绿色(G),5 位用于蓝色(B),故称 RGB565。
  • ARGB8888:常见于带有透明度(Alpha通道)的图像,使用 32 位表示一个像素。红、绿、蓝分量各占 8 位,另外 8 位用作 Alpha 通道来表示不透明度,因此可以支持透明或半透明图像。

1.2 L8格式

在 TouchGFX 官方的文档中,对于 L8 的解释可能不够清晰。在嵌入式 GUI 场景下,L8 指的是 8 位调色板索引 CLUT(Color Look-Up Table,颜色查找表)格式

这时就有朋友要问了:我们为什么要设计 L8 格式?这就需要先了解它的工作原理。

L8 的整套工作流由两部分构成:像素数据调色板。按照传统的 RGB 色彩模式来理解,像素数据就是色彩数据本身?错!在 L8 中,每个像素的数据仅占 8 位(1 字节),这个值并非直接代表 RGB 颜色,而是代表调色板数组的索引

所谓的调色板,就是一个最多包含 256 个颜色项的数组,其中的每个颜色项可以是 RGB565、RGB888 或 ARGB8888 等任意格式的真实颜色值。这样一来,显示的流程就变了:GUI 渲染时,先读取像素的索引值,然后根据这个索引到调色板中查找对应的真实颜色,最后再输出到 LCD 显示。这种方法使得图像本身的存储体积大大降低!

让我们来直观对比一下不同格式下,一张 240x240 分辨率图像的理论大小:

L8与RGB格式图像体积对比表

可见,L8 对于图像体积的压缩效果是巨大的。

于是聪明的你又要问了:那么代价是什么呢?代价就是图像的色彩种类会被严格限制在 256 种以内,否则就无法被成功压缩为 L8 图像!让我们举个例子,请看下图:

一个蓝色梯形灯罩图标

这是一张没有 Alpha 通道的 117×150 大小图像,是我使用 Photoshop 制作的。它保留了色彩梯度,以 #000000 为背景色,#00a2e8 为前景色。大家猜猜,这张图能直接压缩成 L8 格式吗?

答案是:不能。为什么明明只有黑色和蓝色两种颜色却不能被压缩?请注意,在制图时我保留了“色彩梯度”,且以黑色为背景。当我们绘制前景的几何图案时,几何边缘线段的倾斜会导致 PS 软件内部的抗锯齿(AA)算法自动运行,在边缘生成了介于前景色和背景色之间的过渡色(色彩梯度)。这些新生成的颜色使得该图像实际使用的色彩种类超出了 CLUT 的 256 色限制。因此,L8 图像的应用场景在嵌入式 GUI 中较多,且一般用于制作按钮、图标等色彩相对单一的动态控件图像。

1.3 L8图像的制作

那么,我们应该如何制作适合 L8 格式的图标图像呢?经验告诉我主要有两种方法:

  1. 取消抗锯齿制图:这是最直接的方法,从源头上杜绝新颜色的产生。
  2. 对称设计:如果必须开启抗锯齿,应尽可能使图像对称,这样可以减少因抗锯齿而产生的新颜色种类。

在制作 L8 图像时,应使用 8 位色彩空间,并尽可能让图像由纯色块堆叠而成。即使成功将带有复杂色彩梯度的图像转换成了 L8 格式,色彩通常也会有非常严重的分层现象,可用性大大降低。下图为我正在使用 Photoshop 设计本次演示要用到的图标。

Photoshop设计L8图标界面

在设计并制作图标时,推荐在未定稿前保留几何元素的“矢量”属性(如形状图层),不要过早地将图层栅格化,因为这可能导致更严重的色彩越界问题。

按照上述注意事项设计出的 GUI 图标,就可以在导出后通过 RLE 进行高效压缩了。以下是我为本次演示制作的一组图标示例。

一组设计好的L8格式图标

1.4 RLE压缩

针对这种由大面积相同颜色区块构成的图标图片,采用 RLE 压缩是非常有效的。那么什么是 RLE 压缩呢?

或许大家在初学编程时就已经接触过字符串压缩的小案例了。是的,RLE 就是其中最简单的 Run-Length Encoding(行程长度编码),属于无损压缩。

具体举个例子:一个未压缩的 L8 图像,每个像素是 1 字节的索引值。如果连续 100 个像素都是黑色(假设索引为 0),那么存储为:0, 0, 0, ..., 0 (共100字节)。

但是在 RLE 压缩后,存储格式变为“重复次数(1字节),索引值(1字节)”,即:100, 0。仅用 2 个字节就表示了原来 100 字节的内容,压缩比极高。

在新版本的 TouchGFX Designer 中,已经集成了 L8 + RLE 的压缩选项,甚至在 RLE 之外,还提供了 L4、LZW9 等其他压缩方式。

TouchGFX Designer中的压缩选项菜单

02 L8图像在STM32C0上的部署

由于我们使用的是官方开发套件,可以直接通过 TouchGFX Board Setup (TBS) 来创建新工程。

TouchGFX Designer新建工程界面

进入工程后,添加我们制作好的、准备转为 L8 格式的图标图片。

TouchGFX Designer添加图像按钮

图像资源管理列表

根据 TouchGFX 文档,L8 图像格式需要在 Image Format 列中设置。这里我们选择体积最小的 L8_RGB565 格式。

设置图像格式为L8_RGB565

一个关键设置:L8 图像的 Extra Section 必须设置为 IntFlashSection(内部 Flash),否则 L8 图像将无法被正确渲染。

设置L8图像的Extra Section为IntFlashSection

对于不需要设置为 L8 格式的图像,可以选择标准的 RGB 色彩模式,例如 RGB565

设置非L8图像为RGB565格式

当然,我们也可以直接在项目的 application.config 文件中直接指定所有图像的配置。这里以 info.png 的配置为例:

{
  "image_configuration": {
    "dither_algorithm": "2",
    "alpha_dither": "yes",
    "layout_rotation": "0",
    "opaque_image_format": "RGB565",
    "nonopaque_image_format": "ARGB8888",
    "l8_compression": "no",
    "rgb_compression": "no",
    "section": "ExtFlashSection",
    "extra_section": "ExtFlashSection",
    "images": {
      "info.png": {
        "format": "L8_RGB565",
        "extra_section": "IntFlashSection"
      }
    }
  }
}

配置文件的上半部分是全局默认配置,images 对象内则是针对每张图片的详细配置。如果某张图片未在 images 中提及,TouchGFX 会按照上半部分的全局默认配置来处理它。

接着,我们尝试在界面上添加一个 Image 控件,关联我们设置好的 L8 图片,生成代码后烧录到设备,查看图片是否被正确渲染。

在TouchGFX Designer中放置图像控件

STM32C0旋钮屏上正确显示的L8图像

可以看到,图像显示正确。

但是,请注意,事情并没有这么简单就结束了。当前这个版本的 TBS 模板存在一个已知问题:在这个套件的 TBS 工程中,L8 图像如果被放置到 Container(容器)控件内部,是无法被渲染出来的。 不过,这个问题经过我们手动调整项目代码后是可以解决的。

如何解决这个问题,并利用 L8 和 RLE 压缩,最终让整个 GUI 流畅地跑起来?我们将在中篇文章里详细分享。欢迎持续关注,在云栈社区与我们一同探讨更多嵌入式开发的实战技巧。





上一篇:MIT研究揭示AI系统偏见:语言与学历导致LLM回答质量差异与信息不平等
下一篇:华为防火墙Ping与Tracert配置指南:原理、策略与排错
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-26 19:09 , Processed in 0.485268 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表