text-scroller.cc 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
  2. // Copyright (C) 2015 Henner Zeller <h.zeller@acm.org>
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation version 2.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program. If not, see <http://gnu.org/licenses/gpl-2.0.txt>
  15. #include "led-matrix.h"
  16. #include "graphics.h"
  17. #include <string>
  18. #include <getopt.h>
  19. #include <math.h>
  20. #include <signal.h>
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <time.h>
  25. #include <unistd.h>
  26. using namespace rgb_matrix;
  27. volatile bool interrupt_received = false;
  28. static void InterruptHandler(int signo) {
  29. interrupt_received = true;
  30. }
  31. static int usage(const char *progname) {
  32. fprintf(stderr, "usage: %s [options] <text>\n", progname);
  33. fprintf(stderr, "Takes text and scrolls it with speed -s\n");
  34. fprintf(stderr, "Options:\n");
  35. fprintf(stderr,
  36. "\t-f <font-file> : Path to *.bdf-font to be used.\n"
  37. "\t-s <speed> : Approximate letters per second. \n"
  38. "\t Positive: scroll right to left; Negative: scroll left to right\n"
  39. "\t (Zero for no scrolling)\n"
  40. "\t-l <loop-count> : Number of loops through the text. "
  41. "-1 for endless (default)\n"
  42. "\t-b <on-time>,<off-time> : Blink while scrolling. Keep "
  43. "on and off for these amount of scrolled pixels.\n"
  44. "\t-x <x-origin> : Shift X-Origin of displaying text (Default: 0)\n"
  45. "\t-y <y-origin> : Shift Y-Origin of displaying text (Default: 0)\n"
  46. "\t-t <track-spacing>: Spacing pixels between letters (Default: 0)\n"
  47. "\n"
  48. "\t-C <r,g,b> : Text Color. Default 255,255,255 (white)\n"
  49. "\t-B <r,g,b> : Background-Color. Default 0,0,0\n"
  50. "\t-O <r,g,b> : Outline-Color, e.g. to increase contrast.\n"
  51. );
  52. fprintf(stderr, "\nGeneral LED matrix options:\n");
  53. rgb_matrix::PrintMatrixFlags(stderr);
  54. return 1;
  55. }
  56. static bool parseColor(Color *c, const char *str) {
  57. return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
  58. }
  59. static bool FullSaturation(const Color &c) {
  60. return (c.r == 0 || c.r == 255)
  61. && (c.g == 0 || c.g == 255)
  62. && (c.b == 0 || c.b == 255);
  63. }
  64. static void add_micros(struct timespec *accumulator, long micros) {
  65. const long billion = 1000000000;
  66. const int64_t nanos = (int64_t) micros * 1000;
  67. accumulator->tv_sec += nanos / billion;
  68. accumulator->tv_nsec += nanos % billion;
  69. while (accumulator->tv_nsec > billion) {
  70. accumulator->tv_nsec -= billion;
  71. accumulator->tv_sec += 1;
  72. }
  73. }
  74. int main(int argc, char *argv[]) {
  75. RGBMatrix::Options matrix_options;
  76. rgb_matrix::RuntimeOptions runtime_opt;
  77. if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
  78. &matrix_options, &runtime_opt)) {
  79. return usage(argv[0]);
  80. }
  81. Color color(255, 255, 255);
  82. Color bg_color(0, 0, 0);
  83. Color outline_color(0,0,0);
  84. bool with_outline = false;
  85. const char *bdf_font_file = NULL;
  86. std::string line;
  87. bool xorigin_configured = false;
  88. int x_orig = 0;
  89. int y_orig = 0;
  90. int letter_spacing = 0;
  91. float speed = 7.0f;
  92. int loops = -1;
  93. int blink_on = 0;
  94. int blink_off = 0;
  95. int opt;
  96. while ((opt = getopt(argc, argv, "x:y:f:C:B:O:t:s:l:b:")) != -1) {
  97. switch (opt) {
  98. case 's': speed = atof(optarg); break;
  99. case 'b':
  100. if (sscanf(optarg, "%d,%d", &blink_on, &blink_off) == 1) {
  101. blink_off = blink_on;
  102. }
  103. fprintf(stderr, "hz: on=%d off=%d\n", blink_on, blink_off);
  104. break;
  105. case 'l': loops = atoi(optarg); break;
  106. case 'x': x_orig = atoi(optarg); xorigin_configured = true; break;
  107. case 'y': y_orig = atoi(optarg); break;
  108. case 'f': bdf_font_file = strdup(optarg); break;
  109. case 't': letter_spacing = atoi(optarg); break;
  110. case 'C':
  111. if (!parseColor(&color, optarg)) {
  112. fprintf(stderr, "Invalid color spec: %s\n", optarg);
  113. return usage(argv[0]);
  114. }
  115. break;
  116. case 'B':
  117. if (!parseColor(&bg_color, optarg)) {
  118. fprintf(stderr, "Invalid background color spec: %s\n", optarg);
  119. return usage(argv[0]);
  120. }
  121. break;
  122. case 'O':
  123. if (!parseColor(&outline_color, optarg)) {
  124. fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
  125. return usage(argv[0]);
  126. }
  127. with_outline = true;
  128. break;
  129. default:
  130. return usage(argv[0]);
  131. }
  132. }
  133. for (int i = optind; i < argc; ++i) {
  134. line.append(argv[i]).append(" ");
  135. }
  136. if (line.empty()) {
  137. fprintf(stderr, "Add the text you want to print on the command-line.\n");
  138. return usage(argv[0]);
  139. }
  140. if (bdf_font_file == NULL) {
  141. fprintf(stderr, "Need to specify BDF font-file with -f\n");
  142. return usage(argv[0]);
  143. }
  144. /*
  145. * Load font. This needs to be a filename with a bdf bitmap font.
  146. */
  147. rgb_matrix::Font font;
  148. if (!font.LoadFont(bdf_font_file)) {
  149. fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
  150. return 1;
  151. }
  152. /*
  153. * If we want an outline around the font, we create a new font with
  154. * the original font as a template that is just an outline font.
  155. */
  156. rgb_matrix::Font *outline_font = NULL;
  157. if (with_outline) {
  158. outline_font = font.CreateOutlineFont();
  159. }
  160. RGBMatrix *canvas = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
  161. if (canvas == NULL)
  162. return 1;
  163. const bool all_extreme_colors = (matrix_options.brightness == 100)
  164. && FullSaturation(color)
  165. && FullSaturation(bg_color)
  166. && FullSaturation(outline_color);
  167. if (all_extreme_colors)
  168. canvas->SetPWMBits(1);
  169. signal(SIGTERM, InterruptHandler);
  170. signal(SIGINT, InterruptHandler);
  171. printf("CTRL-C for exit.\n");
  172. // Create a new canvas to be used with led_matrix_swap_on_vsync
  173. FrameCanvas *offscreen_canvas = canvas->CreateFrameCanvas();
  174. const int scroll_direction = (speed >= 0) ? -1 : 1;
  175. speed = fabs(speed);
  176. int delay_speed_usec = 1000000;
  177. if (speed > 0) {
  178. delay_speed_usec = 1000000 / speed / font.CharacterWidth('W');
  179. }
  180. if (!xorigin_configured) {
  181. if (speed == 0) {
  182. // There would be no scrolling, so text would never appear. Move to front.
  183. x_orig = with_outline ? 1 : 0;
  184. } else {
  185. x_orig = scroll_direction < 0 ? canvas->width() : 0;
  186. }
  187. }
  188. int x = x_orig;
  189. int y = y_orig;
  190. int length = 0;
  191. struct timespec next_frame = {0, 0};
  192. uint frame_counter = 0;
  193. while (!interrupt_received && loops != 0) {
  194. ++frame_counter;
  195. offscreen_canvas->Fill(bg_color.r, bg_color.g, bg_color.b);
  196. const bool draw_on_frame = (blink_on <= 0)
  197. || (frame_counter % (blink_on + blink_off) < (uint)blink_on);
  198. if (draw_on_frame) {
  199. if (outline_font) {
  200. // The outline font, we need to write with a negative (-2) text-spacing,
  201. // as we want to have the same letter pitch as the regular text that
  202. // we then write on top.
  203. rgb_matrix::DrawText(offscreen_canvas, *outline_font,
  204. x - 1, y + font.baseline(),
  205. outline_color, NULL,
  206. line.c_str(), letter_spacing - 2);
  207. }
  208. // length = holds how many pixels our text takes up
  209. length = rgb_matrix::DrawText(offscreen_canvas, font,
  210. x, y + font.baseline(),
  211. color, NULL,
  212. line.c_str(), letter_spacing);
  213. }
  214. x += scroll_direction;
  215. if ((scroll_direction < 0 && x + length < 0) ||
  216. (scroll_direction > 0 && x > canvas->width())) {
  217. x = x_orig + ((scroll_direction > 0) ? -length : 0);
  218. if (loops > 0) --loops;
  219. }
  220. // Make sure render-time delays are not influencing scroll-time
  221. if (speed > 0) {
  222. if (next_frame.tv_sec == 0) {
  223. // First time. Start timer, but don't wait.
  224. clock_gettime(CLOCK_MONOTONIC, &next_frame);
  225. } else {
  226. add_micros(&next_frame, delay_speed_usec);
  227. clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame, NULL);
  228. }
  229. }
  230. // Swap the offscreen_canvas with canvas on vsync, avoids flickering
  231. offscreen_canvas = canvas->SwapOnVSync(offscreen_canvas);
  232. if (speed <= 0) pause(); // Nothing to scroll.
  233. }
  234. // Finished. Shut down the RGB matrix.
  235. canvas->Clear();
  236. delete canvas;
  237. return 0;
  238. }