Introduction
GFX is a feature rich, high performance graphics library for IoT devices. It uses a novel and extremely flexible method of consuming and producing graphical data. This article aims to show you how to create your own consumers and producers. With this article, you can create new device drivers for display hardware, or your own custom draw targets to transform, store, and/or otherwise manipulate graphical data.
Background
Major Concepts
Draw Targets
GFX presents the concept of draw targets, which are flexible objects that can serve as a source for pixel data, and/or a destination for drawing operations.
There are two types of draw target:
Draw sources present pixel data to downstream consumers, like draw::bitmap<>()
, which draws from a draw source to a draw destination. The methods a draw source supports are covered under Capabilities.
Draw destinations provide a canvas for drawing operations. You can use things like draw::line<>()
, draw::filled_rectangle<>()
and draw::bitmap<>()
to draw onto them.
All draw destinations must support gfx::gfx_result clear(const gfx::rect16& bounds)
which clears a portion of the draw destination, gfx::gfx_result point(gfx::point16 location, pixel_type color)
which draws a single pixel and gfx::gfx_result fill(const gfx::rect16 bounds, pixel_type color)
which fills a rectangular region with the specified color.
Every driver for display hardware is a draw destination. Often, they are also draw sources, which enables reading the pixel data back from the frame buffer, and facilitates features like alpha blending.
For another example, a bitmap<>
is a draw source and a draw destination because it holds graphical data to be drawn to and used later.
A const_bitmap<>
is a draw source only since it cannot be changed. Very few draw targets are not also draw destinations.
Sometimes, a driver will provide no way to read back pixel data due to hardware limitations. An example is the SSD1351 display controller over SPI. The driver for this device is a draw destination only.
Pixel Type
Every draw target has an associated pixel type. This governs the format of the graphical data in memory or possibly in the device's hardware framebuffer. It also basically dictates the "quality" of the display. A grayscale device gsc_pixel<8>
for example, will obviously have less fidelity than an rgb_pixel<16>
, but also takes half the memory. You must expose a pixel_type
member alias from your draw target type. An example would be using pixel_type = gfx::rgb_pixel<16>;
for the 16-bit RGB pixel format.
Palette Type
Some draw targets use indexed pixels and a palette. A good example of this is a color e-paper display. They typically have a very limited primary color palette, 7-colors - including black and white, being the most that I've seen.
If you use an indexed pixel type, you must expose a palette_type which indicates the type of the palette you are going to use. For e-paper displays, this is typically a custom type with hard coded color values. You would typically hold an instance of this type around in your target. We'll cover implementing them downthread. An example declaration would be using palette_type = mypalette;
.
In addition to this, you must also provide a way to access the instance of the palette associated with this draw target. You do so by exposing a palette method with the signature palette_type* palette() const
. Typically, you'd simply return the address of your palette_type
instance.
Capabilities
Each target must expose a type alias for an instantiation of the gfx::gfx_caps<>
template. This instantiation dictates what features/members this target exposes. Most of the features are for optimizing draw destinations though a few effect draw sources: An example would be using caps = gfx::gfx_caps< false,false,false,false,true,true,false>;
.
Let's explore the booleans in order from left to right:
Blt
If true, this target has a uint8_t* begin()
and uint8_t* end()
method that return the beginning and just past the end of the buffer that holds the pixel data, in the binary format dictated by pixel_type
. These members expose the data directly so that it can be written as well as read. Blting is typically the most direct and efficient way of transferring graphical data.
Async
If true, this is a draw target that supports asynchronous versions of its graphics operations. In practice, this means many methods have a corollary method ending in _async
that performs the operation asynchronously if possible, though this is not guaranteed. It also exposes a gfx::gfx_result wait_all_async()
method that waits for any and all pending asynchronous operations to complete. It's fine to forward to a non-asynchronous method if the particular operation or conditions do not support completing it asynchronously.
Batch
This draw destination supports batched writes. Batched writes are writes wherein a rectangular region of the display is selected and then filled with pixels left to right, top to bottom without specifying their individual coordinates. This is a way to cut down on bus traffic. The draw destination exposes gfx::gfx_result begin_batch(const gfx::rect16& bounds)
which starts a batch operation, specifying the destination window. It also exposes gfx::gfx_result write_batch(pixel_type color)
which writes a single pixel at the current batch cursor position within the window, and gfx::gfx_result commit_batch()
which commits the pending batch operation. If another operation is started during a batch, the batch is committed. It's important to balance these calls, and to write out every pixel necessary to fill the window. If you do not, the results are undefined.
Copy From
This draw destination supports an efficient mechanism for copying pixel data from a draw source. It is template method with the signature:
template<typename Source>
gfx::gfx_result copy_from(const gfx::rect16& src_rect,
const Source& src,
gfx::point16 location)
It will copy the source rectangle to this draw destination at the specified location within the draw surface. This method should use the fastest possible means for retrieving data available given the parameters of copy_from<>()
and capabilities of the draw source. For example, blting should be done if possible.
Suspend
This draw destination supports double buffering by way of gfx::gfx_result suspend()
and gfx::gfx_result resume(bool force=false)
. When the draws are suspended, no updates to the display occur, but drawing continues to modify the frame buffer. When resumed, the display is updated. This can reduce flicker, and for an in memory frame buffer, that used by the SSD1306, it can sometimes decrease bus traffic, improving performance. Furthermore, it is necessary for e-paper displays since they are not able to animate.
When suspend is called, typically an internal counter is incremented. When resume is called, that counter is decremented, or if force
is true
it is set to zero. If the counter reaches zero, the display is updated with the contents of the frame buffer. The reason a counter is needed is because suspend()
and resume()
calls must be able to be nested.
Suspend and resume are always implemented when the frame buffer is in local RAM.
Read
This target is a draw source and exposes gfx::gfx_result point(point16 location, pixel_type* out_pixel) const
. This method returns the color at the given location. Unlike writing methods, this method is not required to initialize the device if it is not already initialized.
Copy To
This draw source supports efficient copying of pixel data from this instance to draw destination. It should use the most efficient means possible, with the exception of using the destination's copy_from<>()
method. This signature is as follows:
template<typename Source>
gfx::gfx_result copy_from(const gfx::rect16& src_rect,
const Source& src,
gfx::point16 location)
This draw source must also support Read
if it supports this feature.
Metrics
All draw targets support a way to retrieve their dimensions and their bounding rectangle, anchored to (0, 0). The method signatures are size16 dimensions() const
and rect16 bounds() const
.
Notable Behavior
If a draw target must be initialized, it must do so on first call to any of the draw destination methods, including suspend()
and resume()
. Draw source methods do not have this requirement. The copy_from<>()
and copy_to<>()
methods are templates so that they can take various types of targets and pixel types. They must do automatic conversion where necessary.
Coding this Mess
In order to provide a concrete example, we'll be exploring the htcw_ili9341 driver since it has most of the major capabilities covered above. We'll start with the code and then give it a brief going over after the fact. It's quite lengthy, but most of that is due to the actual communication with the hardware, not the GFX bindings themselves, with the exception of the copy method helpers.
#include <Arduino.h>
#include <gfx_core.hpp>
#include <gfx_draw_helpers.hpp>
#include <gfx_palette.hpp>
#include <gfx_pixel.hpp>
#include <gfx_positioning.hpp>
#include <tft_driver.hpp>
namespace arduino {
template <int8_t PinDC, int8_t PinRst, int8_t PinBL, typename Bus,
uint8_t Rotation = 0, bool BacklightHigh = false,
unsigned int WriteSpeedPercent = 200,
unsigned int ReadSpeedPercent = WriteSpeedPercent>
struct ili9341 final {
constexpr static const int8_t pin_dc = PinDC;
constexpr static const int8_t pin_rst = PinRst;
constexpr static const int8_t pin_bl = PinBL;
constexpr static const uint8_t rotation = Rotation & 3;
constexpr static const size_t max_dma_size = 320 * 240 * 2;
constexpr static const bool backlight_high = BacklightHigh;
constexpr static const float write_speed_multiplier =
(WriteSpeedPercent / 100.0);
constexpr static const float read_speed_multiplier =
(ReadSpeedPercent / 100.0);
using type = ili9341;
using driver = tft_driver<PinDC, PinRst, PinBL, Bus>;
using bus = Bus;
using pixel_type = gfx::rgb_pixel<16>;
using caps = gfx::gfx_caps<false, (bus::dma_size > 0), true, true, false,
bus::readable, true>;
ili9341()
: m_initialized(false), m_dma_initialized(false), m_in_batch(false) {}
~ili9341() {
if (m_dma_initialized) {
bus::deinitialize_dma();
}
if (m_initialized) {
driver::deinitialize();
}
}
bool initialize() {
if (!m_initialized) {
if (driver::initialize()) {
bus::begin_initialization();
bus::set_speed_multiplier(write_speed_multiplier);
bus::begin_write();
bus::begin_transaction();
driver::send_command(0xEF);
driver::send_data8(0x03);
driver::send_data8(0x80);
driver::send_data8(0x02);
driver::send_command(0xCF);
driver::send_data8(0x00);
driver::send_data8(0XC1);
driver::send_data8(0X30);
driver::send_command(0xED);
driver::send_data8(0x64);
driver::send_data8(0x03);
driver::send_data8(0X12);
driver::send_data8(0X81);
driver::send_command(0xE8);
driver::send_data8(0x85);
driver::send_data8(0x00);
driver::send_data8(0x78);
driver::send_command(0xCB);
driver::send_data8(0x39);
driver::send_data8(0x2C);
driver::send_data8(0x00);
driver::send_data8(0x34);
driver::send_data8(0x02);
driver::send_command(0xF7);
driver::send_data8(0x20);
driver::send_command(0xEA);
driver::send_data8(0x00);
driver::send_data8(0x00);
driver::send_command(0xC0); driver::send_data8(0x23);
driver::send_command(0xC1); driver::send_data8(0x10);
driver::send_command(0xC5); driver::send_data8(0x3e);
driver::send_data8(0x28);
driver::send_command(0xC7); driver::send_data8(0x86);
driver::send_command(0x36); driver::send_data8(0x40 | 0x08);
driver::send_command(0x3A);
driver::send_data8(0x55);
driver::send_command(0xB1);
driver::send_data8(0x00);
driver::send_data8(
0x13);
driver::send_command(0xB6); driver::send_data8(0x08);
driver::send_data8(0x82);
driver::send_data8(0x27);
driver::send_command(0xF2); driver::send_data8(0x00);
driver::send_command(0x26); driver::send_data8(0x01);
driver::send_command(0xE0); driver::send_data8(0x0F);
driver::send_data8(0x31);
driver::send_data8(0x2B);
driver::send_data8(0x0C);
driver::send_data8(0x0E);
driver::send_data8(0x08);
driver::send_data8(0x4E);
driver::send_data8(0xF1);
driver::send_data8(0x37);
driver::send_data8(0x07);
driver::send_data8(0x10);
driver::send_data8(0x03);
driver::send_data8(0x0E);
driver::send_data8(0x09);
driver::send_data8(0x00);
driver::send_command(0xE1); driver::send_data8(0x00);
driver::send_data8(0x0E);
driver::send_data8(0x14);
driver::send_data8(0x03);
driver::send_data8(0x11);
driver::send_data8(0x07);
driver::send_data8(0x31);
driver::send_data8(0xC1);
driver::send_data8(0x48);
driver::send_data8(0x08);
driver::send_data8(0x0F);
driver::send_data8(0x0C);
driver::send_data8(0x31);
driver::send_data8(0x36);
driver::send_data8(0x0F);
driver::send_command(0x11); bus::end_transaction();
bus::end_write();
delay(120);
bus::begin_write();
bus::begin_transaction();
driver::send_command(0x29); bus::end_transaction();
bus::end_write();
bus::end_initialization();
bus::begin_write();
bus::begin_transaction();
apply_rotation();
bus::end_transaction();
bus::end_write();
if (pin_bl != -1) {
pinMode(pin_bl, OUTPUT);
digitalWrite(pin_bl, backlight_high);
}
m_initialized = true;
}
}
return m_initialized;
}
inline gfx::size16 dimensions() const {
return rotation & 1 ? gfx::size16(320, 240) : gfx::size16(240, 320);
}
inline gfx::rect16 bounds() const { return dimensions().bounds(); }
inline gfx::gfx_result point(gfx::point16 location, pixel_type color) {
return fill({location.x, location.y, location.x, location.y}, color);
}
inline gfx::gfx_result point_async(gfx::point16 location,
pixel_type color) {
return point(location, color);
}
gfx::gfx_result point(gfx::point16 location, pixel_type* out_color) const {
if (out_color == nullptr) return gfx::gfx_result::invalid_argument;
if (!m_initialized || m_in_batch) return gfx::gfx_result::invalid_state;
if (!bounds().intersects(location)) {
*out_color = pixel_type();
return gfx::gfx_result::success;
}
bus::dma_wait();
bus::set_speed_multiplier(read_speed_multiplier);
bus::begin_read();
bus::cs_low();
set_window({location.x, location.y, location.x, location.y}, true);
bus::direction(INPUT);
bus::read_raw8(); out_color->native_value = ((bus::read_raw8() & 0xF8) << 8) |
((bus::read_raw8() & 0xFC) << 3) |
(bus::read_raw8() >> 3);
bus::cs_high();
bus::end_read();
bus::set_speed_multiplier(write_speed_multiplier);
bus::direction(OUTPUT);
return gfx::gfx_result::success;
}
gfx::gfx_result fill(const gfx::rect16& bounds, pixel_type color) {
if (!initialize())
return gfx::gfx_result::device_error;
else
bus::dma_wait();
gfx::gfx_result rr = commit_batch();
if (rr != gfx::gfx_result::success) {
return rr;
}
if (!bounds.intersects(this->bounds())) return gfx::gfx_result::success;
const gfx::rect16 r = bounds.normalize().crop(this->bounds());
bus::begin_write();
bus::begin_transaction();
set_window(r);
bus::write_raw16_repeat(color.native_value,
(r.x2 - r.x1 + 1) * (r.y2 - r.y1 + 1));
bus::end_transaction();
bus::end_write();
return gfx::gfx_result::success;
}
inline gfx::gfx_result fill_async(const gfx::rect16& bounds,
pixel_type color) {
return fill(bounds, color);
}
inline gfx::gfx_result clear(const gfx::rect16& bounds) {
return fill(bounds, pixel_type());
}
inline gfx::gfx_result clear_async(const gfx::rect16& bounds) {
return clear(bounds);
}
template <typename Source>
inline gfx::gfx_result copy_from(const gfx::rect16& src_rect,
const Source& src, gfx::point16 location) {
if (!initialize()) return gfx::gfx_result::device_error;
gfx::gfx_result rr = commit_batch();
if (rr != gfx::gfx_result::success) {
return rr;
}
return copy_from_impl(src_rect, src, location, false);
}
template <typename Source>
inline gfx::gfx_result copy_from_async(const gfx::rect16& src_rect,
const Source& src,
gfx::point16 location) {
if (!initialize()) return gfx::gfx_result::device_error;
gfx::gfx_result rr = commit_batch();
if (rr != gfx::gfx_result::success) {
return rr;
}
if (!m_dma_initialized) {
if (!bus::initialize_dma()) return gfx::gfx_result::device_error;
m_dma_initialized = true;
}
return copy_from_impl(src_rect, src, location, true);
}
template <typename Destination>
inline gfx::gfx_result copy_to(const gfx::rect16& src_rect,
Destination& dst,
gfx::point16 location) const {
if (!src_rect.intersects(bounds())) return gfx::gfx_result::success;
gfx::rect16 srcr = src_rect.crop(bounds());
gfx::rect16 dstr =
gfx::rect16(location, srcr.dimensions()).crop(dst.bounds());
srcr = gfx::rect16(srcr.location(), dstr.dimensions());
return copy_to_helper<Destination,
!(pixel_type::template has_channel_names<
gfx::channel_name::A>::value)>::copy_to(*this,
srcr,
dst,
dstr);
}
template <typename Destination>
inline gfx::gfx_result copy_to_async(const gfx::rect16& src_rect,
Destination& dst,
gfx::point16 location) const {
return copy_to(src_rect, dst, location);
}
gfx::gfx_result commit_batch() {
if (m_in_batch) {
bus::end_transaction();
bus::end_write();
m_in_batch = false;
}
return gfx::gfx_result::success;
}
inline gfx::gfx_result commit_batch_async() {
return commit_batch();
}
gfx::gfx_result begin_batch(const gfx::rect16& bounds) {
if (!initialize()) return gfx::gfx_result::device_error;
gfx::gfx_result rr = commit_batch();
if (rr != gfx::gfx_result::success) {
return rr;
}
const gfx::rect16 r = bounds.normalize();
bus::begin_write();
bus::begin_transaction();
set_window(r);
m_in_batch = true;
return gfx::gfx_result::success;
}
inline gfx::gfx_result begin_batch_async(const gfx::rect16& bounds) {
return begin_batch(bounds);
}
gfx::gfx_result write_batch(pixel_type color) {
bus::write_raw16(color.native_value);
return gfx::gfx_result::success;
}
inline gfx::gfx_result write_batch_async(pixel_type color) {
return write_batch(color);
}
inline gfx::gfx_result wait_all_async() {
bus::dma_wait();
return gfx::gfx_result::success;
}
private:
bool m_initialized;
bool m_dma_initialized;
bool m_in_batch;
static void set_window(const gfx::rect16& bounds, bool read = false) {
bus::busy_check();
driver::dc_command();
bus::write_raw8(0x2A);
driver::dc_data();
bus::write_raw16(bounds.x1);
bus::write_raw16(bounds.x2);
driver::dc_command();
bus::write_raw8(0x2B);
driver::dc_data();
bus::write_raw16(bounds.y1);
bus::write_raw16(bounds.y2);
driver::dc_command();
bus::write_raw8(read ? 0x2E : 0x2C);
driver::dc_data();
}
template <typename Destination, bool AllowBlt = true>
struct copy_to_helper {
inline static gfx::gfx_result copy_to(const type& src,
const gfx::rect16& srcr,
Destination& dst,
const gfx::rect16& dstr) {
if (gfx::helpers::template is_same<typename Destination::pixel_type,
pixel_type>::value &&
dstr.top_left() == gfx::point16(0, 0)) {
if (AllowBlt && dstr.width() == srcr.width() &&
dstr.height() == srcr.height()) {
if (dstr.top_left() == gfx::point16(0, 0)) {
return copy_to_fast_helper<Destination>::do_copy(srcr,
dst);
}
}
}
size_t dy = 0, dye = dstr.height();
size_t dx, dxe = dstr.width();
gfx::gfx_result r;
gfx::helpers::suspender<Destination, Destination::caps::suspend,
false>
sustok(dst);
r = gfx::helpers::batcher<Destination, Destination::caps::batch,
false>::begin_batch(dst, dstr, false);
if (gfx::gfx_result::success != r) {
return r;
}
int sox = srcr.left(), soy = srcr.top();
int dox = dstr.left(), doy = dstr.top();
while (dy < dye) {
dx = 0;
while (dx < dxe) {
pixel_type spx;
r = src.point(gfx::point16(sox + dx, soy + dy), &spx);
if (gfx::gfx_result::success != r) return r;
typename Destination::pixel_type dpx;
if (pixel_type::template has_channel_names<
gfx::channel_name::A>::value) {
r = gfx::helpers::blend_helper<
type, Destination,
Destination::caps::read>::do_blend(src, spx, dst,
gfx::point16(
dox + dx,
doy + dy),
&dpx);
if (gfx::gfx_result::success != r) {
return r;
}
} else {
r = convert_palette(dst, src, spx, &dpx, nullptr);
if (gfx::gfx_result::success != r) {
return r;
}
}
r = gfx::helpers::batcher<
Destination, Destination::caps::batch,
false>::write_batch(dst,
gfx::point16(dox + dx, doy + dy),
dpx, false);
if (gfx::gfx_result::success != r) return r;
++dx;
}
++dy;
}
return gfx::helpers::batcher<Destination, Destination::caps::batch,
false>::commit_batch(dst, false);
}
};
template <typename Destination>
struct copy_to_fast_helper {
static gfx::gfx_result do_copy(const gfx::rect16& src_rect,
Destination& dst) {
gfx::rect16 r = src_rect.normalize();
bool entire = false;
gfx::size16 bssz = r.dimensions();
size_t bsz = bssz.width * bssz.height * 3;
uint8_t* buf = (uint8_t*)malloc(bsz);
if (buf != nullptr) {
entire = true;
} else {
bsz = bssz.width * 3;
buf = (uint8_t*)malloc(bsz);
}
if (buf != nullptr) {
free(buf);
buf = nullptr;
}
using batch =
gfx::helpers::batcher<Destination, Destination::caps::batch,
Destination::caps::async>;
if (buf == nullptr) {
gfx::helpers::suspender<Destination, Destination::caps::suspend,
Destination::caps::async>
stok(dst, false);
gfx::gfx_result rr = batch::begin_batch(
dst,
{0, 0, uint16_t(r.width() - 1), uint16_t(r.height() - 1)},
false);
if (rr != gfx::gfx_result::success) {
return rr;
}
pixel_type px;
for (int y = r.y1; y <= r.y2; ++y) {
for (int x = r.x1; x <= r.x2; ++x) {
uint8_t buf3[3];
buf = buf3;
fetch_buffer({uint16_t(x), uint16_t(y), uint16_t(x),
uint16_t(y)},
buf, 3);
px.native_value = (((*buf) & 0xF8) << 8);
++buf;
px.native_value |= (((*buf) & 0xFC) << 3);
++buf;
px.native_value |= (*buf >> 3);
batch::write_batch(
dst, {uint16_t(x - r.x1), uint16_t(y - r.y1)}, px,
false);
}
}
rr = batch::commit_batch(dst, false);
if (rr != gfx::gfx_result::success) {
return rr;
}
buf = nullptr;
} else {
if (entire) {
fetch_buffer(r, buf, bsz);
gfx::helpers::suspender<Destination,
Destination::caps::suspend,
Destination::caps::async>
stok(dst, false);
gfx::gfx_result rr =
batch::begin_batch(dst,
{0, 0, uint16_t(r.width() - 1),
uint16_t(r.height() - 1)},
false);
if (rr != gfx::gfx_result::success) {
free(buf);
return rr;
}
uint8_t* bbuf = buf;
while (bsz) {
pixel_type px;
uint16_t x, y;
x = 0;
y = 0;
px.native_value = (((*bbuf) & 0xF8) << 8);
++bbuf;
px.native_value |= (((*bbuf) & 0xFC) << 3);
++bbuf;
px.native_value |= (*bbuf >> 3);
++bbuf;
++x;
if (x > r.x2 - r.x1) {
x = 0;
++y;
}
batch::write_batch(dst, {x, y}, px, false);
bsz -= 3;
}
rr = batch::commit_batch(dst, false);
if (rr != gfx::gfx_result::success) {
free(buf);
return rr;
}
} else {
gfx::helpers::suspender<Destination,
Destination::caps::suspend,
Destination::caps::async>
stok(dst, false);
for (int y = r.y1; y <= r.y2; ++y) {
fetch_buffer(r, buf, bsz);
gfx::gfx_result rr =
batch::begin_batch(dst,
{0, 0, uint16_t(r.width() - 1),
uint16_t(r.height() - 1)},
false);
if (rr != gfx::gfx_result::success) {
free(buf);
return rr;
}
size_t bbsz = bsz;
uint8_t* bbuf = buf;
while (bbsz) {
pixel_type px;
uint16_t x = 0;
px.native_value = (((*bbuf) & 0xF8) << 8);
++bbuf;
px.native_value |= (((*bbuf) & 0xFC) << 3);
++bbuf;
px.native_value |= (*bbuf >> 3);
++bbuf;
++x;
batch::write_batch(dst, {x, uint16_t(y - r.y1)}, px,
false);
bbsz -= 3;
}
rr = batch::commit_batch(dst, false);
if (rr != gfx::gfx_result::success) {
free(buf);
return rr;
}
}
}
}
if (buf != nullptr) {
free(buf);
}
return gfx::gfx_result::success;
}
static void fetch_buffer(const gfx::rect16& r, uint8_t* buf,
size_t len) {
bus::dma_wait();
bus::set_speed_multiplier(read_speed_multiplier);
bus::begin_read();
bus::cs_low();
set_window(r, true);
bus::direction(INPUT);
bus::read_raw8(); bus::read_raw(buf, len);
bus::cs_high();
bus::end_read();
bus::set_speed_multiplier(write_speed_multiplier);
bus::direction(OUTPUT);
}
};
template <typename Source, bool Blt>
struct copy_from_helper {
static gfx::gfx_result do_draw(type* this_, const gfx::rect16& dstr,
const Source& src, gfx::rect16 srcr,
bool async) {
uint16_t w = dstr.dimensions().width;
uint16_t h = dstr.dimensions().height;
gfx::gfx_result rr;
rr = this_->begin_batch(dstr);
if (gfx::gfx_result::success != rr) {
return rr;
}
for (uint16_t y = 0; y < h; ++y) {
for (uint16_t x = 0; x < w; ++x) {
typename Source::pixel_type pp;
rr = src.point(gfx::point16(x + srcr.x1, y + srcr.y1), &pp);
if (rr != gfx::gfx_result::success) return rr;
pixel_type p;
rr = gfx::convert_palette_to(src, pp, &p, nullptr);
if (gfx::gfx_result::success != rr) {
return rr;
}
rr = this_->write_batch(p);
if (gfx::gfx_result::success != rr) {
return rr;
}
}
}
rr = this_->batch_commit();
return rr;
}
};
template <typename Source>
struct copy_from_helper<Source, true> {
static gfx::gfx_result do_draw(type* this_, const gfx::rect16& dstr,
const Source& src, gfx::rect16 srcr,
bool async) {
if (async) {
bus::dma_wait();
}
if (src.bounds().width() == srcr.width() && srcr.x1 == 0) {
bus::begin_write();
set_window(dstr);
if (async) {
bus::write_raw_dma(
src.begin() + (srcr.y1 * src.dimensions().width * 2),
(srcr.y2 - srcr.y1 + 1) * src.dimensions().width * 2);
} else {
bus::write_raw(
src.begin() + (srcr.y1 * src.dimensions().width * 2),
(srcr.y2 - srcr.y1 + 1) * src.dimensions().width * 2);
}
bus::end_write();
return gfx::gfx_result::success;
}
uint16_t yy = 0;
uint16_t hh = srcr.height();
uint16_t ww = src.dimensions().width;
uint16_t pitch = (srcr.x2 - srcr.x1 + 1) * 2;
bus::begin_write();
bus::begin_transaction();
while (yy < hh - !!async) {
gfx::rect16 dr = {dstr.x1, uint16_t(dstr.y1 + yy), dstr.x2,
uint16_t(dstr.y1 + yy)};
set_window(dr);
bus::write_raw(
src.begin() + 2 * (ww * (srcr.y1 + yy) + srcr.x1), pitch);
++yy;
}
if (async) {
gfx::rect16 dr = {dstr.x1, uint16_t(dstr.y1 + yy), dstr.x2,
uint16_t(dstr.y1 + yy)};
set_window(dr);
bus::write_raw_dma(
src.begin() + 2 * (ww * (srcr.y1 + yy) + srcr.x1), pitch);
}
bus::end_transaction();
bus::end_write();
return gfx::gfx_result::success;
}
};
template <typename Source>
gfx::gfx_result copy_from_impl(const gfx::rect16& src_rect,
const Source& src, gfx::point16 location,
bool async) {
gfx::rect16 srcr = src_rect.normalize().crop(src.bounds());
gfx::rect16 dstr(location, src_rect.dimensions());
dstr = dstr.crop(bounds());
if (srcr.width() > dstr.width()) {
srcr.x2 = srcr.x1 + dstr.width() - 1;
}
if (srcr.height() > dstr.height()) {
srcr.y2 = srcr.y1 + dstr.height() - 1;
}
return copy_from_helper < Source,
gfx::helpers::is_same<pixel_type,
typename Source::pixel_type>::value &&
Source::caps::blt > ::do_draw(this, dstr, src, srcr, async);
}
static void apply_rotation() {
bus::begin_write();
driver::send_command(0x36);
switch (rotation) {
case 0:
driver::send_data8(0x40 | 0x08);
break;
case 1:
driver::send_data8(0x20 | 0x08);
break;
case 2:
driver::send_data8(0x80 | 0x08);
break;
case 3:
driver::send_data8(0x20 | 0x40 | 0x80 | 0x08);
break;
}
delayMicroseconds(10);
bus::end_write();
}
};
}
First, we include necessary of headers, mainly several GFX headers which we use in lieu of gfx_cpp14.hpp or gfx.hpp because it can shorten compile times and avoids dependencies on a particular C++ version.
We also include the header for the driver and bus code. The most difficult methods by far are the copy method helpers simply because of the myriad of different routes it must take depending on the copy options and target capabilities.
See Using GFX in Platform IO for a simple way to get the above code into your project.
History
- 25th March, 2022 - Initial submission