주요 내용으로 건너뛰기

캔버스 위젯

캔버스 위젯(Canvas Widget)과 캔버스 위젯 렌더러(Canvas Widget Renderer)는 TouchGFX에 추가되는 기능으로서 높은 성능을 유지하면서 비교적 적은 메모리를 사용하여 기하학적 형상의 부드럽고 안티앨리어싱(anti-aliasing)된 그리기를 제공합니다. 하지만 기하학적 형상 렌더링은 매우 복잡한 작업일 수 있어서 주의를 기울이지 않으면 마이크로컨트롤러 리소스가 쉽게 고갈될 수 있습니다.

캔버스 위젯 렌더러(이하 CWR)는 일반적인 그래픽 API로서 가장 불필요한 그리기 작업을 자동으로 제거하여 프리미티브에 최적화된 그리기 작업을 제공합니다. CWR은 TouchGFX에서 복잡한 기하학적 형상을 그리기 위한 목적으로 사용됩니다. 기하학적 형상은 캔버스 위젯에서 정의합니다. TouchGFX는 다수의 캔버스 위젯을 지원하지만 일반 위젯과 마찬가지로 사용자의 요구사항에 따라 커스텀 캔버스 위젯을 만들 수 있습니다. 캔버스 위젯은 CWR에서 그려질 그림의 기하학적 형상을 정의하지만 그림에서 각 픽셀의 실제 색상은 관련된 Painter 클래스에서 정의합니다. 마찬가지로 TouchGFX에는 다수의 Painter가 제공되지만 사용자의 요구사항에 따라 커스텀 Painter를 만들 수 있습니다.

캔버스 위젯 사용

TouchGFX에서 다른 위젯은 크기가 자동으로 설정됩니다. 예를 들어 비트맵 위젯은 포함된 비트맵의 가로, 세로 길이를 자동으로 가져옵니다. 따라서 비트맵 위젯에서 setXY() 함수를 사용해 비트맵을 디스플레이에 배치해도 충분합니다.

캔버스 위젯에는 자동 측정을 통해 처음부터 설정할 수 있는 기본 크기가 없습니다. 따라서 위젯의 위치는 물론이고 크기까지 정확하게 고려해야 합니다. 그렇지 않으면 캔버스 위젯의 가로, 세로 길이가 0이 되어 디스플레이에 아무것도 그려지지 않습니다.

따라서 setXY() 메소드 대신 setPosition() 메소드를 사용해 캔버스 위젯을 배치하고 크기를 설정합니다. 또한 커스텀 캔버스 위젯을 생성하여 사용하는 방법에 대한 예로서 아래 커스텀 캔버스 위젯을 참조하십시오.

캔버스 위젯의 위치와 크기 설정을 마쳤으면 이제 기하학적 형상이 위젯 내에 그려질 수 있습니다. 좌표계는 위젯(디스플레이 아님)의 왼쪽 상단 모퉁이가 (0, 0)이 되어 X 축이 오른쪽으로, 그리고 Y 축이 아래로 연장됩니다.

캔버스 위젯은 TouchGFX Designer에서도 지원되며, 사용이 간단할 뿐만 아니라 메모리 요구 사항을 자동으로 계산하고, 메모리도 자동으로 할당됩니다.

TouchGFX Designer에서 사용 가능한 캔버스 위젯:

TouchGFX Designer에서 위의 위젯을 사용하면 런타임에서 위젯의 모습을 확인할 수 있어서 위치 및 크기 조정이 더 쉬워집니다.

메모리 할당 및 사용

멋진 안티앨리어싱된 복잡한 기하학적 형상을 생성하기 위해서는 추가적인 메모리가 필요합니다. 이를 위해 CWR에는 렌더링 과정에서 사용할 별도의 메모리 버퍼가 필요합니다. 나머지 TouchGFX와 마찬가지로 CWR에도 동적 메모리 할당 기능이 없습니다.

TouchGFX Designer의 메모리 할당

