graphics.cxx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. #include "graphics.h"
  2. #define SCALE 10
  3. namespace {
  4. // Another algo - https://www.research-collection.ethz.ch/handle/20.500.11850/68976
  5. /*
  6. * Thick line methods courtesy
  7. * https://github.com/ArminJo/Arduino-BlueDisplay/blob/master/src/LocalGUI/ThickLine.hpp
  8. */
  9. const int LOCAL_DISPLAY_WIDTH = 64;
  10. const int LOCAL_DISPLAY_HEIGHT = 128;
  11. /*
  12. * Overlap means drawing additional pixel when changing minor direction
  13. * Needed for drawThickLine, otherwise some pixels will be missing in the thick line
  14. */
  15. const int LINE_OVERLAP_NONE = 0; // No line overlap, like in standard Bresenham
  16. const int LINE_OVERLAP_MAJOR =
  17. 0x01; // Overlap - first go major then minor direction. Pixel is drawn as extension after actual line
  18. const int LINE_OVERLAP_MINOR =
  19. 0x02; // Overlap - first go minor then major direction. Pixel is drawn as extension before next line
  20. const int LINE_OVERLAP_BOTH = 0x03; // Overlap - both
  21. const int LINE_THICKNESS_MIDDLE = 0; // Start point is on the line at center of the thick line
  22. const int LINE_THICKNESS_DRAW_CLOCKWISE = 1; // Start point is on the counter clockwise border line
  23. const int LINE_THICKNESS_DRAW_COUNTERCLOCKWISE = 2; // Start point is on the clockwise border line
  24. /**
  25. * Draws a line from aXStart/aYStart to aXEnd/aYEnd including both ends
  26. * @param aOverlap One of LINE_OVERLAP_NONE, LINE_OVERLAP_MAJOR, LINE_OVERLAP_MINOR, LINE_OVERLAP_BOTH
  27. */
  28. void drawLineOverlap(
  29. Canvas* canvas,
  30. unsigned int aXStart,
  31. unsigned int aYStart,
  32. unsigned int aXEnd,
  33. unsigned int aYEnd,
  34. uint8_t aOverlap) {
  35. int16_t tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
  36. /*
  37. * Clip to display size
  38. */
  39. if(aXStart >= LOCAL_DISPLAY_WIDTH) {
  40. aXStart = LOCAL_DISPLAY_WIDTH - 1;
  41. }
  42. if(aXEnd >= LOCAL_DISPLAY_WIDTH) {
  43. aXEnd = LOCAL_DISPLAY_WIDTH - 1;
  44. }
  45. if(aYStart >= LOCAL_DISPLAY_HEIGHT) {
  46. aYStart = LOCAL_DISPLAY_HEIGHT - 1;
  47. }
  48. if(aYEnd >= LOCAL_DISPLAY_HEIGHT) {
  49. aYEnd = LOCAL_DISPLAY_HEIGHT - 1;
  50. }
  51. if((aXStart == aXEnd) || (aYStart == aYEnd)) {
  52. // horizontal or vertical line -> fillRect() is faster than drawLine()
  53. // fillRect(
  54. // aXStart,
  55. // aYStart,
  56. // aXEnd,
  57. // aYEnd,
  58. // aColor); // you can remove the check and this line if you have no fillRect() or drawLine() available.
  59. canvas_draw_box(canvas, aXStart, aYStart, aXEnd - aXStart, aYEnd - aYStart);
  60. } else {
  61. // calculate direction
  62. tDeltaX = aXEnd - aXStart;
  63. tDeltaY = aYEnd - aYStart;
  64. if(tDeltaX < 0) {
  65. tDeltaX = -tDeltaX;
  66. tStepX = -1;
  67. } else {
  68. tStepX = +1;
  69. }
  70. if(tDeltaY < 0) {
  71. tDeltaY = -tDeltaY;
  72. tStepY = -1;
  73. } else {
  74. tStepY = +1;
  75. }
  76. tDeltaXTimes2 = tDeltaX << 1;
  77. tDeltaYTimes2 = tDeltaY << 1;
  78. // draw start pixel
  79. // drawPixel(aXStart, aYStart, aColor);
  80. canvas_draw_dot(canvas, aXStart, aYStart);
  81. if(tDeltaX > tDeltaY) {
  82. // start value represents a half step in Y direction
  83. tError = tDeltaYTimes2 - tDeltaX;
  84. while(aXStart != aXEnd) {
  85. // step in main direction
  86. aXStart += tStepX;
  87. if(tError >= 0) {
  88. if(aOverlap & LINE_OVERLAP_MAJOR) {
  89. // draw pixel in main direction before changing
  90. // drawPixel(aXStart, aYStart, aColor);
  91. canvas_draw_dot(canvas, aXStart, aYStart);
  92. }
  93. // change Y
  94. aYStart += tStepY;
  95. if(aOverlap & LINE_OVERLAP_MINOR) {
  96. // draw pixel in minor direction before changing
  97. // drawPixel(aXStart - tStepX, aYStart, aColor);
  98. canvas_draw_dot(canvas, aXStart - tStepX, aYStart);
  99. }
  100. tError -= tDeltaXTimes2;
  101. }
  102. tError += tDeltaYTimes2;
  103. // drawPixel(aXStart, aYStart, aColor);
  104. canvas_draw_dot(canvas, aXStart, aYStart);
  105. }
  106. } else {
  107. tError = tDeltaXTimes2 - tDeltaY;
  108. while(aYStart != aYEnd) {
  109. aYStart += tStepY;
  110. if(tError >= 0) {
  111. if(aOverlap & LINE_OVERLAP_MAJOR) {
  112. // draw pixel in main direction before changing
  113. // drawPixel(aXStart, aYStart, aColor);
  114. canvas_draw_dot(canvas, aXStart, aYStart);
  115. }
  116. aXStart += tStepX;
  117. if(aOverlap & LINE_OVERLAP_MINOR) {
  118. // draw pixel in minor direction before changing
  119. // drawPixel(aXStart, aYStart - tStepY, aColor);
  120. canvas_draw_dot(canvas, aXStart, aYStart - tStepY);
  121. }
  122. tError -= tDeltaYTimes2;
  123. }
  124. tError += tDeltaXTimes2;
  125. // drawPixel(aXStart, aYStart, aColor);
  126. canvas_draw_dot(canvas, aXStart, aYStart);
  127. }
  128. }
  129. }
  130. }
  131. /**
  132. * Bresenham with thickness
  133. * No pixel missed and every pixel only drawn once!
  134. * The code is bigger and more complicated than drawThickLineSimple() but it tends to be faster, since drawing a pixel is often a slow operation.
  135. * aThicknessMode can be one of LINE_THICKNESS_MIDDLE, LINE_THICKNESS_DRAW_CLOCKWISE, LINE_THICKNESS_DRAW_COUNTERCLOCKWISE
  136. */
  137. void drawThickLine(
  138. Canvas* canvas,
  139. unsigned int aXStart,
  140. unsigned int aYStart,
  141. unsigned int aXEnd,
  142. unsigned int aYEnd,
  143. unsigned int aThickness,
  144. uint8_t aThicknessMode) {
  145. int16_t i, tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
  146. if(aThickness <= 1) {
  147. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE);
  148. }
  149. /*
  150. * Clip to display size
  151. */
  152. if(aXStart >= LOCAL_DISPLAY_WIDTH) {
  153. aXStart = LOCAL_DISPLAY_WIDTH - 1;
  154. }
  155. if(aXEnd >= LOCAL_DISPLAY_WIDTH) {
  156. aXEnd = LOCAL_DISPLAY_WIDTH - 1;
  157. }
  158. if(aYStart >= LOCAL_DISPLAY_HEIGHT) {
  159. aYStart = LOCAL_DISPLAY_HEIGHT - 1;
  160. }
  161. if(aYEnd >= LOCAL_DISPLAY_HEIGHT) {
  162. aYEnd = LOCAL_DISPLAY_HEIGHT - 1;
  163. }
  164. /**
  165. * For coordinate system with 0.0 top left
  166. * Swap X and Y delta and calculate clockwise (new delta X inverted)
  167. * or counterclockwise (new delta Y inverted) rectangular direction.
  168. * The right rectangular direction for LINE_OVERLAP_MAJOR toggles with each octant
  169. */
  170. tDeltaY = aXEnd - aXStart;
  171. tDeltaX = aYEnd - aYStart;
  172. // mirror 4 quadrants to one and adjust deltas and stepping direction
  173. bool tSwap = true; // count effective mirroring
  174. if(tDeltaX < 0) {
  175. tDeltaX = -tDeltaX;
  176. tStepX = -1;
  177. tSwap = !tSwap;
  178. } else {
  179. tStepX = +1;
  180. }
  181. if(tDeltaY < 0) {
  182. tDeltaY = -tDeltaY;
  183. tStepY = -1;
  184. tSwap = !tSwap;
  185. } else {
  186. tStepY = +1;
  187. }
  188. tDeltaXTimes2 = tDeltaX << 1;
  189. tDeltaYTimes2 = tDeltaY << 1;
  190. bool tOverlap;
  191. // adjust for right direction of thickness from line origin
  192. int tDrawStartAdjustCount = aThickness / 2;
  193. if(aThicknessMode == LINE_THICKNESS_DRAW_COUNTERCLOCKWISE) {
  194. tDrawStartAdjustCount = aThickness - 1;
  195. } else if(aThicknessMode == LINE_THICKNESS_DRAW_CLOCKWISE) {
  196. tDrawStartAdjustCount = 0;
  197. }
  198. /*
  199. * Now tDelta* are positive and tStep* define the direction
  200. * tSwap is false if we mirrored only once
  201. */
  202. // which octant are we now
  203. if(tDeltaX >= tDeltaY) {
  204. // Octant 1, 3, 5, 7 (between 0 and 45, 90 and 135, ... degree)
  205. if(tSwap) {
  206. tDrawStartAdjustCount = (aThickness - 1) - tDrawStartAdjustCount;
  207. tStepY = -tStepY;
  208. } else {
  209. tStepX = -tStepX;
  210. }
  211. /*
  212. * Vector for draw direction of the starting points of lines is rectangular and counterclockwise to main line direction
  213. * Therefore no pixel will be missed if LINE_OVERLAP_MAJOR is used on change in minor rectangular direction
  214. */
  215. // adjust draw start point
  216. tError = tDeltaYTimes2 - tDeltaX;
  217. for(i = tDrawStartAdjustCount; i > 0; i--) {
  218. // change X (main direction here)
  219. aXStart -= tStepX;
  220. aXEnd -= tStepX;
  221. if(tError >= 0) {
  222. // change Y
  223. aYStart -= tStepY;
  224. aYEnd -= tStepY;
  225. tError -= tDeltaXTimes2;
  226. }
  227. tError += tDeltaYTimes2;
  228. }
  229. // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
  230. // drawLine(aXStart, aYStart, aXEnd, aYEnd);
  231. // canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
  232. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE);
  233. // draw aThickness number of lines
  234. tError = tDeltaYTimes2 - tDeltaX;
  235. for(i = aThickness; i > 1; i--) {
  236. // change X (main direction here)
  237. aXStart += tStepX;
  238. aXEnd += tStepX;
  239. tOverlap = LINE_OVERLAP_NONE;
  240. if(tError >= 0) {
  241. // change Y
  242. aYStart += tStepY;
  243. aYEnd += tStepY;
  244. tError -= tDeltaXTimes2;
  245. /*
  246. * Change minor direction reverse to line (main) direction
  247. * because of choosing the right (counter)clockwise draw vector
  248. * Use LINE_OVERLAP_MAJOR to fill all pixel
  249. *
  250. * EXAMPLE:
  251. * 1,2 = Pixel of first 2 lines
  252. * 3 = Pixel of third line in normal line mode
  253. * - = Pixel which will additionally be drawn in LINE_OVERLAP_MAJOR mode
  254. * 33
  255. * 3333-22
  256. * 3333-222211
  257. * 33-22221111
  258. * 221111 ^
  259. * 11 Main direction of start of lines draw vector
  260. * -> Line main direction
  261. * <- Minor direction of counterclockwise of start of lines draw vector
  262. */
  263. tOverlap = LINE_OVERLAP_MAJOR;
  264. }
  265. tError += tDeltaYTimes2;
  266. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
  267. }
  268. } else {
  269. // the other octant 2, 4, 6, 8 (between 45 and 90, 135 and 180, ... degree)
  270. if(tSwap) {
  271. tStepX = -tStepX;
  272. } else {
  273. tDrawStartAdjustCount = (aThickness - 1) - tDrawStartAdjustCount;
  274. tStepY = -tStepY;
  275. }
  276. // adjust draw start point
  277. tError = tDeltaXTimes2 - tDeltaY;
  278. for(i = tDrawStartAdjustCount; i > 0; i--) {
  279. aYStart -= tStepY;
  280. aYEnd -= tStepY;
  281. if(tError >= 0) {
  282. aXStart -= tStepX;
  283. aXEnd -= tStepX;
  284. tError -= tDeltaYTimes2;
  285. }
  286. tError += tDeltaXTimes2;
  287. }
  288. //draw start line
  289. // drawLine(aXStart, aYStart, aXEnd, aYEnd);
  290. canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
  291. // draw aThickness number of lines
  292. tError = tDeltaXTimes2 - tDeltaY;
  293. for(i = aThickness; i > 1; i--) {
  294. aYStart += tStepY;
  295. aYEnd += tStepY;
  296. tOverlap = LINE_OVERLAP_NONE;
  297. if(tError >= 0) {
  298. aXStart += tStepX;
  299. aXEnd += tStepX;
  300. tError -= tDeltaYTimes2;
  301. tOverlap = LINE_OVERLAP_MAJOR;
  302. }
  303. tError += tDeltaXTimes2;
  304. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
  305. }
  306. }
  307. }
  308. /**
  309. * The same as before, but no clipping to display range, some pixel are drawn twice (because of using LINE_OVERLAP_BOTH)
  310. * and direction of thickness changes for each octant (except for LINE_THICKNESS_MIDDLE and aThickness value is odd)
  311. * aThicknessMode can be LINE_THICKNESS_MIDDLE or any other value
  312. *
  313. */
  314. /*
  315. void drawThickLineSimple(
  316. Canvas* canvas,
  317. unsigned int aXStart,
  318. unsigned int aYStart,
  319. unsigned int aXEnd,
  320. unsigned int aYEnd,
  321. unsigned int aThickness,
  322. uint8_t aThicknessMode) {
  323. int16_t i, tDeltaX, tDeltaY, tDeltaXTimes2, tDeltaYTimes2, tError, tStepX, tStepY;
  324. tDeltaY = aXStart - aXEnd;
  325. tDeltaX = aYEnd - aYStart;
  326. // mirror 4 quadrants to one and adjust deltas and stepping direction
  327. if(tDeltaX < 0) {
  328. tDeltaX = -tDeltaX;
  329. tStepX = -1;
  330. } else {
  331. tStepX = +1;
  332. }
  333. if(tDeltaY < 0) {
  334. tDeltaY = -tDeltaY;
  335. tStepY = -1;
  336. } else {
  337. tStepY = +1;
  338. }
  339. tDeltaXTimes2 = tDeltaX << 1;
  340. tDeltaYTimes2 = tDeltaY << 1;
  341. bool tOverlap;
  342. // which octant are we now
  343. if(tDeltaX > tDeltaY) {
  344. if(aThicknessMode == LINE_THICKNESS_MIDDLE) {
  345. // adjust draw start point
  346. tError = tDeltaYTimes2 - tDeltaX;
  347. for(i = aThickness / 2; i > 0; i--) {
  348. // change X (main direction here)
  349. aXStart -= tStepX;
  350. aXEnd -= tStepX;
  351. if(tError >= 0) {
  352. // change Y
  353. aYStart -= tStepY;
  354. aYEnd -= tStepY;
  355. tError -= tDeltaXTimes2;
  356. }
  357. tError += tDeltaYTimes2;
  358. }
  359. }
  360. // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
  361. // drawLine(aXStart, aYStart, aXEnd, aYEnd, aColor);
  362. canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
  363. // draw aThickness lines
  364. tError = tDeltaYTimes2 - tDeltaX;
  365. for(i = aThickness; i > 1; i--) {
  366. // change X (main direction here)
  367. aXStart += tStepX;
  368. aXEnd += tStepX;
  369. tOverlap = LINE_OVERLAP_NONE;
  370. if(tError >= 0) {
  371. // change Y
  372. aYStart += tStepY;
  373. aYEnd += tStepY;
  374. tError -= tDeltaXTimes2;
  375. tOverlap = LINE_OVERLAP_BOTH;
  376. }
  377. tError += tDeltaYTimes2;
  378. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
  379. }
  380. } else {
  381. // adjust draw start point
  382. if(aThicknessMode == LINE_THICKNESS_MIDDLE) {
  383. tError = tDeltaXTimes2 - tDeltaY;
  384. for(i = aThickness / 2; i > 0; i--) {
  385. aYStart -= tStepY;
  386. aYEnd -= tStepY;
  387. if(tError >= 0) {
  388. aXStart -= tStepX;
  389. aXEnd -= tStepX;
  390. tError -= tDeltaYTimes2;
  391. }
  392. tError += tDeltaXTimes2;
  393. }
  394. }
  395. // draw start line. We can alternatively use drawLineOverlap(aXStart, aYStart, aXEnd, aYEnd, LINE_OVERLAP_NONE, aColor) here.
  396. // drawLine(aXStart, aYStart, aXEnd, aYEnd, aColor);
  397. canvas_draw_line(canvas, aXStart, aYStart, aXEnd, aYEnd);
  398. tError = tDeltaXTimes2 - tDeltaY;
  399. for(i = aThickness; i > 1; i--) {
  400. aYStart += tStepY;
  401. aYEnd += tStepY;
  402. tOverlap = LINE_OVERLAP_NONE;
  403. if(tError >= 0) {
  404. aXStart += tStepX;
  405. aXEnd += tStepX;
  406. tError -= tDeltaYTimes2;
  407. tOverlap = LINE_OVERLAP_BOTH;
  408. }
  409. tError += tDeltaXTimes2;
  410. drawLineOverlap(canvas, aXStart, aYStart, aXEnd, aYEnd, tOverlap);
  411. }
  412. }
  413. }
  414. */
  415. }; // namespace
  416. /*
  417. Fontname: micro
  418. Copyright: Public domain font. Share and enjoy.
  419. Glyphs: 18/128
  420. BBX Build Mode: 0
  421. */
  422. const uint8_t u8g2_font_micro_tn[148] =
  423. "\22\0\2\3\2\3\1\4\4\3\5\0\0\5\0\5\0\0\0\0\0\0w \4`\63*\10\67\62Q"
  424. "j\312\0+\7or\321\24\1,\5*r\3-\5\247\62\3.\5*\62\4/\10\67\262\251\60\12"
  425. "\1\60\10\67r)U\12\0\61\6\66rS\6\62\7\67\62r\224\34\63\7\67\62r$\22\64\7\67"
  426. "\62\221\212\14\65\7\67\62\244<\1\66\6\67r#E\67\10\67\62c*\214\0\70\6\67\62TE\71"
  427. "\7\67\62\24\71\1:\6\66\62$\1\0\0\0\4\377\377\0";
  428. // TODO: allow points to be located outside the canvas. currently, the canvas_* methods
  429. // choke on this in some cases, resulting in large vertical/horizontal lines
  430. void gfx_draw_line(Canvas* canvas, float x1, float y1, float x2, float y2) {
  431. canvas_draw_line(
  432. canvas, roundf(x1 / SCALE), roundf(y1 / SCALE), roundf(x2 / SCALE), roundf(y2 / SCALE));
  433. }
  434. void gfx_draw_line(Canvas* canvas, const Vec2& p1, const Vec2& p2) {
  435. gfx_draw_line(canvas, p1.x, p1.y, p2.x, p2.y);
  436. }
  437. void gfx_draw_line_thick(Canvas* canvas, float x1, float y1, float x2, float y2, int thickness) {
  438. x1 = roundf(x1 / SCALE);
  439. y1 = roundf(y1 / SCALE);
  440. x2 = roundf(x2 / SCALE);
  441. y2 = roundf(y2 / SCALE);
  442. drawThickLine(canvas, x1, y1, x2, y2, thickness, LINE_THICKNESS_MIDDLE);
  443. }
  444. void gfx_draw_line_thick(Canvas* canvas, const Vec2& p1, const Vec2& p2, int thickness) {
  445. gfx_draw_line_thick(canvas, p1.x, p1.y, p2.x, p2.y, thickness);
  446. }
  447. void gfx_draw_disc(Canvas* canvas, float x, float y, float r) {
  448. canvas_draw_disc(canvas, roundf(x / SCALE), roundf(y / SCALE), roundf(r / SCALE));
  449. }
  450. void gfx_draw_disc(Canvas* canvas, const Vec2& p, float r) {
  451. gfx_draw_disc(canvas, p.x, p.y, r);
  452. }
  453. void gfx_draw_circle(Canvas* canvas, float x, float y, float r) {
  454. canvas_draw_circle(canvas, roundf(x / SCALE), roundf(y / SCALE), roundf(r / SCALE));
  455. }
  456. void gfx_draw_circle(Canvas* canvas, const Vec2& p, float r) {
  457. gfx_draw_circle(canvas, p.x, p.y, r);
  458. }
  459. void gfx_draw_dot(Canvas* canvas, float x, float y) {
  460. canvas_draw_dot(canvas, roundf(x / SCALE), roundf(y / SCALE));
  461. }
  462. void gfx_draw_dot(Canvas* canvas, const Vec2& p) {
  463. gfx_draw_dot(canvas, p.x, p.y);
  464. }
  465. void gfx_draw_arc(Canvas* canvas, const Vec2& p, float r, float start, float end) {
  466. float adj_end = end;
  467. if(end < start) {
  468. adj_end += (float)M_PI * 2;
  469. }
  470. // initialize to start of arc
  471. float sx = p.x + r * cosf(start);
  472. float sy = p.y - r * sinf(start);
  473. size_t segments = r / 8;
  474. for(size_t i = 1; i <= segments; i++) { // for now, use r to determin number of segments
  475. float nx = p.x + r * cosf(start + i / (segments / (adj_end - start)));
  476. float ny = p.y - r * sinf(start + i / (segments / (adj_end - start)));
  477. gfx_draw_line(canvas, sx, sy, nx, ny);
  478. sx = nx;
  479. sy = ny;
  480. }
  481. }
  482. void gfx_draw_str(Canvas* canvas, int x, int y, Align h, Align v, const char* str) {
  483. canvas_set_custom_u8g2_font(canvas, u8g2_font_micro_tn);
  484. canvas_set_color(canvas, ColorWhite);
  485. int w = canvas_string_width(canvas, str);
  486. if(h == AlignRight) {
  487. canvas_draw_box(canvas, x - 1 - w, y, w + 2, 6);
  488. } else if(h == AlignLeft) {
  489. canvas_draw_box(canvas, x - 1, y, w + 2, 6);
  490. }
  491. canvas_set_color(canvas, ColorBlack);
  492. canvas_draw_str_aligned(canvas, x, y, h, v, str);
  493. canvas_set_font(canvas, FontSecondary); // reset?
  494. }