From 8752236ac28847349e43f01a92a4046e27b520ec Mon Sep 17 00:00:00 2001 From: Bodmer Date: Sat, 24 Feb 2018 19:02:20 +0000 Subject: [PATCH] Add smooth (antialiased) fonts --- Extensions/Button.cpp | 76 + Extensions/Button.h | 38 + Extensions/Smooth_font.cpp | 511 +++++ Extensions/Smooth_font.h | 54 + Extensions/Sprite.cpp | 1409 +++++++++++++ Extensions/Sprite.h | 111 + Extensions/Touch.cpp | 319 +++ Extensions/Touch.h | 24 + Fonts/Font7srle.c | 8 +- README.md | 4 +- TFT_Drivers/ILI9163_Rotation.h | 16 +- TFT_Drivers/ILI9341_Rotation.h | 32 +- TFT_Drivers/RPI_ILI9486_Rotation.h | 32 +- TFT_Drivers/S6D02A1_Rotation.h | 16 +- TFT_Drivers/ST7735_Rotation.h | 32 +- TFT_eSPI.cpp | 1866 ++--------------- TFT_eSPI.h | 228 +- .../Create_font_5/Create_font_5.pde | 483 +++++ .../Create_font_5/data/Final-Frontier.ttf | Bin 0 -> 19800 bytes Tools/PlatformIO/Configuring options.txt | 33 + User_Setup.h | 14 +- User_Setup_Select.h | 4 +- .../Print_Smooth_Font/Print_Smooth_Font.ino | 195 ++ .../data/Final-Frontier-28.vlw | Bin 0 -> 25287 bytes .../Unicode_test/SPIFFS_functions.ino | 83 + .../Unicode_test/Unicode_test.ino | 148 ++ .../Unicode_test/data/Final-Frontier-28.vlw | Bin 0 -> 25287 bytes .../Unicode_test/data/Latin-Hiragana-24.vlw | Bin 0 -> 54478 bytes .../Unicode_test/data/Unicode-Test-72.vlw | Bin 0 -> 36469 bytes .../alphaBlend_Test/alphaBlend_Test.ino | 194 ++ keywords.txt | 8 +- library.json | 2 +- library.properties | 2 +- license.txt | 4 +- 34 files changed, 4031 insertions(+), 1915 deletions(-) create mode 100644 Extensions/Button.cpp create mode 100644 Extensions/Button.h create mode 100644 Extensions/Smooth_font.cpp create mode 100644 Extensions/Smooth_font.h create mode 100644 Extensions/Sprite.cpp create mode 100644 Extensions/Sprite.h create mode 100644 Extensions/Touch.cpp create mode 100644 Extensions/Touch.h create mode 100644 Tools/Create_Smooth_Font/Create_font_5/Create_font_5.pde create mode 100644 Tools/Create_Smooth_Font/Create_font_5/data/Final-Frontier.ttf create mode 100644 Tools/PlatformIO/Configuring options.txt create mode 100644 examples/Smooth Fonts/Print_Smooth_Font/Print_Smooth_Font.ino create mode 100644 examples/Smooth Fonts/Print_Smooth_Font/data/Final-Frontier-28.vlw create mode 100644 examples/Smooth Fonts/Unicode_test/SPIFFS_functions.ino create mode 100644 examples/Smooth Fonts/Unicode_test/Unicode_test.ino create mode 100644 examples/Smooth Fonts/Unicode_test/data/Final-Frontier-28.vlw create mode 100644 examples/Smooth Fonts/Unicode_test/data/Latin-Hiragana-24.vlw create mode 100644 examples/Smooth Fonts/Unicode_test/data/Unicode-Test-72.vlw create mode 100644 examples/Smooth Fonts/alphaBlend_Test/alphaBlend_Test.ino diff --git a/Extensions/Button.cpp b/Extensions/Button.cpp new file mode 100644 index 0000000..88a8bc0 --- /dev/null +++ b/Extensions/Button.cpp @@ -0,0 +1,76 @@ +/*************************************************************************************** +** Code for the GFX button UI element +** Grabbed from Adafruit_GFX library and enhanced to handle any label font +***************************************************************************************/ +TFT_eSPI_Button::TFT_eSPI_Button(void) { + _gfx = 0; +} + +// Classic initButton() function: pass center & size +void TFT_eSPI_Button::initButton( + TFT_eSPI *gfx, int16_t x, int16_t y, uint16_t w, uint16_t h, + uint16_t outline, uint16_t fill, uint16_t textcolor, + char *label, uint8_t textsize) +{ + // Tweak arguments and pass to the newer initButtonUL() function... + initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, + textcolor, label, textsize); +} + +// Newer function instead accepts upper-left corner & size +void TFT_eSPI_Button::initButtonUL( + TFT_eSPI *gfx, int16_t x1, int16_t y1, uint16_t w, uint16_t h, + uint16_t outline, uint16_t fill, uint16_t textcolor, + char *label, uint8_t textsize) +{ + _x1 = x1; + _y1 = y1; + _w = w; + _h = h; + _outlinecolor = outline; + _fillcolor = fill; + _textcolor = textcolor; + _textsize = textsize; + _gfx = gfx; + strncpy(_label, label, 9); +} + +void TFT_eSPI_Button::drawButton(boolean inverted) { + uint16_t fill, outline, text; + + if(!inverted) { + fill = _fillcolor; + outline = _outlinecolor; + text = _textcolor; + } else { + fill = _textcolor; + outline = _outlinecolor; + text = _fillcolor; + } + + uint8_t r = min(_w, _h) / 4; // Corner radius + _gfx->fillRoundRect(_x1, _y1, _w, _h, r, fill); + _gfx->drawRoundRect(_x1, _y1, _w, _h, r, outline); + + _gfx->setTextColor(text); + _gfx->setTextSize(_textsize); + + uint8_t tempdatum = _gfx->getTextDatum(); + _gfx->setTextDatum(MC_DATUM); + _gfx->drawString(_label, _x1 + (_w/2), _y1 + (_h/2)); + _gfx->setTextDatum(tempdatum); +} + +boolean TFT_eSPI_Button::contains(int16_t x, int16_t y) { + return ((x >= _x1) && (x < (_x1 + _w)) && + (y >= _y1) && (y < (_y1 + _h))); +} + +void TFT_eSPI_Button::press(boolean p) { + laststate = currstate; + currstate = p; +} + +boolean TFT_eSPI_Button::isPressed() { return currstate; } +boolean TFT_eSPI_Button::justPressed() { return (currstate && !laststate); } +boolean TFT_eSPI_Button::justReleased() { return (!currstate && laststate); } diff --git a/Extensions/Button.h b/Extensions/Button.h new file mode 100644 index 0000000..e44a8f4 --- /dev/null +++ b/Extensions/Button.h @@ -0,0 +1,38 @@ +/*************************************************************************************** +// The following button class has been ported over from the Adafruit_GFX library so +// should be compatible. +// A slightly different implementation in this TFT_eSPI library allows the button +// legends to be in any font +***************************************************************************************/ + +class TFT_eSPI_Button { + + public: + TFT_eSPI_Button(void); + // "Classic" initButton() uses center & size + void initButton(TFT_eSPI *gfx, int16_t x, int16_t y, + uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, + uint16_t textcolor, char *label, uint8_t textsize); + + // New/alt initButton() uses upper-left corner & size + void initButtonUL(TFT_eSPI *gfx, int16_t x1, int16_t y1, + uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, + uint16_t textcolor, char *label, uint8_t textsize); + void drawButton(boolean inverted = false); + boolean contains(int16_t x, int16_t y); + + void press(boolean p); + boolean isPressed(); + boolean justPressed(); + boolean justReleased(); + + private: + TFT_eSPI *_gfx; + int16_t _x1, _y1; // Coordinates of top-left corner + uint16_t _w, _h; + uint8_t _textsize; + uint16_t _outlinecolor, _fillcolor, _textcolor; + char _label[10]; + + boolean currstate, laststate; +}; diff --git a/Extensions/Smooth_font.cpp b/Extensions/Smooth_font.cpp new file mode 100644 index 0000000..dc41edb --- /dev/null +++ b/Extensions/Smooth_font.cpp @@ -0,0 +1,511 @@ + // Coded by Bodmer 10/2/18, see license in root directory. + // This is part of the TFT_eSPI class and is associated with anti-aliased font functions + + +//////////////////////////////////////////////////////////////////////////////////////// +// New anti-aliased (smoothed) font functions added below +//////////////////////////////////////////////////////////////////////////////////////// + +/*************************************************************************************** +** Function name: loadFont +** Description: loads parameters from a new font vlw file stored in SPIFFS +*************************************************************************************x*/ +void TFT_eSPI::loadFont(String fontName) +{ + /* + The vlw font format does not appear to be documented anywhere, so some reverse + engineering has been applied! + + Header of vlw file comprises 6 uint32_t parameters (24 bytes total): + 1. The gCount (number of character glyphs) + 2. A version number (0xB = 11 for the one I am using) + 3. The font size (in points, not pixels) + 4. Deprecated mboxY parameter (typically set to 0) + 5. Ascent in pixels from baseline to top of "d" + 6. Descent in pixels from baseline to bottom of "p" + + Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes): + 1. Glyph Unicode stored as a 32 bit value + 2. Height of bitmap bounding box + 3. Width of bitmap bounding box + 4. gxAdvance for cursor (setWidth in Processing) + 5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up) + 6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left) + 7. padding value, typically 0 + + The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file. + Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from + 0xFF foreground colour, 0x00 background. The sketch uses a linear interpolation + between the foreground and background RGB component colours. e.g. + pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255 + To gain a performance advantage fixed point arithmetic is used with rounding and + division by 256 (shift right 8 bits is faster). + + After the bitmaps is: + 1 byte for font name string length (excludes null) + a zero terminated character string giving the font name + 1 byte for Postscript name string length + a zero/one terminated character string giving the font name + last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed) + + Then the font name seen by Java when it's created + Then the postscript name of the font + Then a boolean to tell if smoothing is on or not. + + Glyph bitmap example is: + // Cursor coordinate positions for this and next character are marked by 'C' + // C<------- gxAdvance ------->C gxAdvance is how far to move cursor for next glyph cursor position + // | | + // | | ascent is top of "d", descent is bottom of "p" + // +-- gdX --+ ascent + // | +-- gWidth--+ | gdX is offset to left edge of glyph bitmap + // | + x@.........@x + | gdX may be negative e.g. italic "y" tail extending to left of + // | | @@.........@@ | | cursor position, plot top left corner of bitmap at (cursorX + gdX) + // | | @@.........@@ gdY | gWidth and gHeight are glyph bitmap dimensions + // | | .@@@.....@@@@ | | + // | gHeight ....@@@@@..@@ + + <-- baseline + // | | ...........@@ | + // | | ...........@@ | gdY is the offset to the top edge of the bitmap + // | | .@@.......@@. descent plot top edge of bitmap at (cursorY + yAdvance - gdY) + // | + x..@@@@@@@..x | x marks the corner pixels of the bitmap + // | | + // +---------------------------+ yAdvance is y delta for the next line, font size or (ascent + descent) + // some fonts can overlay in y direction so may need a user adjust value + + */ + + _gFontFilename = "/" + fontName + ".vlw"; + + fontFile = SPIFFS.open( _gFontFilename, "r"); + + if(!fontFile) return; + + //unloadFont(); + + fontFile.seek(0, fs::SeekSet); + + gFont.gCount = (uint16_t)readInt32(); // glyph count in file + readInt32(); // vlw encoder version - discard + gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels + readInt32(); // discard + gFont.ascent = (uint16_t)readInt32(); // top of "d" + gFont.descent = (uint16_t)readInt32(); // bottom of "p" + + // These next gFont values will be updated when the Metrics are fetched + gFont.maxAscent = gFont.ascent; // Determined from metrics + gFont.maxDescent = gFont.descent; // Determined from metrics + gFont.yAdvance = gFont.ascent + gFont.descent; + gFont.spaceWidth = gFont.yAdvance / 4; // Guess at space width + + fontLoaded = true; + + // Fetch the metrics for each glyph + loadMetrics(gFont.gCount); + + //fontFile.close(); +} + + +/*************************************************************************************** +** Function name: loadMetrics +** Description: Get the metrics for each glyph and store in RAM +*************************************************************************************x*/ +//#define SHOW_ASCENT_DESCENT +void TFT_eSPI::loadMetrics(uint16_t gCount) +{ + uint32_t headerPtr = 24; + uint32_t bitmapPtr = 24 + gCount * 28; + + gUnicode = (uint16_t*)malloc( gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF) + gHeight = (uint8_t*)malloc( gCount ); // Height of glyph + gWidth = (uint8_t*)malloc( gCount ); // Width of glyph + gxAdvance = (uint8_t*)malloc( gCount ); // xAdvance - to move x cursor + gdY = (int8_t*)malloc( gCount ); // offset from bitmap top edge from lowest point in any character + gdX = (int8_t*)malloc( gCount ); // offset for bitmap left edge relative to cursor X + gBitmap = (uint32_t*)malloc( gCount * 4); // seek pointer to glyph bitmap in SPIFFS file + +#ifdef SHOW_ASCENT_DESCENT + Serial.print("ascent = "); Serial.println(gFont.ascent); + Serial.print("descent = "); Serial.println(gFont.descent); +#endif + + uint16_t gNum = 0; + fontFile.seek(headerPtr, fs::SeekSet); + while (gNum < gCount) + { + gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value + gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph + gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph + gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor + gdY[gNum] = (int8_t)readInt32(); // y delta from baseline + gdX[gNum] = (int8_t)readInt32(); // x delta from cursor + readInt32(); // ignored + + // Different glyph sets have different ascent values not always based on "d", so get maximum glyph ascent + if (gdY[gNum] > gFont.maxAscent) + { + // Avoid UTF coding values and characters that tend to give duff values + if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF)) + { + gFont.maxAscent = gdY[gNum]; +#ifdef SHOW_ASCENT_DESCENT + Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent = "); Serial.println(gFont.maxAscent); +#endif + } + } + + // Different glyph sets have different descent values not always based on "p", so get maximum glyph descent + if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent) + { + // Avoid UTF coding values and characters that tend to give duff values + if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF)) + { + gFont.maxDescent = gHeight[gNum] - gdY[gNum]; +#ifdef SHOW_ASCENT_DESCENT + Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]); +#endif + } + } + + gBitmap[gNum] = bitmapPtr; + + headerPtr += 28; + + bitmapPtr += gWidth[gNum] * gHeight[gNum]; + + gNum++; + yield(); + } + + gFont.yAdvance = gFont.maxAscent + gFont.maxDescent; + + gFont.spaceWidth = (gFont.ascent + gFont.descent) * 2/7; // Guess at space width +} + + +/*************************************************************************************** +** Function name: deleteMetrics +** Description: Delete the old glyph metrics and free up the memory +*************************************************************************************x*/ +void TFT_eSPI::unloadFont( void ) +{ + if (gUnicode) + { + free(gUnicode); + gUnicode = NULL; + } + + if (gHeight) + { + free(gHeight); + gHeight = NULL; + } + + if (gWidth) + { + free(gWidth); + gWidth = NULL; + } + + if (gxAdvance) + { + free(gxAdvance); + gxAdvance = NULL; + } + + if (gdY) + { + free(gdY); + gdY = NULL; + } + + if (gdX) + { + free(gdX); + gdX = NULL; + } + + if (gBitmap) + { + free(gBitmap); + gBitmap = NULL; + } + fontFile.close(); + fontLoaded = false; +} + + +/*************************************************************************************** +** Function name: decodeUTF8 +** Description: Line buffer UTF-8 decoder with fall-back to extended ASCII +*************************************************************************************x*/ +#define DECODE_UTF8 +uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining) +{ + byte c = buf[(*index)++]; + //Serial.print("Byte from string = 0x"); Serial.println(c, HEX); + +#ifdef DECODE_UTF8 + // 7 bit Unicode + if ((c & 0x80) == 0x00) return c; + + // 11 bit Unicode + if (((c & 0xE0) == 0xC0) && (remaining > 1)) + return ((c & 0x1F)<<6) | (buf[(*index)++]&0x3F); + + // 16 bit Unicode + if (((c & 0xF0) == 0xE0) && (remaining > 2)) + return ((c & 0x0F)<<12) | ((buf[(*index)++]&0x3F)<<6) | ((buf[(*index)++]&0x3F)); + + // 21 bit Unicode not supported so fall-back to extended ASCII + // if ((c & 0xF8) == 0xF0) return c; +#endif + + return c; // fall-back to extended ASCII +} + +/*************************************************************************************** +** Function name: decodeUTF8 +** Description: Serial UTF-8 decoder with fall-back to extended ASCII +*************************************************************************************x*/ +uint16_t TFT_eSPI::decodeUTF8(uint8_t c) +{ + +#ifdef DECODE_UTF8 + if (decoderState == 0) + { + // 7 bit Unicode + if ((c & 0x80) == 0x00) return (uint16_t)c; + + // 11 bit Unicode + if ((c & 0xE0) == 0xC0) + { + decoderBuffer = ((c & 0x1F)<<6); + decoderState = 1; + return 0; + } + + // 16 bit Unicode + if ((c & 0xF0) == 0xE0) + { + decoderBuffer = ((c & 0x0F)<<12); + decoderState = 2; + return 0; + } + // 21 bit Unicode not supported so fall-back to extended ASCII + if ((c & 0xF8) == 0xF0) return (uint16_t)c; + } + else + { + if (decoderState == 2) + { + decoderBuffer |= ((c & 0x3F)<<6); + decoderState--; + return 0; + } + else + { + decoderBuffer |= (c & 0x3F); + decoderState = 0; + return decoderBuffer; + } + } +#endif + + return (uint16_t)c; // fall-back to extended ASCII +} + + + +/*************************************************************************************** +** Function name: alphaBlend +** Description: Blend foreground and background and return new colour +*************************************************************************************x*/ +uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc) +{ + // For speed use fixed point maths and rounding to permit a power of 2 division + uint16_t fgR = ((fgc >> 10) & 0x3E) + 1; + uint16_t fgG = ((fgc >> 4) & 0x7E) + 1; + uint16_t fgB = ((fgc << 1) & 0x3E) + 1; + + uint16_t bgR = ((bgc >> 10) & 0x3E) + 1; + uint16_t bgG = ((bgc >> 4) & 0x7E) + 1; + uint16_t bgB = ((bgc << 1) & 0x3E) + 1; + + // Shift right 1 to drop rounding bit and shift right 8 to divide by 256 + uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9); + uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9); + uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9); + + // Combine RGB565 colours into 16 bits + return (r << 11) | (g << 5) | (b << 0); +} + + +/*************************************************************************************** +** Function name: readInt32 +** Description: Get a 32 bit integer from the font file +*************************************************************************************x*/ +uint32_t TFT_eSPI::readInt32(void) +{ + uint32_t val = 0; + val |= fontFile.read() << 24; + val |= fontFile.read() << 16; + val |= fontFile.read() << 8; + val |= fontFile.read(); + return val; +} + + +/*************************************************************************************** +** Function name: getUnicodeIndex +** Description: Get the font file index of a Unicode character +*************************************************************************************x*/ +bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index) +{ + for (uint16_t i = 0; i < gFont.gCount; i++) + { + if (gUnicode[i] == unicode) + { + *index = i; + return true; + } + } + return false; +} + + +/*************************************************************************************** +** Function name: drawGlyph +** Description: Write a character to the TFT cursor position +*************************************************************************************x*/ +// Expects file to be open +void TFT_eSPI::drawGlyph(uint16_t code) +{ + if (code < 0x21) + { + if (code == 0x20) { + cursor_x += gFont.spaceWidth; + return; + } + + if (code == '\n') { + cursor_x = 0; + cursor_y += gFont.yAdvance; + if (cursor_y >= _height) cursor_y = 0; + return; + } + } + + uint16_t gNum = 0; + bool found = getUnicodeIndex(code, &gNum); + + uint16_t fg = textcolor; + uint16_t bg = textbgcolor; + + if (found) + { + + if (textwrapX && (cursor_x + gWidth[gNum] + gdX[gNum] > _width)) + { + cursor_y += gFont.yAdvance; + cursor_x = 0; + } + if (textwrapY && ((cursor_y + gFont.yAdvance) >= _height)) cursor_y = 0; + if (cursor_x == 0) cursor_x -= gdX[gNum]; + + fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is taking >30ms for a significant position shift + + uint8_t pbuffer[gWidth[gNum]]; + + uint16_t xs = 0; + uint16_t dl = 0; + + for (int y = 0; y < gHeight[gNum]; y++) + { + fontFile.read(pbuffer, gWidth[gNum]); //= width()) { + cursorX = -gdX[i]; + + cursorY += gFont.yAdvance; + if (cursorY + gFont.maxAscent + gFont.descent >= height()) { + cursorX = -gdX[i]; + cursorY = 0; + delay(timeDelay); + timeDelay = td; + fillScreen(textbgcolor); + } + } + + setCursor(cursorX, cursorY); + drawGlyph(gUnicode[i]); + cursorX += gxAdvance[i]; + //cursorX += printToSprite( cursorX, cursorY, i ); + yield(); + } + + delay(timeDelay); + fillScreen(textbgcolor); + //fontFile.close(); + +} diff --git a/Extensions/Smooth_font.h b/Extensions/Smooth_font.h new file mode 100644 index 0000000..6ab09fc --- /dev/null +++ b/Extensions/Smooth_font.h @@ -0,0 +1,54 @@ + // Coded by Bodmer 10/2/18, see license in root directory. + // This is part of the TFT_eSPI class and is associated with anti-aliased font functions + + public: + + // These are for the new antialiased fonts + void loadFont(String fontName); + void unloadFont( void ); + bool getUnicodeIndex(uint16_t unicode, uint16_t *index); + + uint16_t decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining); + uint16_t decodeUTF8(uint8_t c); + + uint16_t alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc); + + void drawGlyph(uint16_t code); + void showFont(uint32_t td); + + fs::File fontFile; + + // This is for the whole font + typedef struct + { + uint16_t gCount; // Total number of characters + uint16_t yAdvance; // Line advance + uint16_t spaceWidth; // Width of a space character + int16_t ascent; // Height of top of 'd' above baseline, other characters may be taller + int16_t descent; // Offset to bottom of 'p', other characters may have a larger descent + uint16_t maxAscent; // Maximum ascent found in font + uint16_t maxDescent; // Maximum descent found in font + } fontMetrics; + +fontMetrics gFont = { 0, 0, 0, 0, 0, 0, 0 }; + + // These are for the metrics for each individual glyph (so we don't need to seek this in file and waste time) + uint16_t* gUnicode = NULL; //UTF-16 code, the codes are searched so do not need to be sequential + uint8_t* gHeight = NULL; //cheight + uint8_t* gWidth = NULL; //cwidth + uint8_t* gxAdvance = NULL; //setWidth + int8_t* gdY = NULL; //topExtent + int8_t* gdX = NULL; //leftExtent + uint32_t* gBitmap = NULL; //file pointer to greyscale bitmap + + String _gFontFilename; + + uint8_t decoderState = 0; // UTF8 decoder state + uint16_t decoderBuffer; // Unicode code-point buffer + + bool fontLoaded = false; // Flags when a anti-aliased font is loaded + + private: + + void loadMetrics(uint16_t gCount); + uint32_t readInt32(void); diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp new file mode 100644 index 0000000..ab5d368 --- /dev/null +++ b/Extensions/Sprite.cpp @@ -0,0 +1,1409 @@ +/************************************************************************************** +// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite +// and rendered quickly onto the TFT screen. The class inherits the graphics functions +// from the TFT_eSPI class. Some functions are overridden by this class so that the +// graphics are written to the Sprite rather than the TFT. +// Coded by Bodmer, see license file in root folder +***************************************************************************************/ +/*************************************************************************************** +// Color bytes are swapped when writing to RAM, this introduces a small overhead but +// there is a nett performance gain by using swapped bytes. +***************************************************************************************/ + +/*************************************************************************************** +** Function name: TFT_eSprite +** Description: Class constructor +*************************************************************************************x*/ +TFT_eSprite::TFT_eSprite(TFT_eSPI *tft) +{ + _tft = tft; // Pointer to tft class so we can call member functions + + _iwidth = 0; // Initialise width and height to 0 (it does not exist yet) + _iheight = 0; + _bpp16 = true; + _iswapBytes = false; // Do not swap pushImage colour bytes by default + + _created = false; + + _xs = 0; // window bounds for pushColor + _ys = 0; + _xe = 0; + _ye = 0; + + _xptr = 0; // pushColor coordinate + _yptr = 0; + + _icursor_y = _icursor_x = 0; // Text cursor position +} + + +/*************************************************************************************** +** Function name: createSprite +** Description: Create a sprite (bitmap) of defined width and height +*************************************************************************************x*/ +// cast returned value to (uint8_t*) for 8 bit or (uint16_t*) for 16 bit colours +void* TFT_eSprite::createSprite(int16_t w, int16_t h) +{ + + if ( _created ) + { + if ( _bpp16 ) return _img; + return _img8; + } + + if ( w < 1 || h < 1 ) return NULL; + + _iwidth = w; + _iheight = h; + + _icursor_x = 0; + _icursor_y = 0; + + // Default scroll rectangle and gap fill colour + _sx = 0; + _sy = 0; + _sw = w; + _sh = h; + _scolor = TFT_BLACK; + + // Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates + // this means push/writeColor functions do not need additional bounds checks and + // hence will run faster in normal circumstances. + if(_bpp16) + { + _img = (uint16_t*) calloc(w * h + 1, sizeof(uint16_t)); + if (_img) + { + _created = true; + return _img; + } + } + else + { + _img8 = ( uint8_t*) calloc(w * h + 1, sizeof(uint8_t)); + if (_img8) + { + _created = true; + return _img8; + } + } + + return NULL; +} + + +/*************************************************************************************** +** Function name: setDepth +** Description: Set bits per pixel for colour (8 or 16) +*************************************************************************************x*/ + +void* TFT_eSprite::setColorDepth(int8_t b) +{ + // Can't change an existing sprite's colour depth so delete it + if (_created) + { + if (_bpp16) free(_img); + else free(_img8); + } + + // Now define the new colour depth + if ( b > 8 ) _bpp16 = true; // Bytes per pixel + else _bpp16 = false; + + // If it existed, re-create the sprite with the new colour depth + if (_created) + { + _created = false; + return createSprite(_iwidth, _iheight); + } + + return NULL; +} + + +/*************************************************************************************** +** Function name: deleteSprite +** Description: Delete the sprite to free up memory (RAM) +*************************************************************************************x*/ +void TFT_eSprite::deleteSprite(void) +{ + if (!_created ) return; + + if (_bpp16) free(_img); + else free(_img8); + + _created = false; +} + + +/*************************************************************************************** +** Function name: pushSprite +** Description: Push the sprite to the TFT at x, y +*************************************************************************************x*/ +void TFT_eSprite::pushSprite(int32_t x, int32_t y) +{ + if (!_created ) return; + + if (_bpp16) _tft->pushImage(x, y, _iwidth, _iheight, _img ); + //if (_bpp16) TFT_eSPI::pushImage(x, y, _iwidth, _iheight, _img ); + else _tft->pushImage(x, y, _iwidth, _iheight, _img8); +} + + +/*************************************************************************************** +** Function name: pushSprite +** Description: Push the sprite to the TFT at x, y with transparent colour +*************************************************************************************x*/ +void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp) +{ + if (!_created ) return; + + if (_bpp16) _tft->pushImage(x, y, _iwidth, _iheight, _img, transp ); + else + { + transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3); + _tft->pushImage(x, y, _iwidth, _iheight, _img8, (uint8_t)transp); + } +} + + +/*************************************************************************************** +** Function name: readPixel +** Description: Read 565 colour of a pixel at defined coordinates +*************************************************************************************x*/ +uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y) +{ + if (!_created ) return 0; + + if (_bpp16) + { + uint16_t color = _img[x + y * _iwidth]; + return (color >> 8) | (color << 8); + } + + uint16_t color = _img8[x + y * _iwidth]; + if (color != 0) + { + uint8_t blue[] = {0, 11, 21, 31}; + color = (color & 0xE0)<<8 | (color & 0xC0)<<5 + | (color & 0x1C)<<6 | (color & 0x1C)<<3 + | blue[color & 0x03]; + } + + return color; +} + + +/*************************************************************************************** +** Function name: pushImage +** Description: push 565 colour image into a defined area of a sprite +*************************************************************************************x*/ +void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data) +{ + if ((x > _iwidth) || (y > _iheight) || (w == 0) || (h == 0) || !_created) return; + + if (_bpp16) + { + for (uint32_t yp = y; yp < y + h; yp++) + { + for (uint32_t xp = x; xp < x + w; xp++) + { + uint16_t color = *data++; + if(!_iswapBytes) color = color<<8 | color>>8; + _img[xp + yp * _iwidth] = color; + } + } + } + else + { + for (uint32_t yp = y; yp < y + h; yp++) + { + for (uint32_t xp = x; xp < x + w; xp++) + { + uint16_t color = *data++; + if(_iswapBytes) color = color<<8 | color>>8; + _img8[xp + yp * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); + } + } + } +} + + +/*************************************************************************************** +** Function name: pushImage +** Description: push 565 colour FLASH (PROGMEM) image into a defined area +*************************************************************************************x*/ +void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, const uint16_t *data) +{ + if ((x > _iwidth) || (y > _iheight) || (w == 0) || (h == 0) || !_created) return; + + if (_bpp16) + { + for (uint32_t yp = y; yp < y + h; yp++) + { + for (uint32_t xp = x; xp < x + w; xp++) + { + uint16_t color = pgm_read_word(data++); + if(!_iswapBytes) color = color<<8 | color>>8; + _img[xp + yp * _iwidth] = color; + } + } + } + else + { + for (uint32_t yp = y; yp < y + h; yp++) + { + for (uint32_t xp = x; xp < x + w; xp++) + { + uint16_t color = pgm_read_word(data++); + if(_iswapBytes) color = color<<8 | color>>8; + _img8[xp + yp * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); + } + } + } +} + + +/*************************************************************************************** +** Function name: setSwapBytes +** Description: Used by 16 bit pushImage() to swap byte order in colours +***************************************************************************************/ +void TFT_eSprite::setSwapBytes(bool swap) +{ + _iswapBytes = swap; +} + + +/*************************************************************************************** +** Function name: getSwapBytes +** Description: Return the swap byte order for colours +***************************************************************************************/ +bool TFT_eSprite::getSwapBytes(void) +{ + return _iswapBytes; +} + + +/*************************************************************************************** +** Function name: setWindow +** Description: Set the bounds of a window for pushColor and writeColor +*************************************************************************************x*/ +void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) +{ + bool duff_coord = false; + + if (x0 > x1) swap_coord(x0, x1); + if (y0 > y1) swap_coord(y0, y1); + + if (x0 < 0) x0 = 0; + if (x0 >= _iwidth) duff_coord = true; + if (x1 < 0) x1 = 0; + if (x1 >= _iwidth) x1 = _iwidth - 1; + + if (y0 < 0) y0 = 0; + if (y0 >= _iheight) duff_coord = true; + if (y1 < 0) y1 = 0; + if (y1 >= _iheight) y1 = _iheight - 1; + + if (duff_coord) + { // Point to that extra "off screen" pixel + _xs = 0; + _ys = _iheight; + _xe = 0; + _ye = _iheight; + } + else + { + _xs = x0; + _ys = y0; + _xe = x1; + _ye = y1; + } + + _xptr = _xs; + _yptr = _ys; +} + + +/*************************************************************************************** +** Function name: pushColor +** Description: Send a new pixel to the set window +*************************************************************************************x*/ +void TFT_eSprite::pushColor(uint32_t color) +{ + if (!_created ) return; + + // Write the colour to RAM in set window + if (_bpp16) + _img [_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8); + + else + _img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); + + // Increment x + _xptr++; + + // Wrap on x and y to start, increment y if needed + if (_xptr > _xe) + { + _xptr = _xs; + _yptr++; + if (_yptr > _ye) _yptr = _ys; + } + +} + + +/*************************************************************************************** +** Function name: pushColor +** Description: Send a "len" new pixels to the set window +*************************************************************************************x*/ +void TFT_eSprite::pushColor(uint32_t color, uint16_t len) +{ + if (!_created ) return; + + uint16_t pixelColor; + if (_bpp16) + pixelColor = (uint16_t) (color >> 8) | (color << 8); + + else + pixelColor = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; + + while(len--) writeColor(pixelColor); +} + + +/*************************************************************************************** +** Function name: writeColor +** Description: Write a pixel with pre-formatted colour to the set window +*************************************************************************************x*/ +void TFT_eSprite::writeColor(uint16_t color) +{ + if (!_created ) return; + + // Write 16 bit RGB 565 encoded colour to RAM + if (_bpp16) _img [_xptr + _yptr * _iwidth] = color; + + // Write 8 bit RGB 332 encoded colour to RAM + else _img8[_xptr + _yptr * _iwidth] = (uint8_t) color; + + // Increment x + _xptr++; + + // Wrap on x and y to start, increment y if needed + if (_xptr > _xe) + { + _xptr = _xs; + _yptr++; + if (_yptr > _ye) _yptr = _ys; + } +} + + +/*************************************************************************************** +** Function name: setScrollRect +** Description: Set scroll area within the sprite and the gap fill colour +*************************************************************************************x*/ +void TFT_eSprite::setScrollRect(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t color) +{ + if ((x >= _iwidth) || (y >= _iheight) || !_created ) return; + + if (x < 0) x = 0; + if (y < 0) y = 0; + + if ((x + w) > _iwidth ) w = _iwidth - x; + if ((y + h) > _iheight) h = _iheight - y; + + if ( w < 1 || h < 1) return; + + _sx = x; + _sy = y; + _sw = w; + _sh = h; + + _scolor = color; +} + + +/*************************************************************************************** +** Function name: scroll +** Description: Scroll dx,dy pixels, positive right,down, negative left,up +*************************************************************************************x*/ +void TFT_eSprite::scroll(int16_t dx, int16_t dy) +{ + if (abs(dx) >= _sw || abs(dy) >= _sh) + { + fillRect (_sx, _sy, _sw, _sh, _scolor); + return; + } + + // Fetch the scroll area width and height set by setScrollRect() + uint32_t w = _sw - abs(dx); // line width to copy + uint32_t h = _sh - abs(dy); // lines to copy + int32_t iw = _iwidth; // width of sprite + + // Fetch the x,y origin set by setScrollRect() + uint32_t tx = _sx; // to x + uint32_t fx = _sx; // from x + uint32_t ty = _sy; // to y + uint32_t fy = _sy; // from y + + // Adjust for x delta + if (dx <= 0) fx -= dx; + else tx += dx; + + // Adjust for y delta + if (dy <= 0) fy -= dy; + else + { // Scrolling down so start copy from bottom + ty = ty + _sh - 1; // "To" pointer + iw = -iw; // Pointer moves backwards + fy = ty - dy; // "From" pointer + } + + // Calculate "from y" and "to y" pointers in RAM + uint32_t fyp = fx + fy * _iwidth; + uint32_t typ = tx + ty * _iwidth; + + // Now move the pixels in RAM + if (_bpp16) + { + while (h--) + { // move pixel lines (to, from, byte count) + memmove( _img + typ, _img + fyp, w<<1); + typ += iw; + fyp += iw; + } + } + else + { + while (h--) + { // move pixel lines (to, from, byte count) + memmove( _img8 + typ, _img8 + fyp, w); + typ += iw; + fyp += iw; + } + } + + // Fill the gap left by the scrolling + if (dx > 0) fillRect(_sx, _sy, dx, _sh, _scolor); + if (dx < 0) fillRect(_sx + _sw + dx, _sy, -dx, _sh, _scolor); + if (dy > 0) fillRect(_sx, _sy, _sw, dy, _scolor); + if (dy < 0) fillRect(_sx, _sy + _sh + dy, _sw, -dy, _scolor); +} + + +/*************************************************************************************** +** Function name: fillSprite +** Description: Fill the whole sprite with defined colour +*************************************************************************************x*/ +void TFT_eSprite::fillSprite(uint32_t color) +{ + if (!_created ) return; + + // Use memset if possible as it is super fast + if(( (uint8_t)color == (uint8_t)(color>>8) ) && _bpp16) + memset(_img, (uint8_t)color, _iwidth * _iheight * 2); + else if (!_bpp16) + { + color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; + memset(_img8, (uint8_t)color, _iwidth * _iheight); + } + + else fillRect(0, 0, _iwidth, _iheight, color); +} + + +/*************************************************************************************** +** Function name: setCursor +** Description: Set the sprite text cursor x,y position +*************************************************************************************x*/ +void TFT_eSprite::setCursor(int16_t x, int16_t y) +{ + _icursor_x = x; + _icursor_y = y; +} + + +/*************************************************************************************** +** Function name: width +** Description: Return the width of sprite +*************************************************************************************x*/ +// Return the size of the display +int16_t TFT_eSprite::width(void) +{ + if (!_created ) return 0; + return _iwidth; +} + + +/*************************************************************************************** +** Function name: height +** Description: Return the height of sprite +*************************************************************************************x*/ +int16_t TFT_eSprite::height(void) +{ + if (!_created ) return 0; + return _iheight; +} + + +/*************************************************************************************** +** Function name: drawPixel +** Description: push a single pixel at an arbitrary position +*************************************************************************************x*/ +void TFT_eSprite::drawPixel(uint32_t x, uint32_t y, uint32_t color) +{ + // x and y are unsigned so that -ve coordinates turn into large positive ones + // this make bounds checking a bit faster + if ((x >= _iwidth) || (y >= _iheight) || !_created) return; + + if (_bpp16) + { + color = (color >> 8) | (color << 8); + _img[x+y*_iwidth] = (uint16_t) color; + } + else + { + _img8[x+y*_iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); + } +} + + +/*************************************************************************************** +** Function name: drawLine +** Description: draw a line between 2 arbitrary points +*************************************************************************************x*/ +void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) +{ + if (!_created ) return; + + boolean steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + swap_coord(x0, y0); + swap_coord(x1, y1); + } + + if (x0 > x1) { + swap_coord(x0, x1); + swap_coord(y0, y1); + } + + int32_t dx = x1 - x0, dy = abs(y1 - y0);; + + int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0; + + if (y0 < y1) ystep = 1; + + // Split into steep and not steep for FastH/V separation + if (steep) { + for (; x0 <= x1; x0++) { + dlen++; + err -= dy; + if (err < 0) { + err += dx; + if (dlen == 1) drawPixel(y0, xs, color); + else drawFastVLine(y0, xs, dlen, color); + dlen = 0; y0 += ystep; xs = x0 + 1; + } + } + if (dlen) drawFastVLine(y0, xs, dlen, color); + } + else + { + for (; x0 <= x1; x0++) { + dlen++; + err -= dy; + if (err < 0) { + err += dx; + if (dlen == 1) drawPixel(xs, y0, color); + else drawFastHLine(xs, y0, dlen, color); + dlen = 0; y0 += ystep; xs = x0 + 1; + } + } + if (dlen) drawFastHLine(xs, y0, dlen, color); + } +} + + +/*************************************************************************************** +** Function name: drawFastVLine +** Description: draw a vertical line +*************************************************************************************x*/ +void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) +{ + + if ((x < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; + + if (y < 0) { h += y; y = 0; } + + if ((y + h) > _iheight) h = _iheight - y; + + if (h < 1) return; + + if (_bpp16) + { + color = (color >> 8) | (color << 8); + int32_t yp = x + _iwidth * y; + while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;} + } + else + { + color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; + while (h--) _img8[x + _iwidth * y++] = (uint8_t) color; + } +} + + +/*************************************************************************************** +** Function name: drawFastHLine +** Description: draw a horizontal line +*************************************************************************************x*/ +void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) +{ + + if ((y < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; + + if (x < 0) { w += x; x = 0; } + + if ((x + w) > _iwidth) w = _iwidth - x; + + if (w < 1) return; + + if (_bpp16) + { + color = (color >> 8) | (color << 8); + while (w--) _img[_iwidth * y + x++] = (uint16_t) color; + } + else + { + color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; + memset(_img8+_iwidth * y + x, (uint8_t)color, w); + } +} + + +/*************************************************************************************** +** Function name: fillRect +** Description: draw a filled rectangle +*************************************************************************************x*/ +void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) +{ + if (!_created ) return; + + if (x < 0) { w += x; x = 0; } + + if ((x < 0) || (y < 0) || (x >= _iwidth) || (y >= _iheight)) return; + if ((x + w) > _iwidth) w = _iwidth - x; + if ((y + h) > _iheight) h = _iheight - y; + if ((w < 1) || (h < 1)) return; + + int32_t yp = _iwidth * y + x; + + if (_bpp16) + { + color = (color >> 8) | (color << 8); + uint32_t iw = w; + int32_t ys = yp; + if(h--) {while (iw--) _img[yp++] = (uint16_t) color;} + yp = ys; + while (h--) + { + yp += _iwidth; + memcpy( _img+yp, _img+ys, w<<1); + } + } + else + { + color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; + while (h--) + { + memset(_img8 + yp, (uint8_t)color, w); + yp += _iwidth; + } + } +} + + +/*************************************************************************************** +** Function name: write +** Description: draw characters piped through serial stream +*************************************************************************************x*/ +size_t TFT_eSprite::write(uint8_t utf8) +{ + if (utf8 == '\r') return 1; + +#ifdef SMOOTH_FONT + if(fontLoaded) + { + uint16_t unicode = decodeUTF8(utf8); + if (unicode < 32 && utf8 != '\n') return 0; + + fontFile = SPIFFS.open( _gFontFilename, "r" ); + + if(!fontFile) + { + fontLoaded = false; + return 0; + } + + drawGlyph(unicode); + fontFile.close(); + return 0; + } +#endif + + if (!_created ) return 0; + + + uint8_t uniCode = utf8; // Work with a copy + if (utf8 == '\n') uniCode+=22; // Make it a valid space character to stop errors + else if (utf8 < 32) return 0; + + uint16_t width = 0; + uint16_t height = 0; + +//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + //Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port + //Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port + //delay(5); // Debug optional wait for serial port to flush through +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +#ifdef LOAD_GFXFF + if(!gfxFont) { +#endif +//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + +#ifdef LOAD_FONT2 + if (textfont == 2) + { + if (utf8 > 127) return 0; + // This is 20us faster than using the fontdata structure (0.443ms per character instead of 0.465ms) + width = pgm_read_byte(widtbl_f16 + uniCode-32); + height = chr_hgt_f16; + // Font 2 is rendered in whole byte widths so we must allow for this + width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change + width = width * 8; // Width converted back to pixles + } + #ifdef LOAD_RLE + else + #endif +#endif + +#ifdef LOAD_RLE + { + if ((textfont>2) && (textfont<9)) + { + if (utf8 > 127) return 0; + // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements + // A tad slower than above but this is not significant and is more convenient for the RLE fonts + width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 ); + height= pgm_read_byte( &fontdata[textfont].height ); + } + } +#endif + +#ifdef LOAD_GLCD + if (textfont==1) + { + width = 6; + height = 8; + } +#else + if (textfont==1) return 0; +#endif + + height = height * textsize; + + if (utf8 == '\n') + { + _icursor_y += height; + _icursor_x = 0; + } + else + { + if (textwrapX && (_icursor_x + width * textsize > _iwidth)) + { + _icursor_y += height; + _icursor_x = 0; + } + if (textwrapY && (_icursor_y >= _iheight)) _icursor_y = 0; + _icursor_x += drawChar(uniCode, _icursor_x, _icursor_y, textfont); + } + +//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +#ifdef LOAD_GFXFF + } // Custom GFX font + else + { + + if(utf8 == '\n') { + _icursor_x = 0; + _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } else { + if (uniCode > (uint8_t)pgm_read_byte(&gfxFont->last )) return 0; + if (uniCode < (uint8_t)pgm_read_byte(&gfxFont->first)) return 0; + + uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); + uint8_t w = pgm_read_byte(&glyph->width), + h = pgm_read_byte(&glyph->height); + if((w > 0) && (h > 0)) { // Is there an associated bitmap? + int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); + if(textwrapX && ((_icursor_x + textsize * (xo + w)) > _iwidth)) { + // Drawing character would go off right edge; wrap to new line + _icursor_x = 0; + _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } + if (textwrapY && (_icursor_y >= _iheight)) _icursor_y = 0; + drawChar(_icursor_x, _icursor_y, uniCode, textcolor, textbgcolor, textsize); + } + _icursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; + } + } +#endif // LOAD_GFXFF +//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + return 1; +} + + +/*************************************************************************************** +** Function name: drawChar +** Description: draw a single character in the Adafruit GLCD or freefont +*************************************************************************************x*/ +void TFT_eSprite::drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size) +{ + if (!_created ) return; + + if ((x >= _iwidth) || // Clip right + (y >= _iheight) || // Clip bottom + ((x + 6 * size - 1) < 0) || // Clip left + ((y + 8 * size - 1) < 0)) // Clip top + return; + +#ifdef LOAD_GLCD +//>>>>>>>>>>>>>>>>>> +#ifdef LOAD_GFXFF + if(!gfxFont) { // 'Classic' built-in font +#endif +//>>>>>>>>>>>>>>>>>> + + boolean fillbg = (bg != color); + + if ((size==1) && fillbg) + { + uint8_t column[6]; + uint8_t mask = 0x1; + + for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i); + column[5] = 0; + + int8_t j, k; + for (j = 0; j < 8; j++) { + for (k = 0; k < 5; k++ ) { + if (column[k] & mask) { + drawPixel(x + k, y + j, color); + } + else { + drawPixel(x + k, y + j, bg); + } + } + + mask <<= 1; + + drawPixel(x + k, y + j, bg); + } + } + else + { + for (int8_t i = 0; i < 6; i++ ) { + uint8_t line; + if (i == 5) + line = 0x0; + else + line = pgm_read_byte(font + (c * 5) + i); + + if (size == 1) // default size + { + for (int8_t j = 0; j < 8; j++) { + if (line & 0x1) drawPixel(x + i, y + j, color); + line >>= 1; + } + } + else { // big size + for (int8_t j = 0; j < 8; j++) { + if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color); + else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg); + line >>= 1; + } + } + } + } + +//>>>>>>>>>>>>>>>>>>>>>>>>>>> +#ifdef LOAD_GFXFF + } else { // Custom font +#endif +//>>>>>>>>>>>>>>>>>>>>>>>>>>> +#endif // LOAD_GLCD + +#ifdef LOAD_GFXFF + // Filter out bad characters not present in font + if ((c >= (uint8_t)pgm_read_byte(&gfxFont->first)) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last ))) + { +//>>>>>>>>>>>>>>>>>>>>>>>>>>> + + c -= pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]); + uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap); + + uint16_t bo = pgm_read_word(&glyph->bitmapOffset); + uint8_t w = pgm_read_byte(&glyph->width), + h = pgm_read_byte(&glyph->height), + xa = pgm_read_byte(&glyph->xAdvance); + int8_t xo = pgm_read_byte(&glyph->xOffset), + yo = pgm_read_byte(&glyph->yOffset); + uint8_t xx, yy, bits, bit=0; + int16_t xo16 = 0, yo16 = 0; + + if(size > 1) { + xo16 = xo; + yo16 = yo; + } + + uint16_t hpc = 0; // Horizontal foreground pixel count + for(yy=0; yy>= 1; + } + // Draw pixels for this line as we are about to increment yy + if (hpc) { + if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color); + else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color); + hpc=0; + } + } + } +#endif + + +#ifdef LOAD_GLCD + #ifdef LOAD_GFXFF + } // End classic vs custom font + #endif +#endif + +} + + +/*************************************************************************************** +** Function name: drawChar +** Description: draw a unicode onto the screen +*************************************************************************************x*/ +int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y) +{ + return drawChar(uniCode, x, y, textfont); +} + +int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y, int font) +{ + if (!_created ) return 0; + + if (font==1) + { +#ifdef LOAD_GLCD + #ifndef LOAD_GFXFF + drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); + return 6 * textsize; + #endif +#else + #ifndef LOAD_GFXFF + return 0; + #endif +#endif + +#ifdef LOAD_GFXFF + drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); + if(!gfxFont) { // 'Classic' built-in font + #ifdef LOAD_GLCD + return 6 * textsize; + #else + return 0; + #endif + } + else + { + if((uniCode >= pgm_read_byte(&gfxFont->first)) && (uniCode <= pgm_read_byte(&gfxFont->last) )) + { + uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); + return pgm_read_byte(&glyph->xAdvance) * textsize; + } + else + { + return 0; + } + } +#endif + } + + if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0; + + int width = 0; + int height = 0; + uint32_t flash_address = 0; + uniCode -= 32; + +#ifdef LOAD_FONT2 + if (font == 2) + { + // This is faster than using the fontdata structure + flash_address = pgm_read_dword(&chrtbl_f16[uniCode]); + width = pgm_read_byte(widtbl_f16 + uniCode); + height = chr_hgt_f16; + } + #ifdef LOAD_RLE + else + #endif +#endif + +#ifdef LOAD_RLE + { + if ((font>2) && (font<9)) + { + // This is slower than above but is more convenient for the RLE fonts + flash_address = pgm_read_dword( pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *) ); + width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode ); + height= pgm_read_byte( &fontdata[font].height ); + } + } +#endif + + int w = width; + int pX = 0; + int pY = y; + uint8_t line = 0; + +#ifdef LOAD_FONT2 // chop out code if we do not need it + if (font == 2) { + w = w + 6; // Should be + 7 but we need to compensate for width increment + w = w / 8; + if (x + width * textsize >= _iwidth) return width * textsize ; + + for (int i = 0; i < height; i++) + { + if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor); + + for (int k = 0; k < w; k++) + { + line = pgm_read_byte((uint8_t *)flash_address + w * i + k); + if (line) { + if (textsize == 1) { + pX = x + k * 8; + if (line & 0x80) drawPixel(pX, pY, textcolor); + if (line & 0x40) drawPixel(pX + 1, pY, textcolor); + if (line & 0x20) drawPixel(pX + 2, pY, textcolor); + if (line & 0x10) drawPixel(pX + 3, pY, textcolor); + if (line & 0x08) drawPixel(pX + 4, pY, textcolor); + if (line & 0x04) drawPixel(pX + 5, pY, textcolor); + if (line & 0x02) drawPixel(pX + 6, pY, textcolor); + if (line & 0x01) drawPixel(pX + 7, pY, textcolor); + } + else { + pX = x + k * 8 * textsize; + if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor); + if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor); + if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor); + if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor); + if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor); + if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor); + if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor); + if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor); + } + } + } + pY += textsize; + } + } + + #ifdef LOAD_RLE + else + #endif +#endif //FONT2 + +#ifdef LOAD_RLE //674 bytes of code + // Font is not 2 and hence is RLE encoded + { + w *= height; // Now w is total number of pixels in the character + + if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor); + int16_t color; + if (_bpp16) color = (textcolor >> 8) | (textcolor << 8); + else color = ((textcolor & 0xE000)>>8 | (textcolor & 0x0700)>>6 | (textcolor & 0x0018)>>3); + int px = 0, py = pY; // To hold character block start and end column and row values + int pc = 0; // Pixel count + uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel + uint8_t tnp = 0; // Temporary copy of np for while loop + uint8_t ts = textsize - 1; // Temporary copy of textsize + // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area + // w is total number of pixels to plot to fill character block + while (pc < w) + { + line = pgm_read_byte((uint8_t *)flash_address); + flash_address++; // 20 bytes smaller by incrementing here + if (line & 0x80) { + line &= 0x7F; + line++; + if (ts) { + px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow + py = y + textsize * (pc / width); + } + else { + px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow + py = y + pc / width; + } + while (line--) { + pc++; + setWindow(px, py, px + ts, py + ts); + if (ts) { tnp = np; while (tnp--) writeColor(color); } + else writeColor(color); + + px += textsize; + + if (px >= (x + width * textsize)) + { + px = x; + py += textsize; + } + } + } + else { + line++; + pc += line; + } + } + } + // End of RLE font rendering +#endif + return width * textsize; // x + +} + +#ifdef SMOOTH_FONT +/*************************************************************************************** +** Function name: drawGlyph +** Description: Write a character to the sprite cursor position +*************************************************************************************x*/ +void TFT_eSprite::drawGlyph(uint16_t code) +{ + if (code < 0x21) + { + if (code == 0x20) { + if (_created) _icursor_x += _tft->gFont.spaceWidth; + else _tft->cursor_x += _tft->gFont.spaceWidth; + return; + } + + if (code == '\n') { + if (_created) + { + _icursor_x = 0; + _icursor_y += _tft->gFont.yAdvance; + if (_icursor_y >= _height) _icursor_y = 0; + return; + } + else + { + cursor_x = 0; + cursor_y += gFont.yAdvance; + if (cursor_y >= _height) cursor_y = 0; + return; + } + } + } + + uint16_t gNum = 0; + bool found = _tft->getUnicodeIndex(code, &gNum); + + uint16_t fg = _tft->textcolor; + uint16_t bg = _tft->textbgcolor; + + if (found) + { + + bool newSprite = !_created; + + if (newSprite) + { + createSprite(_tft->gWidth[gNum], _tft->gFont.yAdvance); + if(bg) fillSprite(bg); + _icursor_x = -_tft->gdX[gNum]; + _icursor_y = 0; + } + + fontFile.seek(_tft->gBitmap[gNum], fs::SeekSet); // This is slow for a significant position shift! + + uint8_t pbuffer[_tft->gWidth[gNum]]; + + uint16_t xs = 0; + uint16_t dl = 0; + + for (int y = 0; y < _tft->gHeight[gNum]; y++) + { + fontFile.read(pbuffer, _tft->gWidth[gNum]); + for (int x = 0; x < _tft->gWidth[gNum]; x++) + { + uint8_t pixel = pbuffer[x]; + if (pixel) + { + if (pixel != 0xFF) + { + if (dl) { drawFastHLine( xs, y + _icursor_y + _tft->gFont.maxAscent - _tft->gdY[gNum], dl, fg); dl = 0; } + drawPixel(x + _icursor_x + _tft->gdX[gNum], y + _icursor_y + _tft->gFont.maxAscent - _tft->gdY[gNum], alphaBlend(pixel, fg, bg)); + } + else + { + if (dl==0) xs = x + _icursor_x + _tft->gdX[gNum]; + dl++; + } + } + else + { + if (dl) { drawFastHLine( xs, y + _icursor_y + _tft->gFont.maxAscent - _tft->gdY[gNum], dl, fg); dl = 0; } + } + } + if (dl) { drawFastHLine( xs, y + _icursor_y + _tft->gFont.maxAscent - _tft->gdY[gNum], dl, fg); dl = 0; } + } + + if (newSprite) + { + pushSprite(_tft->cursor_x + _tft->gdX[gNum], _tft->cursor_y, bg); + deleteSprite(); + _tft->cursor_x += _tft->gxAdvance[gNum]; + } + else _icursor_x += _tft->gxAdvance[gNum]; + } + else + { + // Not a Unicode in font so draw a rectangle and move on cursor + drawRect(_icursor_x, _icursor_y + _tft->gFont.maxAscent - _tft->gFont.ascent, _tft->gFont.spaceWidth, _tft->gFont.ascent, fg); + _icursor_x += _tft->gFont.spaceWidth + 1; + } +} + + +/*************************************************************************************** +** Function name: printToSprite +** Description: Write a string to the sprite cursor position +*************************************************************************************x*/ +void TFT_eSprite::printToSprite(String string) +{ + if(!_tft->fontLoaded) return; + int16_t len = string.length(); + char cbuffer[len + 1]; // Add 1 for the null + string.toCharArray(cbuffer, len + 1); // Add 1 for the null, otherwise characters get dropped + printToSprite(cbuffer, len); +} + + +/*************************************************************************************** +** Function name: printToSprite +** Description: Write a string to the sprite cursor position +*************************************************************************************x*/ +void TFT_eSprite::printToSprite(char *cbuffer, int len) //String string) +{ + if(!_tft->fontLoaded) return; + + fontFile = SPIFFS.open( _tft->_gFontFilename, "r" ); + + if(!fontFile) + { + _tft->fontLoaded = false; + return; + } + + uint16_t n = 0; + bool newSprite = !_created; + + if (newSprite) + { + int16_t sWidth = 0; + uint16_t index = 0; + + while (n < len) + { + uint16_t unicode = decodeUTF8((uint8_t*)cbuffer, &n, len - n); + if (_tft->getUnicodeIndex(unicode, &index)) + { + if (n == 0) sWidth -= _tft->gdX[index]; + if (n == len-1) sWidth += ( _tft->gWidth[index] + _tft->gdX[index]); + else sWidth += _tft->gxAdvance[index]; + } + else sWidth += _tft->gFont.spaceWidth + 1; + } + + createSprite(sWidth, _tft->gFont.yAdvance); + uint16_t transparent = TFT_BLACK; + + if (_tft->textbgcolor != TFT_BLACK) fillSprite(_tft->textbgcolor); + } + + n = 0; + + while (n < len) + { + uint16_t unicode = decodeUTF8((uint8_t*)cbuffer, &n, len - n); + //Serial.print("Decoded Unicode = 0x");Serial.println(unicode,HEX); + //Serial.print("n = ");Serial.println(n); + drawGlyph(unicode); + } + + if (newSprite) + { + pushSprite(_tft->cursor_x, _tft->cursor_y); + deleteSprite(); + } + + fontFile.close(); +} + + +/*************************************************************************************** +** Function name: printToSprite +** Description: Print character in a Sprite, create sprite if needed +*************************************************************************************x*/ +int16_t TFT_eSprite::printToSprite(int16_t x, int16_t y, uint16_t index) +{ + bool newSprite = !_created; + int16_t sWidth = _tft->gWidth[index]; + + if (newSprite) + { + createSprite(sWidth, _tft->gFont.yAdvance); + uint16_t transparent = TFT_BLACK; + if (_tft->textbgcolor != TFT_BLACK) fillSprite(_tft->textbgcolor); + + drawGlyph(_tft->gUnicode[index]); + + pushSprite(x + _tft->gdX[index], y, _tft->textbgcolor); + deleteSprite(); + } + + else drawGlyph(_tft->gUnicode[index]); + + return _tft->gxAdvance[index]; +} +#endif diff --git a/Extensions/Sprite.h b/Extensions/Sprite.h new file mode 100644 index 0000000..85945ff --- /dev/null +++ b/Extensions/Sprite.h @@ -0,0 +1,111 @@ +/*************************************************************************************** +// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite +// and rendered quickly onto the TFT screen. The class inherits the graphics functions +// from the TFT_eSPI class. Some functions are overridden by this class so that the +// graphics are written to the Sprite rather than the TFT. +***************************************************************************************/ + +class TFT_eSprite : public TFT_eSPI { + + public: + + TFT_eSprite(TFT_eSPI *tft); + + // Create a sprite of width x height pixels, return a pointer to the RAM area + // Sketch can cast returned value to (uint16_t*) for 16 bit depth if needed + // RAM required is 1 byte per pixel for 8 bit colour depth, 2 bytes for 16 bit + void* createSprite(int16_t width, int16_t height); + + // Delete the sprite to free up the RAM + void deleteSprite(void); + + // Set the colour depth to 8 or 16 bits. Can be used to change depth an existing + // sprite, but clears it to black, returns a new pointer if sprite is re-created. + void* setColorDepth(int8_t b); + + void drawPixel(uint32_t x, uint32_t y, uint32_t color); + + void drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size), + + fillSprite(uint32_t color), + + // Define a window to push 16 bit colour pixels into is a raster order + // Colours are converted to 8 bit if depth is set to 8 + setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1), + pushColor(uint32_t color), + pushColor(uint32_t color, uint16_t len), + // Push a pixel preformatted as a 8 or 16 bit colour (avoids conversion overhead) + writeColor(uint16_t color), + + // Set the scroll zone, top left corner at x,y with defined width and height + // The colour (optional, black is default) is used to fill the gap after the scroll + setScrollRect(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t color = TFT_BLACK), + // Scroll the defined zone dx,dy pixels. Negative values left,up, positive right,down + // dy is optional (default is then no up/down scroll). + // The sprite coordinate frame does not move because pixels are moved + scroll(int16_t dx, int16_t dy = 0), + + drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color), + drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color), + drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color), + + fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color), + + // Set the sprite text cursor position for print class (does not change the TFT screen cursor) + setCursor(int16_t x, int16_t y); + + // Read the colour of a pixel at x,y and return value in 565 format + uint16_t readPixel(int32_t x0, int32_t y0); + + // Write an image (colour bitmap) to the sprite + void pushImage(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint16_t *data); + void pushImage(int32_t x0, int32_t y0, uint32_t w, uint32_t h, const uint16_t *data); + + // Swap the byte order for pushImage() - corrects different image endianness + void setSwapBytes(bool swap); + bool getSwapBytes(void); + + // Push the sprite to the TFT screen, this fn calls pushImage() in the TFT class. + // Optionally a "transparent" colour can be defined, pixels of that colour will not be rendered + void pushSprite(int32_t x, int32_t y); + void pushSprite(int32_t x, int32_t y, uint16_t transparent); + + int16_t drawChar(unsigned int uniCode, int x, int y, int font), + drawChar(unsigned int uniCode, int x, int y); + + // Return the width and height of the sprite + int16_t width(void), + height(void); + + // Used by print class to print text to cursor position + size_t write(uint8_t); + + // Functions associated with anti-aliased fonts + void drawGlyph(uint16_t code); + void printToSprite(String string); + void printToSprite(char *cbuffer, int len); + int16_t printToSprite(int16_t x, int16_t y, uint16_t index); + + private: + + TFT_eSPI *_tft; + + protected: + + uint16_t *_img; // pointer to 16 bit sprite + uint8_t *_img8; // pointer to 8 bit sprite + bool _created, _bpp16; // created and bits per pixel depth flags + + bool _gFont = false; + + int32_t _icursor_x, _icursor_y; + int32_t _xs, _ys, _xe, _ye, _xptr, _yptr; // for setWindow + int32_t _sx, _sy; // x,y for scroll zone + uint32_t _sw, _sh; // w,h for scroll zone + uint32_t _scolor; // gap fill colour for scroll zone + + boolean _iswapBytes; // Swap the byte order for Sprite pushImage() + + int32_t _iwidth, _iheight; // Sprite image width and height + +}; diff --git a/Extensions/Touch.cpp b/Extensions/Touch.cpp new file mode 100644 index 0000000..d077e13 --- /dev/null +++ b/Extensions/Touch.cpp @@ -0,0 +1,319 @@ +// The following touch screen support code by maxpautsch was merged 1/10/17 +// https://github.com/maxpautsch +// Define TOUCH_CS is the user setup file to enable this code +// A demo is provided in examples Generic folder +// Additions by Bodmer to double sample and use Z value to improve detection reliability +// See license in root directory. + +#ifdef TOUCH_CS // If a pin has been allocated to the Touch screen load functions +/*************************************************************************************** +** Function name: getTouchRaw +** Description: read raw touch position. Return false if not pressed. +***************************************************************************************/ +uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ + uint16_t tmp; + CS_H; + + spi_begin_touch(); + + T_CS_L; + + // Start bit + YP sample request for x position + tmp = SPI.transfer(0xd0); + tmp = SPI.transfer(0); + tmp = tmp <<5; + tmp |= 0x1f & (SPI.transfer(0)>>3); + + *x = tmp; + + // Start bit + XP sample request for y position + SPI.transfer(0x90); + tmp = SPI.transfer(0); + tmp = tmp <<5; + tmp |= 0x1f & (SPI.transfer(0)>>3); + + *y = tmp; + + T_CS_H; + + spi_end_touch(); + + return true; +} + +/*************************************************************************************** +** Function name: getTouchRawZ +** Description: read raw pressure on touchpad and return Z value. +***************************************************************************************/ +uint16_t TFT_eSPI::getTouchRawZ(void){ + CS_H; + + spi_begin_touch(); + + T_CS_L; + + // Z sample request + uint16_t tz = 0xFFF; + SPI.transfer(0xb1); + tz += SPI.transfer16(0xc1) >> 3; + tz -= SPI.transfer16(0x91) >> 3; + + T_CS_H; + + spi_end_touch(); + + return tz; +} + +/*************************************************************************************** +** Function name: validTouch +** Description: read validated position. Return false if not pressed. +***************************************************************************************/ +#define _RAWERR 10 // Deadband in position samples +uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ + uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2; + + // Wait until pressure stops increasing + uint16_t z1 = 1; + uint16_t z2 = 0; + while (z1 > z2) + { + z2 = z1; + z1 = getTouchRawZ(); + delay(1); + } + + // Serial.print("Z = ");Serial.println(z1); + + if (z1 <= threshold) return false; + + getTouchRaw(&x_tmp,&y_tmp); + + // Serial.print("Sample 1 x,y = "); Serial.print(x_tmp);Serial.print(",");Serial.print(y_tmp); + // Serial.print(", Z = ");Serial.println(z1); + + delay(1); // Small delay to the next sample + if (getTouchRawZ() <= threshold) return false; + + delay(2); // Small delay to the next sample + getTouchRaw(&x_tmp2,&y_tmp2); + + // Serial.print("Sample 2 x,y = "); Serial.print(x_tmp2);Serial.print(",");Serial.println(y_tmp2); + // Serial.print("Sample difference = ");Serial.print(abs(x_tmp - x_tmp2));Serial.print(",");Serial.println(abs(y_tmp - y_tmp2)); + + if (abs(x_tmp - x_tmp2) > _RAWERR) return false; + if (abs(y_tmp - y_tmp2) > _RAWERR) return false; + + *x = x_tmp; + *y = y_tmp; + + return true; +} + +/*************************************************************************************** +** Function name: getTouch +** Description: read callibrated position. Return false if not pressed. +***************************************************************************************/ +#define Z_THRESHOLD 350 // Touch pressure threshold for validating touches +uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ + uint16_t x_tmp, y_tmp, xx, yy; + + if (threshold<20) threshold = 20; + if (_pressTime > millis()) threshold=20; + + uint8_t n = 5; + uint8_t valid = 0; + while (n--) + { + if (validTouch(&x_tmp, &y_tmp, threshold)) valid++;; + } + + if (valid<1) { _pressTime = 0; return false; } + + _pressTime = millis() + 50; + + if(!touchCalibration_rotate){ + xx=(x_tmp-touchCalibration_x0)*_width/touchCalibration_x1; + yy=(y_tmp-touchCalibration_y0)*_height/touchCalibration_y1; + if(touchCalibration_invert_x) + xx = _width - xx; + if(touchCalibration_invert_y) + yy = _height - yy; + } else { + yy=(x_tmp-touchCalibration_x0)*_height/touchCalibration_x1; + xx=(y_tmp-touchCalibration_y0)*_width/touchCalibration_y1; + if(touchCalibration_invert_x) + xx = _width - xx; + if(touchCalibration_invert_y) + yy = _height - yy; + } + + if (xx >= _width || yy >= _height) return valid; + + _pressX = xx; + _pressY = yy; + *x = _pressX; + *y = _pressY; + return valid; +} + +/*************************************************************************************** +** Function name: calibrateTouch +** Description: generates calibration parameters for touchscreen. +***************************************************************************************/ +void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size){ + int16_t values[] = {0,0,0,0,0,0,0,0}; + uint16_t x_tmp, y_tmp; + + + + for(uint8_t i = 0; i<4; i++){ + fillRect(0, 0, size+1, size+1, color_bg); + fillRect(0, _height-size-1, size+1, size+1, color_bg); + fillRect(_width-size-1, 0, size+1, size+1, color_bg); + fillRect(_width-size-1, _height-size-1, size+1, size+1, color_bg); + + if (i == 5) break; // used to clear the arrows + + switch (i) { + case 0: // up left + drawLine(0, 0, 0, size, color_fg); + drawLine(0, 0, size, 0, color_fg); + drawLine(0, 0, size , size, color_fg); + break; + case 1: // bot left + drawLine(0, _height-size-1, 0, _height-1, color_fg); + drawLine(0, _height-1, size, _height-1, color_fg); + drawLine(size, _height-size-1, 0, _height-1 , color_fg); + break; + case 2: // up right + drawLine(_width-size-1, 0, _width-1, 0, color_fg); + drawLine(_width-size-1, size, _width-1, 0, color_fg); + drawLine(_width-1, size, _width-1, 0, color_fg); + break; + case 3: // bot right + drawLine(_width-size-1, _height-size-1, _width-1, _height-1, color_fg); + drawLine(_width-1, _height-1-size, _width-1, _height-1, color_fg); + drawLine(_width-1-size, _height-1, _width-1, _height-1, color_fg); + break; + } + + // user has to get the chance to release + if(i>0) delay(1000); + + for(uint8_t j= 0; j<8; j++){ + // Use a lower detect threshold as corners tend to be less sensitive + while(!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD/4)); + values[i*2 ] += x_tmp; + values[i*2+1] += y_tmp; + } + values[i*2 ] /= 8; + values[i*2+1] /= 8; + } + + + + // check orientation + // from case 0 to case 1, the y value changed. + // If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched. + touchCalibration_rotate = false; + if(abs(values[0]-values[2]) > abs(values[1]-values[3])){ + touchCalibration_rotate = true; + touchCalibration_x0 = (values[0] + values[4])/2; // calc min x + touchCalibration_x1 = (values[2] + values[6])/2; // calc max x + touchCalibration_y0 = (values[1] + values[3])/2; // calc min y + touchCalibration_y1 = (values[5] + values[7])/2; // calc max y + } else { + touchCalibration_x0 = (values[0] + values[2])/2; // calc min x + touchCalibration_x1 = (values[4] + values[6])/2; // calc max x + touchCalibration_y0 = (values[1] + values[5])/2; // calc min y + touchCalibration_y1 = (values[3] + values[7])/2; // calc max y + } + + // in addition, the touch screen axis could be in the opposit direction of the TFT axis + touchCalibration_invert_x = false; + if(touchCalibration_x0 > touchCalibration_x1){ + values[0]=touchCalibration_x0; + touchCalibration_x0 = touchCalibration_x1; + touchCalibration_x1 = values[0]; + touchCalibration_invert_x = true; + } + touchCalibration_invert_y = false; + if(touchCalibration_y0 > touchCalibration_y1){ + values[0]=touchCalibration_y0; + touchCalibration_y0 = touchCalibration_y1; + touchCalibration_y1 = values[0]; + touchCalibration_invert_y = true; + } + + // pre calculate + touchCalibration_x1 -= touchCalibration_x0; + touchCalibration_y1 -= touchCalibration_y0; + + if(touchCalibration_x0 == 0) touchCalibration_x0 = 1; + if(touchCalibration_x1 == 0) touchCalibration_x1 = 1; + if(touchCalibration_y0 == 0) touchCalibration_y0 = 1; + if(touchCalibration_y1 == 0) touchCalibration_y1 = 1; + + // export parameters, if pointer valid + if(parameters != NULL){ + parameters[0] = touchCalibration_x0; + parameters[1] = touchCalibration_x1; + parameters[2] = touchCalibration_y0; + parameters[3] = touchCalibration_y1; + parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x <<1) | (touchCalibration_invert_y <<2); + } +} + + +/*************************************************************************************** +** Function name: setTouch +** Description: imports calibration parameters for touchscreen. +***************************************************************************************/ +void TFT_eSPI::setTouch(uint16_t *parameters){ + touchCalibration_x0 = parameters[0]; + touchCalibration_x1 = parameters[1]; + touchCalibration_y0 = parameters[2]; + touchCalibration_y1 = parameters[3]; + + if(touchCalibration_x0 == 0) touchCalibration_x0 = 1; + if(touchCalibration_x1 == 0) touchCalibration_x1 = 1; + if(touchCalibration_y0 == 0) touchCalibration_y0 = 1; + if(touchCalibration_y1 == 0) touchCalibration_y1 = 1; + + touchCalibration_rotate = parameters[4] & 0x01; + touchCalibration_invert_x = parameters[4] & 0x02; + touchCalibration_invert_y = parameters[4] & 0x04; +} + + +#else // TOUCH CS is not defined so generate dummy functions that do nothing + +/*************************************************************************************** +** Function name: Dummy functions for case where chip select pin is undefined +** Description: +***************************************************************************************/ + +uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ + return true; +} + +uint16_t TFT_eSPI::getTouchRawZ(void){ + return true; +} + +uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ + return true; +} + +uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ + return true; +} + +void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_bg, uint32_t color_fg, uint8_t size){ +} + +void TFT_eSPI::setTouch(uint16_t *parameters){ +} + +#endif // TOUCH_CS diff --git a/Extensions/Touch.h b/Extensions/Touch.h new file mode 100644 index 0000000..7f3b22a --- /dev/null +++ b/Extensions/Touch.h @@ -0,0 +1,24 @@ + // Coded by Bodmer 10/2/18, see license in root directory. + // This is part of the TFT_eSPI class and is associated with the Touch Screen handlers + + public: + + uint8_t getTouchRaw(uint16_t *x, uint16_t *y); + uint16_t getTouchRawZ(void); + uint8_t getTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600); + + void calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size); + void setTouch(uint16_t *data); + + private: + + inline void spi_begin_touch() __attribute__((always_inline)); + inline void spi_end_touch() __attribute__((always_inline)); + + // These are associated with the Touch Screen handlers + uint8_t validTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600); + // Initialise with example calibration values so processor does not crash if setTouch() not called in setup() + uint16_t touchCalibration_x0 = 300, touchCalibration_x1 = 3600, touchCalibration_y0 = 300, touchCalibration_y1 = 3600; + uint8_t touchCalibration_rotate = 1, touchCalibration_invert_x = 2, touchCalibration_invert_y = 0; + uint32_t _pressTime; + uint16_t _pressX, _pressY; diff --git a/Fonts/Font7srle.c b/Fonts/Font7srle.c index 6235093..bb292d7 100644 --- a/Fonts/Font7srle.c +++ b/Fonts/Font7srle.c @@ -12,7 +12,7 @@ PROGMEM const unsigned char widtbl_f7s[96] = // character width table { 12, 12, 12, 12, 12, 12, 12, 12, // char 32 - 39 - 12, 12, 12, 12, 12, 17, 12, 12, // char 40 - 47 + 12, 12, 12, 12, 12, 32, 12, 12, // char 40 - 47 32, 32, 32, 32, 32, 32, 32, 32, // char 48 - 55 32, 32, 12, 12, 12, 12, 12, 12, // char 56 - 63 12, 12, 12, 12, 12, 12, 12, 12, // char 64 - 71 @@ -32,10 +32,12 @@ PROGMEM const unsigned char chr_f7s_20[] = 0x7F, 0x7F, 0x7F, 0x7F, 0x3F }; +// Make - sign look like a segment PROGMEM const unsigned char chr_f7s_2D[] = { -0x7F, 0x7F, 0x45, 0x8A, 0x05, 0x8A, 0x05, 0x8A, -0x05, 0x8A, 0x7F, 0x7F, 0x7F, 0x2B +0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x27, 0x8E, 0x0E, +0x92, 0x0A, 0x96, 0x09, 0x94, 0x0C, 0x90, 0x7F, +0x7F, 0x7F, 0x7F, 0x7F, 0x47 }; PROGMEM const unsigned char chr_f7s_2E[] = diff --git a/README.md b/README.md index 4647721..79bf602 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # TFT_eSPI +>>> This branch includes new antialiased font capability, this is a work-in-progress <<< + An Arduino IDE compatible graphics and fonts library for ESP8266 and ESP32 processors with a driver for ILI9341, ILI9163, ST7735 and S6D02A1 based TFT displays that support SPI. The library also supports TFT displays designed for the Raspberry Pi that are based on a ILI9486 driver chip with a 480 x 320 pixel screen. This display must be of the Waveshare design and use a 16 bit serial interface based on the 74HC04, 74HC4040 and 2 x 74HC4094 logic chips. A modification to these displays is possible (see mod image in Tools folder) to make many graphics functions much faster (e.g. 23ms to clear the screen, 1.2ms to draw a 72 pixel high numeral). @@ -32,7 +34,7 @@ Configuration of the library font selections, pins used to interface with the TF I have made some changes that will be uploaded soon that improves sprite and image rendering performance by up to 3x faster on the ESP8266. These updates are currently being tested/debugged. -**2. Anti-aliased fonts - see Smooth_font branch for beta version** +**2. Anti-aliased fonts** I have been experimenting with anti-aliased font files in "vlw" format generated by the free [Processing IDE](https://processing.org/). This IDE can be used to generate font files from your computer's font set and include **any** Unicode characters. This means Greek, Japanese and any other UTF-16 glyphs can be used. diff --git a/TFT_Drivers/ILI9163_Rotation.h b/TFT_Drivers/ILI9163_Rotation.h index 7232430..3323169 100644 --- a/TFT_Drivers/ILI9163_Rotation.h +++ b/TFT_Drivers/ILI9163_Rotation.h @@ -7,8 +7,8 @@ switch (rotation) { case 0: writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; #ifdef CGRAM_OFFSET colstart = 0; rowstart = 0; @@ -16,8 +16,8 @@ break; case 1: writedata(TFT_MAD_MV | TFT_MAD_MY | TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; #ifdef CGRAM_OFFSET colstart = 0; rowstart = 0; @@ -25,8 +25,8 @@ break; case 2: writedata(TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; #ifdef CGRAM_OFFSET colstart = 0; rowstart = 32; @@ -34,8 +34,8 @@ break; case 3: writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; #ifdef CGRAM_OFFSET colstart = 32; rowstart = 0; diff --git a/TFT_Drivers/ILI9341_Rotation.h b/TFT_Drivers/ILI9341_Rotation.h index 6966f37..f5e9b38 100644 --- a/TFT_Drivers/ILI9341_Rotation.h +++ b/TFT_Drivers/ILI9341_Rotation.h @@ -11,8 +11,8 @@ #else writedata(TFT_MAD_MX | TFT_MAD_BGR); #endif - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 1: #ifdef M5STACK @@ -20,8 +20,8 @@ #else writedata(TFT_MAD_MV | TFT_MAD_BGR); #endif - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 2: #ifdef M5STACK @@ -29,8 +29,8 @@ #else writedata(TFT_MAD_MY | TFT_MAD_BGR); #endif - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 3: #ifdef M5STACK @@ -38,8 +38,8 @@ #else writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); #endif - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; // These next rotations are for bottom up BMP drawing case 4: @@ -48,8 +48,8 @@ #else writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); #endif - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 5: #ifdef M5STACK @@ -57,8 +57,8 @@ #else writedata(TFT_MAD_MV | TFT_MAD_MX | TFT_MAD_BGR); #endif - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 6: #ifdef M5STACK @@ -66,8 +66,8 @@ #else writedata(TFT_MAD_BGR); #endif - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 7: #ifdef M5STACK @@ -75,8 +75,8 @@ #else writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); #endif - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; } diff --git a/TFT_Drivers/RPI_ILI9486_Rotation.h b/TFT_Drivers/RPI_ILI9486_Rotation.h index 53afc2a..495d675 100644 --- a/TFT_Drivers/RPI_ILI9486_Rotation.h +++ b/TFT_Drivers/RPI_ILI9486_Rotation.h @@ -5,43 +5,43 @@ switch (rotation) { case 0: // Portrait writedata(TFT_MAD_BGR | TFT_MAD_MX); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 1: // Landscape (Portrait + 90) writedata(TFT_MAD_BGR | TFT_MAD_MV); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 2: // Inverter portrait writedata( TFT_MAD_BGR | TFT_MAD_MY); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 3: // Inverted landscape writedata(TFT_MAD_BGR | TFT_MAD_MV | TFT_MAD_MX | TFT_MAD_MY); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 4: // Portrait writedata(TFT_MAD_BGR | TFT_MAD_MX | TFT_MAD_MY); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 5: // Landscape (Portrait + 90) writedata(TFT_MAD_BGR | TFT_MAD_MV | TFT_MAD_MX); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 6: // Inverter portrait writedata( TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 7: // Inverted landscape writedata(TFT_MAD_BGR | TFT_MAD_MV | TFT_MAD_MY); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; } diff --git a/TFT_Drivers/S6D02A1_Rotation.h b/TFT_Drivers/S6D02A1_Rotation.h index dfa6cdb..7fa6eec 100644 --- a/TFT_Drivers/S6D02A1_Rotation.h +++ b/TFT_Drivers/S6D02A1_Rotation.h @@ -7,22 +7,22 @@ switch (rotation) { case 0: writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 1: writedata(TFT_MAD_MV | TFT_MAD_MY | TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 2: writedata(TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 3: writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; } diff --git a/TFT_Drivers/ST7735_Rotation.h b/TFT_Drivers/ST7735_Rotation.h index 6113886..4a8bfdc 100644 --- a/TFT_Drivers/ST7735_Rotation.h +++ b/TFT_Drivers/ST7735_Rotation.h @@ -23,8 +23,8 @@ } else { writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_BGR); } - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 1: if (tabcolor == INITR_BLACKTAB) { @@ -44,8 +44,8 @@ } else { writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); } - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 2: if (tabcolor == INITR_BLACKTAB) { @@ -65,8 +65,8 @@ } else { writedata(TFT_MAD_BGR); } - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 3: if (tabcolor == INITR_BLACKTAB) { @@ -86,30 +86,30 @@ } else { writedata(TFT_MAD_MX | TFT_MAD_MV | TFT_MAD_BGR); } - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; // These next rotations are for bottum up BMP drawing /* case 4: writedata(ST7735_TFT_MAD_MX | ST7735_TFT_MAD_MY | ST7735_TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 5: writedata(ST7735_TFT_MAD_MV | ST7735_TFT_MAD_MX | ST7735_TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; case 6: writedata(ST7735_TFT_MAD_BGR); - _width = _width_orig; - _height = _height_orig; + _width = _init_width; + _height = _init_height; break; case 7: writedata(ST7735_TFT_MAD_MY | ST7735_TFT_MAD_MV | ST7735_TFT_MAD_BGR); - _width = _height_orig; - _height = _width_orig; + _width = _init_height; + _height = _init_width; break; */ } diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index a7c535c..901f490 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -51,21 +51,25 @@ inline void TFT_eSPI::spi_end(void){ #endif } -inline void TFT_eSPI::spi_begin_touch(void){ -#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) - if (locked) {locked = false; SPI.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));} -#else - SPI.setFrequency(SPI_TOUCH_FREQUENCY); -#endif -} +#if defined (TOUCH_CS) && defined (SPI_TOUCH_FREQUENCY) + + inline void TFT_eSPI::spi_begin_touch(void){ + #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) + if (locked) {locked = false; SPI.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0));} + #else + SPI.setFrequency(SPI_TOUCH_FREQUENCY); + #endif + } + + inline void TFT_eSPI::spi_end_touch(void){ + #if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) + if(!inTransaction) {if (!locked) {locked = true; SPI.endTransaction();}} + #else + SPI.setFrequency(SPI_FREQUENCY); + #endif + } -inline void TFT_eSPI::spi_end_touch(void){ -#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) - if(!inTransaction) {if (!locked) {locked = true; SPI.endTransaction();}} -#else - SPI.setFrequency(SPI_FREQUENCY); #endif -} /*************************************************************************************** ** Function name: TFT_eSPI @@ -105,8 +109,8 @@ TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) } #endif - _width_orig = _width = w; // Set by specific xxxxx_Defines.h file or by users sketch - _height_orig = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch + _init_width = _width = w; // Set by specific xxxxx_Defines.h file or by users sketch + _init_height = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch rotation = 0; cursor_y = cursor_x = 0; textfont = 1; @@ -114,7 +118,8 @@ TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) textcolor = 0xFFFF; // White textbgcolor = 0x0000; // Black padX = 0; // No padding - textwrap = true; // Wrap text when using print stream + textwrapX = true; // Wrap text at end of line when using print stream + textwrapY = false; // Wrap text at bottom of screen when using print stream textdatum = TL_DATUM; // Top Left text alignment is default fontsloaded = 0; @@ -123,8 +128,6 @@ TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) locked = true; // ESP32 transaction mutex lock flags inTransaction = false; - _booted = true; - addr_row = 0xFFFF; addr_col = 0xFFFF; @@ -171,8 +174,6 @@ void TFT_eSPI::begin(void) ***************************************************************************************/ void TFT_eSPI::init(void) { - if (_booted) - { #if !defined (ESP32) #ifdef TFT_CS cspinmask = (uint32_t) digitalPinToBitMask(TFT_CS); @@ -193,7 +194,7 @@ void TFT_eSPI::init(void) SPI.pins(6, 7, 8, 0); #endif - SPI.begin(); // This will set HMISO to input + SPI.begin(); // This will set HMISO to input #else #if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP) SPI.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1); @@ -203,35 +204,31 @@ void TFT_eSPI::init(void) #endif - inTransaction = false; - locked = true; + inTransaction = false; + locked = true; - // SUPPORT_TRANSACTIONS is mandatory for ESP32 so the hal mutex is toggled + // SUPPORT_TRANSACTIONS is manadatory for ESP32 so the hal mutex is toggled // so the code here is for ESP8266 only #if !defined (SUPPORT_TRANSACTIONS) && defined (ESP8266) - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(SPI_FREQUENCY); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(SPI_FREQUENCY); #endif // Set to output once again in case D6 (MISO) is used for CS #ifdef TFT_CS - digitalWrite(TFT_CS, HIGH); // Chip select high (inactive) - pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); // Chip select high (inactive) + pinMode(TFT_CS, OUTPUT); #else - SPI.setHwCs(1); // Use hardware SS toggling + SPI.setHwCs(1); // Use hardware SS toggling #endif // Set to output once again in case D6 (MISO) is used for DC #ifdef TFT_DC - digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode - pinMode(TFT_DC, OUTPUT); + digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode + pinMode(TFT_DC, OUTPUT); #endif - _booted = false; - } // end of: if just _booted - - // Toggle RST low to reset #ifdef TFT_RST if (TFT_RST >= 0) { @@ -242,13 +239,13 @@ void TFT_eSPI::init(void) digitalWrite(TFT_RST, HIGH); delay(150); } -#else - // Or use the software reset +#endif + spi_begin(); writecommand(TFT_SWRST); // Software reset spi_end(); - delay(120); // Wait for software reset to complete -#endif + + delay(5); // Wait for software reset to complete spi_begin(); @@ -272,7 +269,6 @@ void TFT_eSPI::init(void) spi_end(); - setRotation(rotation); } @@ -1591,9 +1587,10 @@ void TFT_eSPI::setTextColor(uint16_t c, uint16_t b) ** Function name: setTextWrap ** Description: Define if text should wrap at end of line ***************************************************************************************/ -void TFT_eSPI::setTextWrap(boolean w) +void TFT_eSPI::setTextWrap(boolean wrapX, boolean wrapY) { - textwrap = w; + textwrapX = wrapX; + textwrapY = wrapY; } @@ -1684,7 +1681,35 @@ int16_t TFT_eSPI::textWidth(const char *string) int16_t TFT_eSPI::textWidth(const char *string, int font) { - unsigned int str_width = 0; + int str_width = 0; + +#ifdef SMOOTH_FONT + if(fontLoaded) + { + while (*string) + { + uint16_t unicode = decodeUTF8(*string++); + if (unicode) + { + if (unicode == 0x20) str_width += gFont.spaceWidth; + else + { + uint16_t gNum = 0; + bool found = getUnicodeIndex(unicode, &gNum); + if (found) + { + if(str_width == 0 && gdX[gNum] < 0) str_width -= gdX[gNum]; + if (*string) str_width += gxAdvance[gNum]; + else str_width += (gdX[gNum] + gWidth[gNum]); + } + else str_width += gFont.spaceWidth + 1; + } + } + } + return str_width; + } +#endif + unsigned char uniCode; char *widthtable; @@ -1750,6 +1775,10 @@ uint16_t TFT_eSPI::fontsLoaded(void) ***************************************************************************************/ int16_t TFT_eSPI::fontHeight(int16_t font) { +#ifdef SMOOTH_FONT + if(fontLoaded) return gFont.yAdvance; +#endif + #ifdef LOAD_GFXFF if (font==1) { @@ -2744,10 +2773,9 @@ void TFT_eSPI::pushColor(uint16_t color, uint16_t len) spi_end(); } - /*************************************************************************************** ** Function name: pushColors -** Description: push an aray of pixels for 16 bit raw image drawing +** Description: push an array of pixels for 16 bit raw image drawing ***************************************************************************************/ // Assumed that setWindow() has previously been called @@ -2861,6 +2889,41 @@ else SPI.writeBytes((uint8_t*)data,len<<1); SPI1CMD |= SPIBUSY; } +/* // Smaller version but slower + uint32_t count = 0; + while(len) + { + if(len>15) {count = 16; len -= 16;} + else {count = len; len = 0;} + uint32_t bits = (count*16-1); // bits left to shift - 1 + if (swap) + { + uint16_t* ptr = (uint16_t*)color; + while(count--) + { + *ptr++ = (*(data) >> 8) | (uint16_t)(*(data) << 8); + data++; + } + } + else + { + memcpy(color,data,count<<1); + data += 16; + } + while(SPI1CMD & SPIBUSY) {} + SPI1U1 = (SPI1U1 & mask) | (bits << SPILMOSI) | (bits << SPILMISO); + SPI1W0 = color[0]; + SPI1W1 = color[1]; + SPI1W2 = color[2]; + SPI1W3 = color[3]; + SPI1W4 = color[4]; + SPI1W5 = color[5]; + SPI1W6 = color[6]; + SPI1W7 = color[7]; + SPI1CMD |= SPIBUSY; + } +*/ + while(SPI1CMD & SPIBUSY) {} #endif @@ -3215,15 +3278,33 @@ uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b) /*************************************************************************************** -** Function name: color332 +** Function name: color16to8 ** Description: convert 16 bit colour to an 8 bit 332 RGB colour value ***************************************************************************************/ -uint8_t TFT_eSPI::color332(uint16_t c) +uint8_t TFT_eSPI::color16to8(uint16_t c) { return ((c & 0xE000)>>8) | ((c & 0x0700)>>6) | ((c & 0x0018)>>3); } +/*************************************************************************************** +** Function name: color8to16 +** Description: convert 8 bit colour to a 16 bit 565 colour value +***************************************************************************************/ +uint16_t TFT_eSPI::color8to16(uint8_t color) +{ + uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table + uint16_t color16 = 0; + + // =====Green===== ===============Red============== + color16 = (color & 0x1C)<<6 | (color & 0xC0)<<5 | (color & 0xE0)<<8; + // =====Green===== =======Blue====== + color16 |= (color & 0x1C)<<3 | blue[color & 0x03]; + + return color16; +} + + /*************************************************************************************** ** Function name: invertDisplay ** Description: invert the display colours i = 1 invert, i = 0 normal @@ -3246,6 +3327,27 @@ size_t TFT_eSPI::write(uint8_t utf8) { if (utf8 == '\r') return 1; +#ifdef SMOOTH_FONT + if(fontLoaded) + { + uint16_t unicode = decodeUTF8(utf8); + if (!unicode) return 0; + + //fontFile = SPIFFS.open( _gFontFilename, "r" ); + + if(!fontFile) + { + fontLoaded = false; + return 0; + } + + drawGlyph(unicode); + + //fontFile.close(); + return 1; + } +#endif + uint8_t uniCode = utf8; // Work with a copy if (utf8 == '\n') uniCode+=22; // Make it a valid space character to stop errors else if (utf8 < 32) return 0; @@ -3274,7 +3376,7 @@ size_t TFT_eSPI::write(uint8_t utf8) height = chr_hgt_f16; // Font 2 is rendered in whole byte widths so we must allow for this width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change - width = width * 8; // Width converted back to pixles + width = width * 8; // Width converted back to pixels } #ifdef LOAD_RLE else @@ -3312,12 +3414,12 @@ size_t TFT_eSPI::write(uint8_t utf8) } else { - if (textwrap && (cursor_x + width * textsize > _width)) + if (textwrapX && (cursor_x + width * textsize > _width)) { cursor_y += height; cursor_x = 0; } - //if (cursor_y >= _height) cursor_y = 0; + if (textwrapY && (cursor_y >= _height)) cursor_y = 0; cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont); } @@ -3341,13 +3443,13 @@ size_t TFT_eSPI::write(uint8_t utf8) h = pgm_read_byte(&glyph->height); if((w > 0) && (h > 0)) { // Is there an associated bitmap? int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); - if(textwrap && ((cursor_x + textsize * (xo + w)) > _width)) { + if(textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) { // Drawing character would go off right edge; wrap to new line cursor_x = 0; cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } - //if (cursor_y >= _height) cursor_y = 0; + if (textwrapY && (cursor_y >= _height)) cursor_y = 0; drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize); } cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; @@ -3362,7 +3464,7 @@ size_t TFT_eSPI::write(uint8_t utf8) /*************************************************************************************** ** Function name: drawChar -** Description: draw a unicode onto the screen +** Description: draw a Unicode onto the screen ***************************************************************************************/ int16_t TFT_eSPI::drawChar(unsigned int uniCode, int x, int y) { @@ -3551,7 +3653,7 @@ int16_t TFT_eSPI::drawChar(unsigned int uniCode, int x, int y, int font) while (pc < w) { line = pgm_read_byte((uint8_t *)flash_address); - flash_address++; // 20 bytes smaller by incrementing here + flash_address++; if (line & 0x80) { line &= 0x7F; line++; @@ -3679,7 +3781,7 @@ int16_t TFT_eSPI::drawString(const char *string, int poX, int poY, int font) baseline = cheight; padding =101; // Different padding method used for Free Fonts - // We need to make an adjustment for the botom of the string (eg 'y' character) + // We need to make an adjustment for the bottom of the string (eg 'y' character) if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) || (textdatum == BR_DATUM)) { cheight += glyph_bb * textsize; } @@ -3690,7 +3792,15 @@ int16_t TFT_eSPI::drawString(const char *string, int poX, int poY, int font) if (textdatum || padX) { - // If it is not font 1 (GLCD or free font) get the basline and pixel height of the font + // If it is not font 1 (GLCD or free font) get the baseline and pixel height of the font +#ifdef SMOOTH_FONT + if(fontLoaded) { + baseline = gFont.maxAscent; + cheight = fontHeight(0); + } + + else +#endif if (font!=1) { baseline = pgm_read_byte( &fontdata[font].baseline ) * textsize; cheight = fontHeight(font); @@ -3780,7 +3890,28 @@ int16_t TFT_eSPI::drawString(const char *string, int poX, int poY, int font) } #endif - while (*string) sumX += drawChar(*(string++), poX+sumX, poY, font); +#ifdef SMOOTH_FONT + if(fontLoaded) + { + if (textcolor!=textbgcolor) fillRect(poX, poY, cwidth, cheight, textbgcolor); + //drawLine(poX - 5, poY, poX + 5, poY, TFT_GREEN); + //drawLine(poX, poY - 5, poX, poY + 5, TFT_GREEN); + //fontFile = SPIFFS.open( _gFontFilename, "r"); + if(!fontFile) return 0; + uint16_t len = strlen(string); + uint16_t n = 0; + setCursor(poX, poY); + while (n < len) + { + uint16_t unicode = decodeUTF8((uint8_t*)string, &n, len - n); + drawGlyph(unicode); + } + sumX += cwidth; + //fontFile.close(); + } + else +#endif + while (*string) sumX += drawChar(*(string++), poX+sumX, poY, font); //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // Switch on debugging for the padding areas @@ -4127,16 +4258,17 @@ void spiWriteBlock(uint16_t color, uint32_t repeat) SPI1U = SPIUMOSI | SPIUDUPLEX | SPIUSSE; } -#elif ESP8266 // ESP32 or a ESP8266 running at 80MHz SPI so slow things down -#define BUFFER_SIZE 64 -void spiWriteBlock(uint16_t color, uint32_t repeat) -{ - - uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color}; - SPI.writePattern(&colorBin[0], 2, repeat); - -} +//#elif ESP8266 // ESP32 or a ESP8266 running at 80MHz SPI so slow things down +// +//#define BUFFER_SIZE 64 +//void spiWriteBlock(uint16_t color, uint32_t repeat) +//{ +// +// uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color}; +// SPI.writePattern(&colorBin[0], 2, repeat); +// +//} #else // Low level register based ESP32 code @@ -4174,1601 +4306,19 @@ void spiWriteBlock(uint16_t color, uint32_t repeat) } #endif -// The following touch screen support code by maxpautsch was merged 1/10/17 -// https://github.com/maxpautsch -// Define TOUCH_CS is the user setup file to enable this code -// A demo is provided in examples Generic folder -// Additions by Bodmer to double sample and use Z value to improve detection reliability + +//////////////////////////////////////////////////////////////////////////////////////// #ifdef TOUCH_CS -/*************************************************************************************** -** Function name: getTouchRaw -** Description: read raw touch position. Return false if not pressed. -***************************************************************************************/ -uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ - uint16_t tmp; - CS_H; - - spi_begin_touch(); - - T_CS_L; - - // Start bit + YP sample request for x position - tmp = SPI.transfer(0xd0); - tmp = SPI.transfer(0); - tmp = tmp <<5; - tmp |= 0x1f & (SPI.transfer(0)>>3); - - *x = tmp; - - // Start bit + XP sample request for y position - SPI.transfer(0x90); - tmp = SPI.transfer(0); - tmp = tmp <<5; - tmp |= 0x1f & (SPI.transfer(0)>>3); - - *y = tmp; - - T_CS_H; - - spi_end_touch(); - - return true; -} - -/*************************************************************************************** -** Function name: getTouchRawZ -** Description: read raw pressure on touchpad and return Z value. -***************************************************************************************/ -uint16_t TFT_eSPI::getTouchRawZ(void){ - CS_H; - - spi_begin_touch(); - - T_CS_L; - - // Z sample request - uint16_t tz = 0xFFF; - SPI.transfer(0xb1); - tz += SPI.transfer16(0xc1) >> 3; - tz -= SPI.transfer16(0x91) >> 3; - - T_CS_H; - - spi_end_touch(); - - return tz; -} - -/*************************************************************************************** -** Function name: validTouch -** Description: read validated position. Return false if not pressed. -***************************************************************************************/ -#define _RAWERR 10 // Deadband in position samples -uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ - uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2; - - // Wait until pressure stops increasing - uint16_t z1 = 1; - uint16_t z2 = 0; - while (z1 > z2) - { - z2 = z1; - z1 = getTouchRawZ(); - delay(1); - } - - // Serial.print("Z = ");Serial.println(z1); - - if (z1 <= threshold) return false; - - getTouchRaw(&x_tmp,&y_tmp); - - // Serial.print("Sample 1 x,y = "); Serial.print(x_tmp);Serial.print(",");Serial.print(y_tmp); - // Serial.print(", Z = ");Serial.println(z1); - - delay(1); // Small delay to the next sample - if (getTouchRawZ() <= threshold) return false; - - delay(2); // Small delay to the next sample - getTouchRaw(&x_tmp2,&y_tmp2); - - // Serial.print("Sample 2 x,y = "); Serial.print(x_tmp2);Serial.print(",");Serial.println(y_tmp2); - // Serial.print("Sample difference = ");Serial.print(abs(x_tmp - x_tmp2));Serial.print(",");Serial.println(abs(y_tmp - y_tmp2)); - - if (abs(x_tmp - x_tmp2) > _RAWERR) return false; - if (abs(y_tmp - y_tmp2) > _RAWERR) return false; - - *x = x_tmp; - *y = y_tmp; - - return true; -} - -/*************************************************************************************** -** Function name: getTouch -** Description: read callibrated position. Return false if not pressed. -***************************************************************************************/ -#define Z_THRESHOLD 350 // Touch pressure threshold for validating touches -uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ - uint16_t x_tmp, y_tmp, xx, yy; - - if (threshold<20) threshold = 20; - if (_pressTime > millis()) threshold=20; - - uint8_t n = 5; - uint8_t valid = 0; - while (n--) - { - if (validTouch(&x_tmp, &y_tmp, threshold)) valid++;; - } - - if (valid<1) { _pressTime = 0; return false; } - - _pressTime = millis() + 50; - - if(!touchCalibration_rotate){ - xx=(x_tmp-touchCalibration_x0)*_width/touchCalibration_x1; - yy=(y_tmp-touchCalibration_y0)*_height/touchCalibration_y1; - if(touchCalibration_invert_x) - xx = _width - xx; - if(touchCalibration_invert_y) - yy = _height - yy; - } else { - yy=(x_tmp-touchCalibration_x0)*_height/touchCalibration_x1; - xx=(y_tmp-touchCalibration_y0)*_width/touchCalibration_y1; - if(touchCalibration_invert_x) - xx = _width - xx; - if(touchCalibration_invert_y) - yy = _height - yy; - } - - if (xx >= _width || yy >= _height) return valid; - - _pressX = xx; - _pressY = yy; - *x = _pressX; - *y = _pressY; - return valid; -} - -/*************************************************************************************** -** Function name: calibrateTouch -** Description: generates calibration parameters for touchscreen. -***************************************************************************************/ -void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size){ - int16_t values[] = {0,0,0,0,0,0,0,0}; - uint16_t x_tmp, y_tmp; - - - - for(uint8_t i = 0; i<4; i++){ - fillRect(0, 0, size+1, size+1, color_bg); - fillRect(0, _height-size-1, size+1, size+1, color_bg); - fillRect(_width-size-1, 0, size+1, size+1, color_bg); - fillRect(_width-size-1, _height-size-1, size+1, size+1, color_bg); - - if (i == 5) break; // used to clear the arrows - - switch (i) { - case 0: // up left - drawLine(0, 0, 0, size, color_fg); - drawLine(0, 0, size, 0, color_fg); - drawLine(0, 0, size , size, color_fg); - break; - case 1: // bot left - drawLine(0, _height-size-1, 0, _height-1, color_fg); - drawLine(0, _height-1, size, _height-1, color_fg); - drawLine(size, _height-size-1, 0, _height-1 , color_fg); - break; - case 2: // up right - drawLine(_width-size-1, 0, _width-1, 0, color_fg); - drawLine(_width-size-1, size, _width-1, 0, color_fg); - drawLine(_width-1, size, _width-1, 0, color_fg); - break; - case 3: // bot right - drawLine(_width-size-1, _height-size-1, _width-1, _height-1, color_fg); - drawLine(_width-1, _height-1-size, _width-1, _height-1, color_fg); - drawLine(_width-1-size, _height-1, _width-1, _height-1, color_fg); - break; - } - - // user has to get the chance to release - if(i>0) delay(1000); - - for(uint8_t j= 0; j<8; j++){ - // Use a lower detect threshold as corners tend to be less sensitive - while(!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD/4)); - values[i*2 ] += x_tmp; - values[i*2+1] += y_tmp; - } - values[i*2 ] /= 8; - values[i*2+1] /= 8; - } - - - - // check orientation - // from case 0 to case 1, the y value changed. - // If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched. - touchCalibration_rotate = false; - if(abs(values[0]-values[2]) > abs(values[1]-values[3])){ - touchCalibration_rotate = true; - touchCalibration_x0 = (values[0] + values[4])/2; // calc min x - touchCalibration_x1 = (values[2] + values[6])/2; // calc max x - touchCalibration_y0 = (values[1] + values[3])/2; // calc min y - touchCalibration_y1 = (values[5] + values[7])/2; // calc max y - } else { - touchCalibration_x0 = (values[0] + values[2])/2; // calc min x - touchCalibration_x1 = (values[4] + values[6])/2; // calc max x - touchCalibration_y0 = (values[1] + values[5])/2; // calc min y - touchCalibration_y1 = (values[3] + values[7])/2; // calc max y - } - - // in addition, the touch screen axis could be in the opposit direction of the TFT axis - touchCalibration_invert_x = false; - if(touchCalibration_x0 > touchCalibration_x1){ - values[0]=touchCalibration_x0; - touchCalibration_x0 = touchCalibration_x1; - touchCalibration_x1 = values[0]; - touchCalibration_invert_x = true; - } - touchCalibration_invert_y = false; - if(touchCalibration_y0 > touchCalibration_y1){ - values[0]=touchCalibration_y0; - touchCalibration_y0 = touchCalibration_y1; - touchCalibration_y1 = values[0]; - touchCalibration_invert_y = true; - } - - // pre calculate - touchCalibration_x1 -= touchCalibration_x0; - touchCalibration_y1 -= touchCalibration_y0; - - if(touchCalibration_x0 == 0) touchCalibration_x0 = 1; - if(touchCalibration_x1 == 0) touchCalibration_x1 = 1; - if(touchCalibration_y0 == 0) touchCalibration_y0 = 1; - if(touchCalibration_y1 == 0) touchCalibration_y1 = 1; - - // export parameters, if pointer valid - if(parameters != NULL){ - parameters[0] = touchCalibration_x0; - parameters[1] = touchCalibration_x1; - parameters[2] = touchCalibration_y0; - parameters[3] = touchCalibration_y1; - parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x <<1) | (touchCalibration_invert_y <<2); - } -} - - -/*************************************************************************************** -** Function name: setTouch -** Description: imports calibration parameters for touchscreen. -***************************************************************************************/ -void TFT_eSPI::setTouch(uint16_t *parameters){ - touchCalibration_x0 = parameters[0]; - touchCalibration_x1 = parameters[1]; - touchCalibration_y0 = parameters[2]; - touchCalibration_y1 = parameters[3]; - - if(touchCalibration_x0 == 0) touchCalibration_x0 = 1; - if(touchCalibration_x1 == 0) touchCalibration_x1 = 1; - if(touchCalibration_y0 == 0) touchCalibration_y0 = 1; - if(touchCalibration_y1 == 0) touchCalibration_y1 = 1; - - touchCalibration_rotate = parameters[4] & 0x01; - touchCalibration_invert_x = parameters[4] & 0x02; - touchCalibration_invert_y = parameters[4] & 0x04; -} - -#else // TOUCH CS is not defined so generate dummy functions that do nothing - -/*************************************************************************************** -** Function name: getTouchRaw -** Description: read raw touch position. Return false if not pressed. -***************************************************************************************/ -uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){ - return true; -} - -/*************************************************************************************** -** Function name: getTouchRawZ -** Description: read raw pressure on touchpad and return Z value. -***************************************************************************************/ -uint16_t TFT_eSPI::getTouchRawZ(void){ - return true; -} - -/*************************************************************************************** -** Function name: validTouch -** Description: read validated position. Return false if not pressed. -***************************************************************************************/ -uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ - return true; -} - -/*************************************************************************************** -** Function name: getTouch -** Description: read callibrated position. Return false if not pressed. -***************************************************************************************/ -uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold){ - return true; -} - -/*************************************************************************************** -** Function name: calibrateTouch -** Description: generates calibration parameters for touchscreen. -***************************************************************************************/ -void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_bg, uint32_t color_fg, uint8_t size){ -} - - -/*************************************************************************************** -** Function name: setTouch -** Description: imports calibration parameters for touchscreen. -***************************************************************************************/ -void TFT_eSPI::setTouch(uint16_t *parameters){ -} - -#endif // TOUCH_CS - - - -/*************************************************************************************** -** Code for the GFX button UI element -** Grabbed from Adafruit_GFX library and enhanced to handle any label font -***************************************************************************************/ -TFT_eSPI_Button::TFT_eSPI_Button(void) { - _gfx = 0; -} - -// Classic initButton() function: pass center & size -void TFT_eSPI_Button::initButton( - TFT_eSPI *gfx, int16_t x, int16_t y, uint16_t w, uint16_t h, - uint16_t outline, uint16_t fill, uint16_t textcolor, - char *label, uint8_t textsize) -{ - // Tweak arguments and pass to the newer initButtonUL() function... - initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, - textcolor, label, textsize); -} - -// Newer function instead accepts upper-left corner & size -void TFT_eSPI_Button::initButtonUL( - TFT_eSPI *gfx, int16_t x1, int16_t y1, uint16_t w, uint16_t h, - uint16_t outline, uint16_t fill, uint16_t textcolor, - char *label, uint8_t textsize) -{ - _x1 = x1; - _y1 = y1; - _w = w; - _h = h; - _outlinecolor = outline; - _fillcolor = fill; - _textcolor = textcolor; - _textsize = textsize; - _gfx = gfx; - strncpy(_label, label, 9); -} - -void TFT_eSPI_Button::drawButton(boolean inverted) { - uint16_t fill, outline, text; - - if(!inverted) { - fill = _fillcolor; - outline = _outlinecolor; - text = _textcolor; - } else { - fill = _textcolor; - outline = _outlinecolor; - text = _fillcolor; - } - - uint8_t r = min(_w, _h) / 4; // Corner radius - _gfx->fillRoundRect(_x1, _y1, _w, _h, r, fill); - _gfx->drawRoundRect(_x1, _y1, _w, _h, r, outline); - - _gfx->setTextColor(text); - _gfx->setTextSize(_textsize); - - uint8_t tempdatum = _gfx->getTextDatum(); - _gfx->setTextDatum(MC_DATUM); - _gfx->drawString(_label, _x1 + (_w/2), _y1 + (_h/2)); - _gfx->setTextDatum(tempdatum); -} - -boolean TFT_eSPI_Button::contains(int16_t x, int16_t y) { - return ((x >= _x1) && (x < (_x1 + _w)) && - (y >= _y1) && (y < (_y1 + _h))); -} - -void TFT_eSPI_Button::press(boolean p) { - laststate = currstate; - currstate = p; -} - -boolean TFT_eSPI_Button::isPressed() { return currstate; } -boolean TFT_eSPI_Button::justPressed() { return (currstate && !laststate); } -boolean TFT_eSPI_Button::justReleased() { return (!currstate && laststate); } - - - - /************************************************************************************** -// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite -// and rendered quickly onto the TFT screen. The class inherits the graphics functions -// from the TFT_eSPI class. Some functions are overridden by this class so that the -// graphics are written to the Sprite rather than the TFT. -// Coded by Bodmer -***************************************************************************************/ -/*************************************************************************************** -// Color bytes are swapped when writing to RAM, this introduces a small overhead but -// then rendering to screen can use the faster call. In general rendering graphics in -// the Sprite is very fast, but writing to the TFT is slow, so there is a performance -// gain by using swapped bytes. -***************************************************************************************/ - -/*************************************************************************************** -** Function name: TFT_eSprite -** Description: Class constructor -*************************************************************************************x*/ -TFT_eSprite::TFT_eSprite(TFT_eSPI *tft) -{ - _tft = tft; // Pointer to tft class so we can call member functions - - _iwidth = 0; // Initialise width and height to 0 (it does not exist yet) - _iheight = 0; - _bpp16 = true; - _iswapBytes = false; // Do not swap pushImage colour bytes by default - - _created = false; - - _xs = 0; // window bounds for pushColor - _ys = 0; - _xe = 0; - _ye = 0; - - _xptr = 0; // pushColor coordinate - _yptr = 0; - - _icursor_y = _icursor_x = 0; // Text cursor position -} - - -/*************************************************************************************** -** Function name: createSprite -** Description: Create a sprite (bitmap) of defined width and height -*************************************************************************************x*/ -// returns a uint8_t* pointer, cast returned value to (uint16_t*) for 16 bit colours -uint8_t* TFT_eSprite::createSprite(int16_t w, int16_t h) -{ - - if ( _created ) - { - if ( _bpp16 ) return ( uint8_t*)_img; - return _img8; - } - - if ( w < 1 || h < 1 ) return NULL; - - _iwidth = w; - _iheight = h; - - _sx = 0; - _sy = 0; - _sw = w; - _sh = h; - _scolor = TFT_BLACK; - - // Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates - // this means push/writeColor functions do not need additional bounds checks. - if(_bpp16) - { - _img = (uint16_t*) malloc(w * h * 2 + 1); - if (_img) - { - _created = true; - fillSprite(TFT_BLACK); - return (uint8_t*)_img; - } - } - else - { - _img8 = ( uint8_t*) malloc(w * h + 1); - if (_img8) - { - _created = true; - fillSprite(TFT_BLACK); - return _img8; - } - } - - return NULL; -} - - -/*************************************************************************************** -** Function name: setDepth -** Description: Set bits per pixel for colour (8 or 16) -*************************************************************************************x*/ - -uint8_t* TFT_eSprite::setColorDepth(int8_t b) -{ - // Can't change an existing sprite's colour depth so delete it - if (_created) - { - if (_bpp16) free(_img); - else free(_img8); - } - - // Now define the new colour depth - if ( b > 8 ) _bpp16 = true; // Bytes per pixel - else _bpp16 = false; - - // If it existed, re-create the sprite with the new colour depth - if (_created) - { - _created = false; - return createSprite(_iwidth, _iheight); - } - - return NULL; -} - - -/*************************************************************************************** -** Function name: deleteSprite -** Description: Delete the sprite to free up memory (RAM) -*************************************************************************************x*/ -void TFT_eSprite::deleteSprite(void) -{ - if (!_created ) return; - - if (_bpp16) free(_img); - else free(_img8); - - _created = false; -} - - -/*************************************************************************************** -** Function name: pushSprite -** Description: Push the sprite to the TFT at x, y -*************************************************************************************x*/ -void TFT_eSprite::pushSprite(int32_t x, int32_t y) -{ - if (!_created ) return; - - if (_bpp16) _tft->pushImage(x, y, _iwidth, _iheight, _img ); - else _tft->pushImage(x, y, _iwidth, _iheight, _img8); -} - - -/*************************************************************************************** -** Function name: pushSprite -** Description: Push the sprite to the TFT at x, y with transparent colour -*************************************************************************************x*/ -void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp) -{ - if (!_created ) return; - - if (_bpp16) _tft->pushImage(x, y, _iwidth, _iheight, _img, transp ); - else - { - transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3); - _tft->pushImage(x, y, _iwidth, _iheight, _img8, (uint8_t)transp); - } -} - - -/*************************************************************************************** -** Function name: readPixel -** Description: Read 565 colour of a pixel at defined coordinates -*************************************************************************************x*/ -uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y) -{ - if (!_created ) return 0; - - if (_bpp16) - { - uint16_t color = _img[x + y * _iwidth]; - return (color >> 8) | (color << 8); - } - - uint16_t color = _img8[x + y * _iwidth]; - if (color != 0) { - uint8_t blue[] = {0, 11, 21, 31}; - color = (color & 0xE0)<<8 | (color & 0xC0)<<5 - | (color & 0x1C)<<6 | (color & 0x1C)<<3 - | blue[color & 0x03]; - } - - return color; -} - - -/*************************************************************************************** -** Function name: pushImage -** Description: push 565 colour image into a defined area of a sprite -*************************************************************************************x*/ -void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t *data) -{ - if ((x > _iwidth) || (y > _iheight) || (w == 0) || (h == 0) || !_created) return; - - if (_bpp16) - { - for (uint32_t yp = y; yp < y + h; yp++) - { - for (uint32_t xp = x; xp < x + w; xp++) - { - uint16_t color = *data++; - if(!_iswapBytes) color = color<<8 | color>>8; - _img[xp + yp * _iwidth] = color; - } - } - } - else - { - for (uint32_t yp = y; yp < y + h; yp++) - { - for (uint32_t xp = x; xp < x + w; xp++) - { - uint16_t color = *data++; - if(_iswapBytes) color = color<<8 | color>>8; - _img8[xp + yp * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); - } - } - } -} - - -/*************************************************************************************** -** Function name: pushImage -** Description: push 565 colour FLASH (PROGMEM) image into a defined area -*************************************************************************************x*/ -void TFT_eSprite::pushImage(int32_t x, int32_t y, uint32_t w, uint32_t h, const uint16_t *data) -{ - if ((x > _iwidth) || (y > _iheight) || (w == 0) || (h == 0) || !_created) return; - - if (_bpp16) - { - for (uint32_t yp = y; yp < y + h; yp++) - { - for (uint32_t xp = x; xp < x + w; xp++) - { - uint16_t color = pgm_read_word(data++); - if(!_iswapBytes) color = color<<8 | color>>8; - _img[xp + yp * _iwidth] = color; - } - } - } - else - { - for (uint32_t yp = y; yp < y + h; yp++) - { - for (uint32_t xp = x; xp < x + w; xp++) - { - uint16_t color = pgm_read_word(data++); - if(_iswapBytes) color = color<<8 | color>>8; - _img8[xp + yp * _iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); - } - } - } -} - - -/*************************************************************************************** -** Function name: setSwapBytes -** Description: Used by 16 bit pushImage() to swap byte order in colours -***************************************************************************************/ -void TFT_eSprite::setSwapBytes(bool swap) -{ - _iswapBytes = swap; -} - - -/*************************************************************************************** -** Function name: getSwapBytes -** Description: Return the swap byte order for colours -***************************************************************************************/ -bool TFT_eSprite::getSwapBytes(void) -{ - return _iswapBytes; -} - - -/*************************************************************************************** -** Function name: setWindow -** Description: Set the bounds of a window for pushColor and writeColor -*************************************************************************************x*/ -void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) -{ - bool duff_coord = false; - - if (x0 > x1) swap_coord(x0, x1); - if (y0 > y1) swap_coord(y0, y1); - - if (x0 < 0) x0 = 0; - if (x0 >= _iwidth) duff_coord = true; - if (x1 < 0) x1 = 0; - if (x1 >= _iwidth) x1 = _iwidth - 1; - - if (y0 < 0) y0 = 0; - if (y0 >= _iheight) duff_coord = true; - if (y1 < 0) y1 = 0; - if (y1 >= _iheight) y1 = _iheight - 1; - - if (duff_coord) - { // Point to that extra "off screen" pixel - _xs = 0; - _ys = _iheight; - _xe = 0; - _ye = _iheight; - } - else - { - _xs = x0; - _ys = y0; - _xe = x1; - _ye = y1; - } - - _xptr = _xs; - _yptr = _ys; -} - - -/*************************************************************************************** -** Function name: pushColor -** Description: Send a new pixel to the set window -*************************************************************************************x*/ -void TFT_eSprite::pushColor(uint32_t color) -{ - if (!_created ) return; - - // Write the colour to RAM in set window - if (_bpp16) - _img [_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8); - - else - _img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); - - // Increment x - _xptr++; - - // Wrap on x and y to start, increment y if needed - if (_xptr > _xe) - { - _xptr = _xs; - _yptr++; - if (_yptr > _ye) _yptr = _ys; - } - -} - - -/*************************************************************************************** -** Function name: pushColor -** Description: Send a "len" new pixels to the set window -*************************************************************************************x*/ -void TFT_eSprite::pushColor(uint32_t color, uint16_t len) -{ - if (!_created ) return; - - uint16_t pixelColor; - if (_bpp16) - pixelColor = (uint16_t) (color >> 8) | (color << 8); - - else - pixelColor = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; - - while(len--) writeColor(pixelColor); -} - - -/*************************************************************************************** -** Function name: writeColor -** Description: Write a pixel with pre-formatted colour to the set window -*************************************************************************************x*/ -void TFT_eSprite::writeColor(uint16_t color) -{ - if (!_created ) return; - - // Write 16 bit RGB 565 encoded colour to RAM - if (_bpp16) _img [_xptr + _yptr * _iwidth] = color; - - // Write 8 bit RGB 332 encoded colour to RAM - else _img8[_xptr + _yptr * _iwidth] = (uint8_t) color; - - // Increment x - _xptr++; - - // Wrap on x and y to start, increment y if needed - if (_xptr > _xe) - { - _xptr = _xs; - _yptr++; - if (_yptr > _ye) _yptr = _ys; - } -} - - -/*************************************************************************************** -** Function name: setScrollRect -** Description: Set scroll area within the sprite and the gap fill colour -*************************************************************************************x*/ -void TFT_eSprite::setScrollRect(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t color) -{ - if ((x >= _iwidth) || (y >= _iheight) || !_created ) return; - - if (x < 0) x = 0; - if (y < 0) y = 0; - - if ((x + w) > _iwidth ) w = _iwidth - x; - if ((y + h) > _iheight) h = _iheight - y; - - if ( w < 1 || h < 1) return; - - _sx = x; - _sy = y; - _sw = w; - _sh = h; - - _scolor = color; -} - - -/*************************************************************************************** -** Function name: scroll -** Description: Scroll dx,dy pixels, positive right,down, negative left,up -*************************************************************************************x*/ -void TFT_eSprite::scroll(int16_t dx, int16_t dy) -{ - if (abs(dx) >= _sw || abs(dy) >= _sh) - { - fillRect (_sx, _sy, _sw, _sh, _scolor); - return; - } - - // Fetch the scroll area width and height set by setScrollRect() - uint32_t w = _sw - abs(dx); // line width to copy - uint32_t h = _sh - abs(dy); // lines to copy - int32_t iw = _iwidth; // width of sprite - - // Fetch the x,y origin set by setScrollRect() - uint32_t tx = _sx; // to x - uint32_t fx = _sx; // from x - uint32_t ty = _sy; // to y - uint32_t fy = _sy; // from y - - // Adjust for x delta - if (dx <= 0) fx -= dx; - else tx += dx; - - // Adjust for y delta - if (dy <= 0) fy -= dy; - else - { // Scrolling down so start copy from bottom - ty = ty + _sh - 1; // "To" pointer - iw = -iw; // Pointer moves backwards - fy = ty - dy; // "From" pointer - } - - // Calculate "from y" and "to y" pointers in RAM - uint32_t fyp = fx + fy * _iwidth; - uint32_t typ = tx + ty * _iwidth; - - // Now move the pixels in RAM - if (_bpp16) - { - while (h--) - { // move pixel lines (to, from, byte count) - memmove( _img + typ, _img + fyp, w<<1); - typ += iw; - fyp += iw; - } - } - else - { - while (h--) - { // move pixel lines (to, from, byte count) - memmove( _img8 + typ, _img8 + fyp, w); - typ += iw; - fyp += iw; - } - } - - // Fill the gap left by the scrolling - if (dx > 0) fillRect(_sx, _sy, dx, _sh, _scolor); - if (dx < 0) fillRect(_sx + _sw + dx, _sy, -dx, _sh, _scolor); - if (dy > 0) fillRect(_sx, _sy, _sw, dy, _scolor); - if (dy < 0) fillRect(_sx, _sy + _sh + dy, _sw, -dy, _scolor); -} - - -/*************************************************************************************** -** Function name: fillSprite -** Description: Fill the whole sprite with defined colour -*************************************************************************************x*/ -void TFT_eSprite::fillSprite(uint32_t color) -{ - if (!_created ) return; - - // Use memset if possible as it is super fast - if(( (uint8_t)color == (uint8_t)(color>>8) ) && _bpp16) - memset(_img, (uint8_t)color, _iwidth * _iheight * 2); - else if (!_bpp16) - { - color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; - memset(_img8, (uint8_t)color, _iwidth * _iheight); - } - - else fillRect(0, 0, _iwidth, _iheight, color); -} - - -/*************************************************************************************** -** Function name: setCursor -** Description: Set the sprite text cursor x,y position -*************************************************************************************x*/ -void TFT_eSprite::setCursor(int16_t x, int16_t y) -{ - _icursor_x = x; - _icursor_y = y; -} - - -/*************************************************************************************** -** Function name: width -** Description: Return the width of sprite -*************************************************************************************x*/ -// Return the size of the display -int16_t TFT_eSprite::width(void) -{ - if (!_created ) return 0; - return _iwidth; -} - - -/*************************************************************************************** -** Function name: height -** Description: Return the height of sprite -*************************************************************************************x*/ -int16_t TFT_eSprite::height(void) -{ - if (!_created ) return 0; - return _iheight; -} - - -/*************************************************************************************** -** Function name: drawChar -** Description: draw a single character in the Adafruit GLCD or freefont -*************************************************************************************x*/ -void TFT_eSprite::drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size) -{ - if (!_created ) return; - - if ((x >= _iwidth) || // Clip right - (y >= _iheight) || // Clip bottom - ((x + 6 * size - 1) < 0) || // Clip left - ((y + 8 * size - 1) < 0)) // Clip top - return; - -#ifdef LOAD_GLCD -//>>>>>>>>>>>>>>>>>> -#ifdef LOAD_GFXFF - if(!gfxFont) { // 'Classic' built-in font -#endif -//>>>>>>>>>>>>>>>>>> - - boolean fillbg = (bg != color); - - if ((size==1) && fillbg) - { - uint8_t column[6]; - uint8_t mask = 0x1; - - for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i); - column[5] = 0; - - int8_t j, k; - for (j = 0; j < 8; j++) { - for (k = 0; k < 5; k++ ) { - if (column[k] & mask) { - drawPixel(x + k, y + j, color); - } - else { - drawPixel(x + k, y + j, bg); - } - } - - mask <<= 1; - - drawPixel(x + k, y + j, bg); - } - } - else - { - for (int8_t i = 0; i < 6; i++ ) { - uint8_t line; - if (i == 5) - line = 0x0; - else - line = pgm_read_byte(font + (c * 5) + i); - - if (size == 1) // default size - { - for (int8_t j = 0; j < 8; j++) { - if (line & 0x1) drawPixel(x + i, y + j, color); - line >>= 1; - } - } - else { // big size - for (int8_t j = 0; j < 8; j++) { - if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color); - else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg); - line >>= 1; - } - } - } - } - -//>>>>>>>>>>>>>>>>>>>>>>>>>>> -#ifdef LOAD_GFXFF - } else { // Custom font -#endif -//>>>>>>>>>>>>>>>>>>>>>>>>>>> -#endif // LOAD_GLCD - -#ifdef LOAD_GFXFF - // Filter out bad characters not present in font - if ((c >= (uint8_t)pgm_read_byte(&gfxFont->first)) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last ))) - { -//>>>>>>>>>>>>>>>>>>>>>>>>>>> - - c -= pgm_read_byte(&gfxFont->first); - GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]); - uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap); - - uint16_t bo = pgm_read_word(&glyph->bitmapOffset); - uint8_t w = pgm_read_byte(&glyph->width), - h = pgm_read_byte(&glyph->height), - xa = pgm_read_byte(&glyph->xAdvance); - int8_t xo = pgm_read_byte(&glyph->xOffset), - yo = pgm_read_byte(&glyph->yOffset); - uint8_t xx, yy, bits, bit=0; - int16_t xo16 = 0, yo16 = 0; - - if(size > 1) { - xo16 = xo; - yo16 = yo; - } - - uint16_t hpc = 0; // Horizontal foreground pixel count - for(yy=0; yy>= 1; - } - // Draw pixels for this line as we are about to increment yy - if (hpc) { - if(size == 1) drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color); - else fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color); - hpc=0; - } - } - } + #include "Extensions/Touch.cpp" #endif +#include "Extensions/Button.cpp" -#ifdef LOAD_GLCD - #ifdef LOAD_GFXFF - } // End classic vs custom font - #endif +#include "Extensions/Sprite.cpp" + +#ifdef SMOOTH_FONT + #include "Extensions/Smooth_font.cpp" #endif -} - - -/*************************************************************************************** -** Function name: drawPixel -** Description: push a single pixel at an arbitrary position -*************************************************************************************x*/ -void TFT_eSprite::drawPixel(uint32_t x, uint32_t y, uint32_t color) -{ - // x and y are unsigned so that -ve coordinates turn into large positive ones - // this make bounds checking a bit faster - if ((x >= _iwidth) || (y >= _iheight) || !_created) return; - - if (_bpp16) - { - color = (color >> 8) | (color << 8); - _img[x+y*_iwidth] = (uint16_t) color; - } - else - { - _img8[x+y*_iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3); - } -} - - -/*************************************************************************************** -** Function name: drawLine -** Description: draw a line between 2 arbitrary points -*************************************************************************************x*/ -void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) -{ - if (!_created ) return; - - boolean steep = abs(y1 - y0) > abs(x1 - x0); - if (steep) { - swap_coord(x0, y0); - swap_coord(x1, y1); - } - - if (x0 > x1) { - swap_coord(x0, x1); - swap_coord(y0, y1); - } - - int32_t dx = x1 - x0, dy = abs(y1 - y0);; - - int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0; - - if (y0 < y1) ystep = 1; - - // Split into steep and not steep for FastH/V separation - if (steep) { - for (; x0 <= x1; x0++) { - dlen++; - err -= dy; - if (err < 0) { - err += dx; - if (dlen == 1) drawPixel(y0, xs, color); - else drawFastVLine(y0, xs, dlen, color); - dlen = 0; y0 += ystep; xs = x0 + 1; - } - } - if (dlen) drawFastVLine(y0, xs, dlen, color); - } - else - { - for (; x0 <= x1; x0++) { - dlen++; - err -= dy; - if (err < 0) { - err += dx; - if (dlen == 1) drawPixel(xs, y0, color); - else drawFastHLine(xs, y0, dlen, color); - dlen = 0; y0 += ystep; xs = x0 + 1; - } - } - if (dlen) drawFastHLine(xs, y0, dlen, color); - } -} - - -/*************************************************************************************** -** Function name: drawFastVLine -** Description: draw a vertical line -*************************************************************************************x*/ -void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) -{ - - if ((x < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; - - if (y < 0) { h += y; y = 0; } - - if ((y + h) > _iheight) h = _iheight - y; - - if (h < 1) return; - - if (_bpp16) - { - color = (color >> 8) | (color << 8); - int32_t yp = x + _iwidth * y; - while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;} - } - else - { - color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; - while (h--) _img8[x + _iwidth * y++] = (uint8_t) color; - } -} - - -/*************************************************************************************** -** Function name: drawFastHLine -** Description: draw a horizontal line -*************************************************************************************x*/ -void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) -{ - - if ((y < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return; - - if (x < 0) { w += x; x = 0; } - - if ((x + w) > _iwidth) w = _iwidth - x; - - if (w < 1) return; - - if (_bpp16) - { - color = (color >> 8) | (color << 8); - while (w--) _img[_iwidth * y + x++] = (uint16_t) color; - } - else - { - color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; - memset(_img8+_iwidth * y + x, (uint8_t)color, w); - } -} - - -/*************************************************************************************** -** Function name: fillRect -** Description: draw a filled rectangle -*************************************************************************************x*/ -void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) -{ - if (!_created ) return; - - if (x < 0) { w += x; x = 0; } - - if ((x < 0) || (y < 0) || (x >= _iwidth) || (y >= _iheight)) return; - if ((x + w) > _iwidth) w = _iwidth - x; - if ((y + h) > _iheight) h = _iheight - y; - if ((w < 1) || (h < 1)) return; - - int32_t yp = _iwidth * y + x; - - if (_bpp16) - { - color = (color >> 8) | (color << 8); - uint32_t iw = w; - int32_t ys = yp; - if(h--) {while (iw--) _img[yp++] = (uint16_t) color;} - yp = ys; - while (h--) - { - yp += _iwidth; - memcpy( _img+yp, _img+ys, w<<1); - } - } - else - { - color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3; - while (h--) - { - memset(_img8 + yp, (uint8_t)color, w); - yp += _iwidth; - } - } -} - - -/*************************************************************************************** -** Function name: write -** Description: draw characters piped through serial stream -*************************************************************************************x*/ -size_t TFT_eSprite::write(uint8_t utf8) -{ - if (!_created ) return 0; - - if (utf8 == '\r') return 1; - - uint8_t uniCode = utf8; // Work with a copy - if (utf8 == '\n') uniCode+=22; // Make it a valid space character to stop errors - else if (utf8 < 32) return 0; - - uint16_t width = 0; - uint16_t height = 0; - -//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - //Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port - //Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port - //delay(5); // Debug optional wait for serial port to flush through -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -#ifdef LOAD_GFXFF - if(!gfxFont) { -#endif -//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - -#ifdef LOAD_FONT2 - if (textfont == 2) - { - if (utf8 > 127) return 0; - // This is 20us faster than using the fontdata structure (0.443ms per character instead of 0.465ms) - width = pgm_read_byte(widtbl_f16 + uniCode-32); - height = chr_hgt_f16; - // Font 2 is rendered in whole byte widths so we must allow for this - width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change - width = width * 8; // Width converted back to pixles - } - #ifdef LOAD_RLE - else - #endif -#endif - -#ifdef LOAD_RLE - { - if ((textfont>2) && (textfont<9)) - { - if (utf8 > 127) return 0; - // Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements - // A tad slower than above but this is not significant and is more convenient for the RLE fonts - width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 ); - height= pgm_read_byte( &fontdata[textfont].height ); - } - } -#endif - -#ifdef LOAD_GLCD - if (textfont==1) - { - width = 6; - height = 8; - } -#else - if (textfont==1) return 0; -#endif - - height = height * textsize; - - if (utf8 == '\n') - { - _icursor_y += height; - _icursor_x = 0; - } - else - { - if (textwrap && (_icursor_x + width * textsize > _iwidth)) - { - _icursor_y += height; - _icursor_x = 0; - } - if (_icursor_y >= _iheight) _icursor_y = 0; - _icursor_x += drawChar(uniCode, _icursor_x, _icursor_y, textfont); - } - -//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< -#ifdef LOAD_GFXFF - } // Custom GFX font - else - { - - if(utf8 == '\n') { - _icursor_x = 0; - _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); - } else { - if (uniCode > (uint8_t)pgm_read_byte(&gfxFont->last )) return 0; - if (uniCode < (uint8_t)pgm_read_byte(&gfxFont->first)) return 0; - - uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); - GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); - uint8_t w = pgm_read_byte(&glyph->width), - h = pgm_read_byte(&glyph->height); - if((w > 0) && (h > 0)) { // Is there an associated bitmap? - int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); - if(textwrap && ((_icursor_x + textsize * (xo + w)) > _iwidth)) { - // Drawing character would go off right edge; wrap to new line - _icursor_x = 0; - _icursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); - } - if (_icursor_y >= _iheight) _icursor_y = 0; - drawChar(_icursor_x, _icursor_y, uniCode, textcolor, textbgcolor, textsize); - } - _icursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; - } - } -#endif // LOAD_GFXFF -//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - return 1; -} - - -/*************************************************************************************** -** Function name: drawChar -** Description: draw a unicode onto the screen -*************************************************************************************x*/ -int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y) -{ - return drawChar(uniCode, x, y, textfont); -} - -int16_t TFT_eSprite::drawChar(unsigned int uniCode, int x, int y, int font) -{ - if (!_created ) return 0; - - if (font==1) - { -#ifdef LOAD_GLCD - #ifndef LOAD_GFXFF - drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); - return 6 * textsize; - #endif -#else - #ifndef LOAD_GFXFF - return 0; - #endif -#endif - -#ifdef LOAD_GFXFF - drawChar(x, y, uniCode, textcolor, textbgcolor, textsize); - if(!gfxFont) { // 'Classic' built-in font - #ifdef LOAD_GLCD - return 6 * textsize; - #else - return 0; - #endif - } - else - { - if((uniCode >= pgm_read_byte(&gfxFont->first)) && (uniCode <= pgm_read_byte(&gfxFont->last) )) - { - uint8_t c2 = uniCode - pgm_read_byte(&gfxFont->first); - GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]); - return pgm_read_byte(&glyph->xAdvance) * textsize; - } - else - { - return 0; - } - } -#endif - } - - if ((font>1) && (font<9) && ((uniCode < 32) || (uniCode > 127))) return 0; - - int width = 0; - int height = 0; - uint32_t flash_address = 0; - uniCode -= 32; - -#ifdef LOAD_FONT2 - if (font == 2) - { - // This is faster than using the fontdata structure - flash_address = pgm_read_dword(&chrtbl_f16[uniCode]); - width = pgm_read_byte(widtbl_f16 + uniCode); - height = chr_hgt_f16; - } - #ifdef LOAD_RLE - else - #endif -#endif - -#ifdef LOAD_RLE - { - if ((font>2) && (font<9)) - { - // This is slower than above but is more convenient for the RLE fonts - flash_address = pgm_read_dword( pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode*sizeof(void *) ); - width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode ); - height= pgm_read_byte( &fontdata[font].height ); - } - } -#endif - - int w = width; - int pX = 0; - int pY = y; - uint8_t line = 0; - -#ifdef LOAD_FONT2 // chop out code if we do not need it - if (font == 2) { - w = w + 6; // Should be + 7 but we need to compensate for width increment - w = w / 8; - if (x + width * textsize >= _iwidth) return width * textsize ; - - for (int i = 0; i < height; i++) - { - if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor); - - for (int k = 0; k < w; k++) - { - line = pgm_read_byte((uint8_t *)flash_address + w * i + k); - if (line) { - if (textsize == 1) { - pX = x + k * 8; - if (line & 0x80) drawPixel(pX, pY, textcolor); - if (line & 0x40) drawPixel(pX + 1, pY, textcolor); - if (line & 0x20) drawPixel(pX + 2, pY, textcolor); - if (line & 0x10) drawPixel(pX + 3, pY, textcolor); - if (line & 0x08) drawPixel(pX + 4, pY, textcolor); - if (line & 0x04) drawPixel(pX + 5, pY, textcolor); - if (line & 0x02) drawPixel(pX + 6, pY, textcolor); - if (line & 0x01) drawPixel(pX + 7, pY, textcolor); - } - else { - pX = x + k * 8 * textsize; - if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor); - if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor); - if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor); - if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor); - if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor); - if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor); - if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor); - if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor); - } - } - } - pY += textsize; - } - } - - #ifdef LOAD_RLE - else - #endif -#endif //FONT2 - -#ifdef LOAD_RLE //674 bytes of code - // Font is not 2 and hence is RLE encoded - { - w *= height; // Now w is total number of pixels in the character - - if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor); - int16_t color; - if (_bpp16) color = (textcolor >> 8) | (textcolor << 8); - else color = ((textcolor & 0xE000)>>8 | (textcolor & 0x0700)>>6 | (textcolor & 0x0018)>>3); - int px = 0, py = pY; // To hold character block start and end column and row values - int pc = 0; // Pixel count - uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel - uint8_t tnp = 0; // Temporary copy of np for while loop - uint8_t ts = textsize - 1; // Temporary copy of textsize - // 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area - // w is total number of pixels to plot to fill character block - while (pc < w) - { - line = pgm_read_byte((uint8_t *)flash_address); - flash_address++; // 20 bytes smaller by incrementing here - if (line & 0x80) { - line &= 0x7F; - line++; - if (ts) { - px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow - py = y + textsize * (pc / width); - } - else { - px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow - py = y + pc / width; - } - while (line--) { - pc++; - setWindow(px, py, px + ts, py + ts); - if (ts) { tnp = np; while (tnp--) writeColor(color); } - else writeColor(color); - - px += textsize; - - if (px >= (x + width * textsize)) - { - px = x; - py += textsize; - } - } - } - else { - line++; - pc += line; - } - } - } - // End of RLE font rendering -#endif - return width * textsize; // x + -} +//////////////////////////////////////////////////////////////////////////////////////// diff --git a/TFT_eSPI.h b/TFT_eSPI.h index bae7afe..8148f59 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -1,9 +1,9 @@ /*************************************************** - Arduino TFT graphics library targetted at ESP8266 + Arduino TFT graphics library targeted at ESP8266 and ESP32 based boards. This is a standalone library that contains the - hardware driver, the graphics funtions and the + hardware driver, the graphics functions and the proportional fonts. The larger fonts are Run Length Encoded to reduce @@ -74,6 +74,17 @@ #include +#ifdef SMOOTH_FONT + // Call up the SPIFFS FLASH filing system for the anti-aliased fonts + #define FS_NO_GLOBALS + #include + + #ifdef ESP32 + #include "SPIFFS.h" + #endif +#endif + + #if defined (ESP8266) && defined (D0_USED_FOR_DC) #define DC_C digitalWrite(TFT_DC, LOW) #define DC_D digitalWrite(TFT_DC, HIGH) @@ -255,7 +266,11 @@ template static inline void swap_coord(T& a, T& b) { T t = a; a = b; b = t; } -// This is a structure to conveniently hold infomation on the default fonts +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +// This is a structure to conveniently hold information on the default fonts // Stores pointer to font character image address table, width table and height // Create a null set in case some fonts not used (to prevent crash) @@ -318,7 +333,6 @@ const PROGMEM fontinfo fontdata [] = { }; - // Class functions and variables class TFT_eSPI : public Print { @@ -343,9 +357,8 @@ class TFT_eSPI : public Print { void setWindow(int16_t x0, int16_t y0, int16_t x1, int16_t y1), pushColor(uint16_t color), pushColor(uint16_t color, uint16_t len), - //pushColors(uint16_t *data, uint8_t len), - pushColors(uint8_t *data, uint32_t len), pushColors(uint16_t *data, uint32_t len, bool swap = true), // With byte swap option + pushColors(uint8_t *data, uint32_t len), fillScreen(uint32_t color); @@ -375,7 +388,7 @@ class TFT_eSPI : public Print { setTextColor(uint16_t fgcolor, uint16_t bgcolor), setTextSize(uint8_t size), - setTextWrap(boolean wrap), + setTextWrap(boolean wrapX, boolean wrapY = false), setTextDatum(uint8_t datum), setTextPadding(uint16_t x_width), @@ -427,13 +440,14 @@ class TFT_eSPI : public Print { uint8_t getRotation(void), getTextDatum(void), - color332(uint16_t color565); // Convert 16 bit colour to 8 bits + color16to8(uint16_t color565); // Convert 16 bit colour to 8 bits int16_t getCursorX(void), getCursorY(void); uint16_t fontsLoaded(void), - color565(uint8_t r, uint8_t g, uint8_t b); + color565(uint8_t r, uint8_t g, uint8_t b), + color8to16(uint8_t color332); // Convert 8 bit colour to 16 bits int16_t drawNumber(long long_num,int poX, int poY, int font), drawNumber(long long_num,int poX, int poY), @@ -462,49 +476,39 @@ class TFT_eSPI : public Print { void setAddrWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye); - // These are associated with the Touch Screen handlers - uint8_t getTouchRaw(uint16_t *x, uint16_t *y); - uint16_t getTouchRawZ(void); - uint8_t getTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600); - - void calibrateTouch(uint16_t *data, uint32_t color_fg, uint32_t color_bg, uint8_t size); - void setTouch(uint16_t *data); size_t write(uint8_t); + int32_t cursor_x, cursor_y; + uint32_t textcolor, textbgcolor; + + private: inline void spi_begin() __attribute__((always_inline)); inline void spi_end() __attribute__((always_inline)); - inline void spi_begin_touch() __attribute__((always_inline)); - inline void spi_end_touch() __attribute__((always_inline)); void readAddrWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye); - + uint8_t tabcolor, colstart = 0, rowstart = 0; // some ST7735 displays need this changed - volatile uint32_t *dcport, *csport;//, *mosiport, *clkport, *rsport; + volatile uint32_t *dcport, *csport; - uint32_t cspinmask, dcpinmask, wrpinmask;//, mosipinmask, clkpinmask; + uint32_t cspinmask, dcpinmask, wrpinmask; uint32_t lastColor = 0xFFFF; - // These are associated with the Touch Screen handlers - uint8_t validTouch(uint16_t *x, uint16_t *y, uint16_t threshold = 600); - // Initialise with example calibration values so processor does not crash if setTouch() not called in setup() - uint16_t touchCalibration_x0 = 300, touchCalibration_x1 = 3600, touchCalibration_y0 = 300, touchCalibration_y1 = 3600; - uint8_t touchCalibration_rotate = 1, touchCalibration_invert_x = 2, touchCalibration_invert_y = 0; - uint32_t _pressTime; - uint16_t _pressX, _pressY; protected: - int32_t cursor_x, cursor_y, win_xe, win_ye, padX; + int32_t win_xe, win_ye, padX; - uint32_t _width_orig, _height_orig; // Display w/h as input, used by setRotation() + uint32_t _init_width, _init_height; // Display w/h as input, used by setRotation() uint32_t _width, _height; // Display w/h as modified by current rotation - uint32_t textcolor, textbgcolor, fontsloaded, addr_row, addr_col; + uint32_t addr_row, addr_col; + + uint32_t fontsloaded; uint8_t glyph_ab, // glyph height above baseline glyph_bb, // glyph height below baseline @@ -513,164 +517,32 @@ class TFT_eSPI : public Print { textdatum, // Text reference datum rotation; // Display rotation (0-3) - bool textwrap; // If set, 'wrap' text at right edge of display + bool textwrapX, textwrapY; // If set, 'wrap' text at right and optionally bottom edge of display bool _swapBytes; // Swap the byte order for TFT pushImage() bool locked, inTransaction; // Transaction and mutex lock flags for ESP32 - bool _booted; int32_t _lastColor; #ifdef LOAD_GFXFF - GFXfont - *gfxFont; + GFXfont *gfxFont; #endif -}; +// Load the Touch extension +#ifdef TOUCH_CS + #include "Extensions/Touch.h" +#endif -/*************************************************************************************** -// The following button class has been ported over from the Adafruit_GFX library so -// should be compatible. -// A slightly different implementation in this TFT_eSPI library allows the button -// legends to be in any font -***************************************************************************************/ +// Load the Anti-aliased font extension +#ifdef SMOOTH_FONT + #include "Extensions/Smooth_font.h" +#endif -class TFT_eSPI_Button { - - public: - TFT_eSPI_Button(void); - // "Classic" initButton() uses center & size - void initButton(TFT_eSPI *gfx, int16_t x, int16_t y, - uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, - uint16_t textcolor, char *label, uint8_t textsize); - - // New/alt initButton() uses upper-left corner & size - void initButtonUL(TFT_eSPI *gfx, int16_t x1, int16_t y1, - uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, - uint16_t textcolor, char *label, uint8_t textsize); - void drawButton(boolean inverted = false); - boolean contains(int16_t x, int16_t y); - - void press(boolean p); - boolean isPressed(); - boolean justPressed(); - boolean justReleased(); - - private: - TFT_eSPI *_gfx; - int16_t _x1, _y1; // Coordinates of top-left corner - uint16_t _w, _h; - uint8_t _textsize; - uint16_t _outlinecolor, _fillcolor, _textcolor; - char _label[10]; - - boolean currstate, laststate; -}; - - -/*************************************************************************************** -// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite -// and rendered quickly onto the TFT screen. The class inherits the graphics functions -// from the TFT_eSPI class. Some functions are overridden by this class so that the -// graphics are written to the Sprite rather than the TFT. -***************************************************************************************/ - -class TFT_eSprite : public TFT_eSPI { - - public: - - TFT_eSprite(TFT_eSPI *tft); - - // Create a sprite of width x height pixels, return a pointer to the RAM area - // Sketch can cast returned value to (uint16_t*) for 16 bit depth if needed - // RAM required is 1 byte per pixel for 8 bit colour depth, 2 bytes for 16 bit - uint8_t* createSprite(int16_t width, int16_t height); - - // Delete the sprite to free up the RAM - void deleteSprite(void); - - // Set the colour depth to 8 or 16 bits. Can be used to change depth an existing - // sprite, but clears it to black, returns a new pointer if sprite is re-created. - uint8_t* setColorDepth(int8_t b); - - void drawPixel(uint32_t x, uint32_t y, uint32_t color); - - void drawChar(int32_t x, int32_t y, unsigned char c, uint32_t color, uint32_t bg, uint8_t size), - - fillSprite(uint32_t color), - - // Define a window to push 16 bit colour pixels into is a raster order - // Colours are converted to 8 bit if depth is set to 8 - setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1), - pushColor(uint32_t color), - pushColor(uint32_t color, uint16_t len), - // Push a pixel preformatted as a 8 or 16 bit colour (avoids conversion overhead) - writeColor(uint16_t color), - - // Set the scroll zone, top left corner at x,y with defined width and height - // The colour (optional, black is default) is used to fill the gap after the scroll - setScrollRect(int32_t x, int32_t y, uint32_t w, uint32_t h, uint16_t color = TFT_BLACK), - // Scroll the defined zone dx,dy pixels. Negative values left,up, positive right,down - // dy is optional (default is then no up/down scroll). - // The sprite coordinate frame does not move because pixels are moved - scroll(int16_t dx, int16_t dy = 0), - - drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color), - drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color), - drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color), - - fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color), - - // Set the sprite text cursor position for print class (does not change the TFT screen cursor) - setCursor(int16_t x, int16_t y); - - // Read the colour of a pixel at x,y and return value in 565 format - uint16_t readPixel(int32_t x0, int32_t y0); - - // Write an image (colour bitmap) to the sprite - void pushImage(int32_t x0, int32_t y0, uint32_t w, uint32_t h, uint16_t *data); - void pushImage(int32_t x0, int32_t y0, uint32_t w, uint32_t h, const uint16_t *data); - - // Swap the byte order for pushImage() - corrects different image endianness - void setSwapBytes(bool swap); - bool getSwapBytes(void); - - // Push the sprite to the TFT screen, this fn calls pushImage() in the TFT class. - // Optionally a "transparent" colour can be defined, pixels of that colour will not be rendered - void pushSprite(int32_t x, int32_t y); - void pushSprite(int32_t x, int32_t y, uint16_t transparent); - - int16_t drawChar(unsigned int uniCode, int x, int y, int font), - drawChar(unsigned int uniCode, int x, int y); - - // Return the width and height of the sprite - int16_t width(void), - height(void); - - // Used by print class to print text to cursor position - size_t write(uint8_t); - - private: - - TFT_eSPI *_tft; - - protected: - - uint16_t *_img; // pointer to 16 bit sprite - uint8_t *_img8; // pointer to 8 bit sprite - bool _created, _bpp16; // created and bits per pixel depth flags - - int32_t _icursor_x, _icursor_y; - int32_t _xs, _ys, _xe, _ye, _xptr, _yptr; // for setWindow - int32_t _sx, _sy; // x,y for scroll zone - uint32_t _sw, _sh; // w,h for scroll zone - uint32_t _scolor; // gap fill colour for scroll zone - - boolean _iswapBytes; // Swap the byte order for Sprite pushImage() - - int32_t _iwidth, _iheight; // Sprite image width and height - -}; +}; // End of class TFT_eSPI +// Load the Button Class +#include "Extensions/Button.h" +// Load the Sprite Class +#include "Extensions/Sprite.h" #endif diff --git a/Tools/Create_Smooth_Font/Create_font_5/Create_font_5.pde b/Tools/Create_Smooth_Font/Create_font_5/Create_font_5.pde new file mode 100644 index 0000000..d89d6a9 --- /dev/null +++ b/Tools/Create_Smooth_Font/Create_font_5/Create_font_5.pde @@ -0,0 +1,483 @@ +// Select the character range in the user configure section starting at line 100 + +/* +Software License Agreement (FreeBSD License) + + Copyright (c) 2018 Bodmer (https://github.com/Bodmer) + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. 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 OWNER 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. + + The views and conclusions contained in the software and documentation are those + of the authors and should not be interpreted as representing official policies, + either expressed or implied, of the FreeBSD Project. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////// + + +// This is a processing sketch to create font files for the TFT_eSPI library: + +// https://github.com/Bodmer/TFT_eSPI + +// Coded by Bodmer January 2018 + +// See comments below in code for specifying the font parameters +// (point size, unicode blocks to include etc). Ranges of characers or +// specific individual unicodes can be included in the created font file/ + +// Created fonts are saved in the sketches "FontFiles" folder. Press Ctrl+K to +// see that folder. + +// 16 bit unicodes in the range 0x0000 - 0xFFFF are supported. + +// The sketch will convert True Type (a .ttf or .otf file) file stored in the +// sketches "Data" folder as well as your computers system fonts. + +// To maximise rendering performance only include the characters you will use. +// Characters at the start of the file will render faster than those at the end. + +// Once created the files must be loaded into the ESP32 or ESP8266 SPIFFS memory +// using the Arduino IDE plugin detailed here: +// https://github.com/esp8266/arduino-esp8266fs-plugin +// https://github.com/me-no-dev/arduino-esp32fs-plugin + +// The sketch list all the available PC fonts to the console, you may need to increase +// console line count (in preferences.txt) to stop some fonts scrolling out of view. +// See link in File>Preferences to locate "preferences.txt" file. You must close +// Processing then edit the file lines. If Processing is not closed first then the +// edits will be overwritten by defaults! Edit "preferences.txt" as follows for +// 1000 lines, then save, then run Processing again: + + /* + console.length=1000 // Line 4 in file + console.scrollback.lines=1000 // Line 7 in file + */ + +// Useful links: + /* + + https://en.wikipedia.org/wiki/Unicode_font + + https://www.gnu.org/software/freefont/ + https://www.gnu.org/software/freefont/sources/ + https://www.gnu.org/software/freefont/ranges/ + http://savannah.gnu.org/projects/freefont/ + + http://www.google.com/get/noto/ + + https://github.com/Bodmer/TFT_eSPI + https://github.com/esp8266/arduino-esp8266fs-plugin + https://github.com/me-no-dev/arduino-esp32fs-plugin + + */ +//////////////////////////////////////////////////////////////////////////////////////////////// + +import java.awt.Desktop; + +// >>>>>>>>>> USER CONFIGURED PARAMETERS START HERE <<<<<<<<<< + + +// Use font name for ttf files placed in the "Data" folder or the font number seen in IDE Console for system fonts +// the font numbers are listed when the sketch is run. +// | 1 2 | Maximum filename size for SPIFFS is 32 including leading / +// 1234567890123456789012345 and added point size and .vlw extension, so max is 25 +String fontName = "Final-Frontier"; //Manually crop the filename length later after creation if needed + +String fontType = ".ttf"; //SPIFFS does not accept underscore in filename! +//String fontType = ".otf"; + +// Use font number instead of name, -1 means use name above, or a value >=0 means use system font number from list. +int fontNumber = -1; // << Use [Number] in brackets from the fonts listed in console window + +// Define the font size in points for the created font file +int fontSize = 28; + +// Font size to use in the Processing sketch display window that pops up (can be different to above) +int displayFontSize = 28; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Next we specify which unicode blocks from the the Basic Multilingual Plane (BMP) are included in the final font file. // +// Note: The ttf/otf font file MAY NOT contain all possible Unicode characters, refer to the fonts online documentation. // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static final int[] unicodeBlocks = { + // The list below has been created from the table here: https://en.wikipedia.org/wiki/Unicode_block + // Remove // at start of lines below to include that unicode block, different code ranges can also be specified by + // editting the start and end of range values. Multiple lines from the list below can be included, limited only by + // the final font file size! + + // Block range, //Block name, Code points, Assigned characters, Scripts + // First, last, //Range is inclusive of first and last codes + 0x0021, 0x007E, //Basic Latin, 128, 128, Latin (52 characters), Common (76 characters) + //0x0080, 0x00FF, //Latin-1 Supplement, 128, 128, Latin (64 characters), Common (64 characters) + //0x0100, 0x017F, //Latin Extended-A, 128, 128, Latin + //0x0180, 0x024F, //Latin Extended-B, 208, 208, Latin + //0x0250, 0x02AF, //IPA Extensions, 96, 96, Latin + //0x02B0, 0x02FF, //Spacing Modifier Letters, 80, 80, Bopomofo (2 characters), Latin (14 characters), Common (64 characters) + //0x0300, 0x036F, //Combining Diacritical Marks, 112, 112, Inherited + //0x0370, 0x03FF, //Greek and Coptic, 144, 135, Coptic (14 characters), Greek (117 characters), Common (4 characters) + //0x0400, 0x04FF, //Cyrillic, 256, 256, Cyrillic (254 characters), Inherited (2 characters) + //0x0500, 0x052F, //Cyrillic Supplement, 48, 48, Cyrillic + //0x0530, 0x058F, //Armenian, 96, 89, Armenian (88 characters), Common (1 character) + //0x0590, 0x05FF, //Hebrew, 112, 87, Hebrew + //0x0600, 0x06FF, //Arabic, 256, 255, Arabic (237 characters), Common (6 characters), Inherited (12 characters) + //0x0700, 0x074F, //Syriac, 80, 77, Syriac + //0x0750, 0x077F, //Arabic Supplement, 48, 48, Arabic + //0x0780, 0x07BF, //Thaana, 64, 50, Thaana + //0x07C0, 0x07FF, //NKo, 64, 59, Nko + //0x0800, 0x083F, //Samaritan, 64, 61, Samaritan + //0x0840, 0x085F, //Mandaic, 32, 29, Mandaic + //0x0860, 0x086F, //Syriac Supplement, 16, 11, Syriac + //0x08A0, 0x08FF, //Arabic Extended-A, 96, 73, Arabic (72 characters), Common (1 character) + //0x0900, 0x097F, //Devanagari, 128, 128, Devanagari (124 characters), Common (2 characters), Inherited (2 characters) + //0x0980, 0x09FF, //Bengali, 128, 95, Bengali + //0x0A00, 0x0A7F, //Gurmukhi, 128, 79, Gurmukhi + //0x0A80, 0x0AFF, //Gujarati, 128, 91, Gujarati + //0x0B00, 0x0B7F, //Oriya, 128, 90, Oriya + //0x0B80, 0x0BFF, //Tamil, 128, 72, Tamil + //0x0C00, 0x0C7F, //Telugu, 128, 96, Telugu + //0x0C80, 0x0CFF, //Kannada, 128, 88, Kannada + //0x0D00, 0x0D7F, //Malayalam, 128, 117, Malayalam + //0x0D80, 0x0DFF, //Sinhala, 128, 90, Sinhala + //0x0E00, 0x0E7F, //Thai, 128, 87, Thai (86 characters), Common (1 character) + //0x0E80, 0x0EFF, //Lao, 128, 67, Lao + //0x0F00, 0x0FFF, //Tibetan, 256, 211, Tibetan (207 characters), Common (4 characters) + //0x1000, 0x109F, //Myanmar, 160, 160, Myanmar + //0x10A0, 0x10FF, //Georgian, 96, 88, Georgian (87 characters), Common (1 character) + //0x1100, 0x11FF, //Hangul Jamo, 256, 256, Hangul + //0x1200, 0x137F, //Ethiopic, 384, 358, Ethiopic + //0x1380, 0x139F, //Ethiopic Supplement, 32, 26, Ethiopic + //0x13A0, 0x13FF, //Cherokee, 96, 92, Cherokee + //0x1400, 0x167F, //Unified Canadian Aboriginal Syllabics, 640, 640, Canadian Aboriginal + //0x1680, 0x169F, //Ogham, 32, 29, Ogham + //0x16A0, 0x16FF, //Runic, 96, 89, Runic (86 characters), Common (3 characters) + //0x1700, 0x171F, //Tagalog, 32, 20, Tagalog + //0x1720, 0x173F, //Hanunoo, 32, 23, Hanunoo (21 characters), Common (2 characters) + //0x1740, 0x175F, //Buhid, 32, 20, Buhid + //0x1760, 0x177F, //Tagbanwa, 32, 18, Tagbanwa + //0x1780, 0x17FF, //Khmer, 128, 114, Khmer + //0x1800, 0x18AF, //Mongolian, 176, 156, Mongolian (153 characters), Common (3 characters) + //0x18B0, 0x18FF, //Unified Canadian Aboriginal Syllabics Extended, 80, 70, Canadian Aboriginal + //0x1900, 0x194F, //Limbu, 80, 68, Limbu + //0x1950, 0x197F, //Tai Le, 48, 35, Tai Le + //0x1980, 0x19DF, //New Tai Lue, 96, 83, New Tai Lue + //0x19E0, 0x19FF, //Khmer Symbols, 32, 32, Khmer + //0x1A00, 0x1A1F, //Buginese, 32, 30, Buginese + //0x1A20, 0x1AAF, //Tai Tham, 144, 127, Tai Tham + //0x1AB0, 0x1AFF, //Combining Diacritical Marks Extended, 80, 15, Inherited + //0x1B00, 0x1B7F, //Balinese, 128, 121, Balinese + //0x1B80, 0x1BBF, //Sundanese, 64, 64, Sundanese + //0x1BC0, 0x1BFF, //Batak, 64, 56, Batak + //0x1C00, 0x1C4F, //Lepcha, 80, 74, Lepcha + //0x1C50, 0x1C7F, //Ol Chiki, 48, 48, Ol Chiki + //0x1C80, 0x1C8F, //Cyrillic Extended-C, 16, 9, Cyrillic + //0x1CC0, 0x1CCF, //Sundanese Supplement, 16, 8, Sundanese + //0x1CD0, 0x1CFF, //Vedic Extensions, 48, 42, Common (15 characters), Inherited (27 characters) + //0x1D00, 0x1D7F, //Phonetic Extensions, 128, 128, Cyrillic (2 characters), Greek (15 characters), Latin (111 characters) + //0x1D80, 0x1DBF, //Phonetic Extensions Supplement, 64, 64, Greek (1 character), Latin (63 characters) + //0x1DC0, 0x1DFF, //Combining Diacritical Marks Supplement, 64, 63, Inherited + //0x1E00, 0x1EFF, //Latin Extended Additional, 256, 256, Latin + //0x1F00, 0x1FFF, //Greek Extended, 256, 233, Greek + //0x2000, 0x206F, //General Punctuation, 112, 111, Common (109 characters), Inherited (2 characters) + //0x2070, 0x209F, //Superscripts and Subscripts, 48, 42, Latin (15 characters), Common (27 characters) + //0x20A0, 0x20CF, //Currency Symbols, 48, 32, Common + //0x20D0, 0x20FF, //Combining Diacritical Marks for Symbols, 48, 33, Inherited + //0x2100, 0x214F, //Letterlike Symbols, 80, 80, Greek (1 character), Latin (4 characters), Common (75 characters) + //0x2150, 0x218F, //Number Forms, 64, 60, Latin (41 characters), Common (19 characters) + //0x2190, 0x21FF, //Arrows, 112, 112, Common + //0x2200, 0x22FF, //Mathematical Operators, 256, 256, Common + //0x2300, 0x23FF, //Miscellaneous Technical, 256, 256, Common + //0x2400, 0x243F, //Control Pictures, 64, 39, Common + //0x2440, 0x245F, //Optical Character Recognition, 32, 11, Common + //0x2460, 0x24FF, //Enclosed Alphanumerics, 160, 160, Common + //0x2500, 0x257F, //Box Drawing, 128, 128, Common + //0x2580, 0x259F, //Block Elements, 32, 32, Common + //0x25A0, 0x25FF, //Geometric Shapes, 96, 96, Common + //0x2600, 0x26FF, //Miscellaneous Symbols, 256, 256, Common + //0x2700, 0x27BF, //Dingbats, 192, 192, Common + //0x27C0, 0x27EF, //Miscellaneous Mathematical Symbols-A, 48, 48, Common + //0x27F0, 0x27FF, //Supplemental Arrows-A, 16, 16, Common + //0x2800, 0x28FF, //Braille Patterns, 256, 256, Braille + //0x2900, 0x297F, //Supplemental Arrows-B, 128, 128, Common + //0x2980, 0x29FF, //Miscellaneous Mathematical Symbols-B, 128, 128, Common + //0x2A00, 0x2AFF, //Supplemental Mathematical Operators, 256, 256, Common + //0x2B00, 0x2BFF, //Miscellaneous Symbols and Arrows, 256, 207, Common + //0x2C00, 0x2C5F, //Glagolitic, 96, 94, Glagolitic + //0x2C60, 0x2C7F, //Latin Extended-C, 32, 32, Latin + //0x2C80, 0x2CFF, //Coptic, 128, 123, Coptic + //0x2D00, 0x2D2F, //Georgian Supplement, 48, 40, Georgian + //0x2D30, 0x2D7F, //Tifinagh, 80, 59, Tifinagh + //0x2D80, 0x2DDF, //Ethiopic Extended, 96, 79, Ethiopic + //0x2DE0, 0x2DFF, //Cyrillic Extended-A, 32, 32, Cyrillic + //0x2E00, 0x2E7F, //Supplemental Punctuation, 128, 74, Common + //0x2E80, 0x2EFF, //CJK Radicals Supplement, 128, 115, Han + //0x2F00, 0x2FDF, //Kangxi Radicals, 224, 214, Han + //0x2FF0, 0x2FFF, //Ideographic Description Characters, 16, 12, Common + //0x3000, 0x303F, //CJK Symbols and Punctuation, 64, 64, Han (15 characters), Hangul (2 characters), Common (43 characters), Inherited (4 characters) + //0x3040, 0x309F, //Hiragana, 96, 93, Hiragana (89 characters), Common (2 characters), Inherited (2 characters) + //0x30A0, 0x30FF, //Katakana, 96, 96, Katakana (93 characters), Common (3 characters) + //0x3100, 0x312F, //Bopomofo, 48, 42, Bopomofo + //0x3130, 0x318F, //Hangul Compatibility Jamo, 96, 94, Hangul + //0x3190, 0x319F, //Kanbun, 16, 16, Common + //0x31A0, 0x31BF, //Bopomofo Extended, 32, 27, Bopomofo + //0x31C0, 0x31EF, //CJK Strokes, 48, 36, Common + //0x31F0, 0x31FF, //Katakana Phonetic Extensions, 16, 16, Katakana + //0x3200, 0x32FF, //Enclosed CJK Letters and Months, 256, 254, Hangul (62 characters), Katakana (47 characters), Common (145 characters) + //0x3300, 0x33FF, //CJK Compatibility, 256, 256, Katakana (88 characters), Common (168 characters) + //0x3400, 0x4DBF, //CJK Unified Ideographs Extension A, 6,592, 6,582, Han + //0x4DC0, 0x4DFF, //Yijing Hexagram Symbols, 64, 64, Common + //0x4E00, 0x9FFF, //CJK Unified Ideographs, 20,992, 20,971, Han + //0xA000, 0xA48F, //Yi Syllables, 1,168, 1,165, Yi + //0xA490, 0xA4CF, //Yi Radicals, 64, 55, Yi + //0xA4D0, 0xA4FF, //Lisu, 48, 48, Lisu + //0xA500, 0xA63F, //Vai, 320, 300, Vai + //0xA640, 0xA69F, //Cyrillic Extended-B, 96, 96, Cyrillic + //0xA6A0, 0xA6FF, //Bamum, 96, 88, Bamum + //0xA700, 0xA71F, //Modifier Tone Letters, 32, 32, Common + //0xA720, 0xA7FF, //Latin Extended-D, 224, 160, Latin (155 characters), Common (5 characters) + //0xA800, 0xA82F, //Syloti Nagri, 48, 44, Syloti Nagri + //0xA830, 0xA83F, //Common Indic Number Forms, 16, 10, Common + //0xA840, 0xA87F, //Phags-pa, 64, 56, Phags Pa + //0xA880, 0xA8DF, //Saurashtra, 96, 82, Saurashtra + //0xA8E0, 0xA8FF, //Devanagari Extended, 32, 30, Devanagari + //0xA900, 0xA92F, //Kayah Li, 48, 48, Kayah Li (47 characters), Common (1 character) + //0xA930, 0xA95F, //Rejang, 48, 37, Rejang + //0xA960, 0xA97F, //Hangul Jamo Extended-A, 32, 29, Hangul + //0xA980, 0xA9DF, //Javanese, 96, 91, Javanese (90 characters), Common (1 character) + //0xA9E0, 0xA9FF, //Myanmar Extended-B, 32, 31, Myanmar + //0xAA00, 0xAA5F, //Cham, 96, 83, Cham + //0xAA60, 0xAA7F, //Myanmar Extended-A, 32, 32, Myanmar + //0xAA80, 0xAADF, //Tai Viet, 96, 72, Tai Viet + //0xAAE0, 0xAAFF, //Meetei Mayek Extensions, 32, 23, Meetei Mayek + //0xAB00, 0xAB2F, //Ethiopic Extended-A, 48, 32, Ethiopic + //0xAB30, 0xAB6F, //Latin Extended-E, 64, 54, Latin (52 characters), Greek (1 character), Common (1 character) + //0xAB70, 0xABBF, //Cherokee Supplement, 80, 80, Cherokee + //0xABC0, 0xABFF, //Meetei Mayek, 64, 56, Meetei Mayek + //0xAC00, 0xD7AF, //Hangul Syllables, 11,184, 11,172, Hangul + //0xD7B0, 0xD7FF, //Hangul Jamo Extended-B, 80, 72, Hangul + //0xD800, 0xDB7F, //High Surrogates, 896, 0, Unknown + //0xDB80, 0xDBFF, //High Private Use Surrogates, 128, 0, Unknown + //0xDC00, 0xDFFF, //Low Surrogates, 1,024, 0, Unknown + //0xE000, 0xF8FF, //Private Use Area, 6,400, 6,400, Unknown + //0xF900, 0xFAFF, //CJK Compatibility Ideographs, 512, 472, Han + //0xFB00, 0xFB4F, //Alphabetic Presentation Forms, 80, 58, Armenian (5 characters), Hebrew (46 characters), Latin (7 characters) + //0xFB50, 0xFDFF, //Arabic Presentation Forms-A, 688, 611, Arabic (609 characters), Common (2 characters) + //0xFE00, 0xFE0F, //Variation Selectors, 16, 16, Inherited + //0xFE10, 0xFE1F, //Vertical Forms, 16, 10, Common + //0xFE20, 0xFE2F, //Combining Half Marks, 16, 16, Cyrillic (2 characters), Inherited (14 characters) + //0xFE30, 0xFE4F, //CJK Compatibility Forms, 32, 32, Common + //0xFE50, 0xFE6F, //Small Form Variants, 32, 26, Common + //0xFE70, 0xFEFF, //Arabic Presentation Forms-B, 144, 141, Arabic (140 characters), Common (1 character) + //0xFF00, 0xFFEF, //Halfwidth and Fullwidth Forms, 240, 225, Hangul (52 characters), Katakana (55 characters), Latin (52 characters), Common (66 characters) + //0xFFF0, 0xFFFF, //Specials, 16, 5, Common + + //0x0030, 0x0039, //Example custom range (numbers 0-9) + //0x0041, 0x005A, //Example custom range (Upper case A-Z) + //0x0061, 0x007A, //Example custom range (Lower case a-z) +}; + +// Here we specify specific individual Unicodes to be included (appended at end of selected range) +static final int[] specificUnicodes = { + + // Commonly used codes, add or remove // in next line + // 0x00A3, 0x00B0, 0x00B5, 0x03A9, 0x20AC, // £ ° µ Ω € + + // Numbers and characters for showing time, change next line to //* to use + /* + 0x002B, 0x002D, 0x002E, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, // - + . 0 1 2 3 4 + 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x0061, 0x006D, // 5 6 7 8 9 : a m + 0x0070, // p + //*/ + + // More characters, change next line to //* to use + /* + 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x010C, 0x010D, + 0x010E, 0x010F, 0x0110, 0x0111, 0x0118, 0x0119, 0x011A, 0x011B, + + 0x0131, 0x0139, 0x013A, 0x013D, 0x013E, 0x0141, 0x0142, 0x0143, + 0x0144, 0x0147, 0x0148, 0x0150, 0x0151, 0x0152, 0x0153, 0x0154, + 0x0155, 0x0158, 0x0159, 0x015A, 0x015B, 0x015E, 0x015F, 0x0160, + 0x0161, 0x0162, 0x0163, 0x0164, 0x0165, 0x016E, 0x016F, 0x0170, + 0x0171, 0x0178, 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, + 0x0192, + + 0x02C6, 0x02C7, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x02DD, + 0x03A9, 0x03C0, 0x2013, 0x2014, 0x2018, 0x2019, 0x201A, 0x201C, + 0x201D, 0x201E, 0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2039, + 0x203A, 0x2044, 0x20AC, + + 0x2122, 0x2202, 0x2206, 0x220F, + + 0x2211, 0x221A, 0x221E, 0x222B, 0x2248, 0x2260, 0x2264, 0x2265, + 0x25CA, + + 0xF8FF, 0xFB01, 0xFB02, + //*/ +}; + + +// >>>>>>>>>> USER CONFIGURED PARAMETERS END HERE <<<<<<<<<< +//////////////////////////////////////////////////////////////////////////////////////////////// + +// Variable to hold the inclusive Unicode range (16 bit values only for this sketch) +int firstUnicode = 0; +int lastUnicode = 0; + +PFont myFont; + +void setup() { + + size(1000, 800); + + // Print the available fonts to the console as a list: + String[] fontList = PFont.list(); + printArray(fontList); + + // Set the fontName from the array number or the defined fontName + if (fontNumber >= 0) + { + fontName = fontList[fontNumber]; + fontType = ""; + } + + char[] charset; + int index = 0, count = 0; + + int blockCount = unicodeBlocks.length; + + for (int i = 0; i < blockCount; i+=2) { + firstUnicode = unicodeBlocks[i]; + lastUnicode = unicodeBlocks[i+1]; + if (lastUnicode < firstUnicode) { + delay(100); + System.err.println("ERROR: Bad Unicode range secified, last < first!"); + System.err.print("first in range = 0x" + hex(firstUnicode, 4)); + System.err.println(", last in range = 0x" + hex(lastUnicode, 4)); + while (true); + } + // calculate the number of characters + count += (lastUnicode - firstUnicode + 1); + } + + count += specificUnicodes.length; + + println(); + println("====================="); + println("Creating font file..."); + println("Unicode blocks included = " + (blockCount/2)); + println("Specific unicodes included = " + specificUnicodes.length); + println("Total number of characters = " + count); + + if (count == 0) { + delay(100); + System.err.println("ERROR: No Unicode range or specific codes have been defined!"); + while (true); + } + + // allocate memory + charset = new char[count]; + + for (int i = 0; i < blockCount; i+=2) { + firstUnicode = unicodeBlocks[i]; + lastUnicode = unicodeBlocks[i+1]; + + // loading the range specified + for (int code = firstUnicode; code <= lastUnicode; code++) { + charset[index] = Character.toChars(code)[0]; + index++; + } + } + + // loading the range specified + for (int i = 0; i < specificUnicodes.length; i++) { + charset[index] = Character.toChars(specificUnicodes[i])[0]; + index++; + } + // Create the font in memory + myFont = createFont(fontName+fontType, 32, true, charset); + + // Print a few characters to the sketch window + fill(0, 0, 0); + textFont(myFont); + + // Set the left and top margin + int margin = displayFontSize; + translate(margin/2, margin); + + int gapx = displayFontSize*10/8; + int gapy = displayFontSize*10/8; + index = 0; + fill(0); + + textSize(displayFontSize); + + for (int y = 0; y < height-gapy; y += gapy) { + int x = 0; + while (x < width) { + + int unicode = charset[index]; + float cwidth = textWidth((char)unicode) + 2; + if ( (x + cwidth) > (width - gapx) ) break; + + // Draw the letter to the screen + text(new String(Character.toChars(unicode)), x, y); + + // Move cursor + x += cwidth; + // Increment the counter + index++; + if (index >= count) break; + } + if (index >= count) break; + } + + + // creating font + PFont font; + + font = createFont(fontName+fontType, fontSize, true, charset); + + println("Created font " + fontName + ".vlw"); + + // creating file + try { + print("Saving to sketch FontFiles folder... "); + + OutputStream output = createOutput("FontFiles/" + fontName + str(fontSize) + ".vlw"); + font.save(output); + output.close(); + + println("OK!"); + + delay(100); + + // Open up the FontFiles folder to access the saved file + String path = sketchPath(); + Desktop.getDesktop().open(new File(path+"/FontFiles")); + + System.err.println("All done! Note: Rectangles are displayed for non-existant characters."); + } + catch(IOException e) { + println("Doh! Failed to create the file"); + } +} \ No newline at end of file diff --git a/Tools/Create_Smooth_Font/Create_font_5/data/Final-Frontier.ttf b/Tools/Create_Smooth_Font/Create_font_5/data/Final-Frontier.ttf new file mode 100644 index 0000000000000000000000000000000000000000..823b9a5aeacae39058bd238b431dd2a3e5a9fd60 GIT binary patch literal 19800 zcmdUXd3==Bx&JxuI`8cJlF2fYNit;LX9%DfCLsg}5FvzBWDBAMP!^GoB3PuTNC8De zii)UFDaA;sT5EAduh&{j5h+z`tqYgyQfn>OLh|PKJ!d8%*!FjOf4{$O;C<%2+d0p9 z_UAcghEPI?jjSgkxn|Quh;kah|tm z$;#`~=da-X>lb${Uz$2kjvz$7x^vn5^z)-1!~GkV%)LI1&zL;CA6hzhNe4dhH>Qyg z;m_ABTd^{A-ZB)Qf4XLQNBa5kz=z}#BECnr5gC2$rq>dpx1>I$17xA#6p3I;z5Vxa za?_X)Nsv&%V!2BPZIl)CFnfv-ldLDgA(1t@(DUS#bCHbbv~Jk&5tk4$?b7t&NBw@qoEI)ludK6teM&-Qnb{};QH$RF^egnx{*H}&-ahB#zbUjVfv@J}Hs zF%XRyiHVqrg;*iccH$sT;sQlI#7lg{PXZ)^1W6{zBH3gX$sw1M5PAzk4k9FvSdiq>cfvu1xs=p{i6cppG>}Fz ziZqeYq?wE%W63zuLdKH`WFl!Lmyt2Ym|KBODH*1Z)m!?G zfvbDOL-H26iyov)Xd8K%yo8xPPmYjZkmtzH$+P5VnsykFcU92OoG*3zHS2AZTM z@-8_=PLdPk82L4Mi~N$jPF}^xFJfdi>NDhNjChzFN{@Pg?8lgU$ZoO<;{`!mBjy2# zO?W+Sm(yXlSuJLhQ8TEDEQtaoC5NCF4WZlT-5yCUY%eW2R76Na+x6X~c$%+nI{z5K zKe~-Y)4OSd{X3URON#NoTPP|mNdY>qn?#aNQZYXnY6>lyyP#Xl$7iTpYKY7YEqF4K zxV>#!7=KTaaOU(@G@m3=eyC|tx0*j{Za2AXS~%D(Hw>K~oV#H9W-M zvcGAqjbGL}G&WJ1Ve@M?Q%|edZL(>8jMy&D5z6H)9QW*mTM;BPG0&zVk*|=|0i^5e z%gQRKBf|d)<$E65EpO>PE|ju^+2156aG3X!L)sE!vNR!R5(*S*R07oJ7DSqFv6;}f zS959xvt-W6&dLmC1bl;a;(|(KR~!A9Z8$aC`uh6f3K|x}F8r?+!z!9MA})T!!>%w6 z@reHM9GdCaNdxzE-Ayy%`X_T}`EdQjj>mTDr|<6CuK!^;&6%ygSw|~(iEa96agsib zKBiCGuAjL_pFz9WaS!!vr*NP`-$#8ni+05ay)7Y)iAy3xu!Xe}YNSQf=2Fcb1EtwC zlZG8Rng>jdh6Jb4AdC-4R4$={lNLpUIhom3MJU4@@CVvmN^5HhfO!NT#WYv(xV<^( z1Z-42ky^~b5iP?bv3PAYJ={E@c>--)XLQ=SMz=;nw?4J>sjgM}!_%m=U}SR5(pAD< zz0kI_y>YEr@fWYfen~9jlFaM&9$B&bMB%1&FAKwGjku+`^PbS1OfXyePAd!KF36M* z_PiX{xGgbuWJ2)dh-PZgvTZO>Ingjt;xQqZRjN@{k{Zl)S_94YYcq7{O{}_KD&uZ%byW-#jpQnHP+J}Jj0~A&B=CeO zCrvr64Y#~<#~rV}w)u`%4$f|Dm_56pakjXIL3Y;tDz3kB$L3e{2US&51Nvved#Y+s zGpJK)XLs&+_0>Drqd0mVNWDF~F?Hj$309Y++9a1%uFzZt)haoyus19EK9^U>t?*gX z6PJ~RR7E0?Nx^Lqv^aIt5=qISej29E47XRYxQ0NXMJa^SfZykm?V?r}7ix2biTQR} zR%scXAXYk*@y24Jh?l1LUR06dw9msCo+3&VRI$-$JX{s4r#>~pgn&eH31dDNfoe!e zoKa7>;j$}6HLbtzz>Y0llC;U~wdPsY(Niw3BV@T@n<(fXe)_Kd>4HVU%s20%V>g-w zmtES@Had1?mFtmPw>|pfZQfF^!4y`$QY?Puv^~GzL;X`~KfdAUn8K+Qw?1>$BbgZ; zW{AtkH}W3X0C&PH5c3sG{i&Q%zp@G!wEmmj@*b}2lx&28ya-*l5r4vA)Tmh#1ZTT! z!7C0n>-u_Vt49$$Zl})^E)Z&KoV1%dUii0HUeW(_=e@8T)|>Q)j(w!xzutNeU2^nA z+6f#2=(VlCS5+fsn*qIwrop{>ogTNKhHGQa+8QBRON+Pe-g%e)<;$=9+w=04yY;)D z`=!3)Uh8@~|BuJ$x*Yqh?pE`d#QJNgIVT!V1Kr!GxOtKc6PJ(buKa0CwBb9f9280Y!-m?^-wM6D%HE zseLo;w)(8X3hfKcnrryNXkqEVQ++3Z+lQDlWLVU+283Lx3Bo*;Rm2Bbo~I_pVyzS7 z@d>ly6DG#sD)D|_S6@)DFMXmZI;tNex0E~^d|mPR16`%X)UTiJ|BB#|iw17>0{`kg#P7;6Kn z&8ipPHmiT`+;n;zDck9Cr0j#EhE!J(@yL(%-1nm&-FM&qxAgb_@{Rs3&Hv^vG$QWV zd+5mr_4Ws!c;Z2NK%ex5{xQw^oF4p~X6hd^3T+4YKt6)7Cy;Q!@T*iK!gy+R%hbh( z#ROdxLo%CO%cR-?EVk3AYVhly`wePNxipW?I`;#!Qw>`1wH<{Ifvqy`TQs;xR={>v|p%I+|+lUvb@L?Y7ZxtsVWn_}pun#=UGX!omqB+g4t>{JuMG zI^B6~K|%e$brr5pq2mrMX@Sn1Q^U{%&~eW=nv`nNW=JYL2W3 z1t18+&6UE0p^-wyN+WR9+_&LY%ENv2{juFke*F8w;>Alhtgd?SCVlfXYW(d_x82^_ zdhN7XtEIrHnbY6c)3~Ih@}=8v{iq=``101Sn;r`nEpMK>Y#GC`4k_Cb$X*tyNfZM; z8lXX%A)A8G2{Fs;(t<`%F3Txcs2leti8NcLISfU_Wh^#w#2v6U+?k|(D&yZJG#sVX zG>0B~?q-w27PYLKs~R%&Zvv{?`{5d4&HMBgdl)a*7P!{r3!96_W}f&&5shZuC2jZ}7e-H3a}0yVTWDTL@i3kx;05| z7BhfyA;S!+Tm-81+jY*wh@!>^`{4oo4%53T%OAge{eus0bs3F@b#H#6fBc7c^xqGQ z)7heEGC62c_-Ne5#I27Uxb3$4DSayI>0t`u$g6vs3Qyt?5Glq0tc{yrv@uG;>i_3QR; zT~*&aZ|vB4&GqMo40-#9KRi|ty>{NX(ak@dKmVB@jO!GM_0j8ZdE$v%RzKpn%Q~}R z{DKAJ8)jN7i>^%Ef8xabBd;u+`Li459~w1!T+8M|^KX2E@f;NbX#!@J;zuEVs%q{>>Q(V8D3IZNHf3gCe!@}1h;E-v5!pr;#JLH`6 zlca;^C(+Z7JVNh(jAdG6S>I_fOP&V`MaZy3c_b`kd$nBZv1Ch%i`u;&iY^hjh&02c zO|WMWIid+uTwX~L@-ZRqmzfh8L^Cm!hajzDRuSsW(k%qsv@JG~Q*Y>wm;U(NlTU4# zckP;$_gmc7sPRIAy!6k<4!oVQm$t24vUY=J&W2{N%9HXYJZC1UNaQ=H!)1{`U5_AX zaC51uj@e|8iMd>)2p6(b;Q~_T;)+75(Qs|bUkoU;nAR#v^kd@9;tl$nZl5gbztLNA z+)|ct9St7s-7HByw|wMW(rUM8Hi>>E%6`MUbX1R~*bEjsD7Ony!&q)l3Q2t;=JOg= z$w{>#QNihQ!AR%8@n$3X6C@)-KMfI+myXJiWVtX$XeN0eU~!6oX&;<&W@^gvMZ%z{ zPv%Z9G7t^Gg43<^>+7%Id2j8aD!L`hfAuUqMBjb(*Twn^hoAoJ_q{&9swxKm%h#>G zZKI;lc%U#)KlX(?A2{Rw{cG=h%!3B$Ta(QYV9`0N5Bo5KV#KN80tDZAu!v4-fnR3v z!jOVIr{ru@z;jy4P=z@{aKN>c#n(J;s78=j5M^0L3@#7<2a+c-x$d!Hvu4klHS&SY z!Xk^?9CduZ_hoF8U?padb6xz!pRMrk2hBrBbYcl>auy% z_4tNQ=QLbh5YFUY2lU&s z+jztE3;u9adh`6*;_BwNZ@=l?vDFpls|V9_)Yz-^-~9A3{mXfmjRLm)wAE0btxl9y zP@}61)Rm}1&?F_3`XC#2c<-u;vP?x1RAdE>_B^{x<7rCA`+fEQgywWcyLfy9%R}{L z|0?x`{V$(&OJK(^X!VD^vDgvFuQY3;$!_l*cNP6;BZ@Hk0u}@R z5whKqmfkO{c3bZUFP`;VtiAgNLZ_5W%iDop4eCb2-PC5$$|2aLAU!a2c#V=Fm?2mk zSr8aeg6$Bkik3+$%S**EUaLcHR3fmUu>r-rnD~xxIwVM=`!7g7ZPn|U?3-^Eu0eeD zT?O!>XmGIt&_6*13}5z~XwI>tmLqlk>u=fv8rxxLAm6b#28%77uVHCUhyus>F z()mo9xlk*I@x0*zTfx`82tJNr#y(b;b!n>EX+X{@G$SM*aO3p@^7UObUxWOhoMuo| zmC2ET$x#{wgUvK}D0mxq#Mzv-$lk4Pkw>*^5FSPmotcQ!9JD#)_Q*7sQIID9yc8Ub zr_$4u&tu6Olk`+=IR3?J51uZ1)}t!7&0V@yPz(W@11#u)U;lOA4>q{WUQ=`DjY}lA zEbijQnNuL`W2W(0SCg_tqOi~frKH&3CP-lHjIOTQ+5 zms#@o-jhDPU4OETTgOsFJ|iUQD1FOhQZ$RoVo2B!k=c*{DM;ivsh3(E9@q>jsb&|# za_a5pE33B;HXEtA^yjO!@szRP7z?V&Wht=S+?zkhN~CaG2bnW`$*{rLS$)UUa>Nn= z&@(qc5fAz`tI?vFjiToBIN9QmLDI?uRsc_z`M?Bf{n#pM}IU(~v0~l@UJ0`C| zGl(oI8nglr8fIVx$_1zk5uXeqW_6m#3a^2%+BK(tEG|~dM}FGVKIi=#@7F(Hn5ODpM(y>t z%>N`72kBF6n09~jxBBtVm&D>J!ahnfJEzQK1poMv-RGJv-MNmQK?TAnX7XBWXNS90~`aX85B8*F!KfHX==JSh8z zv_HJH7y&eLX;Knim!vqy~j zN36k2B8Vi7m`NTuQ)8vsss^j)@*+l19SRq6Vnp_63TqjD1nr|`lPt5kSyR~AUz=hT z0tgYx23ZsZsZ?;8fAQ)8EUU*Ydxd8D6eZMrF3>JLOX*LZ?mhg&UB7%BxM5cgf1)ZaUC_GQ)SyzBYOyN5h-^Ics$@~s=uIU0Ku*kWZWS`Q*x4;f{_Mk%Td z3Y@-IGbn=7Lw&T_PC$2VAO=@3_@dXvWES@HzKX~Sj$7XGeHLP=vdWyaL6zP#ShAHb z*t*vFb;zaDzL`lt-)XG=Zz1`lG7*iq?c6hFm{qexX^2~@G0aj05&1QTTu`@Yzoi1H z1{o^V;g-Uoyo+2UEEgOnH(4o9P4##1@1%aB-zRmw`@y4ht@v8M^|G7J|84()1N$Yl z|BA!dQ+NFpvsuOno64dE57vYI1U6kWnqk~E6=j|ZqZu~UmrfESv8z0d33at!WMoMxj_>$yw(rYB3Bskw}1r{V)*Sp5b@A0R`W2PzapV+*__ zVh+Kv7|q2)@^ZsDSs7kX1Un%>6K9n<1guI2utNNBF;#fd1lOlsD25HD)&10Mhu?G| zTCfW3`ok_+MU`fgzT0M26}Ns+NcQ&7SuTTOwTRp4&E9~;;6#nScUiAwMX_GB1l{y& z-RjFQX*R`W6?%m2R;R(LT5_@O!Sa8(h3jiHk!=ZSezR!y`JHwWxKbfX+N-Ub{U(2YWyA{eP=rADU%M&&{U zp6x!-WTMtUYx}GI>Fl2skHZ5M3VK*v&9VmLy7T$ss@^P%$6R1qCyiybcEgv_8=4Un zM~r$B?ixFCcug}$q=qoXcy?+GJ9!50KxKU@)`?P8O^oD9Oy^u6s|Ui!EF0DyDf28Y)OQN))NIt8 z)Ep+5iiD{Msl^K{ayqc$Els5+l(;<1vM>wm2*;~c)I*13cSNp|Md2&ySv|tjsx@ou zF2TG}-g0imDOS3cu!}?ToWh9ph(KG#J*1HN=4^{52HaYfDg?qIWKlz?JCE>!LXIYQ z>{=mA|I7$Xd3Kg?xm`p->_XuN#Sc_IhSQXV?%g{& z%s%hvDO0EaXGMd4!t4QySJRAp)~jmZzMb11c}cR`T9f0((lhmCjK{}rf28*VE@q5Q ztC4$0uwEWTO}Q#S-4^&QcQ^=?Bh(Vk4MW}D-aD)PC3dfAb@Clb_0RsI&&rTYvSPBMki>#) z)A_wW+_h<^-R3qN`>$WV*J}-knQSE#`%=U<&^bs-69qoR*hJO5SQPRAXA@P!R?b_G znJqPAJ5b7VF}9{DnO;<3M9xEDDaxt_!te*W9@zP!HN!l_y!w^*&r$msYUwcB%);4w z?%cXd)56N}uTK27?}T8ru!x`G@Fl2LhD^DxfM&p}hs#hd&8J|rLkNdmnhSB58qJqP znuWDsp}?((0XoA%z4DYS>?RN@QhtmPFYU+D)Z?5@$vYE2E{FX%BO1=DMn8zb85P0d z9%NR>_8olS&K90BuO9hp{lBR1FXA40HI~~sC6Ny|rOk@LVHWo9-tvPVvXuEaB}aeP zo1RTKDC%bvRj7+#c zJz^|o^qA)l5g!gLENYyyw9SyIT>7Y5fJ96o4uzY!X!Bo88g zEXEX_3J9~6$_2}_04_j8^>h7&$b%t36D4Ydk6i8wuAL6DkC9(k!^@YW^QMY#8iNG!H#WJOsr zSyrA5^dsAkoV2!VN$4-A?zrdfj!h~fsqI6sfZ$+VF!3mrJR6j zbWmreK|s-ADvMrItEW6^@m-9IQcOY0m^f%F!^6;O_%Bs`{>ZxamZ_HolI3L$$@21~ zB2Mc^1ySjm=U?c$Nw%c%OwmUQgU>-yO)g1P1&Ls_W^h(V$~9Rl3HaPZHWg}!6zNo0 zTDdIGE>`3U(+gP^%ga1zUQ@3#E9G%7p-gkqff}y~Fh|52tY$x;ZU}sCd;ZiZ*rF#N z-1e@DI-!HM_k8(XvI^Tn`q|g-=S7^BeTVnIgs?|4iAI~P=lQ|naK3pEow<*qYcMk( z+sC20m^lT;kWo-Jz1sHAYf}S)*w5RG1n=)hQVOBr4WvCW&ZG)MDPk*0Vmf1j&eUsV zP?};IijKey;ey_jqF_=(^K8QOC<5q9X&p@{f=Z`rG)oaj!CV&c+RA_$ReQJw`Jmzd zK#j0MGeFr?<>2pB>x9&BNAuOi8~km%ex7V9n$77f*mH#gMCNY%#$m=TMmKccxNQ^JsZSA*gArmNeZObjt;Z zo`(#~Uf`=dZ1xvV{vXUy7?^a*d|-~7kG|eNp`}+z|7b?S+rPRP`5V7C7>B(zIlS$^{-C6qklzhg7@I|I}bjwefvRpi`9CIgdIHaX+sm`SkuT1 ztI$R}7EMzTN<_6=1%H+{#pow;u1&~Ogc(`%LRbZY^&2i0L6Kr?FT@hraGGBKOi>Gg zLfY6q{AcRZzkMsY+LY}m2$pVrX#c|?Sb#Y?eV6nsHyypdJK9H%D}I}K_r{$+Jb%$D zN56NAGA;+G#jWuO5IFOQkVjRDicrvO*AN0C5S&5GZh4BGmF@aBVX$cCzYSp01?<;2 zcBP_Zn&MCOiu&XOba_u!$NWn~ogSC_^^=nvptvGyqfK zWTqmUg)JAvkS)}GH*83ga$Vh`$Rz8i*wz7?kyfEt`F_EcTh=>!05jmwcMj3u>%buo zxyF!?DiDj7gMc&}0jVWy=5m&Y(z?T=70L@XN;?9wXuhTKGkCZZl}*KC47sNW&L8yLOVypr0d#_GTS zB%H!lkvN_4)EukbR0`JhF(0+H_1>}4s#B@R>4LjPIz4EGCX_IarEEt!w2Rb6RklU`J6?$AM=7_2 z(#8R>?S$00`L$R39Z<)0%y#g2M4XiN;z_CL*l~KW-=*43`lQ=d2?zSm9Zb3utlK;W z?ER=&2Z$XteUs7eaTpDVU{5D@95 zdL>nuWKuedeoe7(t63E%9N-MG%Vu*bPED4<7dEnd3L^*Enu1LL$6Q9cX2qHUH3t1| zlcr!7h*oKF0);f^`qvav+dw!qG;t6TCs9Mo{u&v*A&6ZHmHHp`GeAayeZXe^I$?hA zLxRnqimFBB_z3O2`%^es41#8Woa1|3QYmnXLeBHtBxKCe%$jD5=7n>zGyQIt-2$H= z5qr#(g*={x=bSKX<%q4~GtuVdu+*NTs3}$V!G>a1_DO945CYGss>MR<^tJS$e$;AJ z)vrELRjXA$iu1L!4p%f`&Zh>4h9Z?sZ`IvaG)yj)I`uCNP7`)!S?LG#7dFkM8SF-2 zeFjncCSV=G%1|Z=8hxtK7zt(ieO?=@Q<;XFGJq{xO&(z5K*7wzP|T)KPmX1pm^hUy24qf{eT8Lz>rQwn~abtwNjd1vsr* z4T^z%{|%L6@ig#rVAY}2WQ_HidLC*ZB2;pG)_bi!^=)cf zDAwvD3ai$w-mrOSzOYr_+w0cXocl(**CKQ6bwyn%KqSgzK zJ1BbqTVPGoYymS0gcLJe{hQZo*|JXomKBhyV~-zV8yOpHs^Ek8C6pWJ@SA4OylL&M z**6J`N4$2&qwiE4rgJ6^nXUik=_nO$Sb(A-k#5Se{))9eaL9*5*4LEn9Hc< zWTYzyW%)g3MKoY1dwE%DVT6Ap$q(tZ#%&%*FUx3|^nzxC#dna<=3viBG~Q3FR5`=v z$rqV0-}BJhv1jPzt;JXCFTQ;~f5l_*N&52VO3%N2w{i1uv#;16DI8gooOSPg`jIS% zH4$X$J>7eq-g<(X7fY)CXs=UW`R!T#KK(3qkw$Kq_P)PU%FU@R9WiF|hzaX!TpjFd zu=q|--(B)u3foKXL#?3#a&jfUdANaWCU@Z*iu)4}s#GwvYL7p(ciXKC+-esQo}k4= z2A9%WVnVcWXn3Y56&=td)FF1MINUR)_1MQitd~^2Fo!?AX3- z>m9e|$}@ESnx9i;!O)))MSclHF2X;Urj&ri`BGy6z8Qtx3~bGj zx)dq3*EEhYdcNqDAck?=$ye0=&0w*-cxpl1hp>aymC}77ID#zWB6BBbm}o4P4;#&P zd#0}Dr(8+udpB{H*U6r7x{MSNK!G6QobAu*?~kiP3PHd$RxJF&3bT{8D2?~EPubn1 zH0+o#YY+YEvcEo9x_e5!{_QyZ#a9mN=NmOc(?pYZz7T(ZD)kOu@MxLxfd04MeTCQf z&6CDhtz)R;x#{=)Oja5@!e=s@zJE!>-kCEW7@6EU@l3Si5=Hsh4_&1h?|+b0Mk{vy z7{wA*{jom!u_KCtJ=ec@V1s8t%hA`ycX)!GttU@RTpY9vFF$k=Uxe5+468K7KUw0g z$Qk$4l~+D9cHGrj9`Eqs2E$v=RE$`tAdak6Q2nC6TmJ)dJdsf7MEz;w##O;m_q@BC zG-vA^eTy`5eB-X|qpon9#_XDxmk4awA_>ozf3Yj8)LpeOrmxKm4j(amm?uM_n?^2< z`fCEazAPEJZiK#-UfXcfNF{dWu3$}u9Ra}Y*RyYaf5$MY5R`) zKs@vlDpviNqIUJa)zY)bF?HbM3fqXk648Lx#eb}NWT=LgBlkp>%HH7b;5+t12{Bmv zX7qI#s?*o`O%7(W5;*(A`fci1O?Ct*hRccZ6R6m zS+WUHM+<)spTKvQ(oP~u7~jduC%fb=SnIU*osg4#AIL$xyN{Fu_mjZ$13rhBNxig? ztQPl@BtDbkH<(8&v9h+*Ojgsc*wHsb+LdZ6d&p{bUE0W>U(KEqPUAek@0?If0_>jn z9L&`z?2Rw()5T?dXT%RlL~0_%gAe&(;$!C^HwZ5;1pHVe_!{sueCs~k_Z}c3OTZ|; z18+y3!(cgwl^oXSx8h2ykHL7~Ho&2MjevFfSAfI#l?L8J3x^W{!}uoNI6w!7SsZ3_ z7{Q3BHgZ_bVI_yv{H+@F9458=N(?>Ydmg-9*S8q$NxVIo!#2QNh6P}Zx9b2SxKfTh z)=owOhWnldtOG0{HhnR}u1^GX@OD<;2(+^~3}f5^aw&&N4x7+h0e1bc>k~100Y+!A z9au&AC{a>_D^c|S9)5|y#-RhSh*PzgKT`~>3qX4|>te#@Ta3510kU}&bKHvIAIG7c z-G!)0YES%iA%GUcxz2f_~U_MxPR3_%vu>1Ha0FLmi-l zw;46cIjzdULDsGVCKaH}TtIstgAU%#>U$XNY!1Wdxq`%jA^bRhKFJ}YPX!r=ekwqv z#efreyOqO99I{zd@%dI^+^5lIBeJ-WU1#&H!k%CT8Rk`d*44nP06SF~RyD-VA;YSM zWa+yBvpEdw$I*^p+#2-%9^j?=1i%D`Nxcy5CX84^T6lY+{vz6~`YOOl{7M^tvK_tE za(=}y;*aso6no!vKnHJU^-VxKo5L_Lk8uvhK!?R>Cplzvh;a(UxP-(&|JOk;oBkf4 z9h8rwH`dMqh2!XrLB@+X?3@ z`yM!K(_aU)_gMfPyq(ooh;}xIVf0zY--Rq-+&YY#1DNEHQK=5&vMZA?(_!f6J=BkF z7`*}QrvV+joyFVP9JYZ+s4+t-n$g>PfDDsn^ur+IelzIs9_}-}7>6Dh%;GQ`u!Uc5 zfqX*8_}vyV1#eBnbqk<_!z_$D5wl=0fwv}-ksPwICXz<}{3zZY%i%Z<84eT41P+;8 zO~h|`K!@>r9^(KR&J)R0T%U}wJb(@k+3Y4`tf%pu4b*7kbZEm})@FCx_}wWSlPP?@ zQ}}$_@q8irWpr!D^VnvR{u_(W!N+qj!Ek%@H}HJ-BRqHp^@ta6dVFgx}z? z{ecksb%Z$Jj~$;9;@m-q3(vW+!N84YJRcL{oq*$ag!sA%@t5Gh_R9did>Z(MkPJK@ zTu4YJaLL5`S&InCcH;OxAvt~==syG;L#Q8yXW@7OKhsl%1NcWUm%MBopA(Y5l#l{9 zj(;a4dM6=6@^Bm?r0@fA*GKIQU+Ygenm)mGa(fUj`f68qK`^E zS9LWG;9d<{)U**&y9+;0bR!{kz-!p0%*&D4_$`#w?*ZYf9&aPKrTwRC48G{W4bNlovrD%f?TJ+t>St1y>%)vhIQfh zUs7#KGU*lRwm_`(hICsb!{}}4wnW_Y<#ZeU)0648LNW&0DxUxI$fe6xF6vkwTDbhG zj-?Aa*Mt@>Te=cw%a(*jc6N3w4NWQwO`Y4hpkrxSXv&1vNfX;fHMEUt43&mjJ3HpC z=m;&iYQ@UsSIt|svIB2*babvMYr1Ob+|E$ba&&Z6$MQ)XSFY-uyZqvdp^EC#s+!We z>QKedVKuewk6fvV|*G&t2Yuz8G>VN#PD2>F*5P#D=7xScjW;$mDIkN&UYy // Setup file configured for my stock RPi TFT with touch //#include // Setup file for the ESP32 based M5Stack +//#include + //#include // Setup file template for copying/editting diff --git a/examples/Smooth Fonts/Print_Smooth_Font/Print_Smooth_Font.ino b/examples/Smooth Fonts/Print_Smooth_Font/Print_Smooth_Font.ino new file mode 100644 index 0000000..10f9197 --- /dev/null +++ b/examples/Smooth Fonts/Print_Smooth_Font/Print_Smooth_Font.ino @@ -0,0 +1,195 @@ +/* + Sketch to demonstrate using the print class with smooth fonts + + Sketch is writtent for a 240 x 320 display + + Load the font file into SPIFFS first by using the Arduino IDE + Sketch Data Upload menu option. Font files must be stored in the + sketch data folder (Ctrl+k to view). + https://github.com/esp8266/arduino-esp8266fs-plugin + https://github.com/me-no-dev/arduino-esp32fs-plugin + + New font files in the .vlw format can be created using the Processing + sketch in the library Tools folder. The Processing sketch can convert + TrueType fonts in *.ttf or *.otf files. + + Note: SPIFFS does not accept an underscore _ in filenames! + + The library supports 16 bit unicode characters: + https://en.wikipedia.org/wiki/Unicode_font + + The characters supported are in the in the Basic Multilingal Plane: + https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + + Make sure all the display driver and pin connenctions are correct by + editting the User_Setup.h file in the TFT_eSPI library folder. + + ######################################################################### + ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ###### + ######################################################################### +*/ + +// Font file is stored in SPIFFS +#define FS_NO_GLOBALS +#include + +// Graphics and font library +#include +#include + +TFT_eSPI tft = TFT_eSPI(); // Invoke library + +// ------------------------------------------------------------------------- +// Setup +// ------------------------------------------------------------------------- +void setup(void) { + Serial.begin(115200); // Used for messages + + tft.init(); + tft.setRotation(1); + + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); // Stay here twiddling thumbs waiting + } + Serial.println("\r\nInitialisation done."); + + listFiles(); // Lists the files so you can see what is in the SPIFFS + +} + +// ------------------------------------------------------------------------- +// Main loop +// ------------------------------------------------------------------------- +void loop() { + // Wrap test at right and bottom of screen + tft.setTextWrap(true, true); + + // Name of font file (library adds leading / and .vlw) + String fileName = "Final-Frontier-28"; + + // Font and background colour, background colour is used for anti-alias blending + tft.setTextColor(TFT_WHITE, TFT_BLACK); + + // Load the font + tft.loadFont(fileName); + + // Display all characters of the font + tft.showFont(2000); + + // Set "cursor" at top left corner of display (0,0) + // (cursor will move to next line automatically during printing with 'tft.println' + // or stay on the line is there is room for the text with tft.print) + tft.setCursor(0, 0); + + // Set the font colour to be white with a black background, set text size multiplier to 1 + tft.setTextColor(TFT_WHITE, TFT_BLACK); + + // We can now plot text on screen using the "print" class + tft.println("Hello World!"); + + // Set the font colour to be yellow + tft.setTextColor(TFT_YELLOW, TFT_BLACK); + tft.println(1234.56); + + // Set the font colour to be red + tft.setTextColor(TFT_RED, TFT_BLACK); + tft.println((uint32_t)3735928559, HEX); // Should print DEADBEEF + + // Set the font colour to be green with black background + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.println("Anti-aliased font!"); + tft.println(""); + + // Test some print formatting functions + float fnumber = 123.45; + + // Set the font colour to be blue + tft.setTextColor(TFT_BLUE, TFT_BLACK); + tft.print("Float = "); tft.println(fnumber); // Print floating point number + tft.print("Binary = "); tft.println((int)fnumber, BIN); // Print as integer value in binary + tft.print("Hexadecimal = "); tft.println((int)fnumber, HEX); // Print as integer number in Hexadecimal + + // Unload the font to recover used RAM + tft.unloadFont(); + + delay(10000); +} + + +// ------------------------------------------------------------------------- +// List files in ESP8266 or ESP32 SPIFFS memory +// ------------------------------------------------------------------------- +void listFiles(void) { + Serial.println(); + Serial.println("SPIFFS files found:"); + +#ifdef ESP32 + listDir(SPIFFS, "/", true); +#else + fs::Dir dir = SPIFFS.openDir("/"); // Root directory + String line = "====================================="; + + Serial.println(line); + Serial.println(" File name Size"); + Serial.println(line); + + while (dir.next()) { + String fileName = dir.fileName(); + Serial.print(fileName); + int spaces = 25 - fileName.length(); // Tabulate nicely + if (spaces < 0) spaces = 1; + while (spaces--) Serial.print(" "); + fs::File f = dir.openFile("r"); + Serial.print(f.size()); Serial.println(" bytes"); + yield(); + } + + Serial.println(line); +#endif + Serial.println(); + delay(1000); +} + +#ifdef ESP32 +void listDir(fs::FS &fs, const char * dirname, uint8_t levels) { + Serial.printf("Listing directory: %s\n", dirname); + + fs::File root = fs.open(dirname); + if (!root) { + Serial.println("Failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return; + } + + fs::File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("DIR : "); + String fileName = file.name(); + Serial.print(fileName); + if (levels) { + listDir(fs, file.name(), levels - 1); + } + } else { + String fileName = file.name(); + Serial.print(" " + fileName); + int spaces = 32 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + String fileSize = (String) file.size(); + spaces = 8 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + file = root.openNextFile(); + } +} +#endif +// ------------------------------------------------------------------------- diff --git a/examples/Smooth Fonts/Print_Smooth_Font/data/Final-Frontier-28.vlw b/examples/Smooth Fonts/Print_Smooth_Font/data/Final-Frontier-28.vlw new file mode 100644 index 0000000000000000000000000000000000000000..2872fd554bbf34f5e1268770dd8ea3cf55cecdfd GIT binary patch literal 25287 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 literal 0 HcmV?d00001 diff --git a/examples/Smooth Fonts/Unicode_test/SPIFFS_functions.ino b/examples/Smooth Fonts/Unicode_test/SPIFFS_functions.ino new file mode 100644 index 0000000..1415556 --- /dev/null +++ b/examples/Smooth Fonts/Unicode_test/SPIFFS_functions.ino @@ -0,0 +1,83 @@ +/*==================================================================================== + This sketch supports for the ESP6266 and ESP32 SPIFFS filing system + + Created by Bodmer 15th Jan 2017 + ==================================================================================*/ + +//==================================================================================== +// Print a SPIFFS directory list (root directory) +//==================================================================================== + +void listFiles(void) { + Serial.println(); + Serial.println("SPIFFS files found:"); + +#ifdef ESP32 + listDir(SPIFFS, "/", true); +#else + fs::Dir dir = SPIFFS.openDir("/"); // Root directory + String line = "====================================="; + + Serial.println(line); + Serial.println(" File name Size"); + Serial.println(line); + + while (dir.next()) { + String fileName = dir.fileName(); + Serial.print(fileName); + int spaces = 25 - fileName.length(); // Tabulate nicely + if (spaces < 0) spaces = 1; + while (spaces--) Serial.print(" "); + fs::File f = dir.openFile("r"); + Serial.print(f.size()); Serial.println(" bytes"); + yield(); + } + + Serial.println(line); +#endif + Serial.println(); + delay(1000); +} +//==================================================================================== + +#ifdef ESP32 +void listDir(fs::FS &fs, const char * dirname, uint8_t levels) { + Serial.printf("Listing directory: %s\n", dirname); + + fs::File root = fs.open(dirname); + if (!root) { + Serial.println("Failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return; + } + + fs::File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("DIR : "); + String fileName = file.name(); + Serial.print(fileName); + if (levels) { + listDir(fs, file.name(), levels - 1); + } + } else { + String fileName = file.name(); + Serial.print(" " + fileName); + int spaces = 32 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + String fileSize = (String) file.size(); + spaces = 8 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + file = root.openNextFile(); + } +} +#endif diff --git a/examples/Smooth Fonts/Unicode_test/Unicode_test.ino b/examples/Smooth Fonts/Unicode_test/Unicode_test.ino new file mode 100644 index 0000000..aac4926 --- /dev/null +++ b/examples/Smooth Fonts/Unicode_test/Unicode_test.ino @@ -0,0 +1,148 @@ +// Created by Bodmer 24th Jan 2017 - Tested in Arduino IDE 1.8.5 esp8266 Core 2.4.0 + +// The latest Arduino IDE versions support UTF-8 encoding of Unicode characters +// within sketches: +// https://playground.arduino.cc/Code/UTF-8 + +/* + The library expects strings to be in UTF-8 encoded format: + https://www.fileformat.info/info/unicode/utf8.htm + + Creating varaibles needs to be done with care when using character arrays: + char c = 'µ'; // Wrong + char bad[4] = "5µA"; // Wrong + char good[] = "5µA"; // Good + String okay = "5µA"; // Good + + This is because UTF-8 characters outside the basic Latin set occupy more than + 1 byte per character! A 16 bit unicode character occupies 3 bytes! + +*/ + +//==================================================================================== +// Libraries +//==================================================================================== +// Call up the SPIFFS FLASH filing system this is part of the ESP Core + +#include // Hardware-specific library + +TFT_eSPI tft = TFT_eSPI(); // Invoke custom library + +uint16_t bg = TFT_BLACK; +uint16_t fg = TFT_WHITE; + + +//==================================================================================== +// Setup +//==================================================================================== +void setup() +{ + Serial.begin(115200); // Used for messages and the C array generator + + Serial.println("NodeMCU vlw font test!"); + + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); // Stay here twiddling thumbs waiting + } + Serial.println("\r\nInitialisation done."); + + listFiles(); // Lists the files so you can see what is in the SPIFFS + + tft.begin(); + tft.setRotation(0); // portrait + + fg = TFT_WHITE; + bg = TFT_BLACK; +} + +//==================================================================================== +// Loop +//==================================================================================== +void loop() +{ + tft.setTextColor(fg, bg); + + //---------------------------------------------------------------------------- + // Anti-aliased font test + + String test1 = "Hello World"; + + // Load a smooth font from SPIFFS + tft.loadFont("Final-Frontier-28"); + + tft.setRotation(0); + + // Show all characters on screen with 2 second (2000ms) delay between screens + tft.showFont(2000); // Note: This function moves the cursor position! + + tft.fillScreen(bg); + tft.setCursor(0,0); + + tft.println(test1); + + // Remove font parameters from memory to recover RAM + tft.unloadFont(); + + delay(2000); + + //---------------------------------------------------------------------------- + // We can have any random mix of characters in the font + + String test2 = "仝倀"; // Unicodes 0x4EDD, 0x5000 + + tft.loadFont("Unicode-Test-72"); + + tft.setRotation(1); + + // Show all characters on screen with 2 second (2000ms) delay between screens + tft.showFont(2000); // Note: This function moves the cursor position! + + tft.fillScreen(bg); + tft.setCursor(0,0); + + tft.setTextColor(TFT_CYAN, bg); + tft.println(test2); + + tft.setTextColor(TFT_YELLOW, bg); + tft.println("12:00pm"); + + tft.setTextColor(TFT_MAGENTA, bg); + tft.println("1000Ω"); + + // Remove font parameters from memory to recover RAM + tft.unloadFont(); + + delay(2000); + + //---------------------------------------------------------------------------- + // Latin and Hiragana font mix + + String test3 = "こんにちは"; + + tft.loadFont("Latin-Hiragana-24"); + + tft.setRotation(0); + + // Show all characters on screen with 2 second (2000ms) delay between screens + tft.showFont(2000); // Note: This function moves the cursor position! + + tft.fillScreen(bg); + tft.setTextColor(TFT_GREEN, bg); + tft.setCursor(0,0); + + tft.println("Konnichiwa"); + tft.println(test3); + tft.println(); + tft.println("Sayonara"); + tft.println("さようなら"); // Sayonara + + // Remove font parameters from memory to recover RAM + tft.unloadFont(); + + delay(2000); + // + //---------------------------------------------------------------------------- +} +//==================================================================================== + diff --git a/examples/Smooth Fonts/Unicode_test/data/Final-Frontier-28.vlw b/examples/Smooth Fonts/Unicode_test/data/Final-Frontier-28.vlw new file mode 100644 index 0000000000000000000000000000000000000000..2872fd554bbf34f5e1268770dd8ea3cf55cecdfd GIT binary patch literal 25287 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 literal 0 HcmV?d00001 diff --git a/examples/Smooth Fonts/Unicode_test/data/Latin-Hiragana-24.vlw b/examples/Smooth Fonts/Unicode_test/data/Latin-Hiragana-24.vlw new file mode 100644 index 0000000000000000000000000000000000000000..b2f128b4ed054b8c3a488b361f6903e4b557722d GIT binary patch literal 54478 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®~4f}smG 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 E06d{_kN^Mx literal 0 HcmV?d00001 diff --git a/examples/Smooth Fonts/Unicode_test/data/Unicode-Test-72.vlw b/examples/Smooth Fonts/Unicode_test/data/Unicode-Test-72.vlw new file mode 100644 index 0000000000000000000000000000000000000000..c4757564c98f7c46a98dc54f913b83808f2bcb79 GIT binary patch literal 36469 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 // Include the graphics library + +TFT_eSPI tft = TFT_eSPI(); // Create object "tft" + +// ------------------------------------------------------------------------- +// Setup +// ------------------------------------------------------------------------- +void setup(void) { + tft.init(); + tft.setRotation(0); + tft.fillScreen(TFT_DARKGREY); +} + +// ------------------------------------------------------------------------- +// Main loop +// ------------------------------------------------------------------------- +void loop() +{ + // 16 bit colours (5 bits red, 6 bits green, 5 bits blue) + // Blend from white to full spectrum + for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.alphaBlend(a, rainbow(c), TFT_WHITE)); + } + + // Blend from full spectrum to black + for (int a = 255; a > 2; a-=2) + { + for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.alphaBlend(a, rainbow(c), TFT_BLACK)); + } + + // Blend from white to black (32 grey levels) + for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_WHITE)); + tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_RED)); + tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_GREEN)); + tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_BLUE)); + } + + delay(4000); + + // Blend from white to colour (32 grey levels) + for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + //tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_WHITE)); + tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_RED, TFT_WHITE)); + tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_GREEN, TFT_WHITE)); + tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_BLUE, TFT_WHITE)); + } + + delay(4000); + + //* + // Decrease to 8 bit colour (3 bits red, 3 bits green, 2 bits blue) + // Blend from white to full spectrum + for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + // Convert blended 16 bit colour to 8 bits to reduce colour resolution, then map back to 16 bits for displaying + for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.color8to16(tft.color16to8(tft.alphaBlend(a, rainbow(c), 0xFFFF)))); + } + + // Blend from full spectrum to black + for (int a = 255; a > 2; a-=2) + { + // Convert blended 16 bit colour to 8 bits to reduce colour resolution, then map back to 16 bits for displaying + for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.color8to16(tft.color16to8(tft.alphaBlend(a, rainbow(c), 0)))); + } + + // Blend from white to black (4 grey levels - it will draw 4 more with a blue tinge due to lower blue bit count) + // Blend from black to a primary colour + for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + tft.drawFastHLine(192, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_WHITE)))); + tft.drawFastHLine(204, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_RED)))); + tft.drawFastHLine(216, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_GREEN)))); + tft.drawFastHLine(228, a, 12, tft.color8to16(tft.color16to8(tft.alphaBlend(a, TFT_BLACK, TFT_BLUE)))); + } + + delay(4000); + //*/ + + /* + // 16 bit colours (5 bits red, 6 bits green, 5 bits blue) + for (int a = 0; a < 256; a+=2) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + for (int c = 0; c < 192; c++) tft.drawPixel(c, a/2, tft.alphaBlend(a, rainbow(c), TFT_CYAN)); + } + + // Blend from full spectrum to cyan + for (int a = 255; a > 2; a-=2) + { + for (int c = 0; c < 192; c++) tft.drawPixel(c, 128 + (255-a)/2, tft.alphaBlend(a, rainbow(c), TFT_YELLOW)); + } + //*/ + + /* + // Blend other colour transitions for test purposes + for (uint16_t a = 0; a < 255; a++) // Alpha 0 = 100% background, alpha 255 = 100% foreground + { + tft.drawFastHLine(192, a, 12, tft.alphaBlend(a, TFT_WHITE, TFT_WHITE)); // Should show as solid white + tft.drawFastHLine(204, a, 12, tft.alphaBlend(a, TFT_BLACK, TFT_BLACK)); // Should show as solid black + tft.drawFastHLine(216, a, 12, tft.alphaBlend(a, TFT_YELLOW, TFT_CYAN)); // Brightness should be fairly even + tft.drawFastHLine(228, a, 12, tft.alphaBlend(a, TFT_CYAN, TFT_MAGENTA));// Brightness should be fairly even + } + + delay(4000); + //*/ +} + + +// ######################################################################### +// Return a 16 bit rainbow colour +// ######################################################################### +unsigned int rainbow(byte value) +{ + // If 'value' is in the range 0-159 it is converted to a spectrum colour + // from 0 = red through to 127 = blue to 159 = violet + // Extending the range to 0-191 adds a further violet to red band + + value = value%192; + + byte red = 0; // Red is the top 5 bits of a 16 bit colour value + byte green = 0; // Green is the middle 6 bits, but only top 5 bits used here + byte blue = 0; // Blue is the bottom 5 bits + + byte sector = value >> 5; + byte amplit = value & 0x1F; + + switch (sector) + { + case 0: + red = 0x1F; + green = amplit; // Green ramps up + blue = 0; + break; + case 1: + red = 0x1F - amplit; // Red ramps down + green = 0x1F; + blue = 0; + break; + case 2: + red = 0; + green = 0x1F; + blue = amplit; // Blue ramps up + break; + case 3: + red = 0; + green = 0x1F - amplit; // Green ramps down + blue = 0x1F; + break; + case 4: + red = amplit; // Red ramps up + green = 0; + blue = 0x1F; + break; + case 5: + red = 0x1F; + green = 0; + blue = 0x1F - amplit; // Blue ramps down + break; + } + + return red << 11 | green << 6 | blue; +} + + diff --git a/keywords.txt b/keywords.txt index 13748fc..5d4f945 100644 --- a/keywords.txt +++ b/keywords.txt @@ -56,7 +56,8 @@ getRotation KEYWORD2 getTextDatum KEYWORD2 fontsLoaded KEYWORD2 color565 KEYWORD2 -color332 KEYWORD2 +color16to8 KEYWORD2 +color8to16 KEYWORD2 drawNumber KEYWORD2 drawFloat KEYWORD2 drawString KEYWORD2 @@ -98,3 +99,8 @@ pushBitmap KEYWORD2 pushSprite KEYWORD2 setScrollRect KEYWORD2 scroll KEYWORD2 + +alphaBlend KEYWORD2 +showFont KEYWORD2 +loadFont KEYWORD2 +unloadFont KEYWORD2 diff --git a/library.json b/library.json index 2041a92..fd60348 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "TFT_eSPI", - "version": "0.18.17", + "version": "0.18.20", "keywords": "tft, display, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341, ST7735, ILI9163, S6D02A1, ILI9486", "description": "A TFT SPI graphics library for ESP8266 and ESP32", "repository": diff --git a/library.properties b/library.properties index e235cd1..a70b692 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=TFT_eSPI -version=0.18.17 +version=0.18.20 author=Bodmer maintainer=Bodmer sentence=A fast TFT library for ESP8266 processors and the Arduino IDE diff --git a/license.txt b/license.txt index befa9a4..8fe1fb4 100644 --- a/license.txt +++ b/license.txt @@ -28,7 +28,7 @@ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvStartvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv Selected functions from the Adafruit_GFX library (as it was in 2015) have been imported into the TFT_eSPI.cpp file and modified to improve -perfromance, add features and make them compatible with the ESP8266 and +performance, add features and make them compatible with the ESP8266 and ESP32. The fonts from the Adafruit_GFX and Button functions were added later. @@ -68,7 +68,7 @@ POSSIBILITY OF SUCH DAMAGE. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^End^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Due to the evolution of the TFT_eSPI library the orignal code may no longer +Due to the evolution of the TFT_eSPI library the original code may no longer be recognisable, however in most cases the function names can be used as a reference point since the aim is to retain a level of compatibility with the popular Adafruit_GFX graphics functions.