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 zcmZQzWME+6XJ}wxW+?Cv);D5+fIkcj41NL5J|V$w`4tQdj6Mtu3}(r>i3L!83j+h= zujH~41qh$Hfq{YHTv|bTE`-k#z`($GBR!`w4Z>&BU|_Ja%ScU3ff(>S#VEzVz{th` zVuIxvnHWH7z-&0d$iTqN!pg?Z!O6wV!^_7nASfg(A}S^>At@y-BP$mpAFIHisHCi- zs-~`?sim!>tEX>ZXk=_+YG!U>X=QC=%fJv}XYb(Xo{RFz_<)G4L}8FbFaTF$gn=Fo-gUF^DrrFi0{;F-SAWFvv2`AGiWeqGH5YqGw3krGUzerGZ-)!G8i!!Gng=#GMF)#GgvTKGFUNK zGuSZLf_)RgV8>w3;K1O>;Kbm};KJa_;Ktz2;KAU@;Kkt0;KSg{;K$(45Wo<~5X2D7 z5W*135XKPB5XHd25X}GyUN!~}20jLP23-cLe_sE5{$FHZV4lOgiM5w?8RI&}T*eTF z!wiQQ4lwLz*vGJ!VGqM@hFuIh8FnyiW7x>BfngoP3WjA2OBt3hEMS<s!{#tj(-NtWhlMSoE3SF`s8X!n}xi9`g*QElkUqY8kgOIx{*k zax>g!xXEyx;VQ#Lh6@bm8O|`AWH`ofnBgG90kEw*7`8KPW7xv532N&ahE)tp80Ij{ zVkl)$V31~zWZ+`pU|?fla1av}5f%~@;OFDz;pXDxU}s}xVP;}v(AmV$z~HRVmDHu| zloqC|vq_7A!8xR21A}&ixMd`mvH?>YxU?cSFggpM3F+!+>*{E4VA9gnfe?&I8yJ+G zHZn4+Iw`m*WF)3+U{-Zg$WYk8;;fvgkh0Oip(`XpQBhHGBZHz$WIzOn!J?|*ma&0d z)irSgLtuoW^afUEi%99jl*r%+WqswJ()!9TBR4Q=>*{R4VR~W;PL-^xPWCS`G~53K z3keE|Tk7f@)YaLjq}$#LNVj1See`SA`AC%1)aYnOL36oRyusloK-( zHn1o=No?Q-d6+@jNy0UAgF~VM*xef(r8hW$y|BSedV@z~=x zHt=Xk=;|oAX1M6;>|zjPVEuo`q`ZMK(a9}CVS__R#0H0i zNRWNHIvbgoodunR#4QaObanJ}b&l%l*l!Tg(q&)*m2Iq`!jhSRok7V#T9b*5fsI9z zfs=uqi6xPlm4P*posof!iIbmM+mJ!qKv7UpP+d?_kdc9fLE(=BE5mOF1(pN99T=Dx zru~1;c$3wSftx{+LE1rBgqML?n2nQ@naPolhmn<08f=2SmVv5~n5YPwvXYvqi5at^ zC_5jcD6=xNiJH1Pqq&*CpSgKJKo+C3A{U2hzcvTA+?N1T^S}TLGk=ySehE%~?mq#{ z8@Ty7B^a0(!k817^jUiuKyH_F5Mg6t;bGv3V`OGvjb~+tJKEk}PtSl+P#FxF^ye&| z#oGJ#GLtR{gUVkg1_stGU{mB7LL5A0Ihmxnnbg=Ax!5Eb#YLEy8C7}tx!IUl#5jdG zIe1ujNiec9Dnfi~Z*Q+{z^KTqD9o%VY|O04t_a4;!ivnw z!iwgKV9dPl-;)GJ8Nq3clGB$>Wt1}i_cVb~-}>LxDJ!P^dp>p9#D5R08Rg^towZ~% zoWUIO?>TeOzX-+^{~{*-dpZ4I6yq`wp3W#fk#PkB1JnP<{~MVF*u)vw7<3q19Bh>t znD`Yrbr`uAwHWz@*?B}c7#ZalWf&C&a%l$jD_ zAxirj4UaRPL+A3Obh;L^5=!Q)G`}< z7ULDLHIuTHDO<3wc-B?T_WHw2)-g6M9y!w$CNVIuGWh&|&X&TujDed$oI#mEpTU_S z#KFtafk{-JnTL^sQ;wgDfkR$hk%7(Jj7eHnmy?m5g~gf2lGB+{PJ%N^OOAon$-$O6 zQbL81&BDx>If6}ykBRB{|6&b-nqt9rn=yRl}=h*S?o+InxT%6T3I^X1ZJU}Lre z1&p?~mH{ND#YMpp4ha->WhFL#Ms;&VBQtwOadu@;B4AKfVgN-xI0+alvMZZ|LXW9B zFy7UzVeXnKy~|iw+C{|pRCwzdZwiYED)3fMU}pOF@Y(Hu&r&j^WzJ4!^lIf{5*A?T z4RJS1HxgdjF=6@I31Yfp9Nda*AJ}*$*q#fhsy+Poj8Win^HERDaD$E=-$Yq>K;aO@ zoX_xubq)hFgNOqU69Z2?DEl+ALb9Kpfv}*m;FnpfbHJIMk)f4=f$1P?F9Sb=go7X# zCnFCh6O&LFD=z~pqaeuF_VxyhilS^xq9Q`#qKaxvrY1s+8yE!-oH%mi-}^~3XR-G3 z)%{z1@$tWT4SdrXbB`Wm%wb?;kYr$B3W1r*&dI>T&%q=V#>|aus+f?d2ot-ashNp(uMWV+4tjkTA-k-^MC z&)Qs9Q%OpUhl5j?k%yPjjM34~+|)={O-WHfR!l^Yhntm=LD*hYkDHaz8RB;>14d&| z*$Pg=ptypjS$1_jMrCk}$}=jPgOe{bPQh$_MssmFMrdR+rY+KEWaj7N6yfL(ILI#| zEO;QGpF@O`kDr-Qe~|$rBR?Of5XVHneFCDQ{QLbTatLwq@iQ^9_FiNaWD*eMWS=la zmsv_kK%7N)>O>9>L4GD7*2`DexY_vyIXEUx)n$?76O?Awoic%)Q&51NhmC=ep^$-r zRi5Q212==ZgFGiAJEI~aGZz~p3j??+5n^EEVvJ#7<6>px1;>{jsCZHoRTNYfR5Vo- zWR_)O`ulIy-+xR@EJy!-T=w?^lfW`2eg;N{`u}N+2N^Cia4|?Z2=j9?Ft9PQaIi2r zaWXKnGV*|fOxr+Q7#s%bri#qHE>W-$u!NN{lqu<`wy%)rRt#OT8GlBt-1 zmqFS=gn^NrQJj-WgpHBKj*)|bm60E65TiJ|B0Qs*Gu`HE|L4am%C9TX&A5S2oR6uP z^DQTz631IEUQl;n+yASK2N@nRaD&n?Gbg7r8v_HxgN6+DdIsi5CC@g0GqV7Hb928K zb3cDG^8iqn?EkX=PE1j(y$r$(QVv3#!aPC(yd2D|w!)l@Y>c7;po-gG&p;d+|Dww9 zGRwG>iHVb4@(bg0W@au9$xm@?yj1AC8@-c(5 zgAB8@Bo6}zmxK_wiQvW0z`*LkF2=ydBn$I9xabrW1SdRGVL=ga`5H@|7q)Qy`h|>Z{sq1L_k>aQ4dc2uj57b8 zfI?^@0|VoI)_n|u43Z9lj2xWojGPQizKnb#tc=27Ywh(6gc$`v)sm>9DJWG7ny4vH zWK?J8kofmTf`eUNpCySg=2st&5W6HFpCr2w5A$6maRx>PAqEEKcdW}8Q>vTkyyCKM&QAC9weC&ki3M)nhdHuN4^>m>;q(V^9IPjf;UvNtBb3QAv!2hmk{6Py`(Q zB8*Hz9=wtatTK#TjK1vhtZYnbpp2odZ6FMB38<2WIEIf=SrJrlo0_PpgVVXP65EG1 zuMN3t?`djh=QWobEv)<39l^+TW9x*jfPlh?m~s}$o6(V{=eXqR7#`~CcrE3~Wq-jNl4UT9Ai}kyS`cgh`0eTb_-Tl~EbwG;M7IO-5)? zDJu$_ny7(lcxc6@$Zl-JAS%MZ*!r(Mf|38g?iGvvy|mz9mtZ`O+z zf0qu=T{LATlPC)ZKf5H~XCW3|f!qInJo)hN6)4=2{y%34W9?;7XE1WmU=tDNWMF4x z5ftEK7FOp`Q&AKYVC7`yRA=-O;bdX;V3uNFW6}h<%#gueOWS}EoR=Ymw6c;i8@o9u zhQayF#LOI?zZDrL8|ts@YFN0WUzm%FqyFsEe@`A<`*+XEoH34>nVVaX(TVA?cdJ9k z(lwo(^B5Vo$$mR<|KDRq$%{d2LYTyux!E~|xWKinEdvAdRn}exAqG_kSyo0yAr2lE z4n`IsP9Z^l9#$3xb{h^hMpi}i=27RZXO{f?kCmB= zTj1YQmeU;E3LI~LUFYXx>m5qT>iqTI@R*=ny*@S_O#RBXIP`ec=l+;aODPB}WOq?CkQ~@{n z*g-+Z#?Hs6tfa?eYGTHuTRL%dL;b4$QhSdiFRvsI`(GLwS7y(?sHR?+oRDZi;43vaDd{Rhgn#iQ%zA&fRB}#lhZ?rnSoWE(Th_=05#5$JYZ~O zZe$Mj1gKC2<#0qeGs_$4tZA=bw6srzi;J`I+!IE*2iF)Ctjzzd;b7z9;s3XfX_Zf# zcm3jZogH&QasGAxeMZ@TFE0kI3I6+?Pk^0Mkc$@-58zsfSs&c?mT?df;sN*P_!&hR zotZhgSQ#b3;f&Uy69$JVq;tn;4XLVNRhIulaE)bd?#BeKyZkN8{27=aH8Zn50~doB zgQ9~3KO-}!ZWiPgVP|4wV0GeSW@KfQ0NbUfr)^*k>N$afLLJ$zO^iJE6Tnq=C)Co* z|0cs~Zl)}d#bE0gm>3Efoml0WTfx0XMF%N11r{y_4kZRA1~w)ZRz`M4Q$}VkRz_Hd zmr+#F6ogsje+RLw`yIsieCblgc`HCYEr$I6&zWUelNh)elo_lX^pzEv7#$USy;qH zSoi&M;uGNILJe#K@_k z&Lku(%)`hb&&Vh!$H~af#KOhM!oVrc$0Ww+D#gOeswvOp!Jxvz#-t4iKy7UUP#p~F za44~{LrOknMO7m)aaM4?R))6*^chVV`x!q~R7{&`nqkD)D=U!|^G|{C_O1)s{|;=~ z{`%fuaS3*IHV%oyW#yf%Y;26?lA4nCUI!-4e<^bB__ZhCMgz+aZhj6)Q0p8tJ}l1K z%b?Am?x3i~$RMo3!@?oN$SWuUZZBx4sR*$Mxv)twurlg`oT?4#O+nh@p!PMm-C)eF zsHCP2>SdUj+cBEkF~U1f%ue+ytYYF~VjSmpGiC6K@Td#6{oQ%$eo{rXyPIL1x6_QN zOov*zm_+zl&gvQXU2pEb>|w0U!KL`kH86=$cxCgm1m`R@MHz6d0~(uQU|_Rm?PcI& zP-oC~P?3~iVicChUpYu|npp;_Tu;P7GeQR|^%7ddU zXMcawHukvERd?IV*xWBWzCpPq%RvrE(!otJPq)a##7(uC?67eDag(wB*!SpsKCbL!Otke#;7X5tgUUYZD0-!ckn2TvZ5f!lc033 z2ygb9neAiakYI^vz&E?xRpzY|6d9_hs3`}671|g(A4{uPk{gL{e#~mc=`S= zf;XKQ7(wCAI+3-P!GyuZ!CHiopO;ggkwI9OhXv$9K`|~C4rwVSUO`z-Ms{WvQ4wKg zJ~mDnMnipFW=}CbK~@(wWd=4TGf*5DGJxt|l)x8+l^{^(5|Q#51OC~A(mqckQvnCN z1bPM#Yvn=90F3WBxD+|w{kabI6}GGZ$qTHrSbG^17%Ui^9n9GnnFKkwK&=@QP7NLz zP5~Z6UJf=EE^?ZLx)stBJ!G6r}^ z6Y5J-b2BqlXvCq5vn=|Xz$3(~&Qs4+&(0yms3^_O%El)B@4gfV`yUxL4k<;qP}Kf9x-l@oSIw~ z5mx3I;KCUk0?;}p0_-~CnBn1I3U-k_c(fRn z)xZuwib+OAMivG^QDsmuXv-+Tps2*gZVIV%)InJf6in=}?8bDEG2kByqZ(uU4@q|R ze@FiP6x9@AoX*L{!wbuaV5cI5Agk}+>*D{y{%s5aMIDbWBP*i~3(HZ)^W5BQoV@Iy zxSYYjzzixwBpK8kpli)1|tV`X+8#KVNp(APy%ORV&v!KU}R$q;$akF6=4)(4CI#* zfecb;gWaWNz$mE42%OgGNgjM0h~`77+<>zr{s}lY!NRS&jkH00h^tN}#@^u@Qr* z34^*33$#jQV^LO80k;f<%*|AoGyebE$LRF`Kcn-%z5f?7iY7NUw=|^ud%a-(zt<@s zb~2+V%kzKd-+%abp3(5bdq%_k(V=<&o-SMZ?`dv$OiXwVqtx=Hj52wl(V$R+wZ4=X zOdWI-6_^Bs89*Il4$zzli#VqUI}?u}rzU{O@(R0Q=wIG7lj8P!GDIGNd*Sah_tq?wu6 z@3qYaW(31Sr%>3Wp|Bz^inCJ21-{m*CX6BH9UCtc>WsFq5C@Ir>Vtawdb&)q;1FkI5fxJ5%sHVP+RnX4F?@ z@{rRKVP!M~g*K=#(l%f;5)(%*cT5;S<4llZ2W!}GnQ&U_-z-LEMuQ*H?Ck$ezmaoM zXROvzNQ*{`hh^RWerWtl`gaUk-u!0tU}a_0R5Fyaam5xj42%rQ3=C{sti22>40aAC ze2jAJoDz(3!eTru9PEN@;6%>A#mB(Jt;i`Q!6e4$&dSZo3K}RG3>ftpzCtn5<%9!PPpvz+DNm1BDgn%DYW&nEz`r`d&g7#JC5{D02c%i7DJ%3$cA zt}G$|t{J6x*m+nO85O`Kl_#jAl4g`-^iU8MVPjGUB>+%K1sdT-6jbbjA}opuD$r~O znLdRKwStQ*NKMW7@7lj>|E~YL$_A;r*_YkEznrm_`8cff65#&*bJdzPt611!B7$6h zEGy#UKt0C)7g<5A1yKei2MGpV9&S!99#J7aPIeXsb^|URMm9!qXeZHL&wvw>3_-;x zCnLl)B4Xl9CmFAEbF;FFFsl9K{L95?EX2mn!_C;r+$W~M$BUHXXexu2X#@UVYb`bYZO;`^KbmxWK5RfwHe zT7>b_KR$6OZcctSVLqlmOcVKpIQZCkmH0ui$-uzs1J1AN4syH-oDw|DJQCtU94t(1 zLh_7?jJ%8fvO-ZOvS~-IT_8&L6f22P(^A9FNRlH|5jx#{VU5W%A>|z&*F6$+&5r3&B>*}@s@#+;W`5Y(K|QR>N`4`( z)_EEl4j>*W@dc;Rf?U9DUS6upAefM2P;4KFJV3*R#8qa9#F|9 z$-uw}s#$~?3>-8C7|at9D9YHsWX*`< z7}ObT9E>CxMR++S7)2DNxmY-q8F>|zz)oh9l3)_x;S7}%U|^MGXHsTmisE4qfsBS| z8!~{#>V&~vJV<|0)w7uMh(x1IP{U zSOXaJ7@Qp})flA|Ii(c!xEMH888sM11ep{Sg*k=UnONA>RauxBWjTd7nAAkrm?asb zcp1f5LuIuY*q97JsSOf%&;~a&^uQG=B)x-@A2=Pt+{|EVqGHS}32KA?lVoRSXP5jV z!NGoU(YpDQe864ia>on*zB7t{W}d^C#RD3W%<~EHvoQ+^GKPwA3$QXuft-$1 z#waVPn3||)GBPR)D}w`>$(oT}NSsHVr}EFsf8Q9T7)$0a`AC6vZ%_-F$)WD$}uqs z@iQ_qiVHGHGDdQV$+0nNf{SL*w2!DUI2yH4`zp%j#-=7_){Ia&B{s%@r%5~l+%X&+ zlK=imaj;MFaa%HVViF%8x2BO9i`P1E?Ea176@jEcAs(hx%es4KfrSl0;q=dk`5p5d z22}=U2P-}vCMHf!2}UMGaV|y*p=bsJAIb1q>7*{Pr2i@e}5UpKQk_p zmp989W=I&4glyo@qzOyIUQsHp{- zUqBhG5jO{o$HVJANcRRQ_|42f8J&?ynw_1^sr}Gtc6RB1|D@U3^K&kOnNpz9k{w)v z91{He66}K9|H7DDuAluE0!^muLOhIXY8(HC!TAhK4CM?AjFl{Jz_SX94&t25@|?n4 zaw2TZ3PKFbk&?V3tc;-91F(Db43L*D38T0joWH;w{dPwKJtrqUeJ4rKFcZkJFvqdf z>Nz^<={q_7{>RJ)btZ=Z_n%(~2Qe`HkNW4sSj+N;L6kv}LES-4NKud-G+QYz$<8In zC?vzdB*w-R4r;wZW>3MB2jZx1Vbf+bH#Rb3Mq0|Sgq>ZIkwKE3egD4tFrV;1Nhf_h zXD5AqCpP8?m{SC~{#5Qiu&j=i7wS$(_^>Fl`Y}i|7&F*97)diQ@$pH4qgq*?la*6P zQe1?Am0OdOjZvC0oQ0c}RZm5L*+7XYQWMmR1rPIp!%o|PQA|jd5nKYQsm*f%bmaGRZjjYW_#?9V&&U}c)9@bBBnd7xnv9-l>9 zRvlvHWo6-J=Hlo7v!4YiIsEoO3TH-0%(MD2h=bN~unU8VQxR|)MGVvF;T)!eCq!7> z`rx;TO_O;JgEK>zgEu!jlLaFmCo2mJD9>TybOYBMUR+c5HRG9mJfE~7r9 z38M->Q=~edIGY`#C8Gly6FXxhC!;JIGiX7b7AR;DgVn~O;2b1wt_ZKo6~Un_2nugG zMri$xBnHoGkgRo;#kt2LOIx2&^2HUf$E4ZWi<@7{%fL9<1&EvmO~Ou0E(%IcCvT%> zv-Wb`vS?JHrW)j&2M&MctITs4v>9w2jM*7ESy`AB6hZ5q8CldhMLAWtn4~#5L1Rgg ze9|JUYK)4E8gSn;foFL@zK4(0gFTPMk4%_;>^^!D=Apby7HmFZx^fQH@ik4zj%Q$E zaQgq8#ffnTvr#NRboVDGy;2Wnc~FXJBAsk_6ACY8wcHSLBF-m*+rM z`awotofzdFJ!F*s_weE4e~+0Mrfu3ZZR(aSjAySh3jF(U_1eD=jQmVu>n2WGw|?Tp zbqtIQ<^OzGl$qx+s54kN=(Dmg$tbdOfo7IOp)C|;b^$&n30cliE(r!!C4MFqHl`?9 zMntp9UfTdxaDiG-4BCw9=HhaU){Nl6C0Jpr49a}qG`{2`qxioc=bg&A;|I!(}!Jex6ya(`NrhndNYT)^6&cSe9hu72yQ80J#`B zz%4>>tBQ?XN`i?;fK!>3Ns^r@ih)OjHBSXr0_<=MGdIT%?a zIJp>w#2F>om?DKhBl>cnW(y(@DjJE2Lz@O@36HVfGDA6t&(*9SocKVM6*LQ(>91nq zSJL|%2X`5$Kl6)$f%zo!90nBz0|yNSb|wa1PI*QKML8}O4qinba5_^_kdqSRWoPEF z;07)F2CYQ`kHdizm^f%z6nM=$l->k4k&yBo57R0>-)(uITmV`j!#syU3zVuLWrw^XHzzYYBa0fBD3>x9 z6Q?w%IvbNJqXHv4V(pOnrx{n`&RY0j_pt1wp9$^lG*5XbqZWk{zt^?-_ehCf%?tej@rA%vJd_hp^ zV*CG`c?Gy-tn47kAi%}T$;~ApD#*pb%FMuF#lz1CX$*s$Xs>6$C~N{B6cFWPG=-#d zQ1|ub7e;0dPANvFf4ct+8QDN7laVoo@e?BxCkrPZn-D+$pD5;K{QN>}LY%Cup#4#7 z|DUto1ov|E9F+K(7?_2Hxdb@*IC;1jxuhjTxH;LF7#TPXd4(9^F$bDd0L>{t7HfiQ zYffe)M_gx=WR&~F%*eqZ&B*Z2@ZY0c$0|ylm1_drzP99E9E_D?}c{v#g5n%ydE=~>>1_3i} zSw=|n92_vK`3=oMbO#_2%8%+?jtS=5)%baeuy(k?qO%=cX+1dH{{vBmp_pg@Gl8=v_lPTdDhae}H5GVh?fPW% zoOup|AcLucEZ z6yV`xV`ktMmgIyig8^-BVq<3DHJ6lUgscOEloDKw%xp}|W(>wgVvNG#?BeR|=IZ9c zQjEf&b<_;ZLPE^TOpZ)ldd*}DW0JpC>c8Wb)rG<#0YXL9#@BwSlrA?5_?NX!_cvn< zBQp~_qu}emh5y2@F!HA{oBp%WG^#Ie?zT{6>i@UkugJg3Utj*s{P%-Nk{Ps(afrH` z4awc0T^mLWb_{-?o}|5vfeMS9D3gXdCuqgAr<0kkqJ$7X4>Omr8<#YvqO=gR5D%k| zo4uv6u9k`fKbHd=gCHwo0H|=Z2QPa>>OC-WF{;C(MT${ajY(Mv6dA&>#p|Y^X&*IJ zP$aRjg9TYZD|5xg#KA(MB4Uizb#c*kwJ~vZOxZTadzN1_*ut3LuMzj}()FJzIUCFZ z{^jh@`t8H!Y7mgYB%S9XVZ_KHU?k_8%_Ctf@0|MYvQR-HpB5LJw2!_^4j;35d|iE9 zY(xFOW=0t%uCJf|7XFL7!pI-P{Nvw(zY_mS-!d|SBbS$1^`Eq|URDr`Ad5#3&wd`> zC?7$AIQ_UNUS=j0wRC#{RdAYZgr-?>27Lxg2LoMcCI&`zP9adxsVT@xi1M&8a|rWu z>g(xhDuZ_<-{{pTu@?^8H|6Bf7=wHc?Z~x~0`^F^3tXv&&S0aZ+N#0o3#xvN)ufaq(6|@bO zn?d~lWY)=Sptbbk45|zU40a6Z45bXs4BZTq84fV4a#+aD$ixxAxpMJ>2^}dS?8_LK z)-!5raR{>o=x}o~uy3-KVP?@!`0qUO@dEQc(JY^v(Ucv zE2mGHIHA9%v!lJGxhyj^B{?A~Jj~O>)!xq3P+d(@LY$9-K`qv4leN4I%WexM&bf?9 zvx*s6^Ml!$3m8*#V%gYgeHoc+7}J~@1le{lCRgreWJ|Hw%D~2Yh@V+oOIr(6G(i%W zxjHz_G0HKDgBU96jKXXz;ANU-=4R$*s?1_6povChB^6aRcG$usGZk~lgt)jJqd90? z32K%eqq?vhqd2&TR+nQG=VmlBQxyd-YGa3pszRy~b3O3ZD`7DqkQyOjE=G1~Ml~j8 zF(L3|GBojq6aGq4xiN zc>gR%}8%+M9Dl4CKAHrKj)k^3IYK6|=VH z;5fg-z$T5&#LU!`&Dhk0@m510kGX>b8>9cf?Od&;(z+r^Q{6a)0uui9vN-y>%$VpJ zFT(9PBT2X7+Njw22gpgqi&G=MIKnmI>*}v5OkW&w8)VIm6f%`F)H7={I5CwoY-6(gzl`M_Lmbl#hKVfZ3_>hE42mr47%W-T z8F*OMFeEYmV6bIb!Vt%@h9L^9zMP>RWCrshhJ3bp|1YvmW|+Zd%;3Y;&M*x`vz%m@ z!6w4s!=}$LjVX>{0rR*27g^gGX0UKE%wYFnkY)YG(9U*|!3V7GB7+Z`I>QXMFa~8d zMFti&RfZX?z5g$A@cqBa>h%9UtMvbitcw`*na(m?XR`c%AMA(24E8K(4CTxV7@Szr z7@U~DFgP&kV%aTEWL7l;yL4ZLEOzMM4LojLbuY*B=!R$YXH2*(=L4d*HzYBu^gXO;u z3<3;RU=e2qH3k6&A28|9pvIucz|Y{#pvWKyCS}2-9GF!8e+M+$2qyKxq#>9z2J15U z@4}$SUWaNyYyz3~CH^VA2Up zy8XYypvK?>X8ZpK=?wytVgDyEsDo`$XE6Dn&7jTzvR8wF|Nk8Z4FYczOD0~bUy#H@!FktZcpUq&v;16a8fXN^*3G#&z*uO>$X8*S{7%_ls z1hpI07>vOFH3Fv@BXGWdF@#Fkz4blZyW? zGng;N-gAbVP|L-7!2}8iYQU()- zAh1XXSY_D1Sq!G&_%dVQ|F@RGj6vXkB!d})Aeb%t-;cqJK@LnR{-3~L297~92D|^+ z3}y^YU=kD#X5bJoV*sTPGlsDLCmGBc`2XEuFlP|>-@#zcAP8p5{!e5uXOIJvptvvx zn_~_($D9FVjyc#Ia|Zwa6Bx`Hg1}_h|2qs836*$#7Gw}bPz~Iav@P9jlGlL+QEemGLfyodCHP9F{gExZ*gWdl-3?5*Ycrf^Y z*`V0>U;w$?8=Nn^!TG|QLH7S025$zr|92RCz~Vj(0-!twmi1u>{lA^TpMn2BFM~gW zAefZ>KY_uYK@Lnh{J+EC&)^6qLB959Z~?1#1&errNpCO-a)UpEAD9iw5&jIp3~CJi z3?U5O4E_vZU^4vw9fn{A{{Ny3!3=_666CvJ29WPU82JCoGlYP{A%sB?%m&GZfMr7& zKrRUd`!^Kq-!KOL|C$V8VD(`Pg8wx^wHp|FF)%P7<2?)vptE5ZFEKDM&0}C--oe1Y za)5z>nsKawh0UjY%drX*bNvM*jpGF*pD!PHnA{pEMs8cWME+6oWsDt z<-oweb&Y|6yMlp%M~#7jXA%PgZw>`i108lh7-XI?Fvw;wFvtlpFvztrFvv?VFvve(U{GjbU{LtQz@Qkzz@T`5 zfkDZLfkEj71B3D`1_l*51_qTk3=FDy3=C=_3=C?g7#P$iF)(PTFfeFrVqnnhVPMew z#=xM>!@!_@ih)7LiGe}q69a?p3I+x}9R>!ya|{gn9t;cyYzzzr4Gav1A`A?Mrx+NF zvKSbQo-i;N&tYIN31MI`ox#9hR>Qzx*#rkFIS+`5fro*OL5zWcF@#|i z^kfv7uwv|lvRN2J7!O0)pp%ZSL)mN$ zQj9P*JA)PDN5{PUl8n?Mg|wo~)V!3ON`2>nW^MFtoHZW^iQ4W5{PHVaQ-eWhi1$U`S&qV#s7jWyoVlVaQ>q1dHS|bQu&F0>F9`8HyQF859^&7%~}(8A`zRCNY#Ulz>G+?nq@w1-n6y!Hpr4ArEYW z0)ra^$Q2+Lf!tZd5X6uQ?$G8iBr+7?RjI&Wz+lXv%V5M{!l27w$zaR?60u;g0v}Nn z!cfGJ%8<>Vz~IY}&rk*q1%$omwgxlgGo&$;FqDIB1^EM{0^}l4$Q3i_Feos1GUPEN zgF|N}g93vggC*Eb#A#Ij)fuWmi5>&Tfzv+|*(Wr~Gbl1BlW{5&>^vsoPGX8+h-8Rj zh-Qdkh-GkQNMwj(NMK}QWM<1rEiTqG;7l({O)N<*(lcOikPuMh5@i)+R`lTG6^P^v eRrFx?@X+P8VCdb!Y||>Mt // 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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 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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 AeE // 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.