위젯을 스크린의 캔버스에 추가하면 메모리 버퍼가 자동으로 생성됩니다. 이때 버퍼의 크기는 (가로 길이 x 3) x 5 공식에 들어가는 스크린의 가로 길이에 따라 달라집니다. 하지만 이 공식으로 결정되는 버퍼 크기가 모든 시나리오에 적합한 것은 아닙니다. 따라서 아래 이미지와 같이 버퍼 크기를 재정의할 수 있습니다.

스크린 속성에서 재정의하는 캔버스 버퍼 크기

Note
Canvas Widgets을 사용하지 않는 화면의 경우, 위에서 설명한 재정의 기능을 사용해 메모리 버퍼를 생성하는 것도 가능합니다. 이는 User Code에서 Canvas Widgets을 생성할 때 유용합니다.

사용자 코드의 메모리 할당

Canvas Widgets을 사용하는 화면에서 TouchGFX Designer를 사용해 메모리 버퍼를 할당하지 않는 경우에는 수동으로 설정해야 합니다. Screen::setupScreen 메서드에서 이 작업을 수행하는 것이 좋습니다.

이를 Screen 클래스 정의에 개인 멤버로 추가합니다.

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

그런 다음, ScreenView.cppsetupScreen() 메서드에서 다음과 같이 버퍼를 설정하는 라인을 추가할 수 있습니다.

void ScreenView::setupScreen()
{
...
CanvasWidgetRenderer::setupBuffer(canvasBuffer, CANVAS_BUFFER_SIZE);
...
}

그리고 ScreenView.hpp의 소멸자 ~ScreenView()에서는 다음과 같이 버퍼를 재설정하는 줄을 추가할 수 있습니다.

virtual ~ScreenView()
{
touchgfx::CanvasWidgetRenderer::resetBuffer();
}

필요한 CWR 메모리 용량은 애플리케이션에서 그리기 작업을 할 모양의 최대 크기에 따라 결정됩니다. 하지만 예약할 수 있는 메모리는 가장 복잡한 형상일 때 필요한 용량보다 적습니다. CWR은 이러한 상황에 대처하기 위해 형상 그리기 작업을 더 작은 프레임 버퍼 영역으로 분할하는데, 이 경우 형상을 한 번 이상 렌더링 해야 할 수도 있기 때문에 렌더링 시간이 다소 길어지게 됩니다. 시뮬레이터 모드에서 실행할 때 메모리 사용량을 더욱 자세히 분석하여 정밀하게 조정할 수 있습니다. 방법은 간단합니다. 다음 함수 호출을 main.cpp에 추가하기만 하면 됩니다.

CanvasWidgetRenderer::setWriteMemoryUsageReport(true);

이후부터는 그리기 연산을 마칠 때마다 CWR이 필요했던 메모리 용량을 보고합니다(콘솔로 출력합니다). canvas_widget_example의 경우, 첫 번째 드로잉 연산에서 “CWR requires 3604 bytes”라고 출력되고 이어서 두 번째 그리기 연산에서 “CWR requires 7932 bytes (4328 bytes missing)”라고 출력될 수 있습니다. 두 번째에서 CWR에 메모리가 부족한 것으로(이 경우 4328바이트가 부족함) 표시되더라도 애플리케이션은 정상적으로 실행됩니다. 이는 단일 실행으로는 복잡한 그리기 연산을 완료하는 데 필요한 메모리가 부족하다는 것을 CWR이 감지하기 때문입니다. 이에 따라 그리기 연산을 2회로 따로 분할하고 형상은 정상적으로 그려지게 되지만 렌더링 시간은 좀 더 길어지게 됩니다.

따라서 올바른 메모리 버퍼 크기 설정은 메모리와 성능(렌더링 시간)을 서로 절충한 결과입니다. 처음에 시작할 때 유효한 값은 약 3000입니다. 하지만 위의 기법을 사용하면 대체로 더 나은 값을 지정할 수 있습니다. 형상이 너무 복잡하고, 할당된 메모리 버퍼의 크기가 너무 작으면 일부 형상이 그려지지 않으며(일부 세로 픽셀 라인을 건너뜀), 심지어 아무것도 그리지 않을 수도 있습니다. 어떤 경우가 되었든 렌더링 시간이 크게 늘어납니다.

