/* TFT module * * Author: LoBo (loboris@gmail.com, loboris.github) * * Module supporting SPI TFT displays based on ILI9341 & ILI9488 controllers */ #include #include #include #include #include "freertos/FreeRTOS.h" #include "tft.h" #include #include "esp32/rom/tjpgd.h" #define DEG_TO_RAD 0.01745329252 #define deg_to_rad 0.01745329252 + 3.14159265359 #define swap(a, b) { int16_t t = a; a = b; b = t; } #if !defined(max) #define max(A,B) ( (A) > (B) ? (A):(B)) #endif #if !defined(min) #define min(A,B) ( (A) < (B) ? (A):(B)) #endif // Embedded fonts extern uint8_t tft_SmallFont[]; extern uint8_t tft_DefaultFont[]; extern uint8_t tft_Dejavu18[]; extern uint8_t tft_Dejavu24[]; extern uint8_t tft_Ubuntu16[]; extern uint8_t tft_Comic24[]; extern uint8_t tft_minya24[]; extern uint8_t tft_tooney32[]; extern uint8_t tft_def_small[]; // ==== Color definitions constants ============== const color_t TFT_BLACK = { 0, 0, 0 }; const color_t TFT_NAVY = { 0, 0, 128 }; const color_t TFT_DARKGREEN = { 0, 128, 0 }; const color_t TFT_DARKCYAN = { 0, 128, 128 }; const color_t TFT_MAROON = { 128, 0, 0 }; const color_t TFT_PURPLE = { 128, 0, 128 }; const color_t TFT_OLIVE = { 128, 128, 0 }; const color_t TFT_LIGHTGREY = { 192, 192, 192 }; const color_t TFT_DARKGREY = { 128, 128, 128 }; const color_t TFT_BLUE = { 0, 0, 255 }; const color_t TFT_GREEN = { 0, 255, 0 }; const color_t TFT_CYAN = { 0, 255, 255 }; const color_t TFT_RED = { 252, 0, 0 }; const color_t TFT_MAGENTA = { 252, 0, 255 }; const color_t TFT_YELLOW = { 252, 252, 0 }; const color_t TFT_WHITE = { 252, 252, 252 }; const color_t TFT_ORANGE = { 252, 164, 0 }; const color_t TFT_GREENYELLOW = { 172, 252, 44 }; const color_t TFT_PINK = { 252, 192, 202 }; // =============================================== // ============================================================== // ==== Set default values of global variables ================== uint8_t tft_orientation = LANDSCAPE;// screen tft_orientation uint16_t tft_font_rotate = 0; // font rotation uint8_t tft_font_transparent = 0; uint8_t tft_font_forceFixed = 0; uint8_t tft_text_wrap = 0; // character wrapping to new line color_t tft_fg = { 0, 255, 0}; color_t tft_bg = { 0, 0, 0}; uint8_t tft_image_debug = 0; float tft_angleOffset = DEFAULT_ANGLE_OFFSET; int tft_x = 0; int tft_y = 0; uint32_t tft_tp_calx = 7472920; uint32_t tft_tp_caly = 122224794; dispWin_t tft_dispWin = { .x1 = TFT_STATIC_WIDTH_OFFSET, .y1 = TFT_STATIC_HEIGHT_OFFSET, .x2 = DEFAULT_TFT_DISPLAY_WIDTH + TFT_STATIC_WIDTH_OFFSET, .y2 = DEFAULT_TFT_DISPLAY_HEIGHT + TFT_STATIC_HEIGHT_OFFSET, }; Font tft_cfont = { .font = tft_DefaultFont, .x_size = 0, .y_size = 0x0B, .offset = 0, .numchars = 95, .bitmap = 1, }; uint8_t tft_font_buffered_char = 1; uint8_t tft_font_line_space = 0; // ============================================================== typedef struct { uint8_t charCode; int adjYOffset; int width; int height; int xOffset; int xDelta; uint16_t dataPtr; } propFont; static dispWin_t dispWinTemp; static uint8_t *userfont = NULL; static int TFT_OFFSET = 0; static propFont fontChar; static float _arcAngleMax = DEFAULT_ARC_ANGLE_MAX; // ========================================================================= // ** All drawings are clipped to 'tft_dispWin' ** // ** All x,y coordinates in public functions are relative to clip window ** // =========== : Public functions // ----------- : Local functions // ========================================================================= // Compare two colors; return 0 if equal //============================================ int TFT_compare_colors(color_t c1, color_t c2) { if ((c1.r & 0xFC) != (c2.r & 0xFC)) return 1; if ((c1.g & 0xFC) != (c2.g & 0xFC)) return 1; if ((c1.b & 0xFC) != (c2.b & 0xFC)) return 1; return 0; } // draw color pixel on screen //------------------------------------------------------------------------ static void _drawPixel(int16_t x, int16_t y, color_t color, uint8_t sel) { if ((x < tft_dispWin.x1) || (y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return; drawPixel(x, y, color, sel); } //==================================================================== void TFT_drawPixel(int16_t x, int16_t y, color_t color, uint8_t sel) { _drawPixel(x+tft_dispWin.x1, y+tft_dispWin.y1, color, sel); } //=========================================== color_t TFT_readPixel(int16_t x, int16_t y) { if ((x < tft_dispWin.x1) || (y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return TFT_BLACK; return readPixel(x, y); } //-------------------------------------------------------------------------- static void _drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) { // clipping if ((x < tft_dispWin.x1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return; if (y < tft_dispWin.y1) { h -= (tft_dispWin.y1 - y); y = tft_dispWin.y1; } if (h < 0) h = 0; if ((y + h) > (tft_dispWin.y2+1)) h = tft_dispWin.y2 - y + 1; if (h == 0) h = 1; TFT_pushColorRep(x, y, x, y+h-1, color, (uint32_t)h); } //-------------------------------------------------------------------------- static void _drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) { // clipping if ((y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return; if (x < tft_dispWin.x1) { w -= (tft_dispWin.x1 - x); x = tft_dispWin.x1; } if (w < 0) w = 0; if ((x + w) > (tft_dispWin.x2+1)) w = tft_dispWin.x2 - x + 1; if (w == 0) w = 1; TFT_pushColorRep(x, y, x+w-1, y, color, (uint32_t)w); } //====================================================================== void TFT_drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) { _drawFastVLine(x+tft_dispWin.x1, y+tft_dispWin.y1, h, color); } //====================================================================== void TFT_drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) { _drawFastHLine(x+tft_dispWin.x1, y+tft_dispWin.y1, w, color); } // Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer this uses // the eficient FastH/V Line draw routine for segments of 2 pixels or more //---------------------------------------------------------------------------------- static void _drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color) { if (x0 == x1) { if (y0 <= y1) _drawFastVLine(x0, y0, y1-y0, color); else _drawFastVLine(x0, y1, y0-y1, color); return; } if (y0 == y1) { if (x0 <= x1) _drawFastHLine(x0, y0, x1-x0, color); else _drawFastHLine(x1, y0, x0-x1, color); return; } int steep = 0; if (abs(y1 - y0) > abs(x1 - x0)) steep = 1; if (steep) { swap(x0, y0); swap(x1, y1); } if (x0 > x1) { swap(x0, x1); swap(y0, y1); } int16_t dx = x1 - x0, dy = abs(y1 - y0); int16_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, 1); 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, 1); else _drawFastHLine(xs, y0, dlen, color); dlen = 0; y0 += ystep; xs = x0 + 1; } } if (dlen) _drawFastHLine(xs, y0, dlen, color); } } //============================================================================== void TFT_drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color) { _drawLine(x0+tft_dispWin.x1, y0+tft_dispWin.y1, x1+tft_dispWin.x1, y1+tft_dispWin.y1, color); } // fill a rectangle //-------------------------------------------------------------------------------- static void _fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) { // clipping if ((x >= tft_dispWin.x2) || (y > tft_dispWin.y2)) return; if (x < tft_dispWin.x1) { w -= (tft_dispWin.x1 - x); x = tft_dispWin.x1; } if (y < tft_dispWin.y1) { h -= (tft_dispWin.y1 - y); y = tft_dispWin.y1; } if (w < 0) w = 0; if (h < 0) h = 0; if ((x + w) > (tft_dispWin.x2+1)) w = tft_dispWin.x2 - x + 1; if ((y + h) > (tft_dispWin.y2+1)) h = tft_dispWin.y2 - y + 1; if (w == 0) w = 1; if (h == 0) h = 1; TFT_pushColorRep(x, y, x+w-1, y+h-1, color, (uint32_t)(h*w)); } //============================================================================ void TFT_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) { _fillRect(x+tft_dispWin.x1, y+tft_dispWin.y1, w, h, color); } //================================== void TFT_fillScreen(color_t color) { TFT_pushColorRep(TFT_STATIC_X_OFFSET, TFT_STATIC_Y_OFFSET, tft_width + TFT_STATIC_X_OFFSET -1, tft_height + TFT_STATIC_Y_OFFSET -1, color, (uint32_t)(tft_height*tft_width)); } //================================== void TFT_fillWindow(color_t color) { TFT_pushColorRep(tft_dispWin.x1, tft_dispWin.y1, tft_dispWin.x2, tft_dispWin.y2, color, (uint32_t)((tft_dispWin.x2-tft_dispWin.x1+1) * (tft_dispWin.y2-tft_dispWin.y1+1))); } // ^^^============= Basics drawing functions ================================^^^ // ================ Graphics drawing functions ================================== //----------------------------------------------------------------------------------- static void _drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) { _drawFastHLine(x1,y1,w, color); _drawFastVLine(x1+w-1,y1,h, color); _drawFastHLine(x1,y1+h-1,w, color); _drawFastVLine(x1,y1,h, color); } //=============================================================================== void TFT_drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) { _drawRect(x1+tft_dispWin.x1, y1+tft_dispWin.y1, w, h, color); } //------------------------------------------------------------------------------------------------- static void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, color_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; disp_select(); while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & 0x4) { _drawPixel(x0 + x, y0 + y, color, 0); _drawPixel(x0 + y, y0 + x, color, 0); } if (cornername & 0x2) { _drawPixel(x0 + x, y0 - y, color, 0); _drawPixel(x0 + y, y0 - x, color, 0); } if (cornername & 0x8) { _drawPixel(x0 - y, y0 + x, color, 0); _drawPixel(x0 - x, y0 + y, color, 0); } if (cornername & 0x1) { _drawPixel(x0 - y, y0 - x, color, 0); _drawPixel(x0 - x, y0 - y, color, 0); } } disp_deselect(); } // Used to do circles and roundrects //---------------------------------------------------------------------------------------------------------------- static void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, color_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; int16_t ylm = x0 - r; while (x < y) { if (f >= 0) { if (cornername & 0x1) _drawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, color); if (cornername & 0x2) _drawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, color); ylm = x0 - y; y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if ((x0 - x) > ylm) { if (cornername & 0x1) _drawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, color); if (cornername & 0x2) _drawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, color); } } } // Draw a rounded rectangle //============================================================================================= void TFT_drawRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color) { x += tft_dispWin.x1; y += tft_dispWin.y1; // smarter version _drawFastHLine(x + r, y, w - 2 * r, color); // Top _drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom _drawFastVLine(x, y + r, h - 2 * r, color); // Left _drawFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right // draw four corners drawCircleHelper(x + r, y + r, r, 1, color); drawCircleHelper(x + w - r - 1, y + r, r, 2, color); drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color); drawCircleHelper(x + r, y + h - r - 1, r, 8, color); } // Fill a rounded rectangle //============================================================================================= void TFT_fillRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color) { x += tft_dispWin.x1; y += tft_dispWin.y1; // smarter version _fillRect(x + r, y, w - 2 * r, h, color); // draw four corners fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color); } //----------------------------------------------------------------------------------------------- static void _drawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t length, color_t color) { _drawLine( x, y, x + length * cos((angle + tft_angleOffset) * DEG_TO_RAD), y + length * sin((angle + tft_angleOffset) * DEG_TO_RAD), color); } //--------------------------------------------------------------------------------------------------------------- static void _DrawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t start, uint16_t length, color_t color) { _drawLine( x + start * cos((angle + tft_angleOffset) * DEG_TO_RAD), y + start * sin((angle + tft_angleOffset) * DEG_TO_RAD), x + (start + length) * cos((angle + tft_angleOffset) * DEG_TO_RAD), y + (start + length) * sin((angle + tft_angleOffset) * DEG_TO_RAD), color); } //=========================================================================================================== void TFT_drawLineByAngle(uint16_t x, uint16_t y, uint16_t start, uint16_t len, uint16_t angle, color_t color) { x += tft_dispWin.x1; y += tft_dispWin.y1; if (start == 0) _drawLineByAngle(x, y, angle, len, color); else _DrawLineByAngle(x, y, angle, start, len, color); } // Draw a triangle //-------------------------------------------------------------------------------------------------------------------- static void _drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) { _drawLine(x0, y0, x1, y1, color); _drawLine(x1, y1, x2, y2, color); _drawLine(x2, y2, x0, y0, color); } //================================================================================================================ void TFT_drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) { x0 += tft_dispWin.x1; y0 += tft_dispWin.y1; x1 += tft_dispWin.x1; y1 += tft_dispWin.y1; x2 += tft_dispWin.x1; y2 += tft_dispWin.y1; _drawLine(x0, y0, x1, y1, color); _drawLine(x1, y1, x2, y2, color); _drawLine(x2, y2, x0, y0, color); } // Fill a triangle //-------------------------------------------------------------------------------------------------------------------- static void _fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) { int16_t a, b, y, last; // Sort coordinates by Y order (y2 >= y1 >= y0) if (y0 > y1) { swap(y0, y1); swap(x0, x1); } if (y1 > y2) { swap(y2, y1); swap(x2, x1); } if (y0 > y1) { swap(y0, y1); swap(x0, x1); } if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing a = b = x0; if(x1 < a) a = x1; else if(x1 > b) b = x1; if(x2 < a) a = x2; else if(x2 > b) b = x2; _drawFastHLine(a, y0, b-a+1, color); return; } int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, dx12 = x2 - x1, dy12 = y2 - y1; int32_t sa = 0, sb = 0; // For upper part of triangle, find scanline crossings for segments // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 // is included here (and second loop will be skipped, avoiding a /0 // error there), otherwise scanline y1 is skipped here and handled // in the second loop...which also avoids a /0 error here if y0=y1 // (flat-topped triangle). if(y1 == y2) last = y1; // Include y1 scanline else last = y1-1; // Skip it for(y=y0; y<=last; y++) { a = x0 + sa / dy01; b = x0 + sb / dy02; sa += dx01; sb += dx02; /* longhand: a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if(a > b) swap(a,b); _drawFastHLine(a, y, b-a+1, color); } // For lower part of triangle, find scanline crossings for segments // 0-2 and 1-2. This loop is skipped if y1=y2. sa = dx12 * (y - y1); sb = dx02 * (y - y0); for(; y<=y2; y++) { a = x1 + sa / dy12; b = x0 + sb / dy02; sa += dx12; sb += dx02; /* longhand: a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); */ if(a > b) swap(a,b); _drawFastHLine(a, y, b-a+1, color); } } //================================================================================================================ void TFT_fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color) { _fillTriangle( x0 + tft_dispWin.x1, y0 + tft_dispWin.y1, x1 + tft_dispWin.x1, y1 + tft_dispWin.y1, x2 + tft_dispWin.x1, y2 + tft_dispWin.y1, color); } //==================================================================== void TFT_drawCircle(int16_t x, int16_t y, int radius, color_t color) { x += tft_dispWin.x1; y += tft_dispWin.y1; int f = 1 - radius; int ddF_x = 1; int ddF_y = -2 * radius; int x1 = 0; int y1 = radius; disp_select(); _drawPixel(x, y + radius, color, 0); _drawPixel(x, y - radius, color, 0); _drawPixel(x + radius, y, color, 0); _drawPixel(x - radius, y, color, 0); while(x1 < y1) { if (f >= 0) { y1--; ddF_y += 2; f += ddF_y; } x1++; ddF_x += 2; f += ddF_x; _drawPixel(x + x1, y + y1, color, 0); _drawPixel(x - x1, y + y1, color, 0); _drawPixel(x + x1, y - y1, color, 0); _drawPixel(x - x1, y - y1, color, 0); _drawPixel(x + y1, y + x1, color, 0); _drawPixel(x - y1, y + x1, color, 0); _drawPixel(x + y1, y - x1, color, 0); _drawPixel(x - y1, y - x1, color, 0); } disp_deselect(); } //==================================================================== void TFT_fillCircle(int16_t x, int16_t y, int radius, color_t color) { x += tft_dispWin.x1; y += tft_dispWin.y1; _drawFastVLine(x, y-radius, 2*radius+1, color); fillCircleHelper(x, y, radius, 3, 0, color); } //---------------------------------------------------------------------------------------------------------------- static void _draw_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option) { disp_select(); // upper right if ( option & TFT_ELLIPSE_UPPER_RIGHT ) _drawPixel(x0 + x, y0 - y, color, 0); // upper left if ( option & TFT_ELLIPSE_UPPER_LEFT ) _drawPixel(x0 - x, y0 - y, color, 0); // lower right if ( option & TFT_ELLIPSE_LOWER_RIGHT ) _drawPixel(x0 + x, y0 + y, color, 0); // lower left if ( option & TFT_ELLIPSE_LOWER_LEFT ) _drawPixel(x0 - x, y0 + y, color, 0); disp_deselect(); } //===================================================================================================== void TFT_drawEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option) { x0 += tft_dispWin.x1; y0 += tft_dispWin.y1; uint16_t x, y; int32_t xchg, ychg; int32_t err; int32_t rxrx2; int32_t ryry2; int32_t stopx, stopy; rxrx2 = rx; rxrx2 *= rx; rxrx2 *= 2; ryry2 = ry; ryry2 *= ry; ryry2 *= 2; x = rx; y = 0; xchg = 1; xchg -= rx; xchg -= rx; xchg *= ry; xchg *= ry; ychg = rx; ychg *= rx; err = 0; stopx = ryry2; stopx *= rx; stopy = 0; while( stopx >= stopy ) { _draw_ellipse_section(x, y, x0, y0, color, option); y++; stopy += rxrx2; err += ychg; ychg += rxrx2; if ( 2*err+xchg > 0 ) { x--; stopx -= ryry2; err += xchg; xchg += ryry2; } } x = 0; y = ry; xchg = ry; xchg *= ry; ychg = 1; ychg -= ry; ychg -= ry; ychg *= rx; ychg *= rx; err = 0; stopx = 0; stopy = rxrx2; stopy *= ry; while( stopx <= stopy ) { _draw_ellipse_section(x, y, x0, y0, color, option); x++; stopx += ryry2; err += xchg; xchg += ryry2; if ( 2*err+ychg > 0 ) { y--; stopy -= rxrx2; err += ychg; ychg += rxrx2; } } } //----------------------------------------------------------------------------------------------------------------------- static void _draw_filled_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option) { // upper right if ( option & TFT_ELLIPSE_UPPER_RIGHT ) _drawFastVLine(x0+x, y0-y, y+1, color); // upper left if ( option & TFT_ELLIPSE_UPPER_LEFT ) _drawFastVLine(x0-x, y0-y, y+1, color); // lower right if ( option & TFT_ELLIPSE_LOWER_RIGHT ) _drawFastVLine(x0+x, y0, y+1, color); // lower left if ( option & TFT_ELLIPSE_LOWER_LEFT ) _drawFastVLine(x0-x, y0, y+1, color); } //===================================================================================================== void TFT_fillEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option) { x0 += tft_dispWin.x1; y0 += tft_dispWin.y1; uint16_t x, y; int32_t xchg, ychg; int32_t err; int32_t rxrx2; int32_t ryry2; int32_t stopx, stopy; rxrx2 = rx; rxrx2 *= rx; rxrx2 *= 2; ryry2 = ry; ryry2 *= ry; ryry2 *= 2; x = rx; y = 0; xchg = 1; xchg -= rx; xchg -= rx; xchg *= ry; xchg *= ry; ychg = rx; ychg *= rx; err = 0; stopx = ryry2; stopx *= rx; stopy = 0; while( stopx >= stopy ) { _draw_filled_ellipse_section(x, y, x0, y0, color, option); y++; stopy += rxrx2; err += ychg; ychg += rxrx2; if ( 2*err+xchg > 0 ) { x--; stopx -= ryry2; err += xchg; xchg += ryry2; } } x = 0; y = ry; xchg = ry; xchg *= ry; ychg = 1; ychg -= ry; ychg -= ry; ychg *= rx; ychg *= rx; err = 0; stopx = 0; stopy = rxrx2; stopy *= ry; while( stopx <= stopy ) { _draw_filled_ellipse_section(x, y, x0, y0, color, option); x++; stopx += ryry2; err += xchg; xchg += ryry2; if ( 2*err+ychg > 0 ) { y--; stopy -= rxrx2; err += ychg; ychg += rxrx2; } } } // ==== ARC DRAWING =================================================================== //--------------------------------------------------------------------------------------------------------------------------------- static void _fillArcOffsetted(uint16_t cx, uint16_t cy, uint16_t radius, uint16_t thickness, float start, float end, color_t color) { //float sslope = (float)cos_lookup(start) / (float)sin_lookup(start); //float eslope = (float)cos_lookup(end) / (float)sin_lookup(end); float sslope = (cos(start/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(start/_arcAngleMax * 2 * PI) * _arcAngleMax) ; float eslope = (cos(end/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(end/_arcAngleMax * 2 * PI) * _arcAngleMax); if (end == 360) eslope = -1000000; int ir2 = (radius - thickness) * (radius - thickness); int or2 = radius * radius; disp_select(); for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { int x2 = x * x; int y2 = y * y; if ( (x2 + y2 < or2 && x2 + y2 >= ir2) && ( (y > 0 && start < 180 && x <= y * sslope) || (y < 0 && start > 180 && x >= y * sslope) || (y < 0 && start <= 180) || (y == 0 && start <= 180 && x < 0) || (y == 0 && start == 0 && x > 0) ) && ( (y > 0 && end < 180 && x >= y * eslope) || (y < 0 && end > 180 && x <= y * eslope) || (y > 0 && end >= 180) || (y == 0 && end >= 180 && x < 0) || (y == 0 && start == 0 && x > 0) ) ) _drawPixel(cx+x, cy+y, color, 0); } } disp_deselect(); } //=========================================================================================================================== void TFT_drawArc(uint16_t cx, uint16_t cy, uint16_t r, uint16_t th, float start, float end, color_t color, color_t fillcolor) { cx += tft_dispWin.x1; cy += tft_dispWin.y1; if (th < 1) th = 1; if (th > r) th = r; int f = TFT_compare_colors(fillcolor, color); float astart = fmodf(start, _arcAngleMax); float aend = fmodf(end, _arcAngleMax); astart += tft_angleOffset; aend += tft_angleOffset; if (astart < 0) astart += (float)360; if (aend < 0) aend += (float)360; if (aend == 0) aend = (float)360; if (astart > aend) { _fillArcOffsetted(cx, cy, r, th, astart, _arcAngleMax, fillcolor); _fillArcOffsetted(cx, cy, r, th, 0, aend, fillcolor); if (f) { _fillArcOffsetted(cx, cy, r, 1, astart, _arcAngleMax, color); _fillArcOffsetted(cx, cy, r, 1, 0, aend, color); _fillArcOffsetted(cx, cy, r-th, 1, astart, _arcAngleMax, color); _fillArcOffsetted(cx, cy, r-th, 1, 0, aend, color); } } else { _fillArcOffsetted(cx, cy, r, th, astart, aend, fillcolor); if (f) { _fillArcOffsetted(cx, cy, r, 1, astart, aend, color); _fillArcOffsetted(cx, cy, r-th, 1, astart, aend, color); } } if (f) { _drawLine(cx + (r-th) * cos(astart * DEG_TO_RAD), cy + (r-th) * sin(astart * DEG_TO_RAD), cx + (r-1) * cos(astart * DEG_TO_RAD), cy + (r-1) * sin(astart * DEG_TO_RAD), color); _drawLine(cx + (r-th) * cos(aend * DEG_TO_RAD), cy + (r-th) * sin(aend * DEG_TO_RAD), cx + (r-1) * cos(aend * DEG_TO_RAD), cy + (r-1) * sin(aend * DEG_TO_RAD), color); } } //============================================================================================================= void TFT_drawPolygon(int cx, int cy, int sides, int diameter, color_t color, color_t fill, int rot, uint8_t th) { cx += tft_dispWin.x1; cy += tft_dispWin.y1; int deg = rot - tft_angleOffset; int f = TFT_compare_colors(fill, color); if (sides < MIN_POLIGON_SIDES) sides = MIN_POLIGON_SIDES; // This ensures the minimum side number if (sides > MAX_POLIGON_SIDES) sides = MAX_POLIGON_SIDES; // This ensures the maximum side number int Xpoints[sides], Ypoints[sides]; // Set the arrays based on the number of sides entered int rads = 360 / sides; // This equally spaces the points. for (int idx = 0; idx < sides; idx++) { Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * diameter; Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * diameter; } // Draw the polygon on the screen. if (f) { for(int idx = 0; idx < sides; idx++) { if((idx+1) < sides) _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], fill); else _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], fill); } } if (th) { for (int n=0; n 0) { for (int idx = 0; idx < sides; idx++) { Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * (diameter-n); Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * (diameter-n); } } for(int idx = 0; idx < sides; idx++) { if( (idx+1) < sides) _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], color); // draw the lines else _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], color); // finishes the last line to close up the polygon. } } } } /* // Similar to the Polygon function. //===================================================================================== void TFT_drawStar(int cx, int cy, int diameter, color_t color, bool fill, float factor) { cx += tft_dispWin.x1; cy += tft_dispWin.y1; factor = constrain(factor, 1.0, 4.0); uint8_t sides = 5; uint8_t rads = 360 / sides; int Xpoints_O[sides], Ypoints_O[sides], Xpoints_I[sides], Ypoints_I[sides];//Xpoints_T[5], Ypoints_T[5]; for(int idx = 0; idx < sides; idx++) { // makes the outer points Xpoints_O[idx] = cx + sin((float)(idx*rads + 72) * deg_to_rad) * diameter; Ypoints_O[idx] = cy + cos((float)(idx*rads + 72) * deg_to_rad) * diameter; // makes the inner points Xpoints_I[idx] = cx + sin((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor); // 36 is half of 72, and this will allow the inner and outer points to line up like a triangle. Ypoints_I[idx] = cy + cos((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor); } for(int idx = 0; idx < sides; idx++) { if((idx+1) < sides) { if(fill) {// this part below should be self explanatory. It fills in the star. _fillTriangle(cx,cy,Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color); _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color); } else { _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color); _drawLine(Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color); } } else { if(fill) { _fillTriangle(cx,cy,Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color); _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color); } else { _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color); _drawLine(Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color); } } } } */ // ================ Font and string functions ================================== //-------------------------------------------------------- static int load_file_font(const char * fontfile, int info) { int err = 0; char err_msg[256] = {'\0'}; if (userfont != NULL) { free(userfont); userfont = NULL; } struct stat sb; // Open the file FILE *fhndl = fopen(fontfile, "r"); if (!fhndl) { sprintf(err_msg, "Error opening font file '%s'", fontfile); err = 1; goto exit; } // Get file size if (stat(fontfile, &sb) != 0) { sprintf(err_msg, "Error getting font file size"); err = 2; goto exit; } int fsize = sb.st_size; if (fsize < 30) { sprintf(err_msg, "Error getting font file size"); err = 3; goto exit; } userfont = malloc(fsize+4); if (userfont == NULL) { sprintf(err_msg, "Font memory allocation error"); fclose(fhndl); err = 4; goto exit; } int read = fread(userfont, 1, fsize, fhndl); fclose(fhndl); if (read != fsize) { sprintf(err_msg, "Font read error"); err = 5; goto exit; } userfont[read] = 0; if (strstr((char *)(userfont+read-8), "RPH_font") == NULL) { sprintf(err_msg, "Font ID not found"); err = 6; goto exit; } // Check size int size = 0; int numchar = 0; int width = userfont[0]; int height = userfont[1]; uint8_t first = 255; uint8_t last = 0; //int offst = 0; int pminwidth = 255; int pmaxwidth = 0; if (width != 0) { // Fixed font numchar = userfont[3]; first = userfont[2]; last = first + numchar - 1; size = ((width * height * numchar) / 8) + 4; } else { // Proportional font size = 4; // point at first char data uint8_t charCode; int charwidth; do { charCode = userfont[size]; charwidth = userfont[size+2]; if (charCode != 0xFF) { numchar++; if (charwidth != 0) size += ((((charwidth * userfont[size+3])-1) / 8) + 7); else size += 6; if (info) { if (charwidth > pmaxwidth) pmaxwidth = charwidth; if (charwidth < pminwidth) pminwidth = charwidth; if (charCode < first) first = charCode; if (charCode > last) last = charCode; } } else size++; } while ((size < (read-8)) && (charCode != 0xFF)); } if (size != (read-8)) { sprintf(err_msg, "Font size error: found %d expected %d)", size, (read-8)); err = 7; goto exit; } if (info) { if (width != 0) { printf("Fixed width font:\r\n size: %d width: %d height: %d characters: %d (%d~%d)\n", size, width, height, numchar, first, last); } else { printf("Proportional font:\r\n size: %d width: %d~%d height: %d characters: %d (%d~%d)\n", size, pminwidth, pmaxwidth, height, numchar, first, last); } } exit: if (err) { if (userfont) { free(userfont); userfont = NULL; } if (info) printf("Error: %d [%s]\r\n", err, err_msg); } return err; } //------------------------------------------------ int compile_font_file(char *fontfile, uint8_t dbg) { int err = 0; char err_msg[128] = {'\0'}; char outfile[128] = {'\0'}; size_t len; struct stat sb; FILE *ffd = NULL; FILE *ffd_out = NULL; char *sourcebuf = NULL; len = strlen(fontfile); // check here that filename end with ".c". if ((len < 3) || (len > 125) || (strcmp(fontfile + len - 2, ".c") != 0)) { sprintf(err_msg, "not a .c file"); err = 1; goto exit; } sprintf(outfile, "%s", fontfile); sprintf(outfile+strlen(outfile)-1, "fon"); // Open the source file if (stat(fontfile, &sb) != 0) { sprintf(err_msg, "Error opening source file '%s'", fontfile); err = 2; goto exit; } // Open the file ffd = fopen(fontfile, "rb"); if (!ffd) { sprintf(err_msg, "Error opening source file '%s'", fontfile); err = 3; goto exit; } // Open the font file ffd_out= fopen(outfile, "wb"); if (!ffd_out) { sprintf(err_msg, "error opening destination file"); err = 4; goto exit; } // Get file size int fsize = sb.st_size; if (fsize <= 0) { sprintf(err_msg, "source file size error"); err = 5; goto exit; } sourcebuf = malloc(fsize+4); if (sourcebuf == NULL) { sprintf(err_msg, "memory allocation error"); err = 6; goto exit; } char *fbuf = sourcebuf; int rdsize = fread(fbuf, 1, fsize, ffd); fclose(ffd); ffd = NULL; if (rdsize != fsize) { sprintf(err_msg, "error reading from source file"); err = 7; goto exit; } *(fbuf+rdsize) = '\0'; fbuf = strchr(fbuf, '{'); // beginning of font data char *fend = strstr(fbuf, "};"); // end of font data if ((fbuf == NULL) || (fend == NULL) || ((fend-fbuf) < 22)) { sprintf(err_msg, "wrong source file format"); err = 8; goto exit; } fbuf++; *fend = '\0'; char hexstr[5] = {'\0'}; int lastline = 0; fbuf = strstr(fbuf, "0x"); int size = 0; char *nextline; char *numptr; int bptr = 0; while ((fbuf != NULL) && (fbuf < fend) && (lastline == 0)) { nextline = strchr(fbuf, '\n'); // beginning of the next line if (nextline == NULL) { nextline = fend-1; lastline++; } else nextline++; while (fbuf < nextline) { numptr = strstr(fbuf, "0x"); if ((numptr == NULL) || ((fbuf+4) > nextline)) numptr = strstr(fbuf, "0X"); if ((numptr != NULL) && ((numptr+4) <= nextline)) { fbuf = numptr; if (bptr >= 128) { // buffer full, write to file if (fwrite(outfile, 1, 128, ffd_out) != 128) goto error; bptr = 0; size += 128; } memcpy(hexstr, fbuf, 4); hexstr[4] = 0; outfile[bptr++] = (uint8_t)strtol(hexstr, NULL, 0); fbuf += 4; } else fbuf = nextline; } fbuf = nextline; } if (bptr > 0) { size += bptr; if (fwrite(outfile, 1, bptr, ffd_out) != bptr) goto error; } // write font ID sprintf(outfile, "RPH_font"); if (fwrite(outfile, 1, 8, ffd_out) != 8) goto error; fclose(ffd_out); ffd_out = NULL; // === Test compiled font === sprintf(outfile, "%s", fontfile); sprintf(outfile+strlen(outfile)-1, "fon"); uint8_t *uf = userfont; // save userfont pointer userfont = NULL; if (load_file_font(outfile, 1) != 0) { sprintf(err_msg, "Error compiling file!"); err = 10; } else { free(userfont); sprintf(err_msg, "File compiled successfully."); } userfont = uf; // restore userfont goto exit; error: sprintf(err_msg, "error writing to destination file"); err = 9; exit: if (sourcebuf) free(sourcebuf); if (ffd) fclose(ffd); if (ffd_out) fclose(ffd_out); if (dbg) printf("%s\r\n", err_msg); return err; } // ----------------------------------------------------------------------------------------- // Individual Proportional Font Character Format: // ----------------------------------------------------------------------------------------- // Character Code // yOffset (start Y of visible pixels) // Width (width of the visible pixels) // Height (height of the visible pixels) // xOffset (start X of visible pixels) // xDelta (the distance to move the cursor. Effective width of the character.) // Data[n] // ----------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- // Character drawing rectangle is (0, 0) (xDelta-1, tft_cfont.y_size-1) // Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1) //--------------------------------------------------------------------------------------------- //---------------------------------- void getFontCharacters(uint8_t *buf) { if (tft_cfont.bitmap == 2) { //For 7 segment font only characters 0,1,2,3,4,5,6,7,8,9, . , - , : , / are available. for (uint8_t n=0; n < 11; n++) { buf[n] = n + 0x30; } buf[11] = '.'; buf[12] = '-'; buf[13] = '/'; buf[14] = '\0'; return; } if (tft_cfont.x_size > 0) { for (uint8_t n=0; n < tft_cfont.numchars; n++) { buf[n] = tft_cfont.offset + n; } buf[tft_cfont.numchars] = '\0'; return; } uint16_t tempPtr = 4; // point at first char data uint8_t cc, cw, ch, n; n = 0; cc = tft_cfont.font[tempPtr++]; while (cc != 0xFF) { tft_cfont.numchars++; tempPtr++; cw = tft_cfont.font[tempPtr++]; ch = tft_cfont.font[tempPtr++]; tempPtr++; tempPtr++; if (cw != 0) { // packed bits tempPtr += (((cw * ch)-1) / 8) + 1; } buf[n++] = cc; cc = tft_cfont.font[tempPtr++]; } buf[n] = '\0'; } // Set max width & height of the proportional font //----------------------------- static void getMaxWidthHeight() { uint16_t tempPtr = 4; // point at first char data uint8_t cc, cw, ch, cd, cy; tft_cfont.numchars = 0; tft_cfont.max_x_size = 0; cc = tft_cfont.font[tempPtr++]; while (cc != 0xFF) { tft_cfont.numchars++; cy = tft_cfont.font[tempPtr++]; cw = tft_cfont.font[tempPtr++]; ch = tft_cfont.font[tempPtr++]; tempPtr++; cd = tft_cfont.font[tempPtr++]; cy += ch; if (cw > tft_cfont.max_x_size) tft_cfont.max_x_size = cw; if (cd > tft_cfont.max_x_size) tft_cfont.max_x_size = cd; if (ch > tft_cfont.y_size) tft_cfont.y_size = ch; if (cy > tft_cfont.y_size) tft_cfont.y_size = cy; if (cw != 0) { // packed bits tempPtr += (((cw * ch)-1) / 8) + 1; } cc = tft_cfont.font[tempPtr++]; } tft_cfont.size = tempPtr; } // Return the Glyph data for an individual character in the proportional font //------------------------------------ static uint8_t getCharPtr(uint8_t c) { uint16_t tempPtr = 4; // point at first char data do { fontChar.charCode = tft_cfont.font[tempPtr++]; if (fontChar.charCode == 0xFF) return 0; fontChar.adjYOffset = tft_cfont.font[tempPtr++]; fontChar.width = tft_cfont.font[tempPtr++]; fontChar.height = tft_cfont.font[tempPtr++]; fontChar.xOffset = tft_cfont.font[tempPtr++]; fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset); fontChar.xDelta = tft_cfont.font[tempPtr++]; if (c != fontChar.charCode && fontChar.charCode != 0xFF) { if (fontChar.width != 0) { // packed bits tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1; } } } while ((c != fontChar.charCode) && (fontChar.charCode != 0xFF)); fontChar.dataPtr = tempPtr; if (c == fontChar.charCode) { if (tft_font_forceFixed > 0) { // fix width & offset for forced fixed width fontChar.xDelta = tft_cfont.max_x_size; fontChar.xOffset = (fontChar.xDelta - fontChar.width) / 2; } } else return 0; return 1; } /* //----------------------- static void _testFont() { if (tft_cfont.x_size) { printf("FONT TEST: fixed font\r\n"); return; } uint16_t tempPtr = 4; // point at first char data uint8_t c = 0x20; for (c=0x20; c <0xFF; c++) { fontChar.charCode = tft_cfont.font[tempPtr++]; if (fontChar.charCode == 0xFF) break; if (fontChar.charCode != c) { printf("FONT TEST: last sequential char: %d, expected %d\r\n", fontChar.charCode, c); break; } c = fontChar.charCode; fontChar.adjYOffset = tft_cfont.font[tempPtr++]; fontChar.width = tft_cfont.font[tempPtr++]; fontChar.height = tft_cfont.font[tempPtr++]; fontChar.xOffset = tft_cfont.font[tempPtr++]; fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset); fontChar.xDelta = tft_cfont.font[tempPtr++]; if (fontChar.charCode != 0xFF) { if (fontChar.width != 0) { // packed bits tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1; } } } printf("FONT TEST: W=%d H=%d last char: %d [%c]; length: %d\r\n", tft_cfont.max_x_size, tft_cfont.y_size, c, c, tempPtr); } */ //=================================================== void TFT_setFont(uint8_t font, const char *font_file) { tft_cfont.font = NULL; if (font == FONT_7SEG) { tft_cfont.bitmap = 2; tft_cfont.x_size = 24; tft_cfont.y_size = 6; tft_cfont.offset = 0; tft_cfont.color = tft_fg; } else { if (font == USER_FONT) { if (load_file_font(font_file, 0) != 0) tft_cfont.font = tft_DefaultFont; else tft_cfont.font = userfont; } else if (font == DEJAVU18_FONT) tft_cfont.font = tft_Dejavu18; else if (font == DEJAVU24_FONT) tft_cfont.font = tft_Dejavu24; else if (font == UBUNTU16_FONT) tft_cfont.font = tft_Ubuntu16; else if (font == COMIC24_FONT) tft_cfont.font = tft_Comic24; else if (font == MINYA24_FONT) tft_cfont.font = tft_minya24; else if (font == TOONEY32_FONT) tft_cfont.font = tft_tooney32; else if (font == SMALL_FONT) tft_cfont.font = tft_SmallFont; else if (font == DEF_SMALL_FONT) tft_cfont.font = tft_def_small; else tft_cfont.font = tft_DefaultFont; tft_cfont.bitmap = 1; tft_cfont.x_size = tft_cfont.font[0]; tft_cfont.y_size = tft_cfont.font[1]; if (tft_cfont.x_size > 0) { tft_cfont.offset = tft_cfont.font[2]; tft_cfont.numchars = tft_cfont.font[3]; tft_cfont.size = tft_cfont.x_size * tft_cfont.y_size * tft_cfont.numchars; } else { tft_cfont.offset = 4; getMaxWidthHeight(); } //_testFont(); } } // ----------------------------------------------------------------------------------------- // Individual Proportional Font Character Format: // ----------------------------------------------------------------------------------------- // Character Code // yOffset (start Y of visible pixels) // Width (width of the visible pixels) // Height (height of the visible pixels) // xOffset (start X of visible pixels) // xDelta (the distance to move the cursor. Effective width of the character.) // Data[n] // ----------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- // Character drawing rectangle is (0, 0) (xDelta-1, tft_cfont.y_size-1) // Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1) //--------------------------------------------------------------------------------------------- // print non-rotated proportional character // character is already in fontChar //---------------------------------------------- static int printProportionalChar(int x, int y) { uint8_t ch = 0; int i, j, char_width; char_width = ((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta); if ((tft_font_buffered_char) && (!tft_font_transparent)) { int len, bufPos; // === buffer Glyph data for faster sending === len = char_width * tft_cfont.y_size; color_t *color_line = heap_caps_malloc(len*3, MALLOC_CAP_DMA); if (color_line) { // fill with background color for (int n = 0; n < len; n++) { color_line[n] = tft_bg; } // set character pixels to foreground color uint8_t mask = 0x80; for (j=0; j < fontChar.height; j++) { for (i=0; i < fontChar.width; i++) { if (((i + (j*fontChar.width)) % 8) == 0) { mask = 0x80; ch = tft_cfont.font[fontChar.dataPtr++]; } if ((ch & mask) != 0) { // visible pixel bufPos = ((j + fontChar.adjYOffset) * char_width) + (fontChar.xOffset + i); // bufY + bufX color_line[bufPos] = tft_fg; /* bufY = (j + fontChar.adjYOffset) * char_width; bufX = fontChar.xOffset + i; if ((bufX < 0) || (bufX > char_width)) { printf("[%c] X ERR: %d\r\n", fontChar.charCode, bufX); } bufPos = bufY + bufX; if ((bufPos < len) && (bufPos > 0)) color_line[bufPos] = tft_fg; else printf("[%c] ERR: %d > %d W=%d H=%d bufX=%d bufY=%d X=%d Y=%d\r\n", fontChar.charCode, bufPos, len, char_width, tft_cfont.y_size, bufX, bufY, fontChar.xOffset + i, j + fontChar.adjYOffset); */ } mask >>= 1; } } // send to display in one transaction disp_select(); send_data(x, y, x+char_width-1, y+tft_cfont.y_size-1, len, color_line); disp_deselect(); free(color_line); return char_width; } } int cx, cy; if (!tft_font_transparent) _fillRect(x, y, char_width+1, tft_cfont.y_size, tft_bg); // draw Glyph uint8_t mask = 0x80; disp_select(); for (j=0; j < fontChar.height; j++) { for (i=0; i < fontChar.width; i++) { if (((i + (j*fontChar.width)) % 8) == 0) { mask = 0x80; ch = tft_cfont.font[fontChar.dataPtr++]; } if ((ch & mask) !=0) { cx = (uint16_t)(x+fontChar.xOffset+i); cy = (uint16_t)(y+j+fontChar.adjYOffset); _drawPixel(cx, cy, tft_fg, 0); } mask >>= 1; } } disp_deselect(); return char_width; } // non-rotated fixed width character //---------------------------------------------- static void printChar(uint8_t c, int x, int y) { uint8_t i, j, ch, fz, mask; uint16_t k, temp, cx, cy, len; // fz = bytes per char row fz = tft_cfont.x_size/8; if (tft_cfont.x_size % 8) fz++; // get character position in buffer temp = ((c-tft_cfont.offset)*((fz)*tft_cfont.y_size))+4; if ((tft_font_buffered_char) && (!tft_font_transparent)) { // === buffer Glyph data for faster sending === len = tft_cfont.x_size * tft_cfont.y_size; color_t *color_line = heap_caps_malloc(len*3, MALLOC_CAP_DMA); if (color_line) { // fill with background color for (int n = 0; n < len; n++) { color_line[n] = tft_bg; } // set character pixels to foreground color for (j=0; j>= 1; } } temp += (fz); } // send to display in one transaction disp_select(); send_data(x, y, x+tft_cfont.x_size-1, y+tft_cfont.y_size-1, len, color_line); disp_deselect(); free(color_line); return; } } if (!tft_font_transparent) _fillRect(x, y, tft_cfont.x_size, tft_cfont.y_size, tft_bg); disp_select(); for (j=0; j>= 1; } } temp += (fz); } disp_deselect(); } // print rotated proportional character // character is already in fontChar //--------------------------------------------------- static int rotatePropChar(int x, int y, int offset) { uint8_t ch = 0; double radian = tft_font_rotate * DEG_TO_RAD; float cos_radian = cos(radian); float sin_radian = sin(radian); uint8_t mask = 0x80; disp_select(); for (int j=0; j < fontChar.height; j++) { for (int i=0; i < fontChar.width; i++) { if (((i + (j*fontChar.width)) % 8) == 0) { mask = 0x80; ch = tft_cfont.font[fontChar.dataPtr++]; } int newX = (int)(x + (((offset + i) * cos_radian) - ((j+fontChar.adjYOffset)*sin_radian))); int newY = (int)(y + (((j+fontChar.adjYOffset) * cos_radian) + ((offset + i) * sin_radian))); if ((ch & mask) != 0) _drawPixel(newX,newY,tft_fg, 0); else if (!tft_font_transparent) _drawPixel(newX,newY,tft_bg, 0); mask >>= 1; } } disp_deselect(); return fontChar.xDelta+1; } // rotated fixed width character //-------------------------------------------------------- static void rotateChar(uint8_t c, int x, int y, int pos) { uint8_t i,j,ch,fz,mask; uint16_t temp; int newx,newy; double radian = tft_font_rotate*0.0175; float cos_radian = cos(radian); float sin_radian = sin(radian); int zz; if( tft_cfont.x_size < 8 ) fz = tft_cfont.x_size; else fz = tft_cfont.x_size/8; temp=((c-tft_cfont.offset)*((fz)*tft_cfont.y_size))+4; disp_select(); for (j=0; j>= 1; } } temp+=(fz); } disp_deselect(); // calculate x,y for the next char tft_x = (int)(x + ((pos+1) * tft_cfont.x_size * cos_radian)); tft_y = (int)(y + ((pos+1) * tft_cfont.x_size * sin_radian)); } //---------------------- static int _7seg_width() { return (2 * (2 * tft_cfont.y_size + 1)) + tft_cfont.x_size; } //----------------------- static int _7seg_height() { return (3 * (2 * tft_cfont.y_size + 1)) + (2 * tft_cfont.x_size); } // Returns the string width in pixels. // Useful for positions strings on the screen. //=============================== int TFT_getStringWidth(char* str) { int strWidth = 0; if (tft_cfont.bitmap == 2) strWidth = ((_7seg_width()+2) * strlen(str)) - 2; // 7-segment font else if (tft_cfont.x_size != 0) strWidth = strlen(str) * tft_cfont.x_size; // fixed width font else { // calculate the width of the string of proportional characters char* tempStrptr = str; while (*tempStrptr != 0) { if (getCharPtr(*tempStrptr++)) { strWidth += (((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta) + 1); } } strWidth--; } return strWidth; } //=============================================== void TFT_clearStringRect(int x, int y, char *str) { int w = TFT_getStringWidth(str); int h = TFT_getfontheight(); TFT_fillRect(x+tft_dispWin.x1, y+tft_dispWin.y1, w, h, tft_bg); } //============================================================================== /** * bit-encoded bar position of all digits' bcd segments * * 6 * +-----+ * 3 | . | 2 * +--5--+ * 1 | . | 0 * +--.--+ * 4 */ static const uint16_t font_bcd[] = { 0x200, // 0010 0000 0000 // - 0x080, // 0000 1000 0000 // . 0x06C, // 0100 0110 1100 // /, degree 0x05f, // 0000 0101 1111, // 0 0x005, // 0000 0000 0101, // 1 0x076, // 0000 0111 0110, // 2 0x075, // 0000 0111 0101, // 3 0x02d, // 0000 0010 1101, // 4 0x079, // 0000 0111 1001, // 5 0x07b, // 0000 0111 1011, // 6 0x045, // 0000 0100 0101, // 7 0x07f, // 0000 0111 1111, // 8 0x07d, // 0000 0111 1101 // 9 0x900 // 1001 0000 0000 // : }; //----------------------------------------------------------------------------------------------- static void barVert(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) { _fillTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, color); _fillTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, color); _fillRect(x, y+2*w+1, 2*w+1, l, color); if (tft_cfont.offset) { _drawTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, outline); _drawTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, outline); _drawRect(x, y+2*w+1, 2*w+1, l, outline); } } //---------------------------------------------------------------------------------------------- static void barHor(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) { _fillTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, color); _fillTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, color); _fillRect(x+2*w+1, y, l, 2*w+1, color); if (tft_cfont.offset) { _drawTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, outline); _drawTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, outline); _drawRect(x+2*w+1, y, l, 2*w+1, outline); } } //-------------------------------------------------------------------------------------------- static void _draw7seg(int16_t x, int16_t y, int8_t num, int16_t w, int16_t l, color_t color) { /* TODO: clipping */ if (num < 0x2D || num > 0x3A) return; int16_t c = font_bcd[num-0x2D]; int16_t d = 2*w+l+1; // === Clear unused segments === if (!(c & 0x001)) barVert(x+d, y+d, w, l, tft_bg, tft_bg); if (!(c & 0x002)) barVert(x, y+d, w, l, tft_bg, tft_bg); if (!(c & 0x004)) barVert(x+d, y, w, l, tft_bg, tft_bg); if (!(c & 0x008)) barVert(x, y, w, l, tft_bg, tft_bg); if (!(c & 0x010)) barHor(x, y+2*d, w, l, tft_bg, tft_bg); if (!(c & 0x020)) barHor(x, y+d, w, l, tft_bg, tft_bg); if (!(c & 0x040)) barHor(x, y, w, l, tft_bg, tft_bg); if (!(c & 0x080)) { // low point _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_bg); if (tft_cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_bg); } if (!(c & 0x100)) { // down middle point _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_bg); if (tft_cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_bg); } if (!(c & 0x800)) { // up middle point _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_bg); if (tft_cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_bg); } if (!(c & 0x200)) { // middle, minus _fillRect(x+2*w+1, y+d, l, 2*w+1, tft_bg); if (tft_cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, tft_bg); } // === Draw used segments === if (c & 0x001) barVert(x+d, y+d, w, l, color, tft_cfont.color); // down right if (c & 0x002) barVert(x, y+d, w, l, color, tft_cfont.color); // down left if (c & 0x004) barVert(x+d, y, w, l, color, tft_cfont.color); // up right if (c & 0x008) barVert(x, y, w, l, color, tft_cfont.color); // up left if (c & 0x010) barHor(x, y+2*d, w, l, color, tft_cfont.color); // down if (c & 0x020) barHor(x, y+d, w, l, color, tft_cfont.color); // middle if (c & 0x040) barHor(x, y, w, l, color, tft_cfont.color); // up if (c & 0x080) { // low point _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, color); if (tft_cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_cfont.color); } if (c & 0x100) { // down middle point _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, color); if (tft_cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_cfont.color); } if (c & 0x800) { // up middle point _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, color); if (tft_cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_cfont.color); } if (c & 0x200) { // middle, minus _fillRect(x+2*w+1, y+d, l, 2*w+1, color); if (tft_cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, tft_cfont.color); } } //============================================================================== //====================================== void TFT_print(char *st, int x, int y) { int stl, i, tmpw, tmph, fh; uint8_t ch; if (tft_cfont.bitmap == 0) return; // wrong font selected // ** Rotated strings cannot be aligned if ((tft_font_rotate != 0) && ((x <= CENTER) || (y <= CENTER))) return; if ((x < LASTX) || (tft_font_rotate == 0)) TFT_OFFSET = 0; if ((x >= LASTX) && (x < LASTY)) x = tft_x + (x-LASTX); else if (x > CENTER) x += tft_dispWin.x1; if (y >= LASTY) y = tft_y + (y-LASTY); else if (y > CENTER) y += tft_dispWin.y1; // ** Get number of characters in string to print stl = strlen(st); // ** Calculate CENTER, RIGHT or BOTTOM position tmpw = TFT_getStringWidth(st); // string width in pixels fh = tft_cfont.y_size; // font height if ((tft_cfont.x_size != 0) && (tft_cfont.bitmap == 2)) { // 7-segment font fh = (3 * (2 * tft_cfont.y_size + 1)) + (2 * tft_cfont.x_size); // 7-seg character height } if (x == RIGHT) x = tft_dispWin.x2 - tmpw + tft_dispWin.x1; else if (x == CENTER) x = (((tft_dispWin.x2 - tft_dispWin.x1 + 1) - tmpw) / 2) + tft_dispWin.x1; if (y == BOTTOM) y = tft_dispWin.y2 - fh + tft_dispWin.y1; else if (y==CENTER) y = (((tft_dispWin.y2 - tft_dispWin.y1 + 1) - (fh/2)) / 2) + tft_dispWin.y1; if (x < tft_dispWin.x1) x = tft_dispWin.x1; if (y < tft_dispWin.y1) y = tft_dispWin.y1; if ((x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return; tft_x = x; tft_y = y; // ** Adjust y position tmph = tft_cfont.y_size; // font height // for non-proportional fonts, char width is the same for all chars tmpw = tft_cfont.x_size; if (tft_cfont.x_size != 0) { if (tft_cfont.bitmap == 2) { // 7-segment font tmpw = _7seg_width(); // character width tmph = _7seg_height(); // character height } } else TFT_OFFSET = 0; // fixed font; offset not needed if ((tft_y + tmph - 1) > tft_dispWin.y2) return; int offset = TFT_OFFSET; for (i=0; i (tft_dispWin.y2-tmph)) break; tft_x = tft_dispWin.x1; } } else { // ==== other characters ==== if (tft_cfont.x_size == 0) { // for proportional font get character data to 'fontChar' if (getCharPtr(ch)) tmpw = fontChar.xDelta; else continue; } // check if character can be displayed in the current line if ((tft_x+tmpw) > (tft_dispWin.x2)) { if (tft_text_wrap == 0) break; tft_y += tmph + tft_font_line_space; if (tft_y > (tft_dispWin.y2-tmph)) break; tft_x = tft_dispWin.x1; } // Let's print the character if (tft_cfont.x_size == 0) { // == proportional font if (tft_font_rotate == 0) tft_x += printProportionalChar(tft_x, tft_y) + 1; else { // rotated proportional font offset += rotatePropChar(x, y, offset); TFT_OFFSET = offset; } } else { if (tft_cfont.bitmap == 1) { // == fixed font if ((ch < tft_cfont.offset) || ((ch-tft_cfont.offset) > tft_cfont.numchars)) ch = tft_cfont.offset; if (tft_font_rotate == 0) { printChar(ch, tft_x, tft_y); tft_x += tmpw; } else rotateChar(ch, x, y, i); } else if (tft_cfont.bitmap == 2) { // == 7-segment font == _draw7seg(tft_x, tft_y, ch, tft_cfont.y_size, tft_cfont.x_size, tft_fg); tft_x += (tmpw + 2); } } } } } // ================ Service functions ========================================== // Change the screen rotation. // Input: m new rotation value (0 to 3) //================================= void TFT_setRotation(uint8_t rot) { if (rot > 3) { uint8_t madctl = (rot & 0xF8); // for testing, manually set MADCTL register if (disp_select() == ESP_OK) { disp_spi_transfer_cmd_data(TFT_MADCTL, &madctl, 1); disp_deselect(); } } else { tft_orientation = rot; _tft_setRotation(rot); } tft_dispWin.x1 = TFT_STATIC_X_OFFSET; tft_dispWin.y1 = TFT_STATIC_Y_OFFSET; tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1; tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1; TFT_fillScreen(tft_bg); } // Send the command to invert all of the colors. // Input: i 0 to disable inversion; non-zero to enable inversion //========================================== void TFT_invertDisplay(const uint8_t mode) { if (disp_select() == ESP_OK) { if ( mode == INVERT_ON ) disp_spi_transfer_cmd(TFT_INVONN); else disp_spi_transfer_cmd(TFT_INVOFF); disp_deselect(); } } // Select gamma curve // Input: gamma = 0~3 //================================== void TFT_setGammaCurve(uint8_t gm) { uint8_t gamma_curve = (uint8_t)1 << (gm & (uint8_t)0x03); if (disp_select() == ESP_OK) { disp_spi_transfer_cmd_data(TFT_CMD_GAMMASET, &gamma_curve, 1); disp_deselect(); } } //=========================================================== color_t HSBtoRGB(float _hue, float _sat, float _brightness) { float red = 0.0; float green = 0.0; float blue = 0.0; if (_sat == 0.0) { red = _brightness; green = _brightness; blue = _brightness; } else { if (_hue == 360.0) { _hue = 0; } int slice = (int)(_hue / 60.0); float hue_frac = (_hue / 60.0) - slice; float aa = _brightness * (1.0 - _sat); float bb = _brightness * (1.0 - _sat * hue_frac); float cc = _brightness * (1.0 - _sat * (1.0 - hue_frac)); switch(slice) { case 0: red = _brightness; green = cc; blue = aa; break; case 1: red = bb; green = _brightness; blue = aa; break; case 2: red = aa; green = _brightness; blue = cc; break; case 3: red = aa; green = bb; blue = _brightness; break; case 4: red = cc; green = aa; blue = _brightness; break; case 5: red = _brightness; green = aa; blue = bb; break; default: red = 0.0; green = 0.0; blue = 0.0; break; } } color_t color; color.r = ((uint8_t)(red * 255.0)) & 0xFC; color.g = ((uint8_t)(green * 255.0)) & 0xFC; color.b = ((uint8_t)(blue * 255.0)) & 0xFC; return color; } //===================================================================== void TFT_setclipwin(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { tft_dispWin.x1 = x1 + TFT_STATIC_X_OFFSET; tft_dispWin.y1 = y1 + TFT_STATIC_Y_OFFSET; tft_dispWin.x2 = x2 + TFT_STATIC_X_OFFSET; tft_dispWin.y2 = y2 + TFT_STATIC_Y_OFFSET; if (tft_dispWin.x2 >= tft_width + TFT_STATIC_X_OFFSET) tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1; if (tft_dispWin.y2 >= tft_height + TFT_STATIC_Y_OFFSET) tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1; if (tft_dispWin.x1 > tft_dispWin.x2) tft_dispWin.x1 = tft_dispWin.x2; if (tft_dispWin.y1 > tft_dispWin.y2) tft_dispWin.y1 = tft_dispWin.y2; } //===================== void TFT_resetclipwin() { tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1; tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1; tft_dispWin.x1 = TFT_STATIC_X_OFFSET; tft_dispWin.y1 = TFT_STATIC_Y_OFFSET; } //========================================================================== void set_7seg_font_atrib(uint8_t l, uint8_t w, int outline, color_t color) { if (tft_cfont.bitmap != 2) return; if (l < 6) l = 6; if (l > 40) l = 40; if (w < 1) w = 1; if (w > (l/2)) w = l/2; if (w > 12) w = 12; tft_cfont.x_size = l; tft_cfont.y_size = w; tft_cfont.offset = outline; tft_cfont.color = color; } //========================================== int TFT_getfontsize(int *width, int* height) { if (tft_cfont.bitmap == 1) { if (tft_cfont.x_size != 0) *width = tft_cfont.x_size; // fixed width font else *width = tft_cfont.max_x_size; // proportional font *height = tft_cfont.y_size; } else if (tft_cfont.bitmap == 2) { // 7-segment font *width = _7seg_width(); *height = _7seg_height(); } else { *width = 0; *height = 0; return 0; } return 1; } //===================== int TFT_getfontheight() { if (tft_cfont.bitmap == 1) return tft_cfont.y_size; // Bitmap font else if (tft_cfont.bitmap == 2) return _7seg_height(); // 7-segment font return 0; } //==================== void TFT_saveClipWin() { dispWinTemp.x1 = tft_dispWin.x1; dispWinTemp.y1 = tft_dispWin.y1; dispWinTemp.x2 = tft_dispWin.x2; dispWinTemp.y2 = tft_dispWin.y2; } //======================= void TFT_restoreClipWin() { tft_dispWin.x1 = dispWinTemp.x1; tft_dispWin.y1 = dispWinTemp.y1; tft_dispWin.x2 = dispWinTemp.x2; tft_dispWin.y2 = dispWinTemp.y2; } // ================ JPG SUPPORT ================================================ // User defined device identifier typedef struct { FILE *fhndl; // File handler for input function int x; // image top left point X position int y; // image top left point Y position uint8_t *membuff; // memory buffer containing the image uint32_t bufsize; // size of the memory buffer uint32_t bufptr; // memory buffer current position color_t *linbuf[2]; // memory buffer used for display output uint8_t linbuf_idx; } JPGIODEV; // User defined call-back function to input JPEG data from file //--------------------- static UINT tjd_input ( JDEC* jd, // Decompression object BYTE* buff, // Pointer to the read buffer (NULL:skip) UINT nd // Number of bytes to read/skip from input stream ) { int rb = 0; // Device identifier for the session (5th argument of jd_prepare function) JPGIODEV *dev = (JPGIODEV*)jd->device; if (buff) { // Read nd bytes from the input strem rb = fread(buff, 1, nd, dev->fhndl); return rb; // Returns actual number of bytes read } else { // Remove nd bytes from the input stream if (fseek(dev->fhndl, nd, SEEK_CUR) >= 0) return nd; else return 0; } } // User defined call-back function to input JPEG data from memory buffer //------------------------- static UINT tjd_buf_input ( JDEC* jd, // Decompression object BYTE* buff, // Pointer to the read buffer (NULL:skip) UINT nd // Number of bytes to read/skip from input stream ) { // Device identifier for the session (5th argument of jd_prepare function) JPGIODEV *dev = (JPGIODEV*)jd->device; if (!dev->membuff) return 0; if (dev->bufptr >= (dev->bufsize + 2)) return 0; // end of stream if ((dev->bufptr + nd) > (dev->bufsize + 2)) nd = (dev->bufsize + 2) - dev->bufptr; if (buff) { // Read nd bytes from the input strem memcpy(buff, dev->membuff + dev->bufptr, nd); dev->bufptr += nd; return nd; // Returns number of bytes read } else { // Remove nd bytes from the input stream dev->bufptr += nd; return nd; } } // User defined call-back function to output RGB bitmap to display device //---------------------- static UINT tjd_output ( JDEC* jd, // Decompression object of current session void* bitmap, // Bitmap data to be output JRECT* rect // Rectangular region to output ) { // Device identifier for the session (5th argument of jd_prepare function) JPGIODEV *dev = (JPGIODEV*)jd->device; // ** Put the rectangular into the display device ** int x; int y; int dleft, dtop, dright, dbottom; BYTE *src = (BYTE*)bitmap; int left = rect->left + dev->x; int top = rect->top + dev->y; int right = rect->right + dev->x; int bottom = rect->bottom + dev->y; if ((left > tft_dispWin.x2) || (top > tft_dispWin.y2)) return 1; // out of screen area, return if ((right < tft_dispWin.x1) || (bottom < tft_dispWin.y1)) return 1;// out of screen area, return if (left < tft_dispWin.x1) dleft = tft_dispWin.x1; else dleft = left; if (top < tft_dispWin.y1) dtop = tft_dispWin.y1; else dtop = top; if (right > tft_dispWin.x2) dright = tft_dispWin.x2; else dright = right; if (bottom > tft_dispWin.y2) dbottom = tft_dispWin.y2; else dbottom = bottom; if ((dleft > tft_dispWin.x2) || (dtop > tft_dispWin.y2)) return 1; // out of screen area, return if ((dright < tft_dispWin.x1) || (dbottom < tft_dispWin.y1)) return 1; // out of screen area, return uint32_t len = ((dright-dleft+1) * (dbottom-dtop+1)); // calculate length of data if ((len > 0) && (len <= JPG_IMAGE_LINE_BUF_SIZE)) { uint8_t *dest = (uint8_t *)(dev->linbuf[dev->linbuf_idx]); for (y = top; y <= bottom; y++) { for (x = left; x <= right; x++) { // Clip to display area if ((x >= dleft) && (y >= dtop) && (x <= dright) && (y <= dbottom)) { *dest++ = (*src++) & 0xFC; *dest++ = (*src++) & 0xFC; *dest++ = (*src++) & 0xFC; } else src += 3; // skip } } wait_trans_finish(1); send_data(dleft, dtop, dright, dbottom, len, dev->linbuf[dev->linbuf_idx]); dev->linbuf_idx = ((dev->linbuf_idx + 1) & 1); } else { wait_trans_finish(1); printf("Data size error: %d jpg: (%d,%d,%d,%d) disp: (%d,%d,%d,%d)\r\n", len, left,top,right,bottom, dleft,dtop,dright,dbottom); return 0; // stop decompression } return 1; // Continue to decompression } // tft.jpgimage(X, Y, scale, file_name, buf, size] // X & Y can be < 0 ! //================================================================================== void TFT_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size) { JPGIODEV dev; struct stat sb; char *work = NULL; // Pointer to the working buffer (must be 4-byte aligned) UINT sz_work = 3800; // Size of the working buffer (must be power of 2) JDEC jd; // Decompression object (70 bytes) JRESULT rc; dev.linbuf[0] = NULL; dev.linbuf[1] = NULL; dev.linbuf_idx = 0; dev.fhndl = NULL; if (fname == NULL) { // image from buffer dev.membuff = buf; dev.bufsize = size; dev.bufptr = 0; } else { // image from file dev.membuff = NULL; dev.bufsize = 0; dev.bufptr = 0; if (stat(fname, &sb) != 0) { if (tft_image_debug) printf("File error: %ss\r\n", strerror(errno)); goto exit; } dev.fhndl = fopen(fname, "r"); if (!dev.fhndl) { if (tft_image_debug) printf("Error opening file: %s\r\n", strerror(errno)); goto exit; } } if (scale > 3) scale = 3; work = malloc(sz_work); if (work) { if (dev.membuff) rc = jd_prepare(&jd, tjd_buf_input, (void *)work, sz_work, &dev); else rc = jd_prepare(&jd, tjd_input, (void *)work, sz_work, &dev); if (rc == JDR_OK) { if (x == CENTER) x = ((tft_dispWin.x2 - tft_dispWin.x1 + 1 - (int)(jd.width >> scale)) / 2) + tft_dispWin.x1; else if (x == RIGHT) x = tft_dispWin.x2 + 1 - (int)(jd.width >> scale); if (y == CENTER) y = ((tft_dispWin.y2 - tft_dispWin.y1 + 1 - (int)(jd.height >> scale)) / 2) + tft_dispWin.y1; else if (y == BOTTOM) y = tft_dispWin.y2 + 1 - (int)(jd.height >> scale); if (x < ((tft_dispWin.x2-1) * -1)) x = (tft_dispWin.x2-1) * -1; if (y < ((tft_dispWin.y2-1)) * -1) y = (tft_dispWin.y2-1) * -1; if (x > (tft_dispWin.x2-1)) x = tft_dispWin.x2 - 1; if (y > (tft_dispWin.y2-1)) y = tft_dispWin.y2-1; dev.x = x; dev.y = y; dev.linbuf[0] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA); if (dev.linbuf[0] == NULL) { if (tft_image_debug) printf("Error allocating line buffer #0\r\n"); goto exit; } dev.linbuf[1] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA); if (dev.linbuf[1] == NULL) { if (tft_image_debug) printf("Error allocating line buffer #1\r\n"); goto exit; } // Start to decode the JPEG file disp_select(); rc = jd_decomp(&jd, tjd_output, scale); disp_deselect(); if (rc != JDR_OK) { if (tft_image_debug) printf("jpg decompression error %d\r\n", rc); } if (tft_image_debug) printf("Jpg size: %dx%d, position; %d,%d, scale: %d, bytes used: %d\r\n", jd.width, jd.height, x, y, scale, jd.sz_pool); } else { if (tft_image_debug) printf("jpg prepare error %d\r\n", rc); } } else { if (tft_image_debug) printf("work buffer allocation error\r\n"); } exit: if (work) free(work); // free work buffer if (dev.linbuf[0]) free(dev.linbuf[0]); if (dev.linbuf[1]) free(dev.linbuf[1]); if (dev.fhndl) fclose(dev.fhndl); // close input file } //==================================================================================== int TFT_bmp_image(int x, int y, uint8_t scale, char *fname, uint8_t *imgbuf, int size) { FILE *fhndl = NULL; struct stat sb; int i, err=0; int img_xsize, img_ysize, img_xstart, img_xlen, img_ystart, img_ylen; int img_pos, img_pix_pos, scan_lines, rd_len; uint8_t tmpc; uint16_t wtemp; uint32_t temp; int disp_xstart, disp_xend, disp_ystart, disp_yend; uint8_t buf[56]; char err_buf[64]; uint8_t *line_buf[2] = {NULL,NULL}; uint8_t lb_idx = 0; uint8_t *scale_buf = NULL; uint8_t scale_pix; uint16_t co[3] = {0,0,0}; // RGB sum uint8_t npix; if (scale > 7) scale = 7; scale_pix = scale+1; // scale factor ( 1~8 ) if (fname) { // * File name is given, reading image from file if (stat(fname, &sb) != 0) { sprintf(err_buf, "opening file"); err = -1; goto exit; } size = sb.st_size; fhndl = fopen(fname, "r"); if (!fhndl) { sprintf(err_buf, "opening file"); err = -2; goto exit; } i = fread(buf, 1, 54, fhndl); // read header } else { // * Reading image from buffer if ((imgbuf) && (size > 54)) { memcpy(buf, imgbuf, 54); i = 54; } else i = 0; } sprintf(err_buf, "reading header"); if (i != 54) {err = -3; goto exit;} // ** Check image header and get image properties if ((buf[0] != 'B') || (buf[1] != 'M')) {err=-4; goto exit;} // accept only images with 'BM' id memcpy(&temp, buf+2, 4); // file size if (temp != size) {err=-5; goto exit;} memcpy(&img_pos, buf+10, 4); // start of pixel data memcpy(&temp, buf+14, 4); // BMP header size if (temp != 40) {err=-6; goto exit;} memcpy(&wtemp, buf+26, 2); // the number of color planes if (wtemp != 1) {err=-7; goto exit;} memcpy(&wtemp, buf+28, 2); // the number of bits per pixel if (wtemp != 24) {err=-8; goto exit;} memcpy(&temp, buf+30, 4); // the compression method being used if (temp != 0) {err=-9; goto exit;} memcpy(&img_xsize, buf+18, 4); // the bitmap width in pixels memcpy(&img_ysize, buf+22, 4); // the bitmap height in pixels // * scale image dimensions img_xlen = img_xsize / scale_pix; // image display horizontal size img_ylen = img_ysize / scale_pix; // image display vertical size if (x == CENTER) x = ((tft_dispWin.x2 - tft_dispWin.x1 + 1 - img_xlen) / 2) + tft_dispWin.x1; else if (x == RIGHT) x = tft_dispWin.x2 + 1 - img_xlen; if (y == CENTER) y = ((tft_dispWin.y2 - tft_dispWin.y1 + 1 - img_ylen) / 2) + tft_dispWin.y1; else if (y == BOTTOM) y = tft_dispWin.y2 + 1 - img_ylen; if ((x < ((tft_dispWin.x2 + 1) * -1)) || (x > (tft_dispWin.x2 + 1)) || (y < ((tft_dispWin.y2 + 1) * -1)) || (y > (tft_dispWin.y2 + 1))) { sprintf(err_buf, "out of display area (%d,%d", x, y); err = -10; goto exit; } // ** set display and image areas if (x < tft_dispWin.x1) { disp_xstart = tft_dispWin.x1; img_xstart = -x; // image pixel line X offset img_xlen += x; } else { disp_xstart = x; img_xstart = 0; } if (y < tft_dispWin.y1) { disp_ystart = tft_dispWin.y1; img_ystart = -y; // image pixel line Y offset img_ylen += y; } else { disp_ystart = y; img_ystart = 0; } disp_xend = disp_xstart + img_xlen - 1; disp_yend = disp_ystart + img_ylen - 1; if (disp_xend > tft_dispWin.x2) { disp_xend = tft_dispWin.x2; img_xlen = disp_xend - disp_xstart + 1; } if (disp_yend > tft_dispWin.y2) { disp_yend = tft_dispWin.y2; img_ylen = disp_yend - disp_ystart + 1; } if ((img_xlen < 8) || (img_ylen < 8) || (img_xstart >= (img_xsize-2)) || ((img_ysize - img_ystart) < 2)) { sprintf(err_buf, "image too small"); err = -11; goto exit; } // ** Allocate memory for 2 lines of image pixels line_buf[0] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA); if (line_buf[0] == NULL) { sprintf(err_buf, "allocating line buffer #1"); err=-12; goto exit; } line_buf[1] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA); if (line_buf[1] == NULL) { sprintf(err_buf, "allocating line buffer #2"); err=-13; goto exit; } if (scale) { // Allocate memory for scale buffer rd_len = img_xlen * 3 * scale_pix; scale_buf = malloc(rd_len*scale_pix); if (scale_buf == NULL) { sprintf(err_buf, "allocating scale buffer"); err=-14; goto exit; } } else rd_len = img_xlen * 3; // ** ***************************************************** ** // ** BMP images are stored in file from LAST to FIRST line ** // ** ***************************************************** ** /* Used variables: img_xsize horizontal image size in pixels img_ysize number of image lines img_xlen image display horizontal scaled size in pixels img_ylen image display vertical scaled size in pixels img_xstart first pixel in line to be displayed img_ystart first image line to be displayed img_xlen number of pixels in image line to be displayed, starting with 'img_xstart' img_ylen number of lines in image to be displayed, starting with 'img_ystart' rd_len length of color data which are read from image line in bytes */ // Set position in image to the first color data (beginning of the LAST line) img_pos += (img_ystart * (img_xsize*3)); if (fhndl) { if (fseek(fhndl, img_pos, SEEK_SET) != 0) { sprintf(err_buf, "file seek at %d", img_pos); err = -15; goto exit; } } if (tft_image_debug) printf("BMP: image size: (%d,%d) scale: %d disp size: (%d,%d) img xofs: %d img yofs: %d at: %d,%d; line buf: 2* %d scale buf: %d\r\n", img_xsize, img_ysize, scale_pix, img_xlen, img_ylen, img_xstart, img_ystart, disp_xstart, disp_ystart, img_xsize*3, ((scale) ? (rd_len*scale_pix) : 0)); // * Select the display disp_select(); while ((disp_yend >= disp_ystart) && ((img_pos + (img_xsize*3)) <= size)) { if (img_pos > size) { sprintf(err_buf, "EOF reached: %d > %d", img_pos, size); err = -16; goto exit1; } if (scale == 0) { // Read the line of color data into color buffer if (fhndl) { i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file if (i != (img_xsize*3)) { sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); err = -16; goto exit1; } } else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); if (img_xstart > 0) memmove(line_buf[lb_idx], line_buf[lb_idx]+(img_xstart*3), rd_len); // Convert colors BGR-888 (BMP) -> RGB-888 (DISPLAY) === for (i=0; i < rd_len; i += 3) { tmpc = line_buf[lb_idx][i+2] & 0xfc; // save R line_buf[lb_idx][i+2] = line_buf[lb_idx][i] & 0xfc; // B -> R line_buf[lb_idx][i] = tmpc; // R -> B line_buf[lb_idx][i+1] &= 0xfc; // G } img_pos += (img_xsize*3); } else { // scale image, read 'scale_pix' lines and find the average color for (scan_lines=0; scan_lines size) break; if (fhndl) { i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file if (i != (img_xsize*3)) { sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3); err = -17; goto exit1; } } else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3); img_pos += (img_xsize*3); // copy only data which are displayed to scale buffer memcpy(scale_buf + (rd_len * scan_lines), line_buf[lb_idx]+img_xstart, rd_len); } // Populate display line buffer for (int n=0;n<(img_xlen*3);n += 3) { memset(co, 0, sizeof(co)); // initialize color sum npix = 0; // initialize number of pixels in scale rectangle // sum all pixels in scale rectangle for (int sc_line=0; sc_line RGB-888 (DISPLAY) line_buf[lb_idx][n+2] = (uint8_t)(co[0] / npix); // B line_buf[lb_idx][n+1] = (uint8_t)(co[1] / npix); // G line_buf[lb_idx][n] = (uint8_t)(co[2] / npix); // R } } wait_trans_finish(1); send_data(disp_xstart, disp_yend, disp_xend, disp_yend, img_xlen, (color_t *)line_buf[lb_idx]); lb_idx = (lb_idx + 1) & 1; // change buffer disp_yend--; } err = 0; exit1: disp_deselect(); exit: if (scale_buf) free(scale_buf); if (line_buf[0]) free(line_buf[0]); if (line_buf[1]) free(line_buf[1]); if (fhndl) fclose(fhndl); if ((err) && (tft_image_debug)) printf("Error: %d [%s]\r\n", err, err_buf); return err; } // ============= Touch panel functions ========================================= #if USE_TOUCH == TOUCH_TYPE_XPT2046 //------------------------------------------------------- static int tp_get_data_xpt2046(uint8_t type, int samples) { if (tft_ts_spi == NULL) return 0; int n, result, val = 0; uint32_t i = 0; uint32_t vbuf[18]; uint32_t minval, maxval, dif; if (samples < 3) samples = 1; if (samples > 18) samples = 18; // one dummy read result = touch_get_data(type); // read data while (i < 10) { minval = 5000; maxval = 0; // get values for (n=0;n maxval) maxval = result; } if (result < 0) break; dif = maxval - minval; if (dif < 40) break; i++; } if (result < 0) return -1; if (samples > 2) { // remove one min value for (n = 0; n < samples; n++) { if (vbuf[n] == minval) { vbuf[n] = 5000; break; } } // remove one max value for (n = 0; n < samples; n++) { if (vbuf[n] == maxval) { vbuf[n] = 5000; break; } } for (n = 0; n < samples; n++) { if (vbuf[n] < 5000) val += vbuf[n]; } val /= (samples-2); } else val = vbuf[0]; return val; } //----------------------------------------------- static int TFT_read_touch_xpt2046(int *x, int* y) { int res = 0, result = -1; if (spi_lobo_device_select(tft_ts_spi, 0) != ESP_OK) return 0; result = tp_get_data_xpt2046(0xB0, 3); // Z; pressure; touch detect if (result <= 50) goto exit; // touch panel pressed result = tp_get_data_xpt2046(0xD0, 10); if (result < 0) goto exit; *x = result; result = tp_get_data_xpt2046(0x90, 10); if (result < 0) goto exit; *y = result; res = 1; exit: spi_lobo_device_deselect(tft_ts_spi); return res; } #endif //============================================= int TFT_read_touch(int *x, int* y, uint8_t raw) { *x = 0; *y = 0; if (tft_ts_spi == NULL) return 0; #if USE_TOUCH == TOUCH_TYPE_NONE return 0; #else int result = -1; int X=0, Y=0; #if USE_TOUCH == TOUCH_TYPE_XPT2046 uint32_t tft_tp_calx = TP_CALX_XPT2046; uint32_t tft_tp_caly = TP_CALY_XPT2046; result = TFT_read_touch_xpt2046(&X, &Y); if (result == 0) return 0; #elif USE_TOUCH == TOUCH_TYPE_STMPE610 uint32_t tft_tp_calx = TP_CALX_STMPE610; uint32_t tft_tp_caly = TP_CALY_STMPE610; uint16_t Xx, Yy, Z=0; result = stmpe610_get_touch(&Xx, &Yy, &Z); if (result == 0) return 0; X = Xx; Y = Yy; #else return 0; #endif if (raw) { *x = X; *y = Y; return 1; } // Calibrate the result int tmp; int xleft = (tft_tp_calx >> 16) & 0x3FFF; int xright = tft_tp_calx & 0x3FFF; int ytop = (tft_tp_caly >> 16) & 0x3FFF; int ybottom = tft_tp_caly & 0x3FFF; if (((xright - xleft) <= 0) || ((ybottom - ytop) <= 0)) return 0; #if USE_TOUCH == TOUCH_TYPE_XPT2046 int width = tft_width; int height = tft_height; X = ((X - xleft) * height) / (xright - xleft); Y = ((Y - ytop) * width) / (ybottom - ytop); if (X < 0) X = 0; if (X > height-1) X = height-1; if (Y < 0) Y = 0; if (Y > width-1) Y = width-1; switch (tft_orientation) { case PORTRAIT: tmp = X; X = width - Y - 1; Y = tmp; break; case PORTRAIT_FLIP: tmp = X; X = Y; Y = height - tmp - 1; break; case LANDSCAPE_FLIP: X = height - X - 1; Y = width - Y - 1; break; } #elif USE_TOUCH == TOUCH_TYPE_STMPE610 int width = tft_width; int height = tft_height; if (tft_width > tft_height) { width = tft_height; height = tft_width; } X = ((X - xleft) * width) / (xright - xleft); Y = ((Y - ytop) * height) / (ybottom - ytop); if (X < 0) X = 0; if (X > width-1) X = width-1; if (Y < 0) Y = 0; if (Y > height-1) Y = height-1; switch (tft_orientation) { case PORTRAIT_FLIP: X = width - X - 1; Y = height - Y - 1; break; case LANDSCAPE: tmp = X; X = Y; Y = width - tmp -1; break; case LANDSCAPE_FLIP: tmp = X; X = height - Y -1; Y = tmp; break; } #endif *x = X; *y = Y; return 1; #endif }