diff --git a/Adafruit_GFX.cpp b/Adafruit_GFX.cpp index cf3c827..4120730 100644 --- a/Adafruit_GFX.cpp +++ b/Adafruit_GFX.cpp @@ -1882,6 +1882,191 @@ void GFXcanvas1::fillScreen(uint16_t color) { } } +/**************************************************************************/ +/*! + @brief Speed optimized vertical line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param h length of vertical line to be drawn, including first point + @param color Binary (on or off) color to fill with +*/ +/**************************************************************************/ +void GFXcanvas1::drawFastVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { + return; + } + + if (y + h > height()) { + h = height() - y; + } else if (h < 0) { + // convert negative heights to their postive equivalent + h *= -1; + y -= h - 1; + if (y < 0) { + h += y; + y = 0; + } + } + + if (getRotation() == 0) { + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 1) { + int16_t t = x; + x = WIDTH - 1 - y; + y = t; + x -= h - 1; + drawFastRawHLine(x, y, h, color); + } else if (getRotation() == 2) { + x = WIDTH - 1 - x; + y = HEIGHT - 1 - y; + + y -= h - 1; + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 3) { + int16_t t = x; + x = y; + y = HEIGHT - 1 - t; + drawFastRawHLine(x, y, h, color); + } +} + +/**************************************************************************/ +/*! + @brief Speed optimized horizontal line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param w length of horizontal line to be drawn, including first point + @param color Binary (on or off) color to fill with +*/ +/**************************************************************************/ +void GFXcanvas1::drawFastHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { + return; + } + + if (x + w > width()) { + w = width() - x; + } else if (w < 0) { + // convert negative widths to their postive equivalent + w *= -1; + x -= w - 1; + if (x < 0) { + w += x; + x = 0; + } + } + + if (getRotation() == 0) { + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 1) { + int16_t t = x; + x = WIDTH - 1 - y; + y = t; + drawFastRawVLine(x, y, w, color); + } else if (getRotation() == 2) { + x = WIDTH - 1 - x; + y = HEIGHT - 1 - y; + + x -= w - 1; + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 3) { + int16_t t = x; + x = y; + y = HEIGHT - 1 - t; + y -= w - 1; + drawFastRawVLine(x, y, w, color); + } +} + +void GFXcanvas1::drawFastRawVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. + int16_t row_bytes = ((WIDTH + 7) / 8); + uint8_t *buffer = this->getBuffer(); + uint8_t *ptr = &buffer[(x / 8) + y * row_bytes]; + + if (color > 0) { +#ifdef __AVR__ + uint8_t bit_mask = pgm_read_byte(&GFXsetBit[x & 7]); +#else + uint8_t bit_mask = (0x80 >> (x & 7)); +#endif + for (int16_t i = 0; i < h; i++) { + *ptr |= bit_mask; + ptr += row_bytes; + } + } else { +#ifdef __AVR__ + uint8_t bit_mask = pgm_read_byte(&GFXclrBit[x & 7]); +#else + uint8_t bit_mask = ~(0x80 >> (x & 7)); +#endif + for (int16_t i = 0; i < h; i++) { + *ptr &= bit_mask; + ptr += row_bytes; + } + } +} + +void GFXcanvas1::drawFastRawHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. + int16_t rowBytes = ((WIDTH + 7) / 8); + uint8_t *buffer = this->getBuffer(); + uint8_t *ptr = &buffer[(x / 8) + y * rowBytes]; + size_t remainingWidthBits = w; + + // check to see if first byte needs to be partially filled + if ((x & 7) > 0) { + // create bit mask for first byte + uint8_t startByteBitMask = 0x00; + for (int8_t i = (x & 7); ((i < 8) && (remainingWidthBits > 0)); i++) { +#ifdef __AVR__ + startByteBitMask |= pgm_read_byte(&GFXsetBit[i]); +#else + startByteBitMask |= (0x80 >> i); +#endif + remainingWidthBits--; + } + if (color > 0) { + *ptr |= startByteBitMask; + } else { + *ptr &= ~startByteBitMask; + } + + ptr++; + } + + // do the next remainingWidthBits bits + if (remainingWidthBits > 0) { + size_t remainingWholeBytes = remainingWidthBits / 8; + size_t lastByteBits = remainingWidthBits % 8; + uint8_t wholeByteColor = color > 0 ? 0xFF : 0x00; + + memset(ptr, wholeByteColor, remainingWholeBytes); + + if (lastByteBits > 0) { + uint8_t lastByteBitMask = 0x00; + for (int8_t i = 0; i < lastByteBits; i++) { +#ifdef __AVR__ + lastByteBitMask |= pgm_read_byte(&GFXsetBit[i]); +#else + lastByteBitMask |= (0x80 >> i); +#endif + } + ptr += remainingWholeBytes; + + if (color > 0) { + *ptr |= lastByteBitMask; + } else { + *ptr &= ~lastByteBitMask; + } + } + } +} + /**************************************************************************/ /*! @brief Instatiate a GFX 8-bit canvas context for graphics @@ -2001,41 +2186,119 @@ void GFXcanvas8::fillScreen(uint16_t color) { } } -void GFXcanvas8::writeFastHLine(int16_t x, int16_t y, int16_t w, - uint16_t color) { - - if ((x >= _width) || (y < 0) || (y >= _height)) +/**************************************************************************/ +/*! + @brief Speed optimized vertical line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param h length of vertical line to be drawn, including first point + @param color 8-bit Color to fill with. Only lower byte of uint16_t is + used. +*/ +/**************************************************************************/ +void GFXcanvas8::drawFastVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { return; - int16_t x2 = x + w - 1; - if (x2 < 0) - return; - - // Clip left/right - if (x < 0) { - x = 0; - w = x2 + 1; } - if (x2 >= _width) - w = _width - x; - int16_t t; - switch (rotation) { - case 1: - t = x; + if (y + h > height()) { + h = height() - y; + } else if (h < 0) { + // convert negative heights to their postive equivalent + h *= -1; + y -= h - 1; + if (y < 0) { + h += y; + y = 0; + } + } + + if (getRotation() == 0) { + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 1) { + int16_t t = x; x = WIDTH - 1 - y; y = t; - break; - case 2: + x -= h - 1; + drawFastRawHLine(x, y, h, color); + } else if (getRotation() == 2) { x = WIDTH - 1 - x; y = HEIGHT - 1 - y; - break; - case 3: - t = x; + + y -= h - 1; + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 3) { + int16_t t = x; x = y; y = HEIGHT - 1 - t; - break; + drawFastRawHLine(x, y, h, color); + } +} + +/**************************************************************************/ +/*! + @brief Speed optimized horizontal line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param w length of horizontal line to be drawn, including first point + @param color 8-bit Color to fill with. Only lower byte of uint16_t is + used. +*/ +/**************************************************************************/ +void GFXcanvas8::drawFastHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { + return; } + if (x + w > width()) { + w = width() - x; + } else if (w < 0) { + // convert negative widths to their postive equivalent + w *= -1; + x -= w - 1; + if (x < 0) { + w += x; + x = 0; + } + } + + if (getRotation() == 0) { + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 1) { + int16_t t = x; + x = WIDTH - 1 - y; + y = t; + drawFastRawVLine(x, y, w, color); + } else if (getRotation() == 2) { + x = WIDTH - 1 - x; + y = HEIGHT - 1 - y; + + x -= w - 1; + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 3) { + int16_t t = x; + x = y; + y = HEIGHT - 1 - t; + y -= w - 1; + drawFastRawVLine(x, y, w, color); + } +} + +void GFXcanvas8::drawFastRawVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. + uint8_t *buffer_ptr = buffer + y * WIDTH + x; + for (int16_t i = 0; i < h; i++) { + (*buffer_ptr) = color; + buffer_ptr += WIDTH; + } +} + +void GFXcanvas8::drawFastRawHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. memset(buffer + y * WIDTH + x, color, w); } @@ -2185,3 +2448,120 @@ void GFXcanvas16::byteSwap(void) { buffer[i] = __builtin_bswap16(buffer[i]); } } + +/**************************************************************************/ +/*! + @brief Speed optimized vertical line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param h length of vertical line to be drawn, including first point + @param color color 16-bit 5-6-5 Color to draw line with +*/ +/**************************************************************************/ +void GFXcanvas16::drawFastVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { + return; + } + + if (y + h > height()) { + h = height() - y; + } else if (h < 0) { + // convert negative heights to their postive equivalent + h *= -1; + y -= h - 1; + if (y < 0) { + h += y; + y = 0; + } + } + + if (getRotation() == 0) { + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 1) { + int16_t t = x; + x = WIDTH - 1 - y; + y = t; + x -= h - 1; + drawFastRawHLine(x, y, h, color); + } else if (getRotation() == 2) { + x = WIDTH - 1 - x; + y = HEIGHT - 1 - y; + + y -= h - 1; + drawFastRawVLine(x, y, h, color); + } else if (getRotation() == 3) { + int16_t t = x; + x = y; + y = HEIGHT - 1 - t; + drawFastRawHLine(x, y, h, color); + } +} + +/**************************************************************************/ +/*! + @brief Speed optimized horizontal line drawing + @param x Line horizontal start point + @param y Line vertical start point + @param w length of horizontal line to be drawn, including first point + @param color color 16-bit 5-6-5 Color to draw line with +*/ +/**************************************************************************/ +void GFXcanvas16::drawFastHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) { + return; + } + + if (x + w > width()) { + w = width() - x; + } else if (w < 0) { + // convert negative widths to their postive equivalent + w *= -1; + x -= w - 1; + if (x < 0) { + w += x; + x = 0; + } + } + + if (getRotation() == 0) { + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 1) { + int16_t t = x; + x = WIDTH - 1 - y; + y = t; + drawFastRawVLine(x, y, w, color); + } else if (getRotation() == 2) { + x = WIDTH - 1 - x; + y = HEIGHT - 1 - y; + + x -= w - 1; + drawFastRawHLine(x, y, w, color); + } else if (getRotation() == 3) { + int16_t t = x; + x = y; + y = HEIGHT - 1 - t; + y -= w - 1; + drawFastRawVLine(x, y, w, color); + } +} + +void GFXcanvas16::drawFastRawVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. + uint16_t *buffer_ptr = buffer + y * WIDTH + x; + for (int16_t i = 0; i < h; i++) { + (*buffer_ptr) = color; + buffer_ptr += WIDTH; + } +} + +void GFXcanvas16::drawFastRawHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + // x & y already in raw (rotation 0) coordinates, no need to transform. + size_t buffer_index = y * WIDTH + x; + for (int16_t i = buffer_index; i < buffer_index + w; i++) { + buffer[i] = color; + } +} diff --git a/Adafruit_GFX.h b/Adafruit_GFX.h index 5711637..b9d63c6 100644 --- a/Adafruit_GFX.h +++ b/Adafruit_GFX.h @@ -310,6 +310,8 @@ public: ~GFXcanvas1(void); void drawPixel(int16_t x, int16_t y, uint16_t color); void fillScreen(uint16_t color); + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); bool getPixel(int16_t x, int16_t y) const; /**********************************************************************/ /*! @@ -321,6 +323,8 @@ public: protected: bool getRawPixel(int16_t x, int16_t y) const; + void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color); private: uint8_t *buffer; @@ -338,7 +342,8 @@ public: ~GFXcanvas8(void); void drawPixel(int16_t x, int16_t y, uint16_t color); void fillScreen(uint16_t color); - void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); uint8_t getPixel(int16_t x, int16_t y) const; /**********************************************************************/ /*! @@ -350,6 +355,8 @@ public: protected: uint8_t getRawPixel(int16_t x, int16_t y) const; + void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color); private: uint8_t *buffer; @@ -363,6 +370,8 @@ public: void drawPixel(int16_t x, int16_t y, uint16_t color); void fillScreen(uint16_t color); void byteSwap(void); + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); uint16_t getPixel(int16_t x, int16_t y) const; /**********************************************************************/ /*! @@ -374,6 +383,8 @@ public: protected: uint16_t getRawPixel(int16_t x, int16_t y) const; + void drawFastRawVLine(int16_t x, int16_t y, int16_t h, uint16_t color); + void drawFastRawHLine(int16_t x, int16_t y, int16_t w, uint16_t color); private: uint16_t *buffer; diff --git a/examples/GFXcanvas/GFXcanvas.ino b/examples/GFXcanvas/GFXcanvas.ino index 09b00e9..d4e3370 100644 --- a/examples/GFXcanvas/GFXcanvas.ino +++ b/examples/GFXcanvas/GFXcanvas.ino @@ -1,6 +1,7 @@ /*** This example is intended to demonstrate the use of getPixel() versus -getRawPixel() in the GFXcanvas family of classes. +getRawPixel() and the fast horizontal and vertical drawing routines +in the GFXcanvas family of classes, When using the GFXcanvas* classes as the image buffer for a hardware driver, there is a need to get individual pixel color values at given physical @@ -15,9 +16,9 @@ in the image. This is useful for getting the pixel value to map to a hardware pixel location. This method was made protected as only the hardware driver should be accessing it. -The GFXcanvasSerialDemo class in this example will print to Serial the contents -of the underlying GFXcanvas buffer in both the current rotated layout and the -underlying physical layout. +The GFXcanvas*SerialDemo classes in this example will print to Serial the +contents of the underlying GFXcanvas buffer in both the current rotated layout +and the underlying physical layout. ***/ #include "GFXcanvasSerialDemo.h" @@ -26,46 +27,86 @@ underlying physical layout. void setup() { Serial.begin(115200); - // first create a rectangular GFXcanvasSerialDemo object and draw to it - GFXcanvasSerialDemo demo(21, 11); + // first create a rectangular GFXcanvas8SerialDemo object and draw to it + GFXcanvas8SerialDemo demo8(21, 11); - demo.fillScreen(0x00); - demo.setRotation(1); // now canvas is 11x21 - demo.fillCircle(5, 10, 5, 0xAA); - demo.writeLine(0, 0, 10, 0, 0x11); - demo.writeLine(0, 10, 10, 10, 0x22); - demo.writeLine(0, 20, 10, 20, 0x33); - demo.writeLine(0, 0, 0, 20, 0x44); - demo.writeLine(10, 0, 10, 20, 0x55); + demo8.fillScreen(0x00); + demo8.setRotation(1); // now canvas is 11x21 + demo8.fillCircle(5, 10, 5, 0xAA); + demo8.writeFastHLine(0, 0, 11, 0x11); + demo8.writeFastHLine(10, 10, -11, 0x22); + demo8.writeFastHLine(0, 20, 11, 0x33); + demo8.writeFastVLine(0, 0, 21, 0x44); + demo8.writeFastVLine(10, 20, -21, 0x55); Serial.println("Demonstrating GFXcanvas rotated and raw pixels.\n"); // print it out rotated Serial.println("The canvas's content in the rotation of '0':\n"); - demo.setRotation(0); - demo.print(true); + demo8.setRotation(0); + demo8.print(true); Serial.println("\n"); Serial.println("The canvas's content in the rotation of '1' (which is what " "it was drawn in):\n"); - demo.setRotation(1); - demo.print(true); + demo8.setRotation(1); + demo8.print(true); Serial.println("\n"); Serial.println("The canvas's content in the rotation of '2':\n"); - demo.setRotation(2); - demo.print(true); + demo8.setRotation(2); + demo8.print(true); Serial.println("\n"); Serial.println("The canvas's content in the rotation of '3':\n"); - demo.setRotation(3); - demo.print(true); + demo8.setRotation(3); + demo8.print(true); Serial.println("\n"); // print it out unrotated Serial.println("The canvas's content in it's raw, physical layout:\n"); - demo.print(false); + demo8.print(false); + Serial.println("\n"); + + // Demonstrate GFXcanvas1SerialDemo + + GFXcanvas1SerialDemo demo1(21, 11); + demo1.fillScreen(0); + demo1.setRotation(0); + demo1.writeFastHLine(0, 0, 9, 1); + demo1.setRotation(1); + demo1.writeFastHLine(0, 0, 9, 1); + demo1.setRotation(2); + demo1.writeFastHLine(0, 0, 9, 1); + demo1.setRotation(3); + demo1.writeFastHLine(0, 0, 9, 1); + demo1.setRotation(1); + demo1.fillRect(3, 8, 5, 5, 1); + + Serial.println("\nThe GFXcanvas1 raw content after drawing a fast horizontal " + "line in each rotation:\n"); + demo1.print(false); + Serial.println("\n"); + + // Demonstrate GFXcanvas16SerialDemo + + GFXcanvas16SerialDemo demo16(21, 11); + demo16.fillScreen(0); + demo16.setRotation(0); + demo16.writeFastHLine(0, 0, 9, 0x1111); + demo16.setRotation(1); + demo16.writeFastHLine(0, 0, 9, 0x2222); + demo16.setRotation(2); + demo16.writeFastHLine(0, 0, 9, 0x3333); + demo16.setRotation(3); + demo16.writeFastHLine(0, 0, 9, 0x4444); + demo16.setRotation(1); + demo16.fillRect(3, 8, 5, 5, 0x8888); + + Serial.println("\nThe GFXcanvas16 raw content after drawing a fast " + "horizontal line in each rotation:\n"); + demo16.print(false); Serial.println("\n"); } diff --git a/examples/GFXcanvas/GFXcanvasSerialDemo.cpp b/examples/GFXcanvas/GFXcanvasSerialDemo.cpp index 53bd200..ed83a96 100644 --- a/examples/GFXcanvas/GFXcanvasSerialDemo.cpp +++ b/examples/GFXcanvas/GFXcanvasSerialDemo.cpp @@ -1,10 +1,40 @@ #include "GFXcanvasSerialDemo.h" #include -GFXcanvasSerialDemo::GFXcanvasSerialDemo(uint16_t w, uint16_t h) +GFXcanvas1SerialDemo::GFXcanvas1SerialDemo(uint16_t w, uint16_t h) + : GFXcanvas1(w, h) {} + +void GFXcanvas1SerialDemo::print(bool rotated) { + char pixel_buffer[8]; + uint16_t width, height; + + if (rotated) { + width = this->width(); + height = this->height(); + } else { + width = this->WIDTH; + height = this->HEIGHT; + } + + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + bool pixel; + if (rotated) { + pixel = this->getPixel(x, y); + } else { + pixel = this->getRawPixel(x, y); + } + sprintf(pixel_buffer, " %d", pixel); + Serial.print(pixel_buffer); + } + Serial.print("\n"); + } +} + +GFXcanvas8SerialDemo::GFXcanvas8SerialDemo(uint16_t w, uint16_t h) : GFXcanvas8(w, h) {} -void GFXcanvasSerialDemo::print(bool rotated) { +void GFXcanvas8SerialDemo::print(bool rotated) { char pixel_buffer[8]; uint16_t width, height; @@ -30,3 +60,33 @@ void GFXcanvasSerialDemo::print(bool rotated) { Serial.print("\n"); } } + +GFXcanvas16SerialDemo::GFXcanvas16SerialDemo(uint16_t w, uint16_t h) + : GFXcanvas16(w, h) {} + +void GFXcanvas16SerialDemo::print(bool rotated) { + char pixel_buffer[8]; + uint16_t width, height; + + if (rotated) { + width = this->width(); + height = this->height(); + } else { + width = this->WIDTH; + height = this->HEIGHT; + } + + for (uint16_t y = 0; y < height; y++) { + for (uint16_t x = 0; x < width; x++) { + uint16_t pixel; + if (rotated) { + pixel = this->getPixel(x, y); + } else { + pixel = this->getRawPixel(x, y); + } + sprintf(pixel_buffer, " %04x", pixel); + Serial.print(pixel_buffer); + } + Serial.print("\n"); + } +} diff --git a/examples/GFXcanvas/GFXcanvasSerialDemo.h b/examples/GFXcanvas/GFXcanvasSerialDemo.h index 2f8a191..80589d9 100644 --- a/examples/GFXcanvas/GFXcanvasSerialDemo.h +++ b/examples/GFXcanvas/GFXcanvasSerialDemo.h @@ -8,9 +8,49 @@ for a device driver. */ /**********************************************************************/ -class GFXcanvasSerialDemo : public GFXcanvas8 { +class GFXcanvas1SerialDemo : public GFXcanvas1 { public: - GFXcanvasSerialDemo(uint16_t w, uint16_t h); + GFXcanvas1SerialDemo(uint16_t w, uint16_t h); + + /**********************************************************************/ + /*! + @brief Prints the current contents of the canvas to Serial + @param rotated true to print according to the current GFX rotation, + false to print to the native rotation of the canvas (or unrotated). + */ + /**********************************************************************/ + void print(bool rotated); +}; + +/**********************************************************************/ +/*! + @brief Demonstrates using the GFXconvas classes as the backing store + for a device driver. +*/ +/**********************************************************************/ +class GFXcanvas8SerialDemo : public GFXcanvas8 { +public: + GFXcanvas8SerialDemo(uint16_t w, uint16_t h); + + /**********************************************************************/ + /*! + @brief Prints the current contents of the canvas to Serial + @param rotated true to print according to the current GFX rotation, + false to print to the native rotation of the canvas (or unrotated). + */ + /**********************************************************************/ + void print(bool rotated); +}; + +/**********************************************************************/ +/*! + @brief Demonstrates using the GFXconvas classes as the backing store + for a device driver. +*/ +/**********************************************************************/ +class GFXcanvas16SerialDemo : public GFXcanvas16 { +public: + GFXcanvas16SerialDemo(uint16_t w, uint16_t h); /**********************************************************************/ /*!