이 말은 애플리케이션에서 CWR 드로잉을 최대 속도로 렌더링하려면 요청에 따라서 메모리 용량을 할당해야 한다는 것을 의미합니다. 하지만 더욱 느린 렌더링 타이머를 사용한다면 메모리 버퍼를 줄일 수 있는 방법으로 손색이 없습니다.

CWR 좌표계

TouchGFX의 좌표계는 일반적으로 디스플레이에서 비트맵 위치를 결정할 목적으로 픽셀을 처리하는 데 사용됩니다. 비트맵, 텍스트, 기타 그래픽 요소가 모두 좌표계에 배치됩니다. 여기에서 (0, 0)은 왼쪽 상단 픽셀에 해당하며, x-축은 오른쪽으로, y-축은 아래로 연장됩니다. CWR에서는 정수만으로 픽셀을 처리하지 못합니다. 특별한 경우에는 충분할 수도 있지만 일반적으로는 부족합니다. 이해를 돕기 위해 선 폭이 1인 원이 있으며, 이 원이 5 x 5 픽셀의 박스 안에 정확하게 맞아야 한다고 가정하겠습니다. 원의 중심은 (2.5, 2.5)이고, 반경은 2가 되어야 하기 때문에(원주의 양쪽에서 0.5 바깥쪽으로 라인이 드로잉) 중심 좌표에 분수가 필요합니다. 마찬가지로 원이 6 x 6 픽셀의 박스에 정확하게 맞아야 한다면 중심은 (3, 3)이고, 반경은 2.5가 되어야 합니다. 따라서 여기서는 반경에 분수가 필요합니다.

그래픽을 그리기 위한 이러한 좌표 지정 하는 새로운 방법은 (0,0) 픽셀에서는 중심이 CWR 좌표 (0.5, 0.5)에 해당하는 것을 의미합니다. 따라서 스크린의 왼쪽 상단 모퉁이에 픽셀이 포함된 박스는 다음과 같은 윤곽선을 갖습니다. (0,0) -> (1,0) -> (1,1) -> (0,1) -> (0,0).

(0, 0) 픽셀에 해당하는 CWR 좌표계

이러한 방법이 처음에는 혼란스러울 수도 있지만 곧 익숙해지게 됩니다. 비트맵 좌표계가 픽셀을 처리하는 경우에는 동일한 캔버스 위젯 좌표가 픽셀 바로 앞과 위의 간극을 처리합니다.

원은 중심을 올바르게 배치하기 위해 종종 반 픽셀 이동이 되어야 하는 형상이기 때문에 Circle::setPixelCenter() 함수는 해당 픽셀의 중심에 원 중심을 배치하게 됩니다. 즉, 지정된 좌표와 비교해 오른쪽 아래로 반 픽셀 더 이동시킵니다.

커스텀 캔버스 위젯

커스텀 캔버스 위젯을 구현하려면 다음 함수를 사용해 새로운 클래스를 구현해야 합니다.

virtual bool drawCanvasWidget(const Rect& invalidatedArea) const;
virtual Rect getMinimalRect() const;

위에서 drawCanvasWidget() 함수는 커스텀 위젯에 필요한 것을 그려야 하고, getMinimalRect() 함수는 기하학적 형상이 포함되는 위젯에 실제 직사각형을 반환해야 합니다.

Note
위에서 getMinimalRect() 함수가 필요한 이유는 기하학적 형상이 위젯 내에서 움직일 수 있기 때문입니다. 또한 형상이 바뀔 때마다 최소 영역을 무효화할 목적으로 위젯의 크기와 위치를 조정하는 것은 대체로 실용적이지 않습니다.

여기서 getMinimalRect() 함수의 더미 구현체는 return rect;, 즉 위젯의 크기만 반환할 수 있지만 캔버스 위젯이 다시 그려지게 되어 기하학적 형상이 포함된 영역이 아닌 전체 영역을 가리게 됩니다. 기하학적 형상은 대체로 캔버스 위젯의 일부 영역만 차지하기 때문입니다.

