// This is the PDQ re-mixed version of Adafruit's library from Xark // here is the original copyright notice and license: /* This is the core graphics library for all our displays, providing a common set of graphics primitives (points, lines, circles, etc.). It needs to be paired with a hardware-specific library for each display device we carry (to handle the lower-level functions). Adafruit invests time and resources providing this open source code, please support Adafruit & open-source hardware by purchasing products from Adafruit! Copyright (c) 2013 Adafruit Industries. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This PDQ optimized version is by Xark // // Inspiration from Paul Stoffregen and the Teensy 3.1 community. // // GOALS: // 1) Maintain "sketch" compatibility with original Adafruit libraries. // 2) Be as much faster as is reasonably possible honoring goal 1. :-) // 3) Be at least as small as Adafruit libraries. // // I believe all three of these have largely been achieved: // 1) Near full compatibility. Only minor initialization changes in original sketch. // 2) Between ~2.5 and ~12 times faster (fillRect ~2.5x, drawLine ~12x). // An average of ~4x faster over entire "graphictest.ino" benchmark. // // Even if this library is faster, it was based on the Adafruit original. // Adafruit deserves your support for making their library open-source (and // for having some nice LCD modules and all kinds of other great parts too). // Consider giving them your support if possible! #ifndef _PDQ_GFX_H #define _PDQ_GFX_H #include "Arduino.h" #include "Print.h" #ifndef pgm_read_byte #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) #endif #ifndef pgm_read_word #define pgm_read_word(addr) (*(const unsigned short *)(addr)) #endif #ifndef pgm_read_dword #define pgm_read_dword(addr) (*(const unsigned long *)(addr)) #endif #if !defined(__INT_MAX__) || (__INT_MAX__ > 0xFFFFL) #define pgm_read_pointer(addr) ((void *)pgm_read_dword(addr)) #else #define pgm_read_pointer(addr) ((void *)pgm_read_word(addr)) #endif #include "gfxfont.h" #define GFX_FONT_PACKED typedef int coord_t; // type used for coordinates (signed) for parameters (int16_t used for storage) typedef uint16_t color_t; // type used for colors (unsigned) // swap any type template static inline void swapValue(T& x, T& y) { T tmp = x; x = y; y = tmp; } // minimum value for any type template static inline T minValue(T& x, T& y) { return x < y ? x : y; } // maximum value for any type template static inline T maxValue(T& x, T& y) { return x >= y ? x : y; } template class PDQ_GFX : public Print { public: PDQ_GFX(coord_t w, coord_t h); // Constructor (called by HW driver) // Graphic primitives // drawPixel MUST be defined by the driver subclass (and has no generic fall-back): // These are generic versions of routines for drivers that don't provide device-optimized code. // Drivers are required to have these functions (without "_" postfix), but can fall back to using // these if needed (they should not be called directly with "_" postfix or it will bypass any // device-optimized implementations). static void drawLine_(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color); static void drawFastVLine_(coord_t x, coord_t y, coord_t h, color_t color); static void drawFastHLine_(coord_t x, coord_t y, coord_t w, color_t color); static void fillRect_(coord_t x, coord_t y, coord_t w, coord_t h, color_t color); static void fillScreen_(color_t color); // These are usually overridden in the driver subclass to be useful (but not internally referenced) static void setRotation(uint8_t r); // only swaps width/height if not supported by driver static void invertDisplay(boolean i); // only if supported by driver // These exist in PDQ_GFX (and generally have no subclass override) static void drawRect(coord_t x, coord_t y, coord_t w, coord_t h, color_t color); static void drawCircle(coord_t x0, coord_t y0, coord_t r, color_t color); static void drawCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, color_t color); static void fillCircle(coord_t x0, coord_t y0, coord_t r, color_t color); static void fillCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, coord_t delta, color_t color); static void drawTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color); static void fillTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color); static void drawRoundRect(coord_t x0, coord_t y0, coord_t w, coord_t h, coord_t radius, color_t color); static void fillRoundRect(coord_t x0, coord_t y0, coord_t w, coord_t h, coord_t radius, color_t color); static void drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color); static void drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg); static void drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color); static void drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg); static void drawXBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color); static void drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size); static void drawCharGFX(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size); static inline void setCursor(coord_t x, coord_t y); static inline void setBound(coord_t x, coord_t y); static inline void setTextColor(color_t c); static inline void setTextColor(color_t c, color_t bg); static inline void setTextSize(uint8_t s); static inline void setTextWrap(boolean w); static inline void cp437(boolean x=true); static inline void setFont(const GFXfont *f = NULL); static inline coord_t width() __attribute__ ((always_inline)) { return _width; } static inline coord_t height() __attribute__ ((always_inline)) { return _height; } static inline uint8_t getRotation() __attribute__ ((always_inline)) { return rotation; } static inline coord_t getCursorX() __attribute__ ((always_inline)) { return cursor_x; } static inline coord_t getCursorY() __attribute__ ((always_inline)) { return cursor_y; } static inline void getTextBounds(char *string, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi = _width); static inline void getTextBounds(const __FlashStringHelper *s, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi = _width); virtual size_t write(uint8_t); // used by Arduino "Print.h" (and the one required virtual function) protected: static GFXfont* gfxFont; static coord_t WIDTH, HEIGHT; // This is the 'raw' display w/h - never changes static coord_t _width, _height; // Display w/h as modified by current rotation static coord_t cursor_x, cursor_y; static coord_t bound_x1, bound_x2; static color_t textcolor, textbgcolor; static uint8_t textsize; static uint8_t rotation; static boolean wrap; // If set, 'wrap' text at right edge of display static boolean _cp437; // If set, use correct CP437 charset (default is off) }; template class PDQ_GFX_Button_ { public: PDQ_GFX_Button_(); void initButton(PDQ_GFX *gfx, coord_t x, coord_t y, coord_t w, coord_t h, color_t outline, color_t fill, color_t textcolor, const char *label, uint8_t textsize); void drawButton(boolean inverted = false); boolean contains(coord_t x, coord_t y); void press(boolean p); boolean isPressed(); boolean justPressed(); boolean justReleased(); private: PDQ_GFX *_gfx; int16_t _x, _y; int16_t _w, _h; uint8_t _textsize; color_t _outlinecolor, _fillcolor, _textcolor; char _label[10]; boolean currstate, laststate; }; // ----------------------------------------------- extern const unsigned char glcdfont[] PROGMEM; template int16_t PDQ_GFX::WIDTH; // This is the 'raw' display w/h - never changes template int16_t PDQ_GFX::HEIGHT; template int16_t PDQ_GFX::_width; // Display w/h as modified by current rotation template int16_t PDQ_GFX::_height; template int16_t PDQ_GFX::cursor_x; template int16_t PDQ_GFX::cursor_y; template int16_t PDQ_GFX::bound_x1; template int16_t PDQ_GFX::bound_x2; template color_t PDQ_GFX::textcolor; template color_t PDQ_GFX::textbgcolor; template uint8_t PDQ_GFX::textsize; template uint8_t PDQ_GFX::rotation; template boolean PDQ_GFX::wrap; // If set, 'wrap' text at right edge of display template boolean PDQ_GFX::_cp437; // If set, use correct CP437 charset (default is off) template GFXfont *PDQ_GFX::gfxFont; template PDQ_GFX::PDQ_GFX(coord_t w, coord_t h) { WIDTH = (int16_t)w; HEIGHT = (int16_t)h; _width = (int16_t)w; _height = (int16_t)h; cursor_x = 0; cursor_y = 0; bound_x1 = 0; bound_x2 = WIDTH; rotation = 0; textsize = 1; textcolor = 0xffff; textbgcolor = 0xffff; wrap = true; _cp437 = false; gfxFont = NULL; } // Draw a circle outline template void PDQ_GFX::drawCircle(coord_t x0, coord_t y0, coord_t r, color_t color) { coord_t f = 1 - r; coord_t ddF_x = 1; coord_t ddF_y = -2 * r; coord_t x = 0; coord_t y = r; HW::drawPixel(x0 , y0+r, color); HW::drawPixel(x0 , y0-r, color); HW::drawPixel(x0+r, y0 , color); HW::drawPixel(x0-r, y0 , color); while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; HW::drawPixel(x0 + x, y0 + y, color); HW::drawPixel(x0 - x, y0 + y, color); HW::drawPixel(x0 + x, y0 - y, color); HW::drawPixel(x0 - x, y0 - y, color); HW::drawPixel(x0 + y, y0 + x, color); HW::drawPixel(x0 - y, y0 + x, color); HW::drawPixel(x0 + y, y0 - x, color); HW::drawPixel(x0 - y, y0 - x, color); } } template void PDQ_GFX::drawCircleHelper( coord_t x0, coord_t y0, coord_t r, uint8_t cornername, color_t color) { coord_t f = 1 - r; coord_t ddF_x = 1; coord_t ddF_y = -2 * r; coord_t x = 0; coord_t y = r; while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x4) { HW::drawPixel(x0 + x, y0 + y, color); HW::drawPixel(x0 + y, y0 + x, color); } if (cornername & 0x2) { HW::drawPixel(x0 + x, y0 - y, color); HW::drawPixel(x0 + y, y0 - x, color); } if (cornername & 0x8) { HW::drawPixel(x0 - y, y0 + x, color); HW::drawPixel(x0 - x, y0 + y, color); } if (cornername & 0x1) { HW::drawPixel(x0 - y, y0 - x, color); HW::drawPixel(x0 - x, y0 - y, color); } } } template void PDQ_GFX::fillCircle(coord_t x0, coord_t y0, coord_t r, color_t color) { HW::drawFastVLine(x0, y0-r, 2*r+1, color); fillCircleHelper(x0, y0, r, 3, 0, color); } // Used to do circles and roundrects template void PDQ_GFX::fillCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, coord_t delta, color_t color) { coord_t f = 1 - r; coord_t ddF_x = 1; coord_t ddF_y = -2 * r; coord_t x = 0; coord_t y = r; while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x1) { HW::drawFastVLine(x0+x, y0-y, 2*y+1+delta, color); HW::drawFastVLine(x0+y, y0-x, 2*x+1+delta, color); } if (cornername & 0x2) { HW::drawFastVLine(x0-x, y0-y, 2*y+1+delta, color); HW::drawFastVLine(x0-y, y0-x, 2*x+1+delta, color); } } } // Bresenham's algorithm - thx Wikipedia template void PDQ_GFX::drawLine_(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color) { int8_t steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { swapValue(x0, y0); swapValue(x1, y1); } if (x0 > x1) { swapValue(x0, x1); swapValue(y0, y1); } coord_t dx, dy; dx = x1 - x0; dy = abs(y1 - y0); coord_t err = dx / 2; coord_t ystep; if (y0 < y1) { ystep = 1; } else { ystep = -1; } for (; x0<=x1; x0++) { if (steep) { HW::drawPixel(y0, x0, color); } else { HW::drawPixel(x0, y0, color); } err -= dy; if (err < 0) { y0 += ystep; err += dx; } } } // Draw a rectangle template void PDQ_GFX::drawRect(coord_t x, coord_t y, coord_t w, coord_t h, color_t color) { HW::drawFastHLine(x , y , w, color); HW::drawFastHLine(x , y+h-1, w, color); HW::drawFastVLine(x , y , h, color); HW::drawFastVLine(x+w-1, y , h, color); } template void PDQ_GFX::drawFastVLine_(coord_t x, coord_t y, coord_t h, color_t color) { // Used by driver when it has no special support HW::drawLine(x, y, x, y+h-1, color); } template void PDQ_GFX::drawFastHLine_(coord_t x, coord_t y, coord_t w, color_t color) { // Used by driver when it has no special support HW::drawLine(x, y, x+w-1, y, color); } template void PDQ_GFX::fillRect_(coord_t x, coord_t y, coord_t w, coord_t h, color_t color) { // Used by driver when it has no special support for (coord_t i=x; i void PDQ_GFX::fillScreen_(color_t color) { // Used by driver when it has no special support HW::fillRect(0, 0, _width, _height, color); } // Draw a rounded rectangle template void PDQ_GFX::drawRoundRect(coord_t x, coord_t y, coord_t w, coord_t h, coord_t r, color_t color) { // smarter version HW::drawFastHLine(x+r , y , w-2*r, color); // Top HW::drawFastHLine(x+r , y+h-1, w-2*r, color); // Bottom HW::drawFastVLine(x , y+r , h-2*r, color); // Left HW::drawFastVLine(x+w-1, y+r , h-2*r, color); // Right // draw four corners drawCircleHelper(x+r , y+r , r, 1, color); drawCircleHelper(x+w-r-1, y+r , r, 2, color); drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color); drawCircleHelper(x+r , y+h-r-1, r, 8, color); } // Fill a rounded rectangle template void PDQ_GFX::fillRoundRect(coord_t x, coord_t y, coord_t w, coord_t h, coord_t r, color_t color) { // smarter version HW::fillRect(x+r, y, w-2*r, h, color); // draw four corners fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color); fillCircleHelper(x+r , y+r, r, 2, h-2*r-1, color); } // Draw a triangle template void PDQ_GFX::drawTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color) { HW::drawLine(x0, y0, x1, y1, color); HW::drawLine(x1, y1, x2, y2, color); HW::drawLine(x2, y2, x0, y0, color); } // Fill a triangle template void PDQ_GFX::fillTriangle( coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color) { coord_t a, b, y, last; // Sort coordinates by Y order (y2 >= y1 >= y0) if (y0 > y1) { swapValue(y0, y1); swapValue(x0, x1); } if (y1 > y2) { swapValue(y2, y1); swapValue(x2, x1); } if (y0 > y1) { swapValue(y0, y1); swapValue(x0, x1); } if (y0 == y2) // Handle awkward all-on-same-line case as its own thing { a = b = x0; if (x1 < a) a = x1; else if (x1 > b) b = x1; if (x2 < a) a = x2; else if (x2 > b) b = x2; HW::drawFastHLine(a, y0, b-a+1, color); return; } coord_t dx01 = x1 - x0; coord_t dy01 = y1 - y0; coord_t dx02 = x2 - x0; coord_t dy02 = y2 - y0; coord_t dx12 = x2 - x1; coord_t dy12 = y2 - y1; int32_t sa = 0; int32_t sb = 0; // For upper part of triangle, find scanline crossings for segments // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 // is included here (and second loop will be skipped, avoiding a /0 // error there), otherwise scanline y1 is skipped here and handled // in the second loop...which also avoids a /0 error here if y0=y1 // (flat-topped triangle). if (y1 == y2) last = y1; // Include y1 scanline else last = y1-1; // Skip it for (y = y0; y <= last; y++) { a = x0 + sa / dy01; b = x0 + sb / dy02; sa += dx01; sb += dx02; /* longhand: a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if (a > b) swapValue(a, b); HW::drawFastHLine(a, y, b-a+1, color); } // For lower part of triangle, find scanline crossings for segments // 0-2 and 1-2. This loop is skipped if y1=y2. sa = dx12 * (y - y1); sb = dx02 * (y - y0); for (; y <= y2; y++) { a = x1 + sa / dy12; b = x0 + sb / dy02; sa += dx12; sb += dx02; /* longhand: a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if (a > b) swapValue(a, b); HW::drawFastHLine(a, y, b-a+1, color); } } // Draw a 1-bit image (bitmap) at the specified (x, y) position from the // provided bitmap buffer (must be PROGMEM memory) using the specified // foreground color (unset bits are transparent). template void PDQ_GFX::drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color) { coord_t i, j, byteWidth = (w + 7) / 8; uint8_t byte; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (i % 8 == 0) byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); else byte <<= 1; if (byte & 0x80) HW::drawPixel(x+i, y+j, color); } } } // Draw a 1-bit image (bitmap) at the specified (x, y) position from the // provided bitmap buffer (must be PROGMEM memory) using the specified // foreground (for set bits) and background (for clear bits) colors. template void PDQ_GFX::drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg) { coord_t i, j, byteWidth = (w + 7) / 8; uint8_t byte; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (i % 8 == 0) byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); else byte <<= 1; if (byte & 0x80) HW::drawPixel(x+i, y+j, color); else HW::drawPixel(x+i, y+j, bg); } } } // drawBitmap() variant for RAM-resident (not PROGMEM) bitmaps. template void PDQ_GFX::drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color) { coord_t i, j, byteWidth = (w + 7) / 8; uint8_t byte; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (i % 8 == 0) byte = bitmap[j * byteWidth + i / 8]; else byte <<= 1; if (byte & 0x80) HW::drawPixel(x+i, y+j, color); } } } // drawBitmap() variant w/background for RAM-resident (not PROGMEM) bitmaps. template void PDQ_GFX::drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg) { coord_t i, j, byteWidth = (w + 7) / 8; uint8_t byte; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (i % 8 == 0) byte = bitmap[j * byteWidth + i / 8]; else byte <<= 1; if (byte & 0x80) HW::drawPixel(x+i, y+j, color); else HW::drawPixel(x+i, y+j, bg); } } } // Draw XBitMap Files (*.xbm), exported from GIMP, // Usage: Export from GIMP to *.xbm, rename *.xbm to *.c and open in editor. // C Array can be directly used with this function template void PDQ_GFX::drawXBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color) { coord_t i, j, byteWidth = (w + 7) / 8; uint8_t byte; for (j = 0; j < h; j++) { for (i = 0; i < w; i++) { if (i % 8 == 0) byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); else byte >>= 1; if (byte & 0x01) HW::drawPixel(x+i, y+j, color); } } } template size_t PDQ_GFX::write(uint8_t c) { // 'Classic' built-in font if (!gfxFont) { if (c == '\n') { cursor_x = 0; cursor_y += (coord_t)textsize*8; } else if (c != '\r') { HW::drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); cursor_x += textsize*6; if (wrap && (cursor_x > (_width - textsize*6))) { cursor_x = 0; cursor_y += textsize*8; } } } else { if(c == '\n') { cursor_x = 0; cursor_y += (coord_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } else if (c != '\r') { uint8_t first = pgm_read_byte(&gfxFont->first); if ((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { uint8_t c2 = c - pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c2]); uint8_t w = pgm_read_byte(&glyph->width); uint8_t h = pgm_read_byte(&glyph->height); // Is there an associated bitmap? if ((w > 0) && (h > 0)) { coord_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); // sic if(wrap && ((cursor_x + textsize * (xo + w)) >= bound_x2)) { // Drawing character would go off right edge; wrap to new line cursor_x = bound_x1; cursor_y += (coord_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } HW::drawCharGFX(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); } cursor_x += pgm_read_byte(&glyph->xAdvance) * (coord_t)textsize; } } } return 1; } // Draw a character with built-in font template void PDQ_GFX::drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size) { if ((x >= _width) || (y >= _height) || ((x + (6 * size) - 1) < 0) || ((y + (8 * size) - 1) < 0)) return; uint8_t is_opaque = (bg != color); if(!_cp437 && (c >= 176)) // Handle 'classic' charset behavior c++; for (int8_t i=0; i<6; i++) { uint8_t line; if (i == 5) line = 0x0; else line = pgm_read_byte(glcdfont+(c*5)+i); if (size == 1) { for (int8_t j = 0; j < 8; j++) { if (line & 0x1) { HW::drawPixel(x+i, y+j, color); } else if (is_opaque) { HW::drawPixel(x+i, y+j, bg); } line >>= 1; } } else { for (int8_t j = 0; j < 8; j++) { if (line & 0x1) { HW::fillRect(x+(i*size), y+(j*size), size, size, color); } else if (is_opaque) { HW::fillRect(x+(i*size), y+(j*size), size, size, bg); } line >>= 1; } } } } // Draw a character with GFX font template void PDQ_GFX::drawCharGFX(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size) { // Character is assumed previously filtered by write() to eliminate // newlines, returns, non-printable characters, etc. Calling drawChar() // directly with 'bad' characters of font may cause mayhem! c -= pgm_read_byte(&gfxFont->first); GFXglyph *glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); uint8_t *bitmap = (uint8_t *)pgm_read_pointer(&gfxFont->bitmap); uint16_t bo = pgm_read_word(&glyph->bitmapOffset); uint8_t w = pgm_read_byte(&glyph->width); uint8_t h = pgm_read_byte(&glyph->height); // uint8_t xa = pgm_read_byte(&glyph->xAdvance); int8_t xo = pgm_read_byte(&glyph->xOffset); int8_t yo = pgm_read_byte(&glyph->yOffset); // Todo: Add character clipping here // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS. // THIS IS ON PURPOSE AND BY DESIGN. The background color feature // has typically been used with the 'classic' font to overwrite old // screen contents with new data. This ONLY works because the // characters are a uniform size; it's not a sensible thing to do with // proportionally-spaced fonts with glyphs of varying sizes (and that // may overlap). To replace previously-drawn text when using a custom // font, use the getTextBounds() function to determine the smallest // rectangle encompassing a string, erase the area with fillRect(), // then draw new text. This WILL infortunately 'blink' the text, but // is unavoidable. Drawing 'background' pixels will NOT fix this, // only creates a new set of problems. Have an idea to work around // this (a canvas object type for MCUs that can afford the RAM and // displays supporting setAddrWindow() and pushColors()), but haven't // implemented this yet. if (bo & 0x8000) { // packed font uint8_t bits_cnt = 0; uint8_t bits; uint8_t cnt,cnt2; bo &= 0x7FFF; coord_t _y = y+yo; for (coord_t yy=0; yy= w) { cnt2 = w-xx; } else { cnt2 = cnt; } if (bits & 0x8) { if (size == 1) { HW::drawFastHLine(_x, _y, cnt2, color); _x += cnt2; } else { HW::fillRect(x+xo+xx*size, y+yo+yy*size, cnt2*size, size, color); // _x not used if size > 1, so not need to increment it } } else { _x += cnt2; } cnt -= cnt2; if (cnt == 0) { bits >>= 4; cnt = (bits & 0x7)+1; bits_cnt--; } } } } else { uint8_t bit = 0; uint8_t bits = 0; if (size == 1) { coord_t _y = y+yo; for (coord_t yy=0; yy void PDQ_GFX::setCursor(coord_t x, coord_t y) { cursor_x = (int16_t)x; cursor_y = (int16_t)y; } template void PDQ_GFX::setBound(coord_t x1, coord_t x2) { bound_x1 = (int16_t)x1; bound_x2 = (int16_t)x2; } template void PDQ_GFX::setTextSize(uint8_t s) { textsize = (s > 0) ? s : 1; } template void PDQ_GFX::setTextColor(color_t c) { // For 'transparent' background, we'll set the bg // to the same as fg instead of using a flag textcolor = c; textbgcolor = c; } template void PDQ_GFX::setTextColor(color_t c, color_t b) { textcolor = c; textbgcolor = b; } template void PDQ_GFX::setTextWrap(boolean w) { wrap = w; } template void PDQ_GFX::setRotation(uint8_t x) { // Used by driver when it has no special support rotation = x & 3; switch(rotation) { case 0: case 2: _width = WIDTH; _height = HEIGHT; break; case 1: case 3: _width = HEIGHT; _height = WIDTH; break; } } template void PDQ_GFX::cp437(boolean x) { _cp437 = x; } template void PDQ_GFX::setFont(const GFXfont *f) { if (f) // Font struct pointer passed in? { if (!gfxFont) // And no current font struct? { // Switching from classic to new font behavior. // Move cursor pos down 6 pixels so it's on baseline. cursor_y += 6; } } else if (gfxFont) // NULL passed. Current font struct defined? { // Switching from new to classic font behavior. // Move cursor pos up 6 pixels so it's at top-left of char. cursor_y -= 6; } gfxFont = (GFXfont *)f; } // Pass string and a cursor position, returns UL corner and W,H. template void PDQ_GFX::getTextBounds(char *str, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi) { uint8_t c; // Current character coord_t xs = x; coord_t xe = xs+wi; *x1 = x; *y1 = y; *w = *h = 0; if (gfxFont) { GFXglyph *glyph; uint8_t first = pgm_read_byte(&gfxFont->first); uint8_t last = pgm_read_byte(&gfxFont->last); uint8_t gw, gh, xa; int8_t xo, yo; int16_t minx = _width, miny = _height, maxx = -1, maxy = -1; coord_t gx1, gy1, gx2, gy2; coord_t ts = (coord_t)textsize, ya = (coord_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); while((c = *str++)) { if (c != '\n') // Not a newline { if (c != '\r') // Not a carriage return, is normal char { if ((c >= first) && (c <= last)) // Char present in current font { c -= first; glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); gw = pgm_read_byte(&glyph->width); gh = pgm_read_byte(&glyph->height); xa = pgm_read_byte(&glyph->xAdvance); xo = pgm_read_byte(&glyph->xOffset); yo = pgm_read_byte(&glyph->yOffset); if (wrap && ((x + (((int16_t)xo + gw) * ts)) >= xe)) // Line wrap { x = xs; // Reset x to 0 y += ya; // Advance y by 1 line } gx1 = x + xo * ts; gy1 = y + yo * ts; gx2 = gx1 + gw * ts - 1; gy2 = gy1 + gh * ts - 1; if (gx1 < minx) minx = gx1; if (gy1 < miny) miny = gy1; if (gx2 > maxx) maxx = gx2; if (gy2 > maxy) maxy = gy2; x += xa * ts; } } // Carriage return = do nothing } else // Newline { x = xs; // Reset x y += ya; // Advance y by 1 line } } // End of string *x1 = minx; *y1 = miny; if (maxx >= minx) *w = maxx - minx + 1; if (maxy >= miny) *h = maxy - miny + 1; } else // Default font { uint16_t lineWidth = 0, maxWidth = 0; // Width of current, all lines while((c = *str++)) { if (c != '\n') // Not a newline { if (c != '\r') // Not a carriage return, is normal char { lineWidth += textsize * 6; // Includes interchar x gap if (wrap && (cursor_x > (_width - textsize*6))) { x = 0; y += textsize*8; if (lineWidth > maxWidth) // Save widest line maxWidth = lineWidth; lineWidth = textsize * 6; // First char on new line } } // Carriage return = do nothing } else // Newline { x = 0; // Reset x to 0 y += textsize * 8; // Advance y by 1 line if (lineWidth > maxWidth) // Save widest line maxWidth = lineWidth; lineWidth = 0; // Reset lineWidth for new line } } // End of string if (lineWidth) // Add height of last (or only) line y += textsize * 8; *w = maxWidth - 1; // Don't include last interchar x gap *h = y - *y1; } // End classic vs custom font } // Same as above, but for PROGMEM strings template void PDQ_GFX::getTextBounds(const __FlashStringHelper *str, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi) { uint8_t *s = (uint8_t *)str; uint8_t c; coord_t xs = x; coord_t xe = xs+wi; *x1 = x; *y1 = y; *w = *h = 0; if (gfxFont) { GFXglyph *glyph; uint8_t first = pgm_read_byte(&gfxFont->first); uint8_t last = pgm_read_byte(&gfxFont->last); uint8_t gw, gh, xa; int8_t xo, yo; int16_t minx = _width, miny = _height, maxx = -1, maxy = -1; coord_t gx1, gy1, gx2, gy2; coord_t ts = (coord_t)textsize; coord_t ya = ts * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); while((c = pgm_read_byte(s++))) { if (c != '\n') // Not a newline { if (c != '\r') // Not a carriage return, is normal char { if ((c >= first) && (c <= last)) // Char present in current font { c -= first; glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); gw = pgm_read_byte(&glyph->width); gh = pgm_read_byte(&glyph->height); xa = pgm_read_byte(&glyph->xAdvance); xo = pgm_read_byte(&glyph->xOffset); yo = pgm_read_byte(&glyph->yOffset); if (wrap && ((x + (((int16_t)xo + gw) * ts)) >= xe)) // Line wrap { x = xs; // Reset x to 0 y += ya; // Advance y by 1 line } gx1 = x + xo * ts; gy1 = y + yo * ts; gx2 = gx1 + gw * ts - 1; gy2 = gy1 + gh * ts - 1; if (gx1 < minx) minx = gx1; if (gy1 < miny) miny = gy1; if (gx2 > maxx) maxx = gx2; if (gy2 > maxy) maxy = gy2; x += xa * ts; } } // Carriage return = do nothing } else // Newline { x = xs; // Reset x y += ya; // Advance y by 1 line } } // End of string *x1 = minx; *y1 = miny; if (maxx >= minx) *w = maxx - minx + 1; if (maxy >= miny) *h = maxy - miny + 1; } else // Default font { uint16_t lineWidth = 0, maxWidth = 0; // Width of current, all lines while((c = pgm_read_byte(s++))) { if (c != '\n') // Not a newline { if (c != '\r') // Not a carriage return, is normal char { if (wrap && ((x + textsize * 6) >= _width)) { x = 0; // Reset x to 0 y += textsize * 8; // Advance y by 1 line if (lineWidth > maxWidth) // Save widest line maxWidth = lineWidth; lineWidth = textsize * 6; // First char on new line } else // No line wrap, just keep incrementing X { lineWidth += textsize * 6; // Includes interchar x gap } } // Carriage return = do nothing } else // Newline { x = 0; // Reset x to 0 y += textsize * 8; // Advance y by 1 line if (lineWidth > maxWidth) // Save widest line maxWidth = lineWidth; lineWidth = 0; // Reset lineWidth for new line } } // End of string if (lineWidth) // Add height of last (or only) line y += textsize * 8; *w = maxWidth - 1; // Don't include last interchar x gap *h = y - *y1; } // End classic vs custom font } template void PDQ_GFX::invertDisplay(boolean i) { // Used by driver when it has no special support // Do nothing, must be supported by driver } /***************************************************************************/ // code for the GFX button UI element template PDQ_GFX_Button_::PDQ_GFX_Button_() { _gfx = 0; } template void PDQ_GFX_Button_::initButton(PDQ_GFX *gfx, coord_t x, coord_t y, coord_t w, coord_t h, color_t outline, color_t fill, color_t textcolor, const char *label, uint8_t textsize) { _gfx = gfx; _x = x; _y = y; _w = w; _h = h; _outlinecolor = outline; _fillcolor = fill; _textcolor = textcolor; _textsize = textsize; strncpy(_label, label, 9); _label[9] = 0; } template void PDQ_GFX_Button_::drawButton(boolean inverted) { uint16_t fill, outline, text; if (!inverted) { fill = _fillcolor; outline = _outlinecolor; text = _textcolor; } else { fill = _textcolor; outline = _outlinecolor; text = _fillcolor; } _gfx->fillRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, fill); _gfx->drawRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, outline); _gfx->setCursor(_x - strlen(_label)*3*_textsize, _y-4*_textsize); _gfx->setTextColor(text); _gfx->setTextSize(_textsize); _gfx->print(_label); } template boolean PDQ_GFX_Button_::contains(coord_t x, coord_t y) { if ((x < (_x - _w/2)) || (x > (_x + _w/2))) return false; if ((y < (_y - _h/2)) || (y > (_y + _h/2))) return false; return true; } template void PDQ_GFX_Button_::press(boolean p) { laststate = currstate; currstate = p; } template boolean PDQ_GFX_Button_::isPressed() { return currstate; } template boolean PDQ_GFX_Button_::justPressed() { return (currstate && !laststate); } template boolean PDQ_GFX_Button_::justReleased() { return (!currstate && laststate); } #endif // _PDQ_GFX_H