在 GFX 中创建自定义绘制目标





5.00/5 (2投票s)
关于为 GFX 创建驱动程序和其他自定义绘制目标的更深入指南。
引言
GFX 是面向物联网设备的一款功能丰富、高性能的图形库。它采用了一种新颖且极其灵活的方法来消费和生成图形数据。本文旨在向您展示如何创建自己的消费者和生产者。借助本文,您可以为显示硬件创建新的设备驱动程序,或者创建自己的自定义绘制目标来转换、存储和/或以其他方式操作图形数据。
背景
主要概念
绘制目标
GFX 提出了绘制目标的概念,这些是灵活的对象,可以作为像素数据的源,以及/或绘制操作的目标。
有两种绘制目标
绘制源将像素数据呈现给下游消费者,例如 draw::bitmap<>()
,它将数据从绘制源绘制到绘制目标。绘制源支持的方法在“功能”下进行了介绍。
绘制目标为绘制操作提供了一个画布。您可以使用 draw::line<>()
、draw::filled_rectangle<>()
和 draw::bitmap<>()
等工具在其上进行绘制。
所有绘制目标都必须支持 gfx::gfx_result clear(const gfx::rect16& bounds)
(清除绘制目标的一部分)、gfx::gfx_result point(gfx::point16 location, pixel_type color)
(绘制单个像素)和 gfx::gfx_result fill(const gfx::rect16 bounds, pixel_type color)
(用指定颜色填充矩形区域)。
每个显示硬件驱动程序都是一个绘制目标。通常,它们也是绘制源,这使得可以从帧缓冲器中读取像素数据,并促进了诸如 alpha 混合之类的功能。
再举一个例子,bitmap<>
既是绘制源也是绘制目标,因为它包含要绘制到以后使用的图形数据。
const_bitmap<>
仅作为绘制源,因为它无法更改。很少有绘制目标不是绘制目标。
有时,由于硬件限制,驱动程序可能无法读取像素数据。例如,通过 SPI 的 SSD1351 显示控制器。此设备的驱动程序仅为绘制目标。
像素类型
每个绘制目标都有关联的像素类型。这决定了内存中或设备硬件帧缓冲器中图形数据的格式。它还基本上决定了显示的“质量”。例如,灰度设备 gsc_pixel<8>
的保真度显然不如 rgb_pixel<16>
,但占用的内存也少一半。您必须从您的绘制目标类型公开 pixel_type
成员别名。例如,对于 16 位 RGB 像素格式,可以使用 using pixel_type = gfx::rgb_pixel<16>;
。
调色板类型
某些绘制目标使用索引像素和调色板。颜色电子纸显示屏就是一个很好的例子。它们通常具有非常有限的原色调色板,最常见的是 7 种颜色(包括黑白)。
如果您使用索引像素类型,则必须公开 palette_type
,该类型指示您将使用的调色板的类型。对于电子纸显示屏,这通常是一种具有硬编码颜色值的自定义类型。您通常会在目标中保留该类型的实例。我们将在稍后介绍实现它们。声明示例为 using palette_type = mypalette;
。
此外,您还必须提供一种访问与此绘制目标关联的调色板实例的方法。您可以通过公开签名 palette_type* palette() const
的 palette
方法来做到这一点。通常,您只需返回 palette_type
实例的地址。
功能
每个目标都必须公开 gfx::gfx_caps<>
模板的实例化的类型别名。此实例化决定了该目标公开哪些功能/成员。大多数功能用于优化绘制目标,但有少数会影响绘制源:一个示例是 using caps = gfx::gfx_caps< false,false,false,false,true,true,false>;
。
让我们按从左到右的顺序探索布尔值
块传输 (Blt)
如果为 true,则此目标具有 uint8_t* begin()
和 uint8_t* end()
方法,它们返回存储像素数据的缓冲区的开始和结束(不包含)位置,格式由 pixel_type
决定。这些成员直接公开数据,以便可以读取和写入。块传输通常是传输图形数据的最直接、最高效的方式。
异步
如果为 true,则此绘制目标支持其图形操作的异步版本。实际上,这意味着许多方法都有一个以 _async
结尾的对应方法,该方法在可能的情况下异步执行操作,但这并非保证。它还公开了一个 gfx::gfx_result wait_all_async()
方法,该方法等待所有待处理的异步操作完成。如果特定操作或条件不支持异步完成,则转发到非异步方法是可以的。
批处理
此绘制目标支持批量写入。批量写入是指选择显示屏的一个矩形区域,然后逐行(从左到右,从上到下)填充像素,而无需指定其单独的坐标。这是一种减少总线流量的方法。绘制目标公开 gfx::gfx_result begin_batch(const gfx::rect16& bounds)
(开始批处理操作,指定目标窗口),以及 gfx::gfx_result write_batch(pixel_type color)
(在窗口内的当前批处理光标位置写入单个像素)和 gfx::gfx_result commit_batch()
(提交挂起的批处理操作)。如果在批处理过程中启动了另一个操作,则批处理将被提交。平衡这些调用并写入填充窗口所需的所有像素很重要。如果不这样做,结果将是未定义的。
从...复制
此绘制目标支持一种从绘制源复制像素数据的有效机制。它是一个模板方法,其签名如下:
template<typename Source>
gfx::gfx_result copy_from(const gfx::rect16& src_rect,
const Source& src,
gfx::point16 location)
它将在指定位置将源矩形复制到此绘制目标。此方法应使用最快的可能方法来检索数据,同时考虑 copy_from<>()
的参数和绘制源的功能。例如,如果可能,应执行块传输。
暂停
此绘制目标通过 gfx::gfx_result suspend()
和 gfx::gfx_result resume(bool force=false)
支持双缓冲。当绘制被暂停时,显示屏不会更新,但绘制会继续修改帧缓冲器。恢复时,显示屏会更新。这可以减少闪烁,并且对于内存中的帧缓冲器(例如 SSD1306 使用的)来说,有时可以减少总线流量,提高性能。此外,对于电子纸显示屏来说,这是必需的,因为它们无法进行动画。
当调用 suspend()
时,通常会递增内部计数器。当调用 resume()
时,该计数器会递减,或者如果 force
为 true
,则将其设置为零。如果计数器达到零,则显示屏将使用帧缓冲器的内容进行更新。需要计数器的原因是 suspend()
和 resume()
调用必须能够嵌套。
当帧缓冲器位于本地 RAM 中时,总是实现暂停和恢复。
读取
此目标是绘制源,并公开 gfx::gfx_result point(point16 location, pixel_type* out_pixel) const
。此方法返回给定位置的颜色。与写入方法不同,此方法不需要初始化设备(如果尚未初始化)。
复制到
此绘制源支持将像素数据从该实例高效地复制到绘制目标。它应使用最有效的方法,但不能使用目标的 copy_from<>()
方法。其签名如下:
template<typename Source>
gfx::gfx_result copy_from(const gfx::rect16& src_rect,
const Source& src,
gfx::point16 location)
如果此绘制源支持此功能,则它还必须支持 Read
。
指标
所有绘制目标都支持一种方法来检索其尺寸和以 (0, 0) 为锚点的边界矩形。方法签名分别是 size16 dimensions() const
和 rect16 bounds() const
。
值得注意的行为
如果必须初始化绘制目标,则必须在首次调用任何绘制目标方法(包括 suspend()
和 resume()
)时进行初始化。绘制源方法没有此要求。copy_from<>()
和 copy_to<>()
方法是模板,以便它们可以接受各种类型的目标和像素类型。它们必须在必要时执行自动转换。
编写这个混乱的程序
为了提供一个具体的示例,我们将探讨 htcw_ili9341 驱动程序,因为它具有上述大部分主要功能。我们将从代码开始,然后对其进行简要介绍。代码很长,但大部分原因是与硬件的实际通信,而不是 GFX 绑定本身,复制方法助手除外。
#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 {
// the ILI9341 driver for GFX
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;
// this is used for high level device communication
using driver = tft_driver<PinDC, PinRst, PinBL, Bus>;
// this is used for low level device communication
using bus = Bus;
// GFX binding for the pixel format. Natively we use 16-bit RGB
using pixel_type = gfx::rgb_pixel<16>;
// GFX binding for capabilities. Note that some of them, like async
// or reading depend on if the bus supports it.
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) {}
// don't need virtual due to final
~ili9341() {
if (m_dma_initialized) {
bus::deinitialize_dma();
}
if (m_initialized) {
driver::deinitialize();
}
}
// initialize the driver
bool initialize() {
if (!m_initialized) {
// initialize the TFT driver
if (driver::initialize()) {
// we're telling it the driver is in initialization mode
// not necessary for this driver, but we do it for
// completeness
bus::begin_initialization();
// set the speed of the bus
bus::set_speed_multiplier(write_speed_multiplier);
// tell it we're writing
bus::begin_write();
// being a transaction
bus::begin_transaction();
// send a bunch of stuff
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); // Power control
driver::send_data8(0x23); // VRH[5:0]
driver::send_command(0xC1); // Power control
driver::send_data8(0x10); // SAP[2:0];BT[3:0]
driver::send_command(0xC5); // VCM control
driver::send_data8(0x3e);
driver::send_data8(0x28);
driver::send_command(0xC7); // VCM control2
driver::send_data8(0x86); //--
driver::send_command(0x36); // Memory Access Control
driver::send_data8(0x40 | 0x08); // Rotation 0 (portrait mode)
driver::send_command(0x3A);
driver::send_data8(0x55);
driver::send_command(0xB1);
driver::send_data8(0x00);
driver::send_data8(
0x13); // 0x18 79Hz, 0x1B default 70Hz, 0x13 100Hz
driver::send_command(0xB6); // Display Function Control
driver::send_data8(0x08);
driver::send_data8(0x82);
driver::send_data8(0x27);
driver::send_command(0xF2); // 3Gamma Function Disable
driver::send_data8(0x00);
driver::send_command(0x26); // Gamma curve selected
driver::send_data8(0x01);
driver::send_command(0xE0); // Set Gamma
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); // Set Gamma
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); // Exit Sleep
// done with the transaction
bus::end_transaction();
// done writing
bus::end_write();
delay(120);
bus::begin_write();
bus::begin_transaction();
driver::send_command(0x29); // Display on
bus::end_transaction();
bus::end_write();
bus::end_initialization();
bus::begin_write();
bus::begin_transaction();
// set the rotation to what we selected
apply_rotation();
bus::end_transaction();
bus::end_write();
// enable the backlight
if (pin_bl != -1) {
pinMode(pin_bl, OUTPUT);
digitalWrite(pin_bl, backlight_high);
}
m_initialized = true;
}
}
return m_initialized;
}
// the dimensions
inline gfx::size16 dimensions() const {
return rotation & 1 ? gfx::size16(320, 240) : gfx::size16(240, 320);
}
// the bounding rectangle
inline gfx::rect16 bounds() const { return dimensions().bounds(); }
// draws a pixel
inline gfx::gfx_result point(gfx::point16 location, pixel_type color) {
return fill({location.x, location.y, location.x, location.y}, color);
}
// draws a pixel asynchronously
inline gfx::gfx_result point_async(gfx::point16 location,
pixel_type color) {
return point(location, color);
}
// retrieves a pixel
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(); // throw away
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;
}
// fills a rectangular region
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;
}
// fills a rectangular region asynchronously
inline gfx::gfx_result fill_async(const gfx::rect16& bounds,
pixel_type color) {
return fill(bounds, color);
}
// clears a region
inline gfx::gfx_result clear(const gfx::rect16& bounds) {
return fill(bounds, pixel_type());
}
// clears a region asynchronously
inline gfx::gfx_result clear_async(const gfx::rect16& bounds) {
return clear(bounds);
}
// copies from a draw source
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);
}
// copies from a draw source asynchronously
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);
}
// copies to a draw destination
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);
}
// copies to a draw destination asynchronously
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);
}
// commits the current batch
gfx::gfx_result commit_batch() {
if (m_in_batch) {
bus::end_transaction();
bus::end_write();
m_in_batch = false;
}
return gfx::gfx_result::success;
}
// commits the batch asynchronously
inline gfx::gfx_result commit_batch_async() {
return commit_batch();
}
// begins a batch operation
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;
}
// begins a batch asynchronously
inline gfx::gfx_result begin_batch_async(const gfx::rect16& bounds) {
return begin_batch(bounds);
}
// write a pixel at the current cursor position within the
// batch window
gfx::gfx_result write_batch(pixel_type color) {
bus::write_raw16(color.native_value);
return gfx::gfx_result::success;
}
// writes a pixel asynchronously at the current cursor
// position within the batch window
inline gfx::gfx_result write_batch_async(pixel_type color) {
return write_batch(color);
}
// waits for all pending asynchronous operations
// to complete
inline gfx::gfx_result wait_all_async() {
bus::dma_wait();
return gfx::gfx_result::success;
}
private:
// initialized yet?
bool m_initialized;
// dma initialized?
bool m_dma_initialized;
// in a batch?
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(); // throw away
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();
}
// direct blt
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;
}
// line by line blt
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:
// portrait
driver::send_data8(0x40 | 0x08);
break;
case 1:
// landscape
driver::send_data8(0x20 | 0x08);
break;
case 2:
// portrait
driver::send_data8(0x80 | 0x08);
break;
case 3:
// landscape
driver::send_data8(0x20 | 0x40 | 0x80 | 0x08);
break;
}
delayMicroseconds(10);
bus::end_write();
}
};
} // namespace arduino
首先,我们包含必要的头文件,主要是几个 GFX 头文件,我们用它们代替 gfx_cpp14.hpp 或 gfx.hpp,因为这可以缩短编译时间,并避免对特定 C++ 版本的依赖。
我们还包含驱动程序和总线代码的头文件。迄今为止最困难的方法是复制方法助手,这仅仅是因为它必须根据复制选项和目标功能采取的路线多种多样。
请参阅 在 Platform IO 中使用 GFX,了解一种将上述代码引入项目的简单方法。
历史
- 2022 年 3 月 25 日 - 初始提交