캔버스 위젯은 모두 캔버스 클래스를 사용합니다. 이 캔버스 클래스는 위에서 설명했듯이 CWR을 압축합니다. CWR 은 여러 가지 최적화 기능이 자동적으로 적용되지만 무효화 영역된 영역과 관련된 기하학적 형상을 인식하여 무효화 영역 외부에서 불필요하게 그려지는 것을 피하는 것이 성능을 향상시키는데 효과적입니다.

예를 들어 10 x 10 박스 안에 다이아몬드 형상의 사각형을 대략적으로 구현하면 다음과 같습니다.

#include <touchgfx/widgets/canvas/CanvasWidget.hpp>
#include <touchgfx/widgets/canvas/Canvas.hpp>

using namespace touchgfx;

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() 함수가 직사각형을 정확하게 반환하는지 주의해서 살펴야 합니다. 그렇지 않으면 스크린에 그래픽이 잘못 표시될 수 있습니다.

디스플레이에서 Diamond10x10을 보려면 Painter를 해당 다이아몬드 형상으로 전달해서 다음 색상을 설정해야 합니다. Painter에 대한 자세한 내용은 다음 섹션을 참조하십시오. 또한 Diamond10x10의 위치와 크기가 정확해야 합니다. 결과적으로 다음과 같은 모습이 될 수 있습니다.

헤더 파일에서 아래와 같이 선언합니다.

Diamond10x10 box;
PainterRGB565 myPainter; // For 16bpp displays

그러면 코드의 setupScreen()가 다음과 같은 모습이 됩니다.

myPainter.setColor(Color::getColorFromRGB(0xFF, 0x0, 0x0));
box.setPosition(100,100,10,10);
box.setPainter(myPainter);
add(box);

Painter

Painter는 캔버스 위젯 객체를 채우는 채색 방식을 정의하기 때문에 가시적 형상을 만드는 데 필요합니다. Painter는 모든 픽셀에 단일 색상을 제공하거나(예: PainterRGB565), 혹은 제공되는 비트맵에서 각 픽셀을 복사할 수 있습니다(예: PainterRGB565Bitmap). Painter는 픽셀을 프레임버퍼에 직접 쓰기 때문에 선택한 Painter는 프레임버퍼 또는 동적 비트맵의 형식과 일치해야 합니다. TouchGFX는 불투명 색상 전용, 즉 비트맵을 그려야 하는 Painter를 포함해 지원되는 모든 디스플레이에 적합한 Painter와 함께 제공합니다.

Painter 클래스

다음 표에는 TouchGFX에서 사용 가능한 Painter가 나와 있습니다. TouchGFX Designer와 함께 Canvas Widget을 사용하면 TouchGFX Designer가 올바른 Painter를 선택하게 됩니다. 단, Canvas Widget을 사용하는 코드를 직접 작성하는 경우에는 적절한 Painter를 선택해야 합니다.

프레임버퍼 형식색상 Painter비트맵 Painter
BWPainterBWPainterBWBitmap
GRAY2PainterGRAY2PainterGRAY2Bitmap
GRAY4PainterGRAY4PainterGRAY4Bitmap
ABGR2222PainterABGR2222PainterABGR2222Bitmap
ARGB2222PainterARGB2222PainterARGB2222Bitmap
BGRA2222PainterBGRA2222PainterBGRA2222Bitmap
RGBA2222PainterRGBA2222PainterRGBA2222Bitmap
RGB565PainterRGB565PainterRGB565Bitmap, PainterRGB565L8Bitmap
RGB888PainterRGB888PainterRGB888Bitmap, PainterRGB888L8Bitmap
ARGB8888PainterARGB8888PainterARGB8888Bitmap, PainterARGB8888L8Bitmap
XRGB8888PainterXRGB8888PainterXRGB8888Bitmap, PainterXRGB8888L8Bitmap

