Repo for ESP32 Weather Station Development
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

2951 lignes
84KB

  1. /* TFT module
  2. *
  3. * Author: LoBo (loboris@gmail.com, loboris.github)
  4. *
  5. * Module supporting SPI TFT displays based on ILI9341 & ILI9488 controllers
  6. */
  7. #include <stdio.h>
  8. #include <errno.h>
  9. #include <sys/stat.h>
  10. #include <string.h>
  11. #include "freertos/FreeRTOS.h"
  12. #include "tft.h"
  13. #include <math.h>
  14. #include "esp32/rom/tjpgd.h"
  15. #define DEG_TO_RAD 0.01745329252
  16. #define deg_to_rad 0.01745329252 + 3.14159265359
  17. #define swap(a, b) { int16_t t = a; a = b; b = t; }
  18. #if !defined(max)
  19. #define max(A,B) ( (A) > (B) ? (A):(B))
  20. #endif
  21. #if !defined(min)
  22. #define min(A,B) ( (A) < (B) ? (A):(B))
  23. #endif
  24. // Embedded fonts
  25. extern uint8_t tft_SmallFont[];
  26. extern uint8_t tft_DefaultFont[];
  27. extern uint8_t tft_Dejavu18[];
  28. extern uint8_t tft_Dejavu24[];
  29. extern uint8_t tft_Ubuntu16[];
  30. extern uint8_t tft_Comic24[];
  31. extern uint8_t tft_minya24[];
  32. extern uint8_t tft_tooney32[];
  33. extern uint8_t tft_def_small[];
  34. // ==== Color definitions constants ==============
  35. const color_t TFT_BLACK = { 0, 0, 0 };
  36. const color_t TFT_NAVY = { 0, 0, 128 };
  37. const color_t TFT_DARKGREEN = { 0, 128, 0 };
  38. const color_t TFT_DARKCYAN = { 0, 128, 128 };
  39. const color_t TFT_MAROON = { 128, 0, 0 };
  40. const color_t TFT_PURPLE = { 128, 0, 128 };
  41. const color_t TFT_OLIVE = { 128, 128, 0 };
  42. const color_t TFT_LIGHTGREY = { 192, 192, 192 };
  43. const color_t TFT_DARKGREY = { 128, 128, 128 };
  44. const color_t TFT_BLUE = { 0, 0, 255 };
  45. const color_t TFT_GREEN = { 0, 255, 0 };
  46. const color_t TFT_CYAN = { 0, 255, 255 };
  47. const color_t TFT_RED = { 252, 0, 0 };
  48. const color_t TFT_MAGENTA = { 252, 0, 255 };
  49. const color_t TFT_YELLOW = { 252, 252, 0 };
  50. const color_t TFT_WHITE = { 252, 252, 252 };
  51. const color_t TFT_ORANGE = { 252, 164, 0 };
  52. const color_t TFT_GREENYELLOW = { 172, 252, 44 };
  53. const color_t TFT_PINK = { 252, 192, 202 };
  54. // ===============================================
  55. // ==============================================================
  56. // ==== Set default values of global variables ==================
  57. uint8_t tft_orientation = LANDSCAPE;// screen tft_orientation
  58. uint16_t tft_font_rotate = 0; // font rotation
  59. uint8_t tft_font_transparent = 0;
  60. uint8_t tft_font_forceFixed = 0;
  61. uint8_t tft_text_wrap = 0; // character wrapping to new line
  62. color_t tft_fg = { 0, 255, 0};
  63. color_t tft_bg = { 0, 0, 0};
  64. uint8_t tft_image_debug = 0;
  65. float tft_angleOffset = DEFAULT_ANGLE_OFFSET;
  66. int tft_x = 0;
  67. int tft_y = 0;
  68. uint32_t tft_tp_calx = 7472920;
  69. uint32_t tft_tp_caly = 122224794;
  70. dispWin_t tft_dispWin = {
  71. .x1 = TFT_STATIC_WIDTH_OFFSET,
  72. .y1 = TFT_STATIC_HEIGHT_OFFSET,
  73. .x2 = DEFAULT_TFT_DISPLAY_WIDTH + TFT_STATIC_WIDTH_OFFSET,
  74. .y2 = DEFAULT_TFT_DISPLAY_HEIGHT + TFT_STATIC_HEIGHT_OFFSET,
  75. };
  76. Font tft_cfont = {
  77. .font = tft_DefaultFont,
  78. .x_size = 0,
  79. .y_size = 0x0B,
  80. .offset = 0,
  81. .numchars = 95,
  82. .bitmap = 1,
  83. };
  84. uint8_t tft_font_buffered_char = 1;
  85. uint8_t tft_font_line_space = 0;
  86. // ==============================================================
  87. typedef struct {
  88. uint8_t charCode;
  89. int adjYOffset;
  90. int width;
  91. int height;
  92. int xOffset;
  93. int xDelta;
  94. uint16_t dataPtr;
  95. } propFont;
  96. static dispWin_t dispWinTemp;
  97. static uint8_t *userfont = NULL;
  98. static int TFT_OFFSET = 0;
  99. static propFont fontChar;
  100. static float _arcAngleMax = DEFAULT_ARC_ANGLE_MAX;
  101. // =========================================================================
  102. // ** All drawings are clipped to 'tft_dispWin' **
  103. // ** All x,y coordinates in public functions are relative to clip window **
  104. // =========== : Public functions
  105. // ----------- : Local functions
  106. // =========================================================================
  107. // Compare two colors; return 0 if equal
  108. //============================================
  109. int TFT_compare_colors(color_t c1, color_t c2)
  110. {
  111. if ((c1.r & 0xFC) != (c2.r & 0xFC)) return 1;
  112. if ((c1.g & 0xFC) != (c2.g & 0xFC)) return 1;
  113. if ((c1.b & 0xFC) != (c2.b & 0xFC)) return 1;
  114. return 0;
  115. }
  116. // draw color pixel on screen
  117. //------------------------------------------------------------------------
  118. static void _drawPixel(int16_t x, int16_t y, color_t color, uint8_t sel) {
  119. if ((x < tft_dispWin.x1) || (y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return;
  120. drawPixel(x, y, color, sel);
  121. }
  122. //====================================================================
  123. void TFT_drawPixel(int16_t x, int16_t y, color_t color, uint8_t sel) {
  124. _drawPixel(x+tft_dispWin.x1, y+tft_dispWin.y1, color, sel);
  125. }
  126. //===========================================
  127. color_t TFT_readPixel(int16_t x, int16_t y) {
  128. if ((x < tft_dispWin.x1) || (y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return TFT_BLACK;
  129. return readPixel(x, y);
  130. }
  131. //--------------------------------------------------------------------------
  132. static void _drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) {
  133. // clipping
  134. if ((x < tft_dispWin.x1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return;
  135. if (y < tft_dispWin.y1) {
  136. h -= (tft_dispWin.y1 - y);
  137. y = tft_dispWin.y1;
  138. }
  139. if (h < 0) h = 0;
  140. if ((y + h) > (tft_dispWin.y2+1)) h = tft_dispWin.y2 - y + 1;
  141. if (h == 0) h = 1;
  142. TFT_pushColorRep(x, y, x, y+h-1, color, (uint32_t)h);
  143. }
  144. //--------------------------------------------------------------------------
  145. static void _drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) {
  146. // clipping
  147. if ((y < tft_dispWin.y1) || (x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return;
  148. if (x < tft_dispWin.x1) {
  149. w -= (tft_dispWin.x1 - x);
  150. x = tft_dispWin.x1;
  151. }
  152. if (w < 0) w = 0;
  153. if ((x + w) > (tft_dispWin.x2+1)) w = tft_dispWin.x2 - x + 1;
  154. if (w == 0) w = 1;
  155. TFT_pushColorRep(x, y, x+w-1, y, color, (uint32_t)w);
  156. }
  157. //======================================================================
  158. void TFT_drawFastVLine(int16_t x, int16_t y, int16_t h, color_t color) {
  159. _drawFastVLine(x+tft_dispWin.x1, y+tft_dispWin.y1, h, color);
  160. }
  161. //======================================================================
  162. void TFT_drawFastHLine(int16_t x, int16_t y, int16_t w, color_t color) {
  163. _drawFastHLine(x+tft_dispWin.x1, y+tft_dispWin.y1, w, color);
  164. }
  165. // Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer this uses
  166. // the eficient FastH/V Line draw routine for segments of 2 pixels or more
  167. //----------------------------------------------------------------------------------
  168. static void _drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color)
  169. {
  170. if (x0 == x1) {
  171. if (y0 <= y1) _drawFastVLine(x0, y0, y1-y0, color);
  172. else _drawFastVLine(x0, y1, y0-y1, color);
  173. return;
  174. }
  175. if (y0 == y1) {
  176. if (x0 <= x1) _drawFastHLine(x0, y0, x1-x0, color);
  177. else _drawFastHLine(x1, y0, x0-x1, color);
  178. return;
  179. }
  180. int steep = 0;
  181. if (abs(y1 - y0) > abs(x1 - x0)) steep = 1;
  182. if (steep) {
  183. swap(x0, y0);
  184. swap(x1, y1);
  185. }
  186. if (x0 > x1) {
  187. swap(x0, x1);
  188. swap(y0, y1);
  189. }
  190. int16_t dx = x1 - x0, dy = abs(y1 - y0);
  191. int16_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
  192. if (y0 < y1) ystep = 1;
  193. // Split into steep and not steep for FastH/V separation
  194. if (steep) {
  195. for (; x0 <= x1; x0++) {
  196. dlen++;
  197. err -= dy;
  198. if (err < 0) {
  199. err += dx;
  200. if (dlen == 1) _drawPixel(y0, xs, color, 1);
  201. else _drawFastVLine(y0, xs, dlen, color);
  202. dlen = 0; y0 += ystep; xs = x0 + 1;
  203. }
  204. }
  205. if (dlen) _drawFastVLine(y0, xs, dlen, color);
  206. }
  207. else
  208. {
  209. for (; x0 <= x1; x0++) {
  210. dlen++;
  211. err -= dy;
  212. if (err < 0) {
  213. err += dx;
  214. if (dlen == 1) _drawPixel(xs, y0, color, 1);
  215. else _drawFastHLine(xs, y0, dlen, color);
  216. dlen = 0; y0 += ystep; xs = x0 + 1;
  217. }
  218. }
  219. if (dlen) _drawFastHLine(xs, y0, dlen, color);
  220. }
  221. }
  222. //==============================================================================
  223. void TFT_drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, color_t color)
  224. {
  225. _drawLine(x0+tft_dispWin.x1, y0+tft_dispWin.y1, x1+tft_dispWin.x1, y1+tft_dispWin.y1, color);
  226. }
  227. // fill a rectangle
  228. //--------------------------------------------------------------------------------
  229. static void _fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) {
  230. // clipping
  231. if ((x >= tft_dispWin.x2) || (y > tft_dispWin.y2)) return;
  232. if (x < tft_dispWin.x1) {
  233. w -= (tft_dispWin.x1 - x);
  234. x = tft_dispWin.x1;
  235. }
  236. if (y < tft_dispWin.y1) {
  237. h -= (tft_dispWin.y1 - y);
  238. y = tft_dispWin.y1;
  239. }
  240. if (w < 0) w = 0;
  241. if (h < 0) h = 0;
  242. if ((x + w) > (tft_dispWin.x2+1)) w = tft_dispWin.x2 - x + 1;
  243. if ((y + h) > (tft_dispWin.y2+1)) h = tft_dispWin.y2 - y + 1;
  244. if (w == 0) w = 1;
  245. if (h == 0) h = 1;
  246. TFT_pushColorRep(x, y, x+w-1, y+h-1, color, (uint32_t)(h*w));
  247. }
  248. //============================================================================
  249. void TFT_fillRect(int16_t x, int16_t y, int16_t w, int16_t h, color_t color) {
  250. _fillRect(x+tft_dispWin.x1, y+tft_dispWin.y1, w, h, color);
  251. }
  252. //==================================
  253. void TFT_fillScreen(color_t color) {
  254. 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));
  255. }
  256. //==================================
  257. void TFT_fillWindow(color_t color) {
  258. TFT_pushColorRep(tft_dispWin.x1, tft_dispWin.y1, tft_dispWin.x2, tft_dispWin.y2,
  259. color, (uint32_t)((tft_dispWin.x2-tft_dispWin.x1+1) * (tft_dispWin.y2-tft_dispWin.y1+1)));
  260. }
  261. // ^^^============= Basics drawing functions ================================^^^
  262. // ================ Graphics drawing functions ==================================
  263. //-----------------------------------------------------------------------------------
  264. static void _drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) {
  265. _drawFastHLine(x1,y1,w, color);
  266. _drawFastVLine(x1+w-1,y1,h, color);
  267. _drawFastHLine(x1,y1+h-1,w, color);
  268. _drawFastVLine(x1,y1,h, color);
  269. }
  270. //===============================================================================
  271. void TFT_drawRect(uint16_t x1,uint16_t y1,uint16_t w,uint16_t h, color_t color) {
  272. _drawRect(x1+tft_dispWin.x1, y1+tft_dispWin.y1, w, h, color);
  273. }
  274. //-------------------------------------------------------------------------------------------------
  275. static void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, color_t color)
  276. {
  277. int16_t f = 1 - r;
  278. int16_t ddF_x = 1;
  279. int16_t ddF_y = -2 * r;
  280. int16_t x = 0;
  281. int16_t y = r;
  282. disp_select();
  283. while (x < y) {
  284. if (f >= 0) {
  285. y--;
  286. ddF_y += 2;
  287. f += ddF_y;
  288. }
  289. x++;
  290. ddF_x += 2;
  291. f += ddF_x;
  292. if (cornername & 0x4) {
  293. _drawPixel(x0 + x, y0 + y, color, 0);
  294. _drawPixel(x0 + y, y0 + x, color, 0);
  295. }
  296. if (cornername & 0x2) {
  297. _drawPixel(x0 + x, y0 - y, color, 0);
  298. _drawPixel(x0 + y, y0 - x, color, 0);
  299. }
  300. if (cornername & 0x8) {
  301. _drawPixel(x0 - y, y0 + x, color, 0);
  302. _drawPixel(x0 - x, y0 + y, color, 0);
  303. }
  304. if (cornername & 0x1) {
  305. _drawPixel(x0 - y, y0 - x, color, 0);
  306. _drawPixel(x0 - x, y0 - y, color, 0);
  307. }
  308. }
  309. disp_deselect();
  310. }
  311. // Used to do circles and roundrects
  312. //----------------------------------------------------------------------------------------------------------------
  313. static void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, color_t color)
  314. {
  315. int16_t f = 1 - r;
  316. int16_t ddF_x = 1;
  317. int16_t ddF_y = -2 * r;
  318. int16_t x = 0;
  319. int16_t y = r;
  320. int16_t ylm = x0 - r;
  321. while (x < y) {
  322. if (f >= 0) {
  323. if (cornername & 0x1) _drawFastVLine(x0 + y, y0 - x, 2 * x + 1 + delta, color);
  324. if (cornername & 0x2) _drawFastVLine(x0 - y, y0 - x, 2 * x + 1 + delta, color);
  325. ylm = x0 - y;
  326. y--;
  327. ddF_y += 2;
  328. f += ddF_y;
  329. }
  330. x++;
  331. ddF_x += 2;
  332. f += ddF_x;
  333. if ((x0 - x) > ylm) {
  334. if (cornername & 0x1) _drawFastVLine(x0 + x, y0 - y, 2 * y + 1 + delta, color);
  335. if (cornername & 0x2) _drawFastVLine(x0 - x, y0 - y, 2 * y + 1 + delta, color);
  336. }
  337. }
  338. }
  339. // Draw a rounded rectangle
  340. //=============================================================================================
  341. void TFT_drawRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color)
  342. {
  343. x += tft_dispWin.x1;
  344. y += tft_dispWin.y1;
  345. // smarter version
  346. _drawFastHLine(x + r, y, w - 2 * r, color); // Top
  347. _drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom
  348. _drawFastVLine(x, y + r, h - 2 * r, color); // Left
  349. _drawFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right
  350. // draw four corners
  351. drawCircleHelper(x + r, y + r, r, 1, color);
  352. drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
  353. drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
  354. drawCircleHelper(x + r, y + h - r - 1, r, 8, color);
  355. }
  356. // Fill a rounded rectangle
  357. //=============================================================================================
  358. void TFT_fillRoundRect(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t r, color_t color)
  359. {
  360. x += tft_dispWin.x1;
  361. y += tft_dispWin.y1;
  362. // smarter version
  363. _fillRect(x + r, y, w - 2 * r, h, color);
  364. // draw four corners
  365. fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color);
  366. fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color);
  367. }
  368. //-----------------------------------------------------------------------------------------------
  369. static void _drawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t length, color_t color)
  370. {
  371. _drawLine(
  372. x,
  373. y,
  374. x + length * cos((angle + tft_angleOffset) * DEG_TO_RAD),
  375. y + length * sin((angle + tft_angleOffset) * DEG_TO_RAD), color);
  376. }
  377. //---------------------------------------------------------------------------------------------------------------
  378. static void _DrawLineByAngle(int16_t x, int16_t y, int16_t angle, uint16_t start, uint16_t length, color_t color)
  379. {
  380. _drawLine(
  381. x + start * cos((angle + tft_angleOffset) * DEG_TO_RAD),
  382. y + start * sin((angle + tft_angleOffset) * DEG_TO_RAD),
  383. x + (start + length) * cos((angle + tft_angleOffset) * DEG_TO_RAD),
  384. y + (start + length) * sin((angle + tft_angleOffset) * DEG_TO_RAD), color);
  385. }
  386. //===========================================================================================================
  387. void TFT_drawLineByAngle(uint16_t x, uint16_t y, uint16_t start, uint16_t len, uint16_t angle, color_t color)
  388. {
  389. x += tft_dispWin.x1;
  390. y += tft_dispWin.y1;
  391. if (start == 0) _drawLineByAngle(x, y, angle, len, color);
  392. else _DrawLineByAngle(x, y, angle, start, len, color);
  393. }
  394. // Draw a triangle
  395. //--------------------------------------------------------------------------------------------------------------------
  396. static void _drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color)
  397. {
  398. _drawLine(x0, y0, x1, y1, color);
  399. _drawLine(x1, y1, x2, y2, color);
  400. _drawLine(x2, y2, x0, y0, color);
  401. }
  402. //================================================================================================================
  403. void TFT_drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color)
  404. {
  405. x0 += tft_dispWin.x1;
  406. y0 += tft_dispWin.y1;
  407. x1 += tft_dispWin.x1;
  408. y1 += tft_dispWin.y1;
  409. x2 += tft_dispWin.x1;
  410. y2 += tft_dispWin.y1;
  411. _drawLine(x0, y0, x1, y1, color);
  412. _drawLine(x1, y1, x2, y2, color);
  413. _drawLine(x2, y2, x0, y0, color);
  414. }
  415. // Fill a triangle
  416. //--------------------------------------------------------------------------------------------------------------------
  417. static void _fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color)
  418. {
  419. int16_t a, b, y, last;
  420. // Sort coordinates by Y order (y2 >= y1 >= y0)
  421. if (y0 > y1) {
  422. swap(y0, y1); swap(x0, x1);
  423. }
  424. if (y1 > y2) {
  425. swap(y2, y1); swap(x2, x1);
  426. }
  427. if (y0 > y1) {
  428. swap(y0, y1); swap(x0, x1);
  429. }
  430. if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing
  431. a = b = x0;
  432. if(x1 < a) a = x1;
  433. else if(x1 > b) b = x1;
  434. if(x2 < a) a = x2;
  435. else if(x2 > b) b = x2;
  436. _drawFastHLine(a, y0, b-a+1, color);
  437. return;
  438. }
  439. int16_t
  440. dx01 = x1 - x0,
  441. dy01 = y1 - y0,
  442. dx02 = x2 - x0,
  443. dy02 = y2 - y0,
  444. dx12 = x2 - x1,
  445. dy12 = y2 - y1;
  446. int32_t
  447. sa = 0,
  448. sb = 0;
  449. // For upper part of triangle, find scanline crossings for segments
  450. // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
  451. // is included here (and second loop will be skipped, avoiding a /0
  452. // error there), otherwise scanline y1 is skipped here and handled
  453. // in the second loop...which also avoids a /0 error here if y0=y1
  454. // (flat-topped triangle).
  455. if(y1 == y2) last = y1; // Include y1 scanline
  456. else last = y1-1; // Skip it
  457. for(y=y0; y<=last; y++) {
  458. a = x0 + sa / dy01;
  459. b = x0 + sb / dy02;
  460. sa += dx01;
  461. sb += dx02;
  462. /* longhand:
  463. a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
  464. b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
  465. */
  466. if(a > b) swap(a,b);
  467. _drawFastHLine(a, y, b-a+1, color);
  468. }
  469. // For lower part of triangle, find scanline crossings for segments
  470. // 0-2 and 1-2. This loop is skipped if y1=y2.
  471. sa = dx12 * (y - y1);
  472. sb = dx02 * (y - y0);
  473. for(; y<=y2; y++) {
  474. a = x1 + sa / dy12;
  475. b = x0 + sb / dy02;
  476. sa += dx12;
  477. sb += dx02;
  478. /* longhand:
  479. a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
  480. b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
  481. */
  482. if(a > b) swap(a,b);
  483. _drawFastHLine(a, y, b-a+1, color);
  484. }
  485. }
  486. //================================================================================================================
  487. void TFT_fillTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, color_t color)
  488. {
  489. _fillTriangle(
  490. x0 + tft_dispWin.x1, y0 + tft_dispWin.y1,
  491. x1 + tft_dispWin.x1, y1 + tft_dispWin.y1,
  492. x2 + tft_dispWin.x1, y2 + tft_dispWin.y1,
  493. color);
  494. }
  495. //====================================================================
  496. void TFT_drawCircle(int16_t x, int16_t y, int radius, color_t color) {
  497. x += tft_dispWin.x1;
  498. y += tft_dispWin.y1;
  499. int f = 1 - radius;
  500. int ddF_x = 1;
  501. int ddF_y = -2 * radius;
  502. int x1 = 0;
  503. int y1 = radius;
  504. disp_select();
  505. _drawPixel(x, y + radius, color, 0);
  506. _drawPixel(x, y - radius, color, 0);
  507. _drawPixel(x + radius, y, color, 0);
  508. _drawPixel(x - radius, y, color, 0);
  509. while(x1 < y1) {
  510. if (f >= 0) {
  511. y1--;
  512. ddF_y += 2;
  513. f += ddF_y;
  514. }
  515. x1++;
  516. ddF_x += 2;
  517. f += ddF_x;
  518. _drawPixel(x + x1, y + y1, color, 0);
  519. _drawPixel(x - x1, y + y1, color, 0);
  520. _drawPixel(x + x1, y - y1, color, 0);
  521. _drawPixel(x - x1, y - y1, color, 0);
  522. _drawPixel(x + y1, y + x1, color, 0);
  523. _drawPixel(x - y1, y + x1, color, 0);
  524. _drawPixel(x + y1, y - x1, color, 0);
  525. _drawPixel(x - y1, y - x1, color, 0);
  526. }
  527. disp_deselect();
  528. }
  529. //====================================================================
  530. void TFT_fillCircle(int16_t x, int16_t y, int radius, color_t color) {
  531. x += tft_dispWin.x1;
  532. y += tft_dispWin.y1;
  533. _drawFastVLine(x, y-radius, 2*radius+1, color);
  534. fillCircleHelper(x, y, radius, 3, 0, color);
  535. }
  536. //----------------------------------------------------------------------------------------------------------------
  537. static void _draw_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option)
  538. {
  539. disp_select();
  540. // upper right
  541. if ( option & TFT_ELLIPSE_UPPER_RIGHT ) _drawPixel(x0 + x, y0 - y, color, 0);
  542. // upper left
  543. if ( option & TFT_ELLIPSE_UPPER_LEFT ) _drawPixel(x0 - x, y0 - y, color, 0);
  544. // lower right
  545. if ( option & TFT_ELLIPSE_LOWER_RIGHT ) _drawPixel(x0 + x, y0 + y, color, 0);
  546. // lower left
  547. if ( option & TFT_ELLIPSE_LOWER_LEFT ) _drawPixel(x0 - x, y0 + y, color, 0);
  548. disp_deselect();
  549. }
  550. //=====================================================================================================
  551. void TFT_drawEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option)
  552. {
  553. x0 += tft_dispWin.x1;
  554. y0 += tft_dispWin.y1;
  555. uint16_t x, y;
  556. int32_t xchg, ychg;
  557. int32_t err;
  558. int32_t rxrx2;
  559. int32_t ryry2;
  560. int32_t stopx, stopy;
  561. rxrx2 = rx;
  562. rxrx2 *= rx;
  563. rxrx2 *= 2;
  564. ryry2 = ry;
  565. ryry2 *= ry;
  566. ryry2 *= 2;
  567. x = rx;
  568. y = 0;
  569. xchg = 1;
  570. xchg -= rx;
  571. xchg -= rx;
  572. xchg *= ry;
  573. xchg *= ry;
  574. ychg = rx;
  575. ychg *= rx;
  576. err = 0;
  577. stopx = ryry2;
  578. stopx *= rx;
  579. stopy = 0;
  580. while( stopx >= stopy ) {
  581. _draw_ellipse_section(x, y, x0, y0, color, option);
  582. y++;
  583. stopy += rxrx2;
  584. err += ychg;
  585. ychg += rxrx2;
  586. if ( 2*err+xchg > 0 ) {
  587. x--;
  588. stopx -= ryry2;
  589. err += xchg;
  590. xchg += ryry2;
  591. }
  592. }
  593. x = 0;
  594. y = ry;
  595. xchg = ry;
  596. xchg *= ry;
  597. ychg = 1;
  598. ychg -= ry;
  599. ychg -= ry;
  600. ychg *= rx;
  601. ychg *= rx;
  602. err = 0;
  603. stopx = 0;
  604. stopy = rxrx2;
  605. stopy *= ry;
  606. while( stopx <= stopy ) {
  607. _draw_ellipse_section(x, y, x0, y0, color, option);
  608. x++;
  609. stopx += ryry2;
  610. err += xchg;
  611. xchg += ryry2;
  612. if ( 2*err+ychg > 0 ) {
  613. y--;
  614. stopy -= rxrx2;
  615. err += ychg;
  616. ychg += rxrx2;
  617. }
  618. }
  619. }
  620. //-----------------------------------------------------------------------------------------------------------------------
  621. static void _draw_filled_ellipse_section(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, color_t color, uint8_t option)
  622. {
  623. // upper right
  624. if ( option & TFT_ELLIPSE_UPPER_RIGHT ) _drawFastVLine(x0+x, y0-y, y+1, color);
  625. // upper left
  626. if ( option & TFT_ELLIPSE_UPPER_LEFT ) _drawFastVLine(x0-x, y0-y, y+1, color);
  627. // lower right
  628. if ( option & TFT_ELLIPSE_LOWER_RIGHT ) _drawFastVLine(x0+x, y0, y+1, color);
  629. // lower left
  630. if ( option & TFT_ELLIPSE_LOWER_LEFT ) _drawFastVLine(x0-x, y0, y+1, color);
  631. }
  632. //=====================================================================================================
  633. void TFT_fillEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, color_t color, uint8_t option)
  634. {
  635. x0 += tft_dispWin.x1;
  636. y0 += tft_dispWin.y1;
  637. uint16_t x, y;
  638. int32_t xchg, ychg;
  639. int32_t err;
  640. int32_t rxrx2;
  641. int32_t ryry2;
  642. int32_t stopx, stopy;
  643. rxrx2 = rx;
  644. rxrx2 *= rx;
  645. rxrx2 *= 2;
  646. ryry2 = ry;
  647. ryry2 *= ry;
  648. ryry2 *= 2;
  649. x = rx;
  650. y = 0;
  651. xchg = 1;
  652. xchg -= rx;
  653. xchg -= rx;
  654. xchg *= ry;
  655. xchg *= ry;
  656. ychg = rx;
  657. ychg *= rx;
  658. err = 0;
  659. stopx = ryry2;
  660. stopx *= rx;
  661. stopy = 0;
  662. while( stopx >= stopy ) {
  663. _draw_filled_ellipse_section(x, y, x0, y0, color, option);
  664. y++;
  665. stopy += rxrx2;
  666. err += ychg;
  667. ychg += rxrx2;
  668. if ( 2*err+xchg > 0 ) {
  669. x--;
  670. stopx -= ryry2;
  671. err += xchg;
  672. xchg += ryry2;
  673. }
  674. }
  675. x = 0;
  676. y = ry;
  677. xchg = ry;
  678. xchg *= ry;
  679. ychg = 1;
  680. ychg -= ry;
  681. ychg -= ry;
  682. ychg *= rx;
  683. ychg *= rx;
  684. err = 0;
  685. stopx = 0;
  686. stopy = rxrx2;
  687. stopy *= ry;
  688. while( stopx <= stopy ) {
  689. _draw_filled_ellipse_section(x, y, x0, y0, color, option);
  690. x++;
  691. stopx += ryry2;
  692. err += xchg;
  693. xchg += ryry2;
  694. if ( 2*err+ychg > 0 ) {
  695. y--;
  696. stopy -= rxrx2;
  697. err += ychg;
  698. ychg += rxrx2;
  699. }
  700. }
  701. }
  702. // ==== ARC DRAWING ===================================================================
  703. //---------------------------------------------------------------------------------------------------------------------------------
  704. static void _fillArcOffsetted(uint16_t cx, uint16_t cy, uint16_t radius, uint16_t thickness, float start, float end, color_t color)
  705. {
  706. //float sslope = (float)cos_lookup(start) / (float)sin_lookup(start);
  707. //float eslope = (float)cos_lookup(end) / (float)sin_lookup(end);
  708. float sslope = (cos(start/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(start/_arcAngleMax * 2 * PI) * _arcAngleMax) ;
  709. float eslope = (cos(end/_arcAngleMax * 2 * PI) * _arcAngleMax) / (sin(end/_arcAngleMax * 2 * PI) * _arcAngleMax);
  710. if (end == 360) eslope = -1000000;
  711. int ir2 = (radius - thickness) * (radius - thickness);
  712. int or2 = radius * radius;
  713. disp_select();
  714. for (int x = -radius; x <= radius; x++) {
  715. for (int y = -radius; y <= radius; y++) {
  716. int x2 = x * x;
  717. int y2 = y * y;
  718. if (
  719. (x2 + y2 < or2 && x2 + y2 >= ir2) &&
  720. (
  721. (y > 0 && start < 180 && x <= y * sslope) ||
  722. (y < 0 && start > 180 && x >= y * sslope) ||
  723. (y < 0 && start <= 180) ||
  724. (y == 0 && start <= 180 && x < 0) ||
  725. (y == 0 && start == 0 && x > 0)
  726. ) &&
  727. (
  728. (y > 0 && end < 180 && x >= y * eslope) ||
  729. (y < 0 && end > 180 && x <= y * eslope) ||
  730. (y > 0 && end >= 180) ||
  731. (y == 0 && end >= 180 && x < 0) ||
  732. (y == 0 && start == 0 && x > 0)
  733. )
  734. )
  735. _drawPixel(cx+x, cy+y, color, 0);
  736. }
  737. }
  738. disp_deselect();
  739. }
  740. //===========================================================================================================================
  741. 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)
  742. {
  743. cx += tft_dispWin.x1;
  744. cy += tft_dispWin.y1;
  745. if (th < 1) th = 1;
  746. if (th > r) th = r;
  747. int f = TFT_compare_colors(fillcolor, color);
  748. float astart = fmodf(start, _arcAngleMax);
  749. float aend = fmodf(end, _arcAngleMax);
  750. astart += tft_angleOffset;
  751. aend += tft_angleOffset;
  752. if (astart < 0) astart += (float)360;
  753. if (aend < 0) aend += (float)360;
  754. if (aend == 0) aend = (float)360;
  755. if (astart > aend) {
  756. _fillArcOffsetted(cx, cy, r, th, astart, _arcAngleMax, fillcolor);
  757. _fillArcOffsetted(cx, cy, r, th, 0, aend, fillcolor);
  758. if (f) {
  759. _fillArcOffsetted(cx, cy, r, 1, astart, _arcAngleMax, color);
  760. _fillArcOffsetted(cx, cy, r, 1, 0, aend, color);
  761. _fillArcOffsetted(cx, cy, r-th, 1, astart, _arcAngleMax, color);
  762. _fillArcOffsetted(cx, cy, r-th, 1, 0, aend, color);
  763. }
  764. }
  765. else {
  766. _fillArcOffsetted(cx, cy, r, th, astart, aend, fillcolor);
  767. if (f) {
  768. _fillArcOffsetted(cx, cy, r, 1, astart, aend, color);
  769. _fillArcOffsetted(cx, cy, r-th, 1, astart, aend, color);
  770. }
  771. }
  772. if (f) {
  773. _drawLine(cx + (r-th) * cos(astart * DEG_TO_RAD), cy + (r-th) * sin(astart * DEG_TO_RAD),
  774. cx + (r-1) * cos(astart * DEG_TO_RAD), cy + (r-1) * sin(astart * DEG_TO_RAD), color);
  775. _drawLine(cx + (r-th) * cos(aend * DEG_TO_RAD), cy + (r-th) * sin(aend * DEG_TO_RAD),
  776. cx + (r-1) * cos(aend * DEG_TO_RAD), cy + (r-1) * sin(aend * DEG_TO_RAD), color);
  777. }
  778. }
  779. //=============================================================================================================
  780. void TFT_drawPolygon(int cx, int cy, int sides, int diameter, color_t color, color_t fill, int rot, uint8_t th)
  781. {
  782. cx += tft_dispWin.x1;
  783. cy += tft_dispWin.y1;
  784. int deg = rot - tft_angleOffset;
  785. int f = TFT_compare_colors(fill, color);
  786. if (sides < MIN_POLIGON_SIDES) sides = MIN_POLIGON_SIDES; // This ensures the minimum side number
  787. if (sides > MAX_POLIGON_SIDES) sides = MAX_POLIGON_SIDES; // This ensures the maximum side number
  788. int Xpoints[sides], Ypoints[sides]; // Set the arrays based on the number of sides entered
  789. int rads = 360 / sides; // This equally spaces the points.
  790. for (int idx = 0; idx < sides; idx++) {
  791. Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * diameter;
  792. Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * diameter;
  793. }
  794. // Draw the polygon on the screen.
  795. if (f) {
  796. for(int idx = 0; idx < sides; idx++) {
  797. if((idx+1) < sides) _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], fill);
  798. else _fillTriangle(cx,cy,Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], fill);
  799. }
  800. }
  801. if (th) {
  802. for (int n=0; n<th; n++) {
  803. if (n > 0) {
  804. for (int idx = 0; idx < sides; idx++) {
  805. Xpoints[idx] = cx + sin((float)(idx*rads + deg) * deg_to_rad) * (diameter-n);
  806. Ypoints[idx] = cy + cos((float)(idx*rads + deg) * deg_to_rad) * (diameter-n);
  807. }
  808. }
  809. for(int idx = 0; idx < sides; idx++) {
  810. if( (idx+1) < sides)
  811. _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[idx+1],Ypoints[idx+1], color); // draw the lines
  812. else
  813. _drawLine(Xpoints[idx],Ypoints[idx],Xpoints[0],Ypoints[0], color); // finishes the last line to close up the polygon.
  814. }
  815. }
  816. }
  817. }
  818. /*
  819. // Similar to the Polygon function.
  820. //=====================================================================================
  821. void TFT_drawStar(int cx, int cy, int diameter, color_t color, bool fill, float factor)
  822. {
  823. cx += tft_dispWin.x1;
  824. cy += tft_dispWin.y1;
  825. factor = constrain(factor, 1.0, 4.0);
  826. uint8_t sides = 5;
  827. uint8_t rads = 360 / sides;
  828. int Xpoints_O[sides], Ypoints_O[sides], Xpoints_I[sides], Ypoints_I[sides];//Xpoints_T[5], Ypoints_T[5];
  829. for(int idx = 0; idx < sides; idx++) {
  830. // makes the outer points
  831. Xpoints_O[idx] = cx + sin((float)(idx*rads + 72) * deg_to_rad) * diameter;
  832. Ypoints_O[idx] = cy + cos((float)(idx*rads + 72) * deg_to_rad) * diameter;
  833. // makes the inner points
  834. Xpoints_I[idx] = cx + sin((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor);
  835. // 36 is half of 72, and this will allow the inner and outer points to line up like a triangle.
  836. Ypoints_I[idx] = cy + cos((float)(idx*rads + 36) * deg_to_rad) * ((float)(diameter)/factor);
  837. }
  838. for(int idx = 0; idx < sides; idx++) {
  839. if((idx+1) < sides) {
  840. if(fill) {// this part below should be self explanatory. It fills in the star.
  841. _fillTriangle(cx,cy,Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color);
  842. _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color);
  843. }
  844. else {
  845. _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx+1],Ypoints_I[idx+1], color);
  846. _drawLine(Xpoints_I[idx],Ypoints_I[idx],Xpoints_O[idx],Ypoints_O[idx], color);
  847. }
  848. }
  849. else {
  850. if(fill) {
  851. _fillTriangle(cx,cy,Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color);
  852. _fillTriangle(cx,cy,Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color);
  853. }
  854. else {
  855. _drawLine(Xpoints_O[idx],Ypoints_O[idx],Xpoints_I[idx],Ypoints_I[idx], color);
  856. _drawLine(Xpoints_I[0],Ypoints_I[0],Xpoints_O[idx],Ypoints_O[idx], color);
  857. }
  858. }
  859. }
  860. }
  861. */
  862. // ================ Font and string functions ==================================
  863. //--------------------------------------------------------
  864. static int load_file_font(const char * fontfile, int info)
  865. {
  866. int err = 0;
  867. char err_msg[256] = {'\0'};
  868. if (userfont != NULL) {
  869. free(userfont);
  870. userfont = NULL;
  871. }
  872. struct stat sb;
  873. // Open the file
  874. FILE *fhndl = fopen(fontfile, "r");
  875. if (!fhndl) {
  876. sprintf(err_msg, "Error opening font file '%s'", fontfile);
  877. err = 1;
  878. goto exit;
  879. }
  880. // Get file size
  881. if (stat(fontfile, &sb) != 0) {
  882. sprintf(err_msg, "Error getting font file size");
  883. err = 2;
  884. goto exit;
  885. }
  886. int fsize = sb.st_size;
  887. if (fsize < 30) {
  888. sprintf(err_msg, "Error getting font file size");
  889. err = 3;
  890. goto exit;
  891. }
  892. userfont = malloc(fsize+4);
  893. if (userfont == NULL) {
  894. sprintf(err_msg, "Font memory allocation error");
  895. fclose(fhndl);
  896. err = 4;
  897. goto exit;
  898. }
  899. int read = fread(userfont, 1, fsize, fhndl);
  900. fclose(fhndl);
  901. if (read != fsize) {
  902. sprintf(err_msg, "Font read error");
  903. err = 5;
  904. goto exit;
  905. }
  906. userfont[read] = 0;
  907. if (strstr((char *)(userfont+read-8), "RPH_font") == NULL) {
  908. sprintf(err_msg, "Font ID not found");
  909. err = 6;
  910. goto exit;
  911. }
  912. // Check size
  913. int size = 0;
  914. int numchar = 0;
  915. int width = userfont[0];
  916. int height = userfont[1];
  917. uint8_t first = 255;
  918. uint8_t last = 0;
  919. //int offst = 0;
  920. int pminwidth = 255;
  921. int pmaxwidth = 0;
  922. if (width != 0) {
  923. // Fixed font
  924. numchar = userfont[3];
  925. first = userfont[2];
  926. last = first + numchar - 1;
  927. size = ((width * height * numchar) / 8) + 4;
  928. }
  929. else {
  930. // Proportional font
  931. size = 4; // point at first char data
  932. uint8_t charCode;
  933. int charwidth;
  934. do {
  935. charCode = userfont[size];
  936. charwidth = userfont[size+2];
  937. if (charCode != 0xFF) {
  938. numchar++;
  939. if (charwidth != 0) size += ((((charwidth * userfont[size+3])-1) / 8) + 7);
  940. else size += 6;
  941. if (info) {
  942. if (charwidth > pmaxwidth) pmaxwidth = charwidth;
  943. if (charwidth < pminwidth) pminwidth = charwidth;
  944. if (charCode < first) first = charCode;
  945. if (charCode > last) last = charCode;
  946. }
  947. }
  948. else size++;
  949. } while ((size < (read-8)) && (charCode != 0xFF));
  950. }
  951. if (size != (read-8)) {
  952. sprintf(err_msg, "Font size error: found %d expected %d)", size, (read-8));
  953. err = 7;
  954. goto exit;
  955. }
  956. if (info) {
  957. if (width != 0) {
  958. printf("Fixed width font:\r\n size: %d width: %d height: %d characters: %d (%d~%d)\n",
  959. size, width, height, numchar, first, last);
  960. }
  961. else {
  962. printf("Proportional font:\r\n size: %d width: %d~%d height: %d characters: %d (%d~%d)\n",
  963. size, pminwidth, pmaxwidth, height, numchar, first, last);
  964. }
  965. }
  966. exit:
  967. if (err) {
  968. if (userfont) {
  969. free(userfont);
  970. userfont = NULL;
  971. }
  972. if (info) printf("Error: %d [%s]\r\n", err, err_msg);
  973. }
  974. return err;
  975. }
  976. //------------------------------------------------
  977. int compile_font_file(char *fontfile, uint8_t dbg)
  978. {
  979. int err = 0;
  980. char err_msg[128] = {'\0'};
  981. char outfile[128] = {'\0'};
  982. size_t len;
  983. struct stat sb;
  984. FILE *ffd = NULL;
  985. FILE *ffd_out = NULL;
  986. char *sourcebuf = NULL;
  987. len = strlen(fontfile);
  988. // check here that filename end with ".c".
  989. if ((len < 3) || (len > 125) || (strcmp(fontfile + len - 2, ".c") != 0)) {
  990. sprintf(err_msg, "not a .c file");
  991. err = 1;
  992. goto exit;
  993. }
  994. sprintf(outfile, "%s", fontfile);
  995. sprintf(outfile+strlen(outfile)-1, "fon");
  996. // Open the source file
  997. if (stat(fontfile, &sb) != 0) {
  998. sprintf(err_msg, "Error opening source file '%s'", fontfile);
  999. err = 2;
  1000. goto exit;
  1001. }
  1002. // Open the file
  1003. ffd = fopen(fontfile, "rb");
  1004. if (!ffd) {
  1005. sprintf(err_msg, "Error opening source file '%s'", fontfile);
  1006. err = 3;
  1007. goto exit;
  1008. }
  1009. // Open the font file
  1010. ffd_out= fopen(outfile, "wb");
  1011. if (!ffd_out) {
  1012. sprintf(err_msg, "error opening destination file");
  1013. err = 4;
  1014. goto exit;
  1015. }
  1016. // Get file size
  1017. int fsize = sb.st_size;
  1018. if (fsize <= 0) {
  1019. sprintf(err_msg, "source file size error");
  1020. err = 5;
  1021. goto exit;
  1022. }
  1023. sourcebuf = malloc(fsize+4);
  1024. if (sourcebuf == NULL) {
  1025. sprintf(err_msg, "memory allocation error");
  1026. err = 6;
  1027. goto exit;
  1028. }
  1029. char *fbuf = sourcebuf;
  1030. int rdsize = fread(fbuf, 1, fsize, ffd);
  1031. fclose(ffd);
  1032. ffd = NULL;
  1033. if (rdsize != fsize) {
  1034. sprintf(err_msg, "error reading from source file");
  1035. err = 7;
  1036. goto exit;
  1037. }
  1038. *(fbuf+rdsize) = '\0';
  1039. fbuf = strchr(fbuf, '{'); // beginning of font data
  1040. char *fend = strstr(fbuf, "};"); // end of font data
  1041. if ((fbuf == NULL) || (fend == NULL) || ((fend-fbuf) < 22)) {
  1042. sprintf(err_msg, "wrong source file format");
  1043. err = 8;
  1044. goto exit;
  1045. }
  1046. fbuf++;
  1047. *fend = '\0';
  1048. char hexstr[5] = {'\0'};
  1049. int lastline = 0;
  1050. fbuf = strstr(fbuf, "0x");
  1051. int size = 0;
  1052. char *nextline;
  1053. char *numptr;
  1054. int bptr = 0;
  1055. while ((fbuf != NULL) && (fbuf < fend) && (lastline == 0)) {
  1056. nextline = strchr(fbuf, '\n'); // beginning of the next line
  1057. if (nextline == NULL) {
  1058. nextline = fend-1;
  1059. lastline++;
  1060. }
  1061. else nextline++;
  1062. while (fbuf < nextline) {
  1063. numptr = strstr(fbuf, "0x");
  1064. if ((numptr == NULL) || ((fbuf+4) > nextline)) numptr = strstr(fbuf, "0X");
  1065. if ((numptr != NULL) && ((numptr+4) <= nextline)) {
  1066. fbuf = numptr;
  1067. if (bptr >= 128) {
  1068. // buffer full, write to file
  1069. if (fwrite(outfile, 1, 128, ffd_out) != 128) goto error;
  1070. bptr = 0;
  1071. size += 128;
  1072. }
  1073. memcpy(hexstr, fbuf, 4);
  1074. hexstr[4] = 0;
  1075. outfile[bptr++] = (uint8_t)strtol(hexstr, NULL, 0);
  1076. fbuf += 4;
  1077. }
  1078. else fbuf = nextline;
  1079. }
  1080. fbuf = nextline;
  1081. }
  1082. if (bptr > 0) {
  1083. size += bptr;
  1084. if (fwrite(outfile, 1, bptr, ffd_out) != bptr) goto error;
  1085. }
  1086. // write font ID
  1087. sprintf(outfile, "RPH_font");
  1088. if (fwrite(outfile, 1, 8, ffd_out) != 8) goto error;
  1089. fclose(ffd_out);
  1090. ffd_out = NULL;
  1091. // === Test compiled font ===
  1092. sprintf(outfile, "%s", fontfile);
  1093. sprintf(outfile+strlen(outfile)-1, "fon");
  1094. uint8_t *uf = userfont; // save userfont pointer
  1095. userfont = NULL;
  1096. if (load_file_font(outfile, 1) != 0) {
  1097. sprintf(err_msg, "Error compiling file!");
  1098. err = 10;
  1099. }
  1100. else {
  1101. free(userfont);
  1102. sprintf(err_msg, "File compiled successfully.");
  1103. }
  1104. userfont = uf; // restore userfont
  1105. goto exit;
  1106. error:
  1107. sprintf(err_msg, "error writing to destination file");
  1108. err = 9;
  1109. exit:
  1110. if (sourcebuf) free(sourcebuf);
  1111. if (ffd) fclose(ffd);
  1112. if (ffd_out) fclose(ffd_out);
  1113. if (dbg) printf("%s\r\n", err_msg);
  1114. return err;
  1115. }
  1116. // -----------------------------------------------------------------------------------------
  1117. // Individual Proportional Font Character Format:
  1118. // -----------------------------------------------------------------------------------------
  1119. // Character Code
  1120. // yOffset (start Y of visible pixels)
  1121. // Width (width of the visible pixels)
  1122. // Height (height of the visible pixels)
  1123. // xOffset (start X of visible pixels)
  1124. // xDelta (the distance to move the cursor. Effective width of the character.)
  1125. // Data[n]
  1126. // -----------------------------------------------------------------------------------------
  1127. //---------------------------------------------------------------------------------------------
  1128. // Character drawing rectangle is (0, 0) (xDelta-1, tft_cfont.y_size-1)
  1129. // Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1)
  1130. //---------------------------------------------------------------------------------------------
  1131. //----------------------------------
  1132. void getFontCharacters(uint8_t *buf)
  1133. {
  1134. if (tft_cfont.bitmap == 2) {
  1135. //For 7 segment font only characters 0,1,2,3,4,5,6,7,8,9, . , - , : , / are available.
  1136. for (uint8_t n=0; n < 11; n++) {
  1137. buf[n] = n + 0x30;
  1138. }
  1139. buf[11] = '.';
  1140. buf[12] = '-';
  1141. buf[13] = '/';
  1142. buf[14] = '\0';
  1143. return;
  1144. }
  1145. if (tft_cfont.x_size > 0) {
  1146. for (uint8_t n=0; n < tft_cfont.numchars; n++) {
  1147. buf[n] = tft_cfont.offset + n;
  1148. }
  1149. buf[tft_cfont.numchars] = '\0';
  1150. return;
  1151. }
  1152. uint16_t tempPtr = 4; // point at first char data
  1153. uint8_t cc, cw, ch, n;
  1154. n = 0;
  1155. cc = tft_cfont.font[tempPtr++];
  1156. while (cc != 0xFF) {
  1157. tft_cfont.numchars++;
  1158. tempPtr++;
  1159. cw = tft_cfont.font[tempPtr++];
  1160. ch = tft_cfont.font[tempPtr++];
  1161. tempPtr++;
  1162. tempPtr++;
  1163. if (cw != 0) {
  1164. // packed bits
  1165. tempPtr += (((cw * ch)-1) / 8) + 1;
  1166. }
  1167. buf[n++] = cc;
  1168. cc = tft_cfont.font[tempPtr++];
  1169. }
  1170. buf[n] = '\0';
  1171. }
  1172. // Set max width & height of the proportional font
  1173. //-----------------------------
  1174. static void getMaxWidthHeight()
  1175. {
  1176. uint16_t tempPtr = 4; // point at first char data
  1177. uint8_t cc, cw, ch, cd, cy;
  1178. tft_cfont.numchars = 0;
  1179. tft_cfont.max_x_size = 0;
  1180. cc = tft_cfont.font[tempPtr++];
  1181. while (cc != 0xFF) {
  1182. tft_cfont.numchars++;
  1183. cy = tft_cfont.font[tempPtr++];
  1184. cw = tft_cfont.font[tempPtr++];
  1185. ch = tft_cfont.font[tempPtr++];
  1186. tempPtr++;
  1187. cd = tft_cfont.font[tempPtr++];
  1188. cy += ch;
  1189. if (cw > tft_cfont.max_x_size) tft_cfont.max_x_size = cw;
  1190. if (cd > tft_cfont.max_x_size) tft_cfont.max_x_size = cd;
  1191. if (ch > tft_cfont.y_size) tft_cfont.y_size = ch;
  1192. if (cy > tft_cfont.y_size) tft_cfont.y_size = cy;
  1193. if (cw != 0) {
  1194. // packed bits
  1195. tempPtr += (((cw * ch)-1) / 8) + 1;
  1196. }
  1197. cc = tft_cfont.font[tempPtr++];
  1198. }
  1199. tft_cfont.size = tempPtr;
  1200. }
  1201. // Return the Glyph data for an individual character in the proportional font
  1202. //------------------------------------
  1203. static uint8_t getCharPtr(uint8_t c) {
  1204. uint16_t tempPtr = 4; // point at first char data
  1205. do {
  1206. fontChar.charCode = tft_cfont.font[tempPtr++];
  1207. if (fontChar.charCode == 0xFF) return 0;
  1208. fontChar.adjYOffset = tft_cfont.font[tempPtr++];
  1209. fontChar.width = tft_cfont.font[tempPtr++];
  1210. fontChar.height = tft_cfont.font[tempPtr++];
  1211. fontChar.xOffset = tft_cfont.font[tempPtr++];
  1212. fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset);
  1213. fontChar.xDelta = tft_cfont.font[tempPtr++];
  1214. if (c != fontChar.charCode && fontChar.charCode != 0xFF) {
  1215. if (fontChar.width != 0) {
  1216. // packed bits
  1217. tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1;
  1218. }
  1219. }
  1220. } while ((c != fontChar.charCode) && (fontChar.charCode != 0xFF));
  1221. fontChar.dataPtr = tempPtr;
  1222. if (c == fontChar.charCode) {
  1223. if (tft_font_forceFixed > 0) {
  1224. // fix width & offset for forced fixed width
  1225. fontChar.xDelta = tft_cfont.max_x_size;
  1226. fontChar.xOffset = (fontChar.xDelta - fontChar.width) / 2;
  1227. }
  1228. }
  1229. else return 0;
  1230. return 1;
  1231. }
  1232. /*
  1233. //-----------------------
  1234. static void _testFont() {
  1235. if (tft_cfont.x_size) {
  1236. printf("FONT TEST: fixed font\r\n");
  1237. return;
  1238. }
  1239. uint16_t tempPtr = 4; // point at first char data
  1240. uint8_t c = 0x20;
  1241. for (c=0x20; c <0xFF; c++) {
  1242. fontChar.charCode = tft_cfont.font[tempPtr++];
  1243. if (fontChar.charCode == 0xFF) break;
  1244. if (fontChar.charCode != c) {
  1245. printf("FONT TEST: last sequential char: %d, expected %d\r\n", fontChar.charCode, c);
  1246. break;
  1247. }
  1248. c = fontChar.charCode;
  1249. fontChar.adjYOffset = tft_cfont.font[tempPtr++];
  1250. fontChar.width = tft_cfont.font[tempPtr++];
  1251. fontChar.height = tft_cfont.font[tempPtr++];
  1252. fontChar.xOffset = tft_cfont.font[tempPtr++];
  1253. fontChar.xOffset = fontChar.xOffset < 0x80 ? fontChar.xOffset : -(0xFF - fontChar.xOffset);
  1254. fontChar.xDelta = tft_cfont.font[tempPtr++];
  1255. if (fontChar.charCode != 0xFF) {
  1256. if (fontChar.width != 0) {
  1257. // packed bits
  1258. tempPtr += (((fontChar.width * fontChar.height)-1) / 8) + 1;
  1259. }
  1260. }
  1261. }
  1262. 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);
  1263. }
  1264. */
  1265. //===================================================
  1266. void TFT_setFont(uint8_t font, const char *font_file)
  1267. {
  1268. tft_cfont.font = NULL;
  1269. if (font == FONT_7SEG) {
  1270. tft_cfont.bitmap = 2;
  1271. tft_cfont.x_size = 24;
  1272. tft_cfont.y_size = 6;
  1273. tft_cfont.offset = 0;
  1274. tft_cfont.color = tft_fg;
  1275. }
  1276. else {
  1277. if (font == USER_FONT) {
  1278. if (load_file_font(font_file, 0) != 0) tft_cfont.font = tft_DefaultFont;
  1279. else tft_cfont.font = userfont;
  1280. }
  1281. else if (font == DEJAVU18_FONT) tft_cfont.font = tft_Dejavu18;
  1282. else if (font == DEJAVU24_FONT) tft_cfont.font = tft_Dejavu24;
  1283. else if (font == UBUNTU16_FONT) tft_cfont.font = tft_Ubuntu16;
  1284. else if (font == COMIC24_FONT) tft_cfont.font = tft_Comic24;
  1285. else if (font == MINYA24_FONT) tft_cfont.font = tft_minya24;
  1286. else if (font == TOONEY32_FONT) tft_cfont.font = tft_tooney32;
  1287. else if (font == SMALL_FONT) tft_cfont.font = tft_SmallFont;
  1288. else if (font == DEF_SMALL_FONT) tft_cfont.font = tft_def_small;
  1289. else tft_cfont.font = tft_DefaultFont;
  1290. tft_cfont.bitmap = 1;
  1291. tft_cfont.x_size = tft_cfont.font[0];
  1292. tft_cfont.y_size = tft_cfont.font[1];
  1293. if (tft_cfont.x_size > 0) {
  1294. tft_cfont.offset = tft_cfont.font[2];
  1295. tft_cfont.numchars = tft_cfont.font[3];
  1296. tft_cfont.size = tft_cfont.x_size * tft_cfont.y_size * tft_cfont.numchars;
  1297. }
  1298. else {
  1299. tft_cfont.offset = 4;
  1300. getMaxWidthHeight();
  1301. }
  1302. //_testFont();
  1303. }
  1304. }
  1305. // -----------------------------------------------------------------------------------------
  1306. // Individual Proportional Font Character Format:
  1307. // -----------------------------------------------------------------------------------------
  1308. // Character Code
  1309. // yOffset (start Y of visible pixels)
  1310. // Width (width of the visible pixels)
  1311. // Height (height of the visible pixels)
  1312. // xOffset (start X of visible pixels)
  1313. // xDelta (the distance to move the cursor. Effective width of the character.)
  1314. // Data[n]
  1315. // -----------------------------------------------------------------------------------------
  1316. //---------------------------------------------------------------------------------------------
  1317. // Character drawing rectangle is (0, 0) (xDelta-1, tft_cfont.y_size-1)
  1318. // Character visible pixels rectangle is (xOffset, yOffset) (xOffset+Width-1, yOffset+Height-1)
  1319. //---------------------------------------------------------------------------------------------
  1320. // print non-rotated proportional character
  1321. // character is already in fontChar
  1322. //----------------------------------------------
  1323. static int printProportionalChar(int x, int y) {
  1324. uint8_t ch = 0;
  1325. int i, j, char_width;
  1326. char_width = ((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta);
  1327. if ((tft_font_buffered_char) && (!tft_font_transparent)) {
  1328. int len, bufPos;
  1329. // === buffer Glyph data for faster sending ===
  1330. len = char_width * tft_cfont.y_size;
  1331. color_t *color_line = heap_caps_malloc(len*3, MALLOC_CAP_DMA);
  1332. if (color_line) {
  1333. // fill with background color
  1334. for (int n = 0; n < len; n++) {
  1335. color_line[n] = tft_bg;
  1336. }
  1337. // set character pixels to foreground color
  1338. uint8_t mask = 0x80;
  1339. for (j=0; j < fontChar.height; j++) {
  1340. for (i=0; i < fontChar.width; i++) {
  1341. if (((i + (j*fontChar.width)) % 8) == 0) {
  1342. mask = 0x80;
  1343. ch = tft_cfont.font[fontChar.dataPtr++];
  1344. }
  1345. if ((ch & mask) != 0) {
  1346. // visible pixel
  1347. bufPos = ((j + fontChar.adjYOffset) * char_width) + (fontChar.xOffset + i); // bufY + bufX
  1348. color_line[bufPos] = tft_fg;
  1349. /*
  1350. bufY = (j + fontChar.adjYOffset) * char_width;
  1351. bufX = fontChar.xOffset + i;
  1352. if ((bufX < 0) || (bufX > char_width)) {
  1353. printf("[%c] X ERR: %d\r\n", fontChar.charCode, bufX);
  1354. }
  1355. bufPos = bufY + bufX;
  1356. if ((bufPos < len) && (bufPos > 0)) color_line[bufPos] = tft_fg;
  1357. else printf("[%c] ERR: %d > %d W=%d H=%d bufX=%d bufY=%d X=%d Y=%d\r\n",
  1358. fontChar.charCode, bufPos, len, char_width, tft_cfont.y_size, bufX, bufY, fontChar.xOffset + i, j + fontChar.adjYOffset);
  1359. */
  1360. }
  1361. mask >>= 1;
  1362. }
  1363. }
  1364. // send to display in one transaction
  1365. disp_select();
  1366. send_data(x, y, x+char_width-1, y+tft_cfont.y_size-1, len, color_line);
  1367. disp_deselect();
  1368. free(color_line);
  1369. return char_width;
  1370. }
  1371. }
  1372. int cx, cy;
  1373. if (!tft_font_transparent) _fillRect(x, y, char_width+1, tft_cfont.y_size, tft_bg);
  1374. // draw Glyph
  1375. uint8_t mask = 0x80;
  1376. disp_select();
  1377. for (j=0; j < fontChar.height; j++) {
  1378. for (i=0; i < fontChar.width; i++) {
  1379. if (((i + (j*fontChar.width)) % 8) == 0) {
  1380. mask = 0x80;
  1381. ch = tft_cfont.font[fontChar.dataPtr++];
  1382. }
  1383. if ((ch & mask) !=0) {
  1384. cx = (uint16_t)(x+fontChar.xOffset+i);
  1385. cy = (uint16_t)(y+j+fontChar.adjYOffset);
  1386. _drawPixel(cx, cy, tft_fg, 0);
  1387. }
  1388. mask >>= 1;
  1389. }
  1390. }
  1391. disp_deselect();
  1392. return char_width;
  1393. }
  1394. // non-rotated fixed width character
  1395. //----------------------------------------------
  1396. static void printChar(uint8_t c, int x, int y) {
  1397. uint8_t i, j, ch, fz, mask;
  1398. uint16_t k, temp, cx, cy, len;
  1399. // fz = bytes per char row
  1400. fz = tft_cfont.x_size/8;
  1401. if (tft_cfont.x_size % 8) fz++;
  1402. // get character position in buffer
  1403. temp = ((c-tft_cfont.offset)*((fz)*tft_cfont.y_size))+4;
  1404. if ((tft_font_buffered_char) && (!tft_font_transparent)) {
  1405. // === buffer Glyph data for faster sending ===
  1406. len = tft_cfont.x_size * tft_cfont.y_size;
  1407. color_t *color_line = heap_caps_malloc(len*3, MALLOC_CAP_DMA);
  1408. if (color_line) {
  1409. // fill with background color
  1410. for (int n = 0; n < len; n++) {
  1411. color_line[n] = tft_bg;
  1412. }
  1413. // set character pixels to foreground color
  1414. for (j=0; j<tft_cfont.y_size; j++) {
  1415. for (k=0; k < fz; k++) {
  1416. ch = tft_cfont.font[temp+k];
  1417. mask=0x80;
  1418. for (i=0; i<8; i++) {
  1419. if ((ch & mask) !=0) color_line[(j*tft_cfont.x_size) + (i+(k*8))] = tft_fg;
  1420. mask >>= 1;
  1421. }
  1422. }
  1423. temp += (fz);
  1424. }
  1425. // send to display in one transaction
  1426. disp_select();
  1427. send_data(x, y, x+tft_cfont.x_size-1, y+tft_cfont.y_size-1, len, color_line);
  1428. disp_deselect();
  1429. free(color_line);
  1430. return;
  1431. }
  1432. }
  1433. if (!tft_font_transparent) _fillRect(x, y, tft_cfont.x_size, tft_cfont.y_size, tft_bg);
  1434. disp_select();
  1435. for (j=0; j<tft_cfont.y_size; j++) {
  1436. for (k=0; k < fz; k++) {
  1437. ch = tft_cfont.font[temp+k];
  1438. mask=0x80;
  1439. for (i=0; i<8; i++) {
  1440. if ((ch & mask) !=0) {
  1441. cx = (uint16_t)(x+i+(k*8));
  1442. cy = (uint16_t)(y+j);
  1443. _drawPixel(cx, cy, tft_fg, 0);
  1444. }
  1445. mask >>= 1;
  1446. }
  1447. }
  1448. temp += (fz);
  1449. }
  1450. disp_deselect();
  1451. }
  1452. // print rotated proportional character
  1453. // character is already in fontChar
  1454. //---------------------------------------------------
  1455. static int rotatePropChar(int x, int y, int offset) {
  1456. uint8_t ch = 0;
  1457. double radian = tft_font_rotate * DEG_TO_RAD;
  1458. float cos_radian = cos(radian);
  1459. float sin_radian = sin(radian);
  1460. uint8_t mask = 0x80;
  1461. disp_select();
  1462. for (int j=0; j < fontChar.height; j++) {
  1463. for (int i=0; i < fontChar.width; i++) {
  1464. if (((i + (j*fontChar.width)) % 8) == 0) {
  1465. mask = 0x80;
  1466. ch = tft_cfont.font[fontChar.dataPtr++];
  1467. }
  1468. int newX = (int)(x + (((offset + i) * cos_radian) - ((j+fontChar.adjYOffset)*sin_radian)));
  1469. int newY = (int)(y + (((j+fontChar.adjYOffset) * cos_radian) + ((offset + i) * sin_radian)));
  1470. if ((ch & mask) != 0) _drawPixel(newX,newY,tft_fg, 0);
  1471. else if (!tft_font_transparent) _drawPixel(newX,newY,tft_bg, 0);
  1472. mask >>= 1;
  1473. }
  1474. }
  1475. disp_deselect();
  1476. return fontChar.xDelta+1;
  1477. }
  1478. // rotated fixed width character
  1479. //--------------------------------------------------------
  1480. static void rotateChar(uint8_t c, int x, int y, int pos) {
  1481. uint8_t i,j,ch,fz,mask;
  1482. uint16_t temp;
  1483. int newx,newy;
  1484. double radian = tft_font_rotate*0.0175;
  1485. float cos_radian = cos(radian);
  1486. float sin_radian = sin(radian);
  1487. int zz;
  1488. if( tft_cfont.x_size < 8 ) fz = tft_cfont.x_size;
  1489. else fz = tft_cfont.x_size/8;
  1490. temp=((c-tft_cfont.offset)*((fz)*tft_cfont.y_size))+4;
  1491. disp_select();
  1492. for (j=0; j<tft_cfont.y_size; j++) {
  1493. for (zz=0; zz<(fz); zz++) {
  1494. ch = tft_cfont.font[temp+zz];
  1495. mask = 0x80;
  1496. for (i=0; i<8; i++) {
  1497. newx=(int)(x+(((i+(zz*8)+(pos*tft_cfont.x_size))*cos_radian)-((j)*sin_radian)));
  1498. newy=(int)(y+(((j)*cos_radian)+((i+(zz*8)+(pos*tft_cfont.x_size))*sin_radian)));
  1499. if ((ch & mask) != 0) _drawPixel(newx,newy,tft_fg, 0);
  1500. else if (!tft_font_transparent) _drawPixel(newx,newy,tft_bg, 0);
  1501. mask >>= 1;
  1502. }
  1503. }
  1504. temp+=(fz);
  1505. }
  1506. disp_deselect();
  1507. // calculate x,y for the next char
  1508. tft_x = (int)(x + ((pos+1) * tft_cfont.x_size * cos_radian));
  1509. tft_y = (int)(y + ((pos+1) * tft_cfont.x_size * sin_radian));
  1510. }
  1511. //----------------------
  1512. static int _7seg_width()
  1513. {
  1514. return (2 * (2 * tft_cfont.y_size + 1)) + tft_cfont.x_size;
  1515. }
  1516. //-----------------------
  1517. static int _7seg_height()
  1518. {
  1519. return (3 * (2 * tft_cfont.y_size + 1)) + (2 * tft_cfont.x_size);
  1520. }
  1521. // Returns the string width in pixels.
  1522. // Useful for positions strings on the screen.
  1523. //===============================
  1524. int TFT_getStringWidth(char* str)
  1525. {
  1526. int strWidth = 0;
  1527. if (tft_cfont.bitmap == 2) strWidth = ((_7seg_width()+2) * strlen(str)) - 2; // 7-segment font
  1528. else if (tft_cfont.x_size != 0) strWidth = strlen(str) * tft_cfont.x_size; // fixed width font
  1529. else {
  1530. // calculate the width of the string of proportional characters
  1531. char* tempStrptr = str;
  1532. while (*tempStrptr != 0) {
  1533. if (getCharPtr(*tempStrptr++)) {
  1534. strWidth += (((fontChar.width > fontChar.xDelta) ? fontChar.width : fontChar.xDelta) + 1);
  1535. }
  1536. }
  1537. strWidth--;
  1538. }
  1539. return strWidth;
  1540. }
  1541. //===============================================
  1542. void TFT_clearStringRect(int x, int y, char *str)
  1543. {
  1544. int w = TFT_getStringWidth(str);
  1545. int h = TFT_getfontheight();
  1546. TFT_fillRect(x+tft_dispWin.x1, y+tft_dispWin.y1, w, h, tft_bg);
  1547. }
  1548. //==============================================================================
  1549. /**
  1550. * bit-encoded bar position of all digits' bcd segments
  1551. *
  1552. * 6
  1553. * +-----+
  1554. * 3 | . | 2
  1555. * +--5--+
  1556. * 1 | . | 0
  1557. * +--.--+
  1558. * 4
  1559. */
  1560. static const uint16_t font_bcd[] = {
  1561. 0x200, // 0010 0000 0000 // -
  1562. 0x080, // 0000 1000 0000 // .
  1563. 0x06C, // 0100 0110 1100 // /, degree
  1564. 0x05f, // 0000 0101 1111, // 0
  1565. 0x005, // 0000 0000 0101, // 1
  1566. 0x076, // 0000 0111 0110, // 2
  1567. 0x075, // 0000 0111 0101, // 3
  1568. 0x02d, // 0000 0010 1101, // 4
  1569. 0x079, // 0000 0111 1001, // 5
  1570. 0x07b, // 0000 0111 1011, // 6
  1571. 0x045, // 0000 0100 0101, // 7
  1572. 0x07f, // 0000 0111 1111, // 8
  1573. 0x07d, // 0000 0111 1101 // 9
  1574. 0x900 // 1001 0000 0000 // :
  1575. };
  1576. //-----------------------------------------------------------------------------------------------
  1577. static void barVert(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) {
  1578. _fillTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, color);
  1579. _fillTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, color);
  1580. _fillRect(x, y+2*w+1, 2*w+1, l, color);
  1581. if (tft_cfont.offset) {
  1582. _drawTriangle(x+1, y+2*w, x+w, y+w+1, x+2*w-1, y+2*w, outline);
  1583. _drawTriangle(x+1, y+2*w+l+1, x+w, y+3*w+l, x+2*w-1, y+2*w+l+1, outline);
  1584. _drawRect(x, y+2*w+1, 2*w+1, l, outline);
  1585. }
  1586. }
  1587. //----------------------------------------------------------------------------------------------
  1588. static void barHor(int16_t x, int16_t y, int16_t w, int16_t l, color_t color, color_t outline) {
  1589. _fillTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, color);
  1590. _fillTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, color);
  1591. _fillRect(x+2*w+1, y, l, 2*w+1, color);
  1592. if (tft_cfont.offset) {
  1593. _drawTriangle(x+2*w, y+2*w-1, x+w+1, y+w, x+2*w, y+1, outline);
  1594. _drawTriangle(x+2*w+l+1, y+2*w-1, x+3*w+l, y+w, x+2*w+l+1, y+1, outline);
  1595. _drawRect(x+2*w+1, y, l, 2*w+1, outline);
  1596. }
  1597. }
  1598. //--------------------------------------------------------------------------------------------
  1599. static void _draw7seg(int16_t x, int16_t y, int8_t num, int16_t w, int16_t l, color_t color) {
  1600. /* TODO: clipping */
  1601. if (num < 0x2D || num > 0x3A) return;
  1602. int16_t c = font_bcd[num-0x2D];
  1603. int16_t d = 2*w+l+1;
  1604. // === Clear unused segments ===
  1605. if (!(c & 0x001)) barVert(x+d, y+d, w, l, tft_bg, tft_bg);
  1606. if (!(c & 0x002)) barVert(x, y+d, w, l, tft_bg, tft_bg);
  1607. if (!(c & 0x004)) barVert(x+d, y, w, l, tft_bg, tft_bg);
  1608. if (!(c & 0x008)) barVert(x, y, w, l, tft_bg, tft_bg);
  1609. if (!(c & 0x010)) barHor(x, y+2*d, w, l, tft_bg, tft_bg);
  1610. if (!(c & 0x020)) barHor(x, y+d, w, l, tft_bg, tft_bg);
  1611. if (!(c & 0x040)) barHor(x, y, w, l, tft_bg, tft_bg);
  1612. if (!(c & 0x080)) {
  1613. // low point
  1614. _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_bg);
  1615. if (tft_cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_bg);
  1616. }
  1617. if (!(c & 0x100)) {
  1618. // down middle point
  1619. _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_bg);
  1620. if (tft_cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_bg);
  1621. }
  1622. if (!(c & 0x800)) {
  1623. // up middle point
  1624. _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_bg);
  1625. if (tft_cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_bg);
  1626. }
  1627. if (!(c & 0x200)) {
  1628. // middle, minus
  1629. _fillRect(x+2*w+1, y+d, l, 2*w+1, tft_bg);
  1630. if (tft_cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, tft_bg);
  1631. }
  1632. // === Draw used segments ===
  1633. if (c & 0x001) barVert(x+d, y+d, w, l, color, tft_cfont.color); // down right
  1634. if (c & 0x002) barVert(x, y+d, w, l, color, tft_cfont.color); // down left
  1635. if (c & 0x004) barVert(x+d, y, w, l, color, tft_cfont.color); // up right
  1636. if (c & 0x008) barVert(x, y, w, l, color, tft_cfont.color); // up left
  1637. if (c & 0x010) barHor(x, y+2*d, w, l, color, tft_cfont.color); // down
  1638. if (c & 0x020) barHor(x, y+d, w, l, color, tft_cfont.color); // middle
  1639. if (c & 0x040) barHor(x, y, w, l, color, tft_cfont.color); // up
  1640. if (c & 0x080) {
  1641. // low point
  1642. _fillRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, color);
  1643. if (tft_cfont.offset) _drawRect(x+(d/2), y+2*d, 2*w+1, 2*w+1, tft_cfont.color);
  1644. }
  1645. if (c & 0x100) {
  1646. // down middle point
  1647. _fillRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, color);
  1648. if (tft_cfont.offset) _drawRect(x+(d/2), y+d+2*w+1, 2*w+1, l/2, tft_cfont.color);
  1649. }
  1650. if (c & 0x800) {
  1651. // up middle point
  1652. _fillRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, color);
  1653. if (tft_cfont.offset) _drawRect(x+(d/2), y+(2*w)+1+(l/2), 2*w+1, l/2, tft_cfont.color);
  1654. }
  1655. if (c & 0x200) {
  1656. // middle, minus
  1657. _fillRect(x+2*w+1, y+d, l, 2*w+1, color);
  1658. if (tft_cfont.offset) _drawRect(x+2*w+1, y+d, l, 2*w+1, tft_cfont.color);
  1659. }
  1660. }
  1661. //==============================================================================
  1662. //======================================
  1663. void TFT_print(char *st, int x, int y) {
  1664. int stl, i, tmpw, tmph, fh;
  1665. uint8_t ch;
  1666. if (tft_cfont.bitmap == 0) return; // wrong font selected
  1667. // ** Rotated strings cannot be aligned
  1668. if ((tft_font_rotate != 0) && ((x <= CENTER) || (y <= CENTER))) return;
  1669. if ((x < LASTX) || (tft_font_rotate == 0)) TFT_OFFSET = 0;
  1670. if ((x >= LASTX) && (x < LASTY)) x = tft_x + (x-LASTX);
  1671. else if (x > CENTER) x += tft_dispWin.x1;
  1672. if (y >= LASTY) y = tft_y + (y-LASTY);
  1673. else if (y > CENTER) y += tft_dispWin.y1;
  1674. // ** Get number of characters in string to print
  1675. stl = strlen(st);
  1676. // ** Calculate CENTER, RIGHT or BOTTOM position
  1677. tmpw = TFT_getStringWidth(st); // string width in pixels
  1678. fh = tft_cfont.y_size; // font height
  1679. if ((tft_cfont.x_size != 0) && (tft_cfont.bitmap == 2)) {
  1680. // 7-segment font
  1681. fh = (3 * (2 * tft_cfont.y_size + 1)) + (2 * tft_cfont.x_size); // 7-seg character height
  1682. }
  1683. if (x == RIGHT) x = tft_dispWin.x2 - tmpw + tft_dispWin.x1;
  1684. else if (x == CENTER) x = (((tft_dispWin.x2 - tft_dispWin.x1 + 1) - tmpw) / 2) + tft_dispWin.x1;
  1685. if (y == BOTTOM) y = tft_dispWin.y2 - fh + tft_dispWin.y1;
  1686. else if (y==CENTER) y = (((tft_dispWin.y2 - tft_dispWin.y1 + 1) - (fh/2)) / 2) + tft_dispWin.y1;
  1687. if (x < tft_dispWin.x1) x = tft_dispWin.x1;
  1688. if (y < tft_dispWin.y1) y = tft_dispWin.y1;
  1689. if ((x > tft_dispWin.x2) || (y > tft_dispWin.y2)) return;
  1690. tft_x = x;
  1691. tft_y = y;
  1692. // ** Adjust y position
  1693. tmph = tft_cfont.y_size; // font height
  1694. // for non-proportional fonts, char width is the same for all chars
  1695. tmpw = tft_cfont.x_size;
  1696. if (tft_cfont.x_size != 0) {
  1697. if (tft_cfont.bitmap == 2) { // 7-segment font
  1698. tmpw = _7seg_width(); // character width
  1699. tmph = _7seg_height(); // character height
  1700. }
  1701. }
  1702. else TFT_OFFSET = 0; // fixed font; offset not needed
  1703. if ((tft_y + tmph - 1) > tft_dispWin.y2) return;
  1704. int offset = TFT_OFFSET;
  1705. for (i=0; i<stl; i++) {
  1706. ch = st[i]; // get string character
  1707. if (ch == 0x0D) { // === '\r', erase to eol ====
  1708. if ((!tft_font_transparent) && (tft_font_rotate==0)) _fillRect(tft_x, tft_y, tft_dispWin.x2+1-tft_x, tmph, tft_bg);
  1709. }
  1710. else if (ch == 0x0A) { // ==== '\n', new line ====
  1711. if (tft_cfont.bitmap == 1) {
  1712. tft_y += tmph + tft_font_line_space;
  1713. if (tft_y > (tft_dispWin.y2-tmph)) break;
  1714. tft_x = tft_dispWin.x1;
  1715. }
  1716. }
  1717. else { // ==== other characters ====
  1718. if (tft_cfont.x_size == 0) {
  1719. // for proportional font get character data to 'fontChar'
  1720. if (getCharPtr(ch)) tmpw = fontChar.xDelta;
  1721. else continue;
  1722. }
  1723. // check if character can be displayed in the current line
  1724. if ((tft_x+tmpw) > (tft_dispWin.x2)) {
  1725. if (tft_text_wrap == 0) break;
  1726. tft_y += tmph + tft_font_line_space;
  1727. if (tft_y > (tft_dispWin.y2-tmph)) break;
  1728. tft_x = tft_dispWin.x1;
  1729. }
  1730. // Let's print the character
  1731. if (tft_cfont.x_size == 0) {
  1732. // == proportional font
  1733. if (tft_font_rotate == 0) tft_x += printProportionalChar(tft_x, tft_y) + 1;
  1734. else {
  1735. // rotated proportional font
  1736. offset += rotatePropChar(x, y, offset);
  1737. TFT_OFFSET = offset;
  1738. }
  1739. }
  1740. else {
  1741. if (tft_cfont.bitmap == 1) {
  1742. // == fixed font
  1743. if ((ch < tft_cfont.offset) || ((ch-tft_cfont.offset) > tft_cfont.numchars)) ch = tft_cfont.offset;
  1744. if (tft_font_rotate == 0) {
  1745. printChar(ch, tft_x, tft_y);
  1746. tft_x += tmpw;
  1747. }
  1748. else rotateChar(ch, x, y, i);
  1749. }
  1750. else if (tft_cfont.bitmap == 2) {
  1751. // == 7-segment font ==
  1752. _draw7seg(tft_x, tft_y, ch, tft_cfont.y_size, tft_cfont.x_size, tft_fg);
  1753. tft_x += (tmpw + 2);
  1754. }
  1755. }
  1756. }
  1757. }
  1758. }
  1759. // ================ Service functions ==========================================
  1760. // Change the screen rotation.
  1761. // Input: m new rotation value (0 to 3)
  1762. //=================================
  1763. void TFT_setRotation(uint8_t rot) {
  1764. if (rot > 3) {
  1765. uint8_t madctl = (rot & 0xF8); // for testing, manually set MADCTL register
  1766. if (disp_select() == ESP_OK) {
  1767. disp_spi_transfer_cmd_data(TFT_MADCTL, &madctl, 1);
  1768. disp_deselect();
  1769. }
  1770. }
  1771. else {
  1772. tft_orientation = rot;
  1773. _tft_setRotation(rot);
  1774. }
  1775. tft_dispWin.x1 = TFT_STATIC_X_OFFSET;
  1776. tft_dispWin.y1 = TFT_STATIC_Y_OFFSET;
  1777. tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1;
  1778. tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1;
  1779. TFT_fillScreen(tft_bg);
  1780. }
  1781. // Send the command to invert all of the colors.
  1782. // Input: i 0 to disable inversion; non-zero to enable inversion
  1783. //==========================================
  1784. void TFT_invertDisplay(const uint8_t mode) {
  1785. if (disp_select() == ESP_OK) {
  1786. if ( mode == INVERT_ON ) disp_spi_transfer_cmd(TFT_INVONN);
  1787. else disp_spi_transfer_cmd(TFT_INVOFF);
  1788. disp_deselect();
  1789. }
  1790. }
  1791. // Select gamma curve
  1792. // Input: gamma = 0~3
  1793. //==================================
  1794. void TFT_setGammaCurve(uint8_t gm) {
  1795. uint8_t gamma_curve = (uint8_t)1 << (gm & (uint8_t)0x03);
  1796. if (disp_select() == ESP_OK) {
  1797. disp_spi_transfer_cmd_data(TFT_CMD_GAMMASET, &gamma_curve, 1);
  1798. disp_deselect();
  1799. }
  1800. }
  1801. //===========================================================
  1802. color_t HSBtoRGB(float _hue, float _sat, float _brightness) {
  1803. float red = 0.0;
  1804. float green = 0.0;
  1805. float blue = 0.0;
  1806. if (_sat == 0.0) {
  1807. red = _brightness;
  1808. green = _brightness;
  1809. blue = _brightness;
  1810. } else {
  1811. if (_hue == 360.0) {
  1812. _hue = 0;
  1813. }
  1814. int slice = (int)(_hue / 60.0);
  1815. float hue_frac = (_hue / 60.0) - slice;
  1816. float aa = _brightness * (1.0 - _sat);
  1817. float bb = _brightness * (1.0 - _sat * hue_frac);
  1818. float cc = _brightness * (1.0 - _sat * (1.0 - hue_frac));
  1819. switch(slice) {
  1820. case 0:
  1821. red = _brightness;
  1822. green = cc;
  1823. blue = aa;
  1824. break;
  1825. case 1:
  1826. red = bb;
  1827. green = _brightness;
  1828. blue = aa;
  1829. break;
  1830. case 2:
  1831. red = aa;
  1832. green = _brightness;
  1833. blue = cc;
  1834. break;
  1835. case 3:
  1836. red = aa;
  1837. green = bb;
  1838. blue = _brightness;
  1839. break;
  1840. case 4:
  1841. red = cc;
  1842. green = aa;
  1843. blue = _brightness;
  1844. break;
  1845. case 5:
  1846. red = _brightness;
  1847. green = aa;
  1848. blue = bb;
  1849. break;
  1850. default:
  1851. red = 0.0;
  1852. green = 0.0;
  1853. blue = 0.0;
  1854. break;
  1855. }
  1856. }
  1857. color_t color;
  1858. color.r = ((uint8_t)(red * 255.0)) & 0xFC;
  1859. color.g = ((uint8_t)(green * 255.0)) & 0xFC;
  1860. color.b = ((uint8_t)(blue * 255.0)) & 0xFC;
  1861. return color;
  1862. }
  1863. //=====================================================================
  1864. void TFT_setclipwin(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
  1865. {
  1866. tft_dispWin.x1 = x1 + TFT_STATIC_X_OFFSET;
  1867. tft_dispWin.y1 = y1 + TFT_STATIC_Y_OFFSET;
  1868. tft_dispWin.x2 = x2 + TFT_STATIC_X_OFFSET;
  1869. tft_dispWin.y2 = y2 + TFT_STATIC_Y_OFFSET;
  1870. if (tft_dispWin.x2 >= tft_width + TFT_STATIC_X_OFFSET) tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1;
  1871. if (tft_dispWin.y2 >= tft_height + TFT_STATIC_Y_OFFSET) tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1;
  1872. if (tft_dispWin.x1 > tft_dispWin.x2) tft_dispWin.x1 = tft_dispWin.x2;
  1873. if (tft_dispWin.y1 > tft_dispWin.y2) tft_dispWin.y1 = tft_dispWin.y2;
  1874. }
  1875. //=====================
  1876. void TFT_resetclipwin()
  1877. {
  1878. tft_dispWin.x2 = tft_width + TFT_STATIC_X_OFFSET -1;
  1879. tft_dispWin.y2 = tft_height + TFT_STATIC_Y_OFFSET -1;
  1880. tft_dispWin.x1 = TFT_STATIC_X_OFFSET;
  1881. tft_dispWin.y1 = TFT_STATIC_Y_OFFSET;
  1882. }
  1883. //==========================================================================
  1884. void set_7seg_font_atrib(uint8_t l, uint8_t w, int outline, color_t color) {
  1885. if (tft_cfont.bitmap != 2) return;
  1886. if (l < 6) l = 6;
  1887. if (l > 40) l = 40;
  1888. if (w < 1) w = 1;
  1889. if (w > (l/2)) w = l/2;
  1890. if (w > 12) w = 12;
  1891. tft_cfont.x_size = l;
  1892. tft_cfont.y_size = w;
  1893. tft_cfont.offset = outline;
  1894. tft_cfont.color = color;
  1895. }
  1896. //==========================================
  1897. int TFT_getfontsize(int *width, int* height)
  1898. {
  1899. if (tft_cfont.bitmap == 1) {
  1900. if (tft_cfont.x_size != 0) *width = tft_cfont.x_size; // fixed width font
  1901. else *width = tft_cfont.max_x_size; // proportional font
  1902. *height = tft_cfont.y_size;
  1903. }
  1904. else if (tft_cfont.bitmap == 2) {
  1905. // 7-segment font
  1906. *width = _7seg_width();
  1907. *height = _7seg_height();
  1908. }
  1909. else {
  1910. *width = 0;
  1911. *height = 0;
  1912. return 0;
  1913. }
  1914. return 1;
  1915. }
  1916. //=====================
  1917. int TFT_getfontheight()
  1918. {
  1919. if (tft_cfont.bitmap == 1) return tft_cfont.y_size; // Bitmap font
  1920. else if (tft_cfont.bitmap == 2) return _7seg_height(); // 7-segment font
  1921. return 0;
  1922. }
  1923. //====================
  1924. void TFT_saveClipWin()
  1925. {
  1926. dispWinTemp.x1 = tft_dispWin.x1;
  1927. dispWinTemp.y1 = tft_dispWin.y1;
  1928. dispWinTemp.x2 = tft_dispWin.x2;
  1929. dispWinTemp.y2 = tft_dispWin.y2;
  1930. }
  1931. //=======================
  1932. void TFT_restoreClipWin()
  1933. {
  1934. tft_dispWin.x1 = dispWinTemp.x1;
  1935. tft_dispWin.y1 = dispWinTemp.y1;
  1936. tft_dispWin.x2 = dispWinTemp.x2;
  1937. tft_dispWin.y2 = dispWinTemp.y2;
  1938. }
  1939. // ================ JPG SUPPORT ================================================
  1940. // User defined device identifier
  1941. typedef struct {
  1942. FILE *fhndl; // File handler for input function
  1943. int x; // image top left point X position
  1944. int y; // image top left point Y position
  1945. uint8_t *membuff; // memory buffer containing the image
  1946. uint32_t bufsize; // size of the memory buffer
  1947. uint32_t bufptr; // memory buffer current position
  1948. color_t *linbuf[2]; // memory buffer used for display output
  1949. uint8_t linbuf_idx;
  1950. } JPGIODEV;
  1951. // User defined call-back function to input JPEG data from file
  1952. //---------------------
  1953. static UINT tjd_input (
  1954. JDEC* jd, // Decompression object
  1955. BYTE* buff, // Pointer to the read buffer (NULL:skip)
  1956. UINT nd // Number of bytes to read/skip from input stream
  1957. )
  1958. {
  1959. int rb = 0;
  1960. // Device identifier for the session (5th argument of jd_prepare function)
  1961. JPGIODEV *dev = (JPGIODEV*)jd->device;
  1962. if (buff) { // Read nd bytes from the input strem
  1963. rb = fread(buff, 1, nd, dev->fhndl);
  1964. return rb; // Returns actual number of bytes read
  1965. }
  1966. else { // Remove nd bytes from the input stream
  1967. if (fseek(dev->fhndl, nd, SEEK_CUR) >= 0) return nd;
  1968. else return 0;
  1969. }
  1970. }
  1971. // User defined call-back function to input JPEG data from memory buffer
  1972. //-------------------------
  1973. static UINT tjd_buf_input (
  1974. JDEC* jd, // Decompression object
  1975. BYTE* buff, // Pointer to the read buffer (NULL:skip)
  1976. UINT nd // Number of bytes to read/skip from input stream
  1977. )
  1978. {
  1979. // Device identifier for the session (5th argument of jd_prepare function)
  1980. JPGIODEV *dev = (JPGIODEV*)jd->device;
  1981. if (!dev->membuff) return 0;
  1982. if (dev->bufptr >= (dev->bufsize + 2)) return 0; // end of stream
  1983. if ((dev->bufptr + nd) > (dev->bufsize + 2)) nd = (dev->bufsize + 2) - dev->bufptr;
  1984. if (buff) { // Read nd bytes from the input strem
  1985. memcpy(buff, dev->membuff + dev->bufptr, nd);
  1986. dev->bufptr += nd;
  1987. return nd; // Returns number of bytes read
  1988. }
  1989. else { // Remove nd bytes from the input stream
  1990. dev->bufptr += nd;
  1991. return nd;
  1992. }
  1993. }
  1994. // User defined call-back function to output RGB bitmap to display device
  1995. //----------------------
  1996. static UINT tjd_output (
  1997. JDEC* jd, // Decompression object of current session
  1998. void* bitmap, // Bitmap data to be output
  1999. JRECT* rect // Rectangular region to output
  2000. )
  2001. {
  2002. // Device identifier for the session (5th argument of jd_prepare function)
  2003. JPGIODEV *dev = (JPGIODEV*)jd->device;
  2004. // ** Put the rectangular into the display device **
  2005. int x;
  2006. int y;
  2007. int dleft, dtop, dright, dbottom;
  2008. BYTE *src = (BYTE*)bitmap;
  2009. int left = rect->left + dev->x;
  2010. int top = rect->top + dev->y;
  2011. int right = rect->right + dev->x;
  2012. int bottom = rect->bottom + dev->y;
  2013. if ((left > tft_dispWin.x2) || (top > tft_dispWin.y2)) return 1; // out of screen area, return
  2014. if ((right < tft_dispWin.x1) || (bottom < tft_dispWin.y1)) return 1;// out of screen area, return
  2015. if (left < tft_dispWin.x1) dleft = tft_dispWin.x1;
  2016. else dleft = left;
  2017. if (top < tft_dispWin.y1) dtop = tft_dispWin.y1;
  2018. else dtop = top;
  2019. if (right > tft_dispWin.x2) dright = tft_dispWin.x2;
  2020. else dright = right;
  2021. if (bottom > tft_dispWin.y2) dbottom = tft_dispWin.y2;
  2022. else dbottom = bottom;
  2023. if ((dleft > tft_dispWin.x2) || (dtop > tft_dispWin.y2)) return 1; // out of screen area, return
  2024. if ((dright < tft_dispWin.x1) || (dbottom < tft_dispWin.y1)) return 1; // out of screen area, return
  2025. uint32_t len = ((dright-dleft+1) * (dbottom-dtop+1)); // calculate length of data
  2026. if ((len > 0) && (len <= JPG_IMAGE_LINE_BUF_SIZE)) {
  2027. uint8_t *dest = (uint8_t *)(dev->linbuf[dev->linbuf_idx]);
  2028. for (y = top; y <= bottom; y++) {
  2029. for (x = left; x <= right; x++) {
  2030. // Clip to display area
  2031. if ((x >= dleft) && (y >= dtop) && (x <= dright) && (y <= dbottom)) {
  2032. *dest++ = (*src++) & 0xFC;
  2033. *dest++ = (*src++) & 0xFC;
  2034. *dest++ = (*src++) & 0xFC;
  2035. }
  2036. else src += 3; // skip
  2037. }
  2038. }
  2039. wait_trans_finish(1);
  2040. send_data(dleft, dtop, dright, dbottom, len, dev->linbuf[dev->linbuf_idx]);
  2041. dev->linbuf_idx = ((dev->linbuf_idx + 1) & 1);
  2042. }
  2043. else {
  2044. wait_trans_finish(1);
  2045. 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);
  2046. return 0; // stop decompression
  2047. }
  2048. return 1; // Continue to decompression
  2049. }
  2050. // tft.jpgimage(X, Y, scale, file_name, buf, size]
  2051. // X & Y can be < 0 !
  2052. //==================================================================================
  2053. void TFT_jpg_image(int x, int y, uint8_t scale, char *fname, uint8_t *buf, int size)
  2054. {
  2055. JPGIODEV dev;
  2056. struct stat sb;
  2057. char *work = NULL; // Pointer to the working buffer (must be 4-byte aligned)
  2058. UINT sz_work = 3800; // Size of the working buffer (must be power of 2)
  2059. JDEC jd; // Decompression object (70 bytes)
  2060. JRESULT rc;
  2061. dev.linbuf[0] = NULL;
  2062. dev.linbuf[1] = NULL;
  2063. dev.linbuf_idx = 0;
  2064. dev.fhndl = NULL;
  2065. if (fname == NULL) {
  2066. // image from buffer
  2067. dev.membuff = buf;
  2068. dev.bufsize = size;
  2069. dev.bufptr = 0;
  2070. }
  2071. else {
  2072. // image from file
  2073. dev.membuff = NULL;
  2074. dev.bufsize = 0;
  2075. dev.bufptr = 0;
  2076. if (stat(fname, &sb) != 0) {
  2077. if (tft_image_debug) printf("File error: %ss\r\n", strerror(errno));
  2078. goto exit;
  2079. }
  2080. dev.fhndl = fopen(fname, "r");
  2081. if (!dev.fhndl) {
  2082. if (tft_image_debug) printf("Error opening file: %s\r\n", strerror(errno));
  2083. goto exit;
  2084. }
  2085. }
  2086. if (scale > 3) scale = 3;
  2087. work = malloc(sz_work);
  2088. if (work) {
  2089. if (dev.membuff) rc = jd_prepare(&jd, tjd_buf_input, (void *)work, sz_work, &dev);
  2090. else rc = jd_prepare(&jd, tjd_input, (void *)work, sz_work, &dev);
  2091. if (rc == JDR_OK) {
  2092. if (x == CENTER) x = ((tft_dispWin.x2 - tft_dispWin.x1 + 1 - (int)(jd.width >> scale)) / 2) + tft_dispWin.x1;
  2093. else if (x == RIGHT) x = tft_dispWin.x2 + 1 - (int)(jd.width >> scale);
  2094. if (y == CENTER) y = ((tft_dispWin.y2 - tft_dispWin.y1 + 1 - (int)(jd.height >> scale)) / 2) + tft_dispWin.y1;
  2095. else if (y == BOTTOM) y = tft_dispWin.y2 + 1 - (int)(jd.height >> scale);
  2096. if (x < ((tft_dispWin.x2-1) * -1)) x = (tft_dispWin.x2-1) * -1;
  2097. if (y < ((tft_dispWin.y2-1)) * -1) y = (tft_dispWin.y2-1) * -1;
  2098. if (x > (tft_dispWin.x2-1)) x = tft_dispWin.x2 - 1;
  2099. if (y > (tft_dispWin.y2-1)) y = tft_dispWin.y2-1;
  2100. dev.x = x;
  2101. dev.y = y;
  2102. dev.linbuf[0] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA);
  2103. if (dev.linbuf[0] == NULL) {
  2104. if (tft_image_debug) printf("Error allocating line buffer #0\r\n");
  2105. goto exit;
  2106. }
  2107. dev.linbuf[1] = heap_caps_malloc(JPG_IMAGE_LINE_BUF_SIZE*3, MALLOC_CAP_DMA);
  2108. if (dev.linbuf[1] == NULL) {
  2109. if (tft_image_debug) printf("Error allocating line buffer #1\r\n");
  2110. goto exit;
  2111. }
  2112. // Start to decode the JPEG file
  2113. disp_select();
  2114. rc = jd_decomp(&jd, tjd_output, scale);
  2115. disp_deselect();
  2116. if (rc != JDR_OK) {
  2117. if (tft_image_debug) printf("jpg decompression error %d\r\n", rc);
  2118. }
  2119. 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);
  2120. }
  2121. else {
  2122. if (tft_image_debug) printf("jpg prepare error %d\r\n", rc);
  2123. }
  2124. }
  2125. else {
  2126. if (tft_image_debug) printf("work buffer allocation error\r\n");
  2127. }
  2128. exit:
  2129. if (work) free(work); // free work buffer
  2130. if (dev.linbuf[0]) free(dev.linbuf[0]);
  2131. if (dev.linbuf[1]) free(dev.linbuf[1]);
  2132. if (dev.fhndl) fclose(dev.fhndl); // close input file
  2133. }
  2134. //====================================================================================
  2135. int TFT_bmp_image(int x, int y, uint8_t scale, char *fname, uint8_t *imgbuf, int size)
  2136. {
  2137. FILE *fhndl = NULL;
  2138. struct stat sb;
  2139. int i, err=0;
  2140. int img_xsize, img_ysize, img_xstart, img_xlen, img_ystart, img_ylen;
  2141. int img_pos, img_pix_pos, scan_lines, rd_len;
  2142. uint8_t tmpc;
  2143. uint16_t wtemp;
  2144. uint32_t temp;
  2145. int disp_xstart, disp_xend, disp_ystart, disp_yend;
  2146. uint8_t buf[56];
  2147. char err_buf[64];
  2148. uint8_t *line_buf[2] = {NULL,NULL};
  2149. uint8_t lb_idx = 0;
  2150. uint8_t *scale_buf = NULL;
  2151. uint8_t scale_pix;
  2152. uint16_t co[3] = {0,0,0}; // RGB sum
  2153. uint8_t npix;
  2154. if (scale > 7) scale = 7;
  2155. scale_pix = scale+1; // scale factor ( 1~8 )
  2156. if (fname) {
  2157. // * File name is given, reading image from file
  2158. if (stat(fname, &sb) != 0) {
  2159. sprintf(err_buf, "opening file");
  2160. err = -1;
  2161. goto exit;
  2162. }
  2163. size = sb.st_size;
  2164. fhndl = fopen(fname, "r");
  2165. if (!fhndl) {
  2166. sprintf(err_buf, "opening file");
  2167. err = -2;
  2168. goto exit;
  2169. }
  2170. i = fread(buf, 1, 54, fhndl); // read header
  2171. }
  2172. else {
  2173. // * Reading image from buffer
  2174. if ((imgbuf) && (size > 54)) {
  2175. memcpy(buf, imgbuf, 54);
  2176. i = 54;
  2177. }
  2178. else i = 0;
  2179. }
  2180. sprintf(err_buf, "reading header");
  2181. if (i != 54) {err = -3; goto exit;}
  2182. // ** Check image header and get image properties
  2183. if ((buf[0] != 'B') || (buf[1] != 'M')) {err=-4; goto exit;} // accept only images with 'BM' id
  2184. memcpy(&temp, buf+2, 4); // file size
  2185. if (temp != size) {err=-5; goto exit;}
  2186. memcpy(&img_pos, buf+10, 4); // start of pixel data
  2187. memcpy(&temp, buf+14, 4); // BMP header size
  2188. if (temp != 40) {err=-6; goto exit;}
  2189. memcpy(&wtemp, buf+26, 2); // the number of color planes
  2190. if (wtemp != 1) {err=-7; goto exit;}
  2191. memcpy(&wtemp, buf+28, 2); // the number of bits per pixel
  2192. if (wtemp != 24) {err=-8; goto exit;}
  2193. memcpy(&temp, buf+30, 4); // the compression method being used
  2194. if (temp != 0) {err=-9; goto exit;}
  2195. memcpy(&img_xsize, buf+18, 4); // the bitmap width in pixels
  2196. memcpy(&img_ysize, buf+22, 4); // the bitmap height in pixels
  2197. // * scale image dimensions
  2198. img_xlen = img_xsize / scale_pix; // image display horizontal size
  2199. img_ylen = img_ysize / scale_pix; // image display vertical size
  2200. if (x == CENTER) x = ((tft_dispWin.x2 - tft_dispWin.x1 + 1 - img_xlen) / 2) + tft_dispWin.x1;
  2201. else if (x == RIGHT) x = tft_dispWin.x2 + 1 - img_xlen;
  2202. if (y == CENTER) y = ((tft_dispWin.y2 - tft_dispWin.y1 + 1 - img_ylen) / 2) + tft_dispWin.y1;
  2203. else if (y == BOTTOM) y = tft_dispWin.y2 + 1 - img_ylen;
  2204. if ((x < ((tft_dispWin.x2 + 1) * -1)) || (x > (tft_dispWin.x2 + 1)) || (y < ((tft_dispWin.y2 + 1) * -1)) || (y > (tft_dispWin.y2 + 1))) {
  2205. sprintf(err_buf, "out of display area (%d,%d", x, y);
  2206. err = -10;
  2207. goto exit;
  2208. }
  2209. // ** set display and image areas
  2210. if (x < tft_dispWin.x1) {
  2211. disp_xstart = tft_dispWin.x1;
  2212. img_xstart = -x; // image pixel line X offset
  2213. img_xlen += x;
  2214. }
  2215. else {
  2216. disp_xstart = x;
  2217. img_xstart = 0;
  2218. }
  2219. if (y < tft_dispWin.y1) {
  2220. disp_ystart = tft_dispWin.y1;
  2221. img_ystart = -y; // image pixel line Y offset
  2222. img_ylen += y;
  2223. }
  2224. else {
  2225. disp_ystart = y;
  2226. img_ystart = 0;
  2227. }
  2228. disp_xend = disp_xstart + img_xlen - 1;
  2229. disp_yend = disp_ystart + img_ylen - 1;
  2230. if (disp_xend > tft_dispWin.x2) {
  2231. disp_xend = tft_dispWin.x2;
  2232. img_xlen = disp_xend - disp_xstart + 1;
  2233. }
  2234. if (disp_yend > tft_dispWin.y2) {
  2235. disp_yend = tft_dispWin.y2;
  2236. img_ylen = disp_yend - disp_ystart + 1;
  2237. }
  2238. if ((img_xlen < 8) || (img_ylen < 8) || (img_xstart >= (img_xsize-2)) || ((img_ysize - img_ystart) < 2)) {
  2239. sprintf(err_buf, "image too small");
  2240. err = -11;
  2241. goto exit;
  2242. }
  2243. // ** Allocate memory for 2 lines of image pixels
  2244. line_buf[0] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA);
  2245. if (line_buf[0] == NULL) {
  2246. sprintf(err_buf, "allocating line buffer #1");
  2247. err=-12;
  2248. goto exit;
  2249. }
  2250. line_buf[1] = heap_caps_malloc(img_xsize*3, MALLOC_CAP_DMA);
  2251. if (line_buf[1] == NULL) {
  2252. sprintf(err_buf, "allocating line buffer #2");
  2253. err=-13;
  2254. goto exit;
  2255. }
  2256. if (scale) {
  2257. // Allocate memory for scale buffer
  2258. rd_len = img_xlen * 3 * scale_pix;
  2259. scale_buf = malloc(rd_len*scale_pix);
  2260. if (scale_buf == NULL) {
  2261. sprintf(err_buf, "allocating scale buffer");
  2262. err=-14;
  2263. goto exit;
  2264. }
  2265. }
  2266. else rd_len = img_xlen * 3;
  2267. // ** ***************************************************** **
  2268. // ** BMP images are stored in file from LAST to FIRST line **
  2269. // ** ***************************************************** **
  2270. /* Used variables:
  2271. img_xsize horizontal image size in pixels
  2272. img_ysize number of image lines
  2273. img_xlen image display horizontal scaled size in pixels
  2274. img_ylen image display vertical scaled size in pixels
  2275. img_xstart first pixel in line to be displayed
  2276. img_ystart first image line to be displayed
  2277. img_xlen number of pixels in image line to be displayed, starting with 'img_xstart'
  2278. img_ylen number of lines in image to be displayed, starting with 'img_ystart'
  2279. rd_len length of color data which are read from image line in bytes
  2280. */
  2281. // Set position in image to the first color data (beginning of the LAST line)
  2282. img_pos += (img_ystart * (img_xsize*3));
  2283. if (fhndl) {
  2284. if (fseek(fhndl, img_pos, SEEK_SET) != 0) {
  2285. sprintf(err_buf, "file seek at %d", img_pos);
  2286. err = -15;
  2287. goto exit;
  2288. }
  2289. }
  2290. 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",
  2291. 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));
  2292. // * Select the display
  2293. disp_select();
  2294. while ((disp_yend >= disp_ystart) && ((img_pos + (img_xsize*3)) <= size)) {
  2295. if (img_pos > size) {
  2296. sprintf(err_buf, "EOF reached: %d > %d", img_pos, size);
  2297. err = -16;
  2298. goto exit1;
  2299. }
  2300. if (scale == 0) {
  2301. // Read the line of color data into color buffer
  2302. if (fhndl) {
  2303. i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file
  2304. if (i != (img_xsize*3)) {
  2305. sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3);
  2306. err = -16;
  2307. goto exit1;
  2308. }
  2309. }
  2310. else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3);
  2311. if (img_xstart > 0) memmove(line_buf[lb_idx], line_buf[lb_idx]+(img_xstart*3), rd_len);
  2312. // Convert colors BGR-888 (BMP) -> RGB-888 (DISPLAY) ===
  2313. for (i=0; i < rd_len; i += 3) {
  2314. tmpc = line_buf[lb_idx][i+2] & 0xfc; // save R
  2315. line_buf[lb_idx][i+2] = line_buf[lb_idx][i] & 0xfc; // B -> R
  2316. line_buf[lb_idx][i] = tmpc; // R -> B
  2317. line_buf[lb_idx][i+1] &= 0xfc; // G
  2318. }
  2319. img_pos += (img_xsize*3);
  2320. }
  2321. else {
  2322. // scale image, read 'scale_pix' lines and find the average color
  2323. for (scan_lines=0; scan_lines<scale_pix; scan_lines++) {
  2324. if (img_pos > size) break;
  2325. if (fhndl) {
  2326. i = fread(line_buf[lb_idx], 1, img_xsize*3, fhndl); // read line from file
  2327. if (i != (img_xsize*3)) {
  2328. sprintf(err_buf, "file read at %d (%d<>%d)", img_pos, i, img_xsize*3);
  2329. err = -17;
  2330. goto exit1;
  2331. }
  2332. }
  2333. else memcpy(line_buf[lb_idx], imgbuf+img_pos, img_xsize*3);
  2334. img_pos += (img_xsize*3);
  2335. // copy only data which are displayed to scale buffer
  2336. memcpy(scale_buf + (rd_len * scan_lines), line_buf[lb_idx]+img_xstart, rd_len);
  2337. }
  2338. // Populate display line buffer
  2339. for (int n=0;n<(img_xlen*3);n += 3) {
  2340. memset(co, 0, sizeof(co)); // initialize color sum
  2341. npix = 0; // initialize number of pixels in scale rectangle
  2342. // sum all pixels in scale rectangle
  2343. for (int sc_line=0; sc_line<scan_lines; sc_line++) {
  2344. // Get colors position in scale buffer
  2345. img_pix_pos = (rd_len * sc_line) + (n * scale_pix);
  2346. for (int sc_col=0; sc_col<scale_pix; sc_col++) {
  2347. co[0] += scale_buf[img_pix_pos];
  2348. co[1] += scale_buf[img_pix_pos + 1];
  2349. co[2] += scale_buf[img_pix_pos + 2];
  2350. npix++;
  2351. }
  2352. }
  2353. // Place the average in display buffer, convert BGR-888 (BMP) -> RGB-888 (DISPLAY)
  2354. line_buf[lb_idx][n+2] = (uint8_t)(co[0] / npix); // B
  2355. line_buf[lb_idx][n+1] = (uint8_t)(co[1] / npix); // G
  2356. line_buf[lb_idx][n] = (uint8_t)(co[2] / npix); // R
  2357. }
  2358. }
  2359. wait_trans_finish(1);
  2360. send_data(disp_xstart, disp_yend, disp_xend, disp_yend, img_xlen, (color_t *)line_buf[lb_idx]);
  2361. lb_idx = (lb_idx + 1) & 1; // change buffer
  2362. disp_yend--;
  2363. }
  2364. err = 0;
  2365. exit1:
  2366. disp_deselect();
  2367. exit:
  2368. if (scale_buf) free(scale_buf);
  2369. if (line_buf[0]) free(line_buf[0]);
  2370. if (line_buf[1]) free(line_buf[1]);
  2371. if (fhndl) fclose(fhndl);
  2372. if ((err) && (tft_image_debug)) printf("Error: %d [%s]\r\n", err, err_buf);
  2373. return err;
  2374. }
  2375. // ============= Touch panel functions =========================================
  2376. #if USE_TOUCH == TOUCH_TYPE_XPT2046
  2377. //-------------------------------------------------------
  2378. static int tp_get_data_xpt2046(uint8_t type, int samples)
  2379. {
  2380. if (tft_ts_spi == NULL) return 0;
  2381. int n, result, val = 0;
  2382. uint32_t i = 0;
  2383. uint32_t vbuf[18];
  2384. uint32_t minval, maxval, dif;
  2385. if (samples < 3) samples = 1;
  2386. if (samples > 18) samples = 18;
  2387. // one dummy read
  2388. result = touch_get_data(type);
  2389. // read data
  2390. while (i < 10) {
  2391. minval = 5000;
  2392. maxval = 0;
  2393. // get values
  2394. for (n=0;n<samples;n++) {
  2395. result = touch_get_data(type);
  2396. if (result < 0) break;
  2397. vbuf[n] = result;
  2398. if (result < minval) minval = result;
  2399. if (result > maxval) maxval = result;
  2400. }
  2401. if (result < 0) break;
  2402. dif = maxval - minval;
  2403. if (dif < 40) break;
  2404. i++;
  2405. }
  2406. if (result < 0) return -1;
  2407. if (samples > 2) {
  2408. // remove one min value
  2409. for (n = 0; n < samples; n++) {
  2410. if (vbuf[n] == minval) {
  2411. vbuf[n] = 5000;
  2412. break;
  2413. }
  2414. }
  2415. // remove one max value
  2416. for (n = 0; n < samples; n++) {
  2417. if (vbuf[n] == maxval) {
  2418. vbuf[n] = 5000;
  2419. break;
  2420. }
  2421. }
  2422. for (n = 0; n < samples; n++) {
  2423. if (vbuf[n] < 5000) val += vbuf[n];
  2424. }
  2425. val /= (samples-2);
  2426. }
  2427. else val = vbuf[0];
  2428. return val;
  2429. }
  2430. //-----------------------------------------------
  2431. static int TFT_read_touch_xpt2046(int *x, int* y)
  2432. {
  2433. int res = 0, result = -1;
  2434. if (spi_lobo_device_select(tft_ts_spi, 0) != ESP_OK) return 0;
  2435. result = tp_get_data_xpt2046(0xB0, 3); // Z; pressure; touch detect
  2436. if (result <= 50) goto exit;
  2437. // touch panel pressed
  2438. result = tp_get_data_xpt2046(0xD0, 10);
  2439. if (result < 0) goto exit;
  2440. *x = result;
  2441. result = tp_get_data_xpt2046(0x90, 10);
  2442. if (result < 0) goto exit;
  2443. *y = result;
  2444. res = 1;
  2445. exit:
  2446. spi_lobo_device_deselect(tft_ts_spi);
  2447. return res;
  2448. }
  2449. #endif
  2450. //=============================================
  2451. int TFT_read_touch(int *x, int* y, uint8_t raw)
  2452. {
  2453. *x = 0;
  2454. *y = 0;
  2455. if (tft_ts_spi == NULL) return 0;
  2456. #if USE_TOUCH == TOUCH_TYPE_NONE
  2457. return 0;
  2458. #else
  2459. int result = -1;
  2460. int X=0, Y=0;
  2461. #if USE_TOUCH == TOUCH_TYPE_XPT2046
  2462. uint32_t tft_tp_calx = TP_CALX_XPT2046;
  2463. uint32_t tft_tp_caly = TP_CALY_XPT2046;
  2464. result = TFT_read_touch_xpt2046(&X, &Y);
  2465. if (result == 0) return 0;
  2466. #elif USE_TOUCH == TOUCH_TYPE_STMPE610
  2467. uint32_t tft_tp_calx = TP_CALX_STMPE610;
  2468. uint32_t tft_tp_caly = TP_CALY_STMPE610;
  2469. uint16_t Xx, Yy, Z=0;
  2470. result = stmpe610_get_touch(&Xx, &Yy, &Z);
  2471. if (result == 0) return 0;
  2472. X = Xx;
  2473. Y = Yy;
  2474. #else
  2475. return 0;
  2476. #endif
  2477. if (raw) {
  2478. *x = X;
  2479. *y = Y;
  2480. return 1;
  2481. }
  2482. // Calibrate the result
  2483. int tmp;
  2484. int xleft = (tft_tp_calx >> 16) & 0x3FFF;
  2485. int xright = tft_tp_calx & 0x3FFF;
  2486. int ytop = (tft_tp_caly >> 16) & 0x3FFF;
  2487. int ybottom = tft_tp_caly & 0x3FFF;
  2488. if (((xright - xleft) <= 0) || ((ybottom - ytop) <= 0)) return 0;
  2489. #if USE_TOUCH == TOUCH_TYPE_XPT2046
  2490. int width = tft_width;
  2491. int height = tft_height;
  2492. X = ((X - xleft) * height) / (xright - xleft);
  2493. Y = ((Y - ytop) * width) / (ybottom - ytop);
  2494. if (X < 0) X = 0;
  2495. if (X > height-1) X = height-1;
  2496. if (Y < 0) Y = 0;
  2497. if (Y > width-1) Y = width-1;
  2498. switch (tft_orientation) {
  2499. case PORTRAIT:
  2500. tmp = X;
  2501. X = width - Y - 1;
  2502. Y = tmp;
  2503. break;
  2504. case PORTRAIT_FLIP:
  2505. tmp = X;
  2506. X = Y;
  2507. Y = height - tmp - 1;
  2508. break;
  2509. case LANDSCAPE_FLIP:
  2510. X = height - X - 1;
  2511. Y = width - Y - 1;
  2512. break;
  2513. }
  2514. #elif USE_TOUCH == TOUCH_TYPE_STMPE610
  2515. int width = tft_width;
  2516. int height = tft_height;
  2517. if (tft_width > tft_height) {
  2518. width = tft_height;
  2519. height = tft_width;
  2520. }
  2521. X = ((X - xleft) * width) / (xright - xleft);
  2522. Y = ((Y - ytop) * height) / (ybottom - ytop);
  2523. if (X < 0) X = 0;
  2524. if (X > width-1) X = width-1;
  2525. if (Y < 0) Y = 0;
  2526. if (Y > height-1) Y = height-1;
  2527. switch (tft_orientation) {
  2528. case PORTRAIT_FLIP:
  2529. X = width - X - 1;
  2530. Y = height - Y - 1;
  2531. break;
  2532. case LANDSCAPE:
  2533. tmp = X;
  2534. X = Y;
  2535. Y = width - tmp -1;
  2536. break;
  2537. case LANDSCAPE_FLIP:
  2538. tmp = X;
  2539. X = height - Y -1;
  2540. Y = tmp;
  2541. break;
  2542. }
  2543. #endif
  2544. *x = X;
  2545. *y = Y;
  2546. return 1;
  2547. #endif
  2548. }