GFX 深入:第二部分 - 电子墨水屏





5.00/5 (3投票s)
有效利用 GFX 处理电子纸/电子墨水显示屏
引言
请注意,本文没有直接关联的代码,因为它已包含在上述链接的主代码库中。
这是我的 GFX 深入剖析系列文章的第二部分。第一部分 在这里。
电子纸显示屏是现代电子设备,它们使用真实的墨水来渲染图形和文本,就像普通的喷墨打印机一样。然而,与打印机不同的是,它们可以无限次重复使用同一表面,使其能够用作电子阅读器等设备的显示屏。也就是说,这些显示屏的特性与传统的 TFT、LCD 或 OLED 显示屏大不相同,因此使用它们需要格外小心。
首先,电子纸显示屏通常是单色的,黑底白字。大多数显示屏无法实现灰度,因为与基于光的显示屏不同,您无法简单地改变颜色的强度。
其次,彩色显示屏通常价格昂贵,并且颜色选择非常有限,通常只有 3 种颜色,对于那些真正花哨(读作昂贵)的显示屏,我最多见过 7 种颜色。无法混合所使用的墨水,因此您无法将黄色和蓝色混合来生成绿色,例如。
最后,显示刷新率可能需要几秒钟,尤其是彩色显示屏。这些显示屏无法进行动画处理,在使用 GFX 时必须格外小心,以避免因刷新率慢而导致的性能问题。
概念
我们将使用我目前编写的两种电子纸显示屏的驱动程序。GFX 的电子纸显示屏驱动程序可以通过*抖动*(dithering)来虚拟化灰度和颜色混合,抖动是一种通过混合相邻像素的颜色来欺骗眼睛的技术。例如,与其显示一系列灰色像素,不如显示一系列交替的黑白像素来欺骗眼睛,使其看到一条灰蒙蒙的线。颜色也以同样的方式工作。要绘制紫色,您将交替使用红色和蓝色。
在 GFX 中,这通过虚拟化比设备原生支持的“更深”的像素类型来实现。例如,与其暴露单色像素 (gsc_pixel<1>
),不如将其扩展为灰度,例如 gsc_pixel<4>
或 gsc_pixel<8>
。黑白显示屏仅允许灰度虚拟化,而彩色显示屏可以虚拟化除索引像素以外的任何像素类型。
这项功能使我的电子纸驱动程序在显示功能方面成为同类产品中的佼佼者。如果还有其他支持物联网(IoT)的驱动程序,我并不知情。
成本是内存使用量和速度,尤其是对于彩色显示屏。您的帧缓冲区在内存中,其大小基于虚拟化像素,这意味着如果您在 3 色电子纸显示屏上虚拟化 16 位颜色,最终帧缓冲区的大小将是原来的* 8 倍*!因此,当您使用虚拟化时,尤其是在彩色驱动程序上,除非您拥有 ESP32 WROVER 8MB 或类似设备,否则您将没有太多 RAM 用于其他任何事情。
即使您通过虚拟化选择退出抖动,GFX 在转换为索引像素格式时始终执行最近色匹配到调色板,这包括彩色电子纸显示屏调色板。因此,在一个黑、白、红显示屏上,如果您加载一张包含红色的 JPEG 图片,JPEG 图片中的红色部分将在显示屏上显示为红色。
让我们来看看实际效果
这是一幅著名的沃霍尔作品的打印图,我们将将其加载到一个黑、白、红三色显示屏上
请原谅我杂乱不堪的工作台,但这是在没有通过虚拟化进行抖动的 3 色显示屏上显示的图像
这张图是通过虚拟化 16 位 RGB 颜色像素进行抖动处理后的同一张图像
最后,让我们看看在带抖动的黑白显示屏上的安迪·沃霍尔的脸,这样您就可以看到灰度效果如何
看到了他脸部的细节吗?如果没有虚拟化,您只能看到太阳镜和嘴巴。
使用虚拟化
我已经尽力使其尽可能简单。您的代码只需要一个更改即可支持这些功能,那就是在驱动程序的类型实例化中
// instantiate a color e-paper driver with
// virtualization
using lcd_type = gdeh0154z90<PIN_NUM_CS,
PIN_NUM_DC,
PIN_NUM_RST,
PIN_NUM_BUSY,
rgb_pixel<16>>;
请注意,最后一个参数指定了 16 位 RGB 像素类型。指定像素类型是启用抖动虚拟化的方式。如果您没有指定此参数,则只会使用最近色匹配。
对于黑白驱动程序,情况略有不同,因为您不能指定任意像素类型。在这种情况下,您只需将位深度指定为最后一个参数。例如,指定 8 将通过虚拟化/抖动获得 256 色灰度。
// instantiate a black and white e-paper
// driver with virtualization
using lcd_type = depg0290b<PIN_NUM_CS,
PIN_NUM_DC,
PIN_NUM_RST,
PIN_NUM_BUSY,
8>;
之后,您只需像往常一样使用它们,只是现在您可以有效地访问更多的颜色和灰度。
处理刷新
电子纸显示屏的刷新速度不快。因此,强烈建议您在绘制时暂停,并在完成整个帧的绘制后再恢复。
draw::suspend(lcd);
draw::filled_rectangle(lcd,(srect16)lcd.bounds(),lcd_color::white);
rect16 image_bounds(0,0,335,255);
rect16 crop_bounds(0,0,127,127);
File fs = SPIFFS.open("/image3.jpg");
draw::image(lcd,(srect16)crop_bounds,&fs,crop_bounds.center(image_bounds));
fs.close();
fs = SPIFFS.open("/image3.jpg");
draw::image(lcd,{0,lcd.height-128,127,lcd.height-1},&fs,crop_bounds.center(image_bounds));
fs.close();
const font& f = Bm437_Acer_VGA_8x8_FON;
const char* text = "GFX Demo by\r\n honey the\r\n codewitch";
ssize16 fd=f.measure_text({128,128},text);
draw::text(lcd,fd.bounds().center((srect16)lcd.bounds()),text,f,lcd_color::black);
draw::resume(lcd);
您注意到绘图调用被 suspend<>()
和 resume<>()
调用包围了吗?如果这样做,显示屏将在尝试渲染该帧时刷新多次。
关注点
在 GFX 的底层,实现了 3 种抖动算法,您可以将它们用于自己的代码中。我在这里不介绍如何使用它们,因为它们基本上是实现细节,但您可以查看使用这些代码的驱动程序,了解如何自己操作。
历史
- 2021 年 6 月 18 日 - 首次提交