비트맵 Painter는 다양한 비트맵 형식을 지원합니다.

Painter지원되는 비트맵 형식
PainterBWBitmapBW, BW_RLE
PainterGRAY2BitmapGRAY2
PainterGRAY4BitmapGRAY4
PainterABGR2222BitmapABGR2222
PainterARGB2222BitmapARGB2222
PainterBGRA2222BitmapBGRA2222
PainterRGBA2222BitmapRGBA2222
PainterRGB565BitmapRGB565, ARGB8888
PainterRGB565L8BitmapL8_RGB565, L8_RGB888, L8_ARGB8888
PainterRGB888BitmapRGB888, ARGB8888
PainterRGB888L8BitmapL8_RGB565, L8_RGB888, L8_ARGB8888
PainterARGB8888BitmapRGB565, RGB888, ARGB8888
PainterARGB8888L8BitmapL8_RGB565, L8_RGB888, L8_ARGB8888
PainterXRGB8888BitmapRGB565 (no transparency), RGB888, ARGB8888
PainterXRGB8888L8BitmapL8_RGB565, L8_RGB888, L8_ARGB8888

타일링된 비트맵

비트맵에서 픽셀을 그리는 Painter는 Canvas Widget의 왼쪽 상단에 비트맵을 배치합니다. 비트맵 치수를 벗어난 형상의 픽셀은 드로잉이 되지 않습니다.

전체 형상을 덮을 수 있게 위젯(타일링된)을 반복하도록 비트맵 Painter를 구성할 수 있습니다.

Painter에서 setTiled(bool) 메서드를 호출하면 타일링이 활성화됩니다.

    PainterRGB888Bitmap bitmapPainter;
...
bitmapPainter.setBitmap(touchgfx::Bitmap(BITMAP_BLUE_LOGO_TOUCHGFX_LOGO_ID));
bitmapPainter.setTiled(true);

TouchGFX Designer에서는 타일링을 사용할 수 없습니다.

Image, 비트맵 Painter가 포함된 Circle, 타일링 비트맵 Painter가 포함된 Circle을 보여주는 애플리케이션입니다.

커스텀 Painter

TouchGFX에는 대부분의 사용 사례 시나리오를 지원하는 사전 정의된 Painter 클래스 세트가 함께 제공되지만, 커스텀 Painter를 구현할 수도 있습니다.

이 섹션에서는 영감을 얻을 수 있는 몇 가지 예제를 제공합니다. 여기 나온 예제는 16bpp RGB565에만 해당되는 내용입니다. 다른 프레임 버퍼 형식의 경우에는 약간의 수정이 필요합니다.

커스텀 Painter는 AbstractPainter의 하위 클래스에 불과합니다. 16bpp(RGB565) 프레임버퍼용 Painter는 AbstractPainterRGB565 클래스를 슈퍼클래스로 사용할 수 있습니다 24bpp(RGB888) 프레임버퍼용 Painter는 AbstractPainterRGB888을 슈퍼클래스로 사용할 수 있습니다.

이러한 슈퍼클래스는 추상 클래스입니다. 커스텀 Painter 클래스는 다음과 같이 메서드를 구현해야 합니다.

    virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const = 0;

destination은 프레임버퍼(위젯의 왼쪽 가장자리)에서의 시작 위치를 가리킵니다.
offset은 이 시작 위치에서 첫 번째 픽셀을 배치할 픽셀의 개수입니다.
widgetX, widgetY는 위젯에 대한 첫 번째 픽셀의 상대 좌표(프레임버퍼 좌표계에서 주어짐)입니다.
countalpha가 지정된 상태에서 그리는 픽셀의 개수입니다.

캔버스 위젯은 이 메서드를 여러 번 호출하기 때문에 paint의 구현 속도가 느려서는 안됩니다. 캔버스 위젯이 자주 업데이트되지 않는 경우에는 속도가 덜 중요합니다.

색상 Painter

가장 단순한 형태의 Painter는 프레임버퍼에 고정된 색상 값을 씁니다. 이를 구현하는 방법은 다음과 같습니다.

