65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2022 年 3 月 26 日

MIT

7分钟阅读

viewsIcon

4971

关于为 GFX 创建驱动程序和其他自定义绘制目标的更深入指南。

GFX driver draw targets

引言

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() constpalette 方法来做到这一点。通常,您只需返回 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() 时,该计数器会递减,或者如果 forcetrue,则将其设置为零。如果计数器达到零,则显示屏将使用帧缓冲器的内容进行更新。需要计数器的原因是 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() constrect16 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.hppgfx.hpp,因为这可以缩短编译时间,并避免对特定 C++ 版本的依赖。

我们还包含驱动程序和总线代码的头文件。迄今为止最困难的方法是复制方法助手,这仅仅是因为它必须根据复制选项和目标功能采取的路线多种多样。

请参阅 在 Platform IO 中使用 GFX,了解一种将上述代码引入项目的简单方法。

历史

  • 2022 年 3 月 25 日 - 初始提交
© . All rights reserved.