画布控件
画布控件和画布控件渲染器是强大的多功能TouchGFX插件,在使用相对较小的存储空间的同时保持高性能,可提供平滑、抗锯齿效果良好的几何图形绘制。 但是,渲染几何图形必然是成本非常高的操作,如果使用不小心,很容易对微控制器资源造成浪费。
画布控件渲染器(Canvas Widget Renderer,以下简称CWR)是一种通用图形API,为图元提供优化绘制,自动消除最多余的绘制。 TouchGFX使用CWR可绘制复杂的几何图形。 通过画布控件(Canvas Widget)定义几何图形。 TouchGFX支持许多画布控件,但是就像普通控件一样,您可以创自定义画布控件来满足您的需求。 画布控件定义要通过CWR绘制的图形的几何形状,而图形中每个像素的实际颜色则由相关Painter类定义。 同样地,TouchGFX自带许多Painter,但是您也可以创建自定义Painter来满足您的需求。
使用画布控件
TouchGFX中其他控件的大小是自动设置的。 例如,可以自动获取位图控件的宽度和高度。 因此,在位图控件上使用 setXY()
将位图放置在显示屏上就足够了。
画布控件没有默认大小,其值既可以自动确定,也可以一开始就设置。 不仅要注意位置,还要正确地确定控件的大小,否则画布控件的宽度和高度将为零,并且不会在显示屏上绘制任何内容。
因此,不要使用 setXY()
,而是使用 setPosition()
来放置和调整画布控件的大小。 关于如何创建和使用自定义画布控件的示例另请参见下文Custom Canvas Widget部分。
在设置了画布空间的位置和大小后,可以在其内部绘制几何图形。 坐标系将使 (0, 0) 位于控件(不是显示屏)的左上角,X轴向右延伸且Y轴向下延伸。
TouchGFX设计器也支持“画布控件(Canvas Widget)”,让使用变得简单,可自动计算内存需求和自动分配内存。
TouchGFX设计器中提供的控件,基于CanvasWidget:
通过TouchGFX Designer使用这些控件时,可通过显示控件在运行时的状态,使得放置和大小调整非常简单。
存储空间分配和使用
为了生成反锯齿效果良好的复杂几何图形,需要额外的存储空间。 为此,CWR必须具有专门分配的存储缓冲区,以便在渲染过程中使用。 CWR与TouchGFX的其余部分一样,没有动态存储空间分配。
TouchGFX Designer中的存储空间分配
在向屏幕的画布添加控件时,会自动生成存储缓冲区。 缓冲区大小基于屏幕宽度,计算公式为 (宽度 × 3) × 5。 但是,这并非所有情况下的理想缓冲区大小。 因此,可以重写缓冲区大小,如下图所示。
用户代码中的存储空间分配
内存缓冲区可以在 target/main.cpp
和 simulator/main.cpp
中分配和设置,也可以按屏幕设置和分配。
static const uint16_t CANVAS_BUFFER_SIZE = 3600;
static uint8_t canvasBuffer[CANVAS_BUFFER_SIZE]
定义内存缓冲区大小的静态常量和实际内存缓冲区可以定义在main.cpp
或ScreenView.hpp
开头。
然后在 main.cpp
的 main()
方法或 ScreenView.cpp
的 setupScreen()
方法中可以添加以下设置缓冲区的行。
CanvasWidgetRenderer::setupBuffer(canvasBuffer, CANVAS_BUFFER_SIZE);
需要的CWR存储空间的量取决于要在应用中绘制的最大图形大小。 但是,您可以保留比最复杂形状所需内存空间更少的内存。 为了应对这种情况,CWR将图形绘制分割成较小的帧缓冲区部分,在这种情况下,由于有时需要不止一次地渲染图像,因此渲染时间稍长。 在模拟器模式下运行时,可以更细致地查看存储空间消耗并进行微调。 只需向main.cpp中添加以下函数:
CanvasWidgetRenderer::setWriteMemoryUsageReport(true);
现在,无论绘制操作何时结束,CWR都将报告(在控制台上打印)所需存储空间的大小。 对于canvas_widget_example,可以是“CWR需要3604字节”(第一个绘制操作),然后是“CWR需要7932字节(4328字节缺失)”(第二个绘制操作)。 尽管显示CWR没有足够存储空间(本例中为4328字节缺失),应用仍正常运行。 这是因为CWR检测到可用于完成一次运行中复杂绘制操作的存储空间太少。 为此,它将绘制操作分割成两项独立的绘制操作,可以很好地绘制图形,但需要更多时间渲染。
因此,设置正确的存储缓冲区大小以便在存储空间与性能(渲染时间)之间取得平衡。 好的起始值通常约为3000,但使用上述技巧通常可以确定更优值。 如果图形过于复杂且分配的存储缓冲区过小,将不绘制部分图形(一些垂直像素线会被跳过),并且有可能不绘制任何内容。 在任何情况下,渲染时间都会增加许多。
这意味着如果您希望应用以最快速度渲染CWR绘图,您需要满足分配请求的存储空间大小。 但是,如果能够接受更长的渲染时间,完全可以缩小存储缓冲区。
CWR坐标系
TouchGFX中的坐标系通常用于寻址像素,以便在显示屏上定位位图。 位图、文本和其他图形元素都位于坐标系中,其中 (0,0) 是左上角像素,X轴向右延伸,Y轴向下延伸。 在CWR中,能够使用整数寻址像素是不够的,尽管在特殊情况下已经足够,在一般情况下远远不够。 为了证明这一点,假设有一个线宽为1的圆,必须被精确地嵌入一个5x5像素的方块中。 此圆的中心必须位于 (2.5, 2.5),半径必须是2,因此中心坐标需为小数。 类似地,如果圆应嵌入一个6x6像素的方块,则中心必须位于 (3, 3),半径必须是2.5,因此半径需为小数。
这种新的图形绘制坐标寻址方式意味着 (0,0) 处像素的中心的CWR坐标为 (0.5, 0.5)。 因此,包含屏幕左上角像素的方块的轮廓如下:(0,0) -> (1,0) -> (1,1) -> (0,1) -> (0,0)。
尽管最初看起来令人困惑,但很快会发现这是十分自然的。 位图的坐标系寻址像素,画布控件的同一坐标系寻址像素之前有间隙。
因为圆形通常需要移动半个像素才能正确放置圆心,所以Circle::setPixelCenter()
函数会将圆心放置在给定像素的中心,也就是说,从指定的坐标再向右和向下移动半个像素 .
自定义画布控件
实现自定义画布控件需要用下列函数实现新类:
virtual bool drawCanvasWidget(const Rect& invalidatedArea) const;
virtual Rect getMinimalRect() const;
drawCanvasWidget()
必须绘制自定义控件需要绘制的任何内容,并且 getMinimalRect()
应该返回 Widget 中包含几何形状的实际矩形。
Note
getMinimalRect()
的原因在于可以在其控件内部到处移动几何图形,并且当形状变为仅使最小可能区域无效时,改变控件的大小和重新定位控件通常都不切实际。 函数 getMinimalRect()
的虚拟实现可能只 返回rect;
这是控件的大小,但这会导致被画布控件覆盖的整个区域的重新绘制,而不只是包含几何图形的画布控件的一部分。 几何图形通常只占据画布控件的一小部分。
画布控件全部使用Canvas类,它如上文所述压缩Canvas Widget Renderer。 CWR有许多自动应用的优化,尽管知道几何图形与无效区域有关,避免无效区域之外的不必要绘制,始终是一种提升性能的好方法。
在10x10方块内部粗略实现一个菱形块,方式可能像这样:
class Diamond10x10 : public CanvasWidget
{
public:
virtual Rect getMinimalRect() const
{
return Rect(0,0,10,10);
}
virtual bool drawCanvasWidget(const Rect& invalidatedArea) const
{
Canvas canvas(this, invalidatedArea);
canvas.moveTo(5,0);
canvas.lineTo(10,5);
canvas.lineTo(5,10);
canvas.lineTo(0,5);
return canvas.render(); // Shape is automatically closed
}
};
Note
getMinimalRect()
返回到正确矩形,否则屏幕上的图形可能是错误的。为了在显示屏上看到10x10Diamond,必须使用继承自 CanvasWidget 的 Diamond 10x10::setPainter()
设置颜色。 另外,必须正确地放置Diamond10x10并调整其大小。 方式可能像这样:
在头文件中声明
Diamond10x10 box;
PainterRGB565 myPainter;
并且代码中应有类似这样的内容:
myPainter.setColor(Color::getColorFromRGB(0xFF, 0x0, 0x0));
box.setPosition(100,100,10,10);
box.setPainter(myPainter);
add(box);
绘图器(#painters)
绘图器定义一个配色方案,用于填充‘Canvas Widget’对象,因此绘图器需要使形状可见。 绘图器可以为所有像素提供单一颜色(例如PainterRGB565
),或者从提供的位图中复制每个像素(例如PainterRGB565Bitmap
)。 由于绘图器直接将像素写入帧缓存区,因此所选的绘图器必须匹配目标显示屏或动态位图的规范。 TouchGFX提供的绘图器面向所有支持的显示屏,专门用于纯色或位图绘制。
从位图绘制像素的绘图器也可以绘制平铺的位图,这有助于减少内存需求。
定制绘图器
尽管TouchGFX提供一组预定义的绘图器类,涵盖了大多数用例场景,但也可以实现定制绘图器。
为了实现定制的绘图器,必须注意永远不要在帧缓存区之外进行写操作(这是由抽象绘图器基类处理的)。 这是一个自定义 Painter 的示例,我们将使用它来将对象绘制为红色,只需要实现函数 renderNext()
。 更多信息见AbstractPainter。
class RedPainter : public AbstractPainterRGB565
{
public:
virtual bool renderNext(uint8_t &red, uint8_t &green, uint8_t &blue, uint8_t &alpha)
{
red = 0xFF;
green = 0x00;
blue = 0x00;
alpha = 0xFF;
}
};
为了在红色上方画一个方块对象,将以下代码放入头文件:
Diamond10x10 box;
RedPainter redPaint;
并将以下内容放入代码:
box.setPosition(100,100,10,10);
box.setPainter(redPaint);
add(box);
请注意,可以使用更多方法来创建特殊Painters,例如 renderInit()
,然而,TouchGFX 有一些通用的Painters,涵盖了大多数应用。