#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class RedPainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset; // Address of first pixel to paint
const uint16_t* const lineend = framebuffer + count; // Address of last pixel to paint
const uint16_t redColor565 = 0xF800; // Full red in RGB565
do
{
*framebuffer = redColor565;
} while (++framebuffer < lineend);
}
};

반드시 Painter의 인스턴스를 생성하고 이를 Canvas Widget에 할당해야 합니다. 다음과 같이 클래스에 Painter 유형의 멤버를 추가합니다.

Circle myCircle;
RedPainter myPainter;

그러면 코드의 setupScreen()가 다음과 같은 모습이 됩니다.

...
myCircle.setPainter(myPainter);
...

RedPainter는 Circle을 페인팅합니다. 오른쪽에는 확대된 부분이 나와 있습니다.

위의 RedPainter 클래스는 alpha 매개변수를 무시합니다. 이렇게 하면 모든 픽셀이 완전히 빨간색이 되어서 가장자리가 거칠어집니다(알파 블렌딩이 수행되지 않음). 필요한 경우 블렌딩을 수행할 수 있도록 alpha 매개변수를 사용해 코드를 약간 업데이트함으로써 이를 개선할 수 있습니다.

#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class AlphaRedPainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset; // Address of first pixel to paint
const uint16_t* const lineend = framebuffer + count;
const uint16_t redColor565 = 0xF800; // Full red in RGB565
do
{
if (alpha == 0xFF)
{
*framebuffer = redColor565; // Write red to framebuffer
}
else
{
*framebuffer = alphaBlend(redColor565, *framebuffer, alpha); // Blend red with the framebuffer color
}
} while (++framebuffer < lineend);
}
};

alphaBlend 함수는 두 개의 RGB565 픽셀을 첫 번째 픽셀에 지정된 알파와 블렌딩합니다. 이 함수는 슈퍼클래스 AbstractPainterRGB565에서 제공합니다. 이러한 코드 덕분에 이제 원의 가장자리가 매끄러워졌습니다.

RedPainter는 원을 페인팅합니다. 오른쪽의 확대된 부분은 알파 블렌딩을 보여줍니다.

WidgetXWidgetY 매개변수는 특정 영역으로 그리기 동작을 제한하는 데 사용할 수 있습니다. 예를 들어, 여기 나와 있는 Painter는 가로 줄을 하나 건너 하나씩 그리기를 합니다. WidgetY는 이를 제어하는 데 사용됩니다.

#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
using namespace touchgfx;
class StripePainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
if ((widgetY & 2) == 0)
{
return; // Do not draw anything on line 0,1, 4,5, 8,9, etc.
}
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
if (alpha == 0xFF)
{
do
{
*framebuffer = 0xF800;
} while (++framebuffer < lineend);
}
else
{
do
{
*framebuffer = alphaBlend(0xF800, *framebuffer, alpha);
} while (++framebuffer < lineend);
}
}
};

RedPainter는 원을 페인팅합니다. 오른쪽에는 확대된 부분이 나와 있습니다.

프레임버퍼 변경

이 섹션에 나오는 Painter는 프레임버퍼에 특정 내용을 페인팅하는 것이 아니라, 프레임버퍼를 그레이 스케일로 변경합니다. 프레임버퍼의 픽셀 값(Circle의 배경에 있는 Widget이 쓴 값)을 읽고, 녹색 구성 요소를 추출하고 이를 사용해 회색 색상 값(빨간색, 녹색, 파란색에 대해 동일한 값)을 생성하고, 이 값을 다시 프레임 버퍼에 쓰는 방법으로 변경을 수행합니다.

이러한 프레임버퍼 읽기 및 수정 원칙을 사용해 많은 유사한 기술들을 개발할 수 있습니다.

