跳转到主要内容

画布控件

画布控件和画布控件渲染器是强大的多功能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 Designer中也支持画布控件,使用简单,可自动存储空间分配。

TouchGFXDesigner中基于画布控件的控件:

通过TouchGFX Designer使用这些控件时,可通过显示控件在运行时的状态,使得放置和大小调整非常简单。

存储空间分配和使用

为了生成反锯齿效果良好的复杂几何图形,需要额外的存储空间。 为此,CWR必须具有专门分配的存储缓冲区,以便在渲染过程中使用。 CWR与TouchGFX的其余部分一样,没有动态存储空间分配。

TouchGFX Designer中的存储空间分配

在向屏幕的画布添加控件时,会自动生成存储缓冲区。 缓冲区大小基于屏幕宽度,计算公式为 (宽度 × 3) × 5。 但是,这并非所有情况下的理想缓冲区大小。 因此,可以重写缓冲区大小,如下图所示。

在屏幕属性中重写画布缓冲区大小

用户代码中的存储空间分配

内存缓冲区可以在 target/main.cppsimulator/main.cpp 中分配和设置,也可以按屏幕设置和分配。

static const uint16_t CANVAS_BUFFER_SIZE = 3600;
static uint8_t canvasBuffer[CANVAS_BUFFER_SIZE]

定义内存缓冲区大小的静态常量和实际内存缓冲区可以定义在main.cppScreenView.hpp开头。

然后在 main.cppmain() 方法或 ScreenView.cppsetupScreen() 方法中可以添加以下设置缓冲区的行。

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)。

(0,0) 处像素的CWR坐标系

尽管最初看起来令人困惑,但很快会发现这是十分自然的。 位图的坐标系寻址像素,画布控件的同一坐标系寻址像素之前有间隙。

自定义画布控件

实现自定义画布控件需要用下列函数实现新类:

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::getColorFrom24BitRGB(0xFF, 0x0, 0x0));
box.setPosition(100,100,10,10);
box.setPainter(myPainter);
add(box);

Painter

Painter定义用于填充画布控件对象的颜色方案。 TouchGFX自带一组预定义Painter类,但可以轻松实现自定义Painter。

为了实现自定义 Painter,必须注意不要在帧缓冲区之外写入。 自定义 Painter 中的此类错误可能导致严重地崩溃。 这是一个自定义 Painter 的示例,我们将使用它来将对象绘制为红色,只需要实现函数 renderNext()。 更多信息见AbstractPainter。

class Red : 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;
Red redPaint;

并将以下内容放入代码:

box.setPosition(100,100,10,10);
box.setPainter(redPaint);
add(box);

请注意,可以使用更多方法来创建特殊Painters,例如 renderInit(),然而,TouchGFX 有一些通用的Painters,涵盖了大多数应用。