#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
#include <touchgfx/Color.hpp>
using namespace touchgfx;
class GrayscalePainter : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
do
{
const uint8_t green = Color::getGreenFromRGB565(*framebuffer) & 0xF8; // Keep only 5 bits of the green
const uint16_t color565 = LCD16bpp::getNativeColorFromRGB(green, green, green);
if (alpha == 0xFF)
{
*framebuffer = color565;
}
else
{
*framebuffer = alphaBlend(color565, *framebuffer, alpha);
}
} while (++framebuffer < lineend);
}
};

왼쪽은 원래 배경입니다. 오른쪽에서는 원 Painter가 원의 내부 픽셀을 그레이스케일로 바꿨습니다.

회전된 디스플레이의 커스텀 컨테이너

애플리케이션이 회전된 디스플레이를 사용한다면 커스텀 컨테이너 코드는 페인팅에서 좌표를 사용하는 경우에 이를 고려해야 합니다.

다음은 회전된 디스플레이에서 사용되는 StripePainter입니다.

RedPainter는 원을 페인팅합니다. 오른쪽에는 확대된 부분이 나와 있습니다.

Image, Text 및 Button이 TouchGFX Engine에 의해 회전되었지만, 줄무늬가 평행해야 하는 텍스트에 수직임을 알 수 있습니다. 라인은 회전이 되지 않았습니다.
문제는 프레임버퍼가 회전되지 않기 때문에 Painter가 순차 주소(프레임버퍼의 픽셀)에서 페인팅을 수행하면 이전과 마찬가지로 라인의 방향이 지정된다는 것입니다(회전되지 않음).

페인팅 수행 여부를 결정할 수 있도록 WidgetX를 사용해 문제를 해결할 수 있습니다. widgetXwidgetY 매개변수는 프레임버퍼 좌표계에서 주어집니다. 이는 widgetX가 디스플레이에서 아래로 내려갈 때 커지고, 디스플레이 좌표계의 y에 해당한다는 것을 의미합니다.

#include <touchgfx/widgets/canvas/AbstractPainterRGB565.hpp>
#include <touchgfx/Color.hpp>
using namespace touchgfx;
class StripePainterRotate90 : public AbstractPainterRGB565
{
public:
virtual void paint(uint8_t* destination, int16_t offset, int16_t widgetX, int16_t widgetY, int16_t count, uint8_t alpha) const
{
uint16_t* framebuffer = reinterpret_cast<uint16_t*>(destination) + offset;
const uint16_t* const lineend = framebuffer + count;
if (alpha == 0xFF)
{
do
{
if (widgetX++ & 2)
{
*framebuffer = 0xF800;
}
} while (++framebuffer < lineend);
}
else
{
do
{
if (widgetX++ & 2)
{
*framebuffer = alphaBlend(0xF800, *framebuffer, alpha);
}
} while (++framebuffer < lineend);
}
}
};

이제 줄무늬의 방향이 올바르게 지정되었습니다.

StripePainterRotate90은 Circle을 페인팅합니다.

채우기 규칙

Shape 위젯에서 Fill-Non-Zero 또는 Fill-Even-Odd 두 가지 채우기 규칙 중에서 선택을 할 수 있습니다. Fill-Non-Zero 규칙은 다음과 같습니다. 아래 두 그림은 두 채우기 규칙 간의 차이점을 보여줍니다.

Fill-Even-Odd 규칙을 사용해 시작 Shape를 페인팅

Fill-Non-Zero rule 규칙을 사용해 시작 Shape를 페인팅

Even-Odd 채우기 규칙은 가장자리 짝수(여기서는 0 또는 2개)를 교차시켜서 외부에서 도달할 수 있는 픽셀을 페인팅하지 않습니다.

Non-Zero 규칙은 픽셀에 대한 경로 상에서 왼쪽에서 오른쪽으로 이동하고 오른쪽에서 왼쪽으로 이동하는 가장자리 숫자를 뺍니다. 카운팅한 개수가 0이 아닌 경우에는 픽셀이 페인팅됩니다.

채우기 규칙은 코드에서 손쉽게 설정할 수 있습니다.

    touchgfx::Shape<5> shape1;
....
shape1.setFillingRule(Rasterizer::FILL_EVEN_ODD);