removing submodule
Some checks failed
build lightwatch / build (push) Failing after 5m6s

This commit is contained in:
2023-09-03 01:14:34 -04:00
parent d539dc9119
commit ed18dd911a
190 changed files with 1174526 additions and 4 deletions

View File

@@ -0,0 +1,7 @@
demo
minimal-example
text-example
c-example
clock
scrolling-text-example
ledcat

View File

@@ -0,0 +1,60 @@
CFLAGS=-Wall -O3 -g -Wextra -Wno-unused-parameter
CXXFLAGS=$(CFLAGS)
OBJECTS=demo-main.o minimal-example.o c-example.o text-example.o scrolling-text-example.o clock.o ledcat.o input-example.o pixel-mover.o
BINARIES=demo minimal-example c-example text-example scrolling-text-example clock ledcat input-example pixel-mover
# Where our library resides. You mostly only need to change the
# RGB_LIB_DISTRIBUTION, this is where the library is checked out.
RGB_LIB_DISTRIBUTION=..
RGB_INCDIR=$(RGB_LIB_DISTRIBUTION)/include
RGB_LIBDIR=$(RGB_LIB_DISTRIBUTION)/lib
RGB_LIBRARY_NAME=rgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) -lrt -lm -lpthread
# To compile image-example
MAGICK_CXXFLAGS?=$(shell GraphicsMagick++-config --cppflags --cxxflags)
MAGICK_LDFLAGS?=$(shell GraphicsMagick++-config --ldflags --libs)
all : $(BINARIES)
$(RGB_LIBRARY): FORCE
$(MAKE) -C $(RGB_LIBDIR)
demo : demo-main.o $(RGB_LIBRARY)
$(CXX) $< -o $@ $(LDFLAGS)
minimal-example : minimal-example.o
input-example : input-example.o
text-example: text-example.o
scrolling-text-example : scrolling-text-example.o
clock : clock.o
ledcat : ledcat.o
pixel-mover : pixel-mover.o
# All the binaries that have the same name as the object file.q
% : %.o $(RGB_LIBRARY)
$(CXX) $< -o $@ $(LDFLAGS)
image-example.o : image-example.cc
$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) $(MAGICK_CXXFLAGS) -c -o $@ $<
image-example: image-example.o $(RGB_LIBRARY)
$(CXX) $< -o $@ $(LDFLAGS) $(MAGICK_LDFLAGS)
# Since the C example uses the C++ library underneath, which depends on C++
# runtime stuff, you still have to also link -lstdc++
c-example : c-example.o $(RGB_LIBRARY)
$(CC) $< -o $@ $(LDFLAGS) -lstdc++
%.o : %.cc
$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) -c -o $@ $<
%.o : %.c
$(CC) -I$(RGB_INCDIR) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJECTS) $(BINARIES)
FORCE:
.PHONY: FORCE

View File

@@ -0,0 +1,461 @@
Running some demos
------------------
Let's start by running some demos, then we can dive into code. The
[demo-main.cc](demo-main.cc) has some testing demos. Via command line flags,
you can choose the display type you have (16x32 or 32x32), and how many you
have chained and paralleled. For detailed description of these flags see the
[main README section](../README.md#changing-parameters-via-command-line-flags)
about it.
```
$ make
$ sudo ./demo
usage: ./demo <options> -D <demo-nr> [optional parameter]
Options:
-D <demo-nr> : Always needs to be set
--led-gpio-mapping=<name> : Name of GPIO mapping used. Default "regular"
--led-rows=<rows> : Panel rows. Typically 8, 16, 32 or 64. (Default: 32).
--led-cols=<cols> : Panel columns. Typically 32 or 64. (Default: 32).
--led-chain=<chained> : Number of daisy-chained panels. (Default: 1).
--led-parallel=<parallel> : Parallel chains. range=1..3 (Default: 1).
--led-multiplexing=<0..17> : Mux type: 0=direct; 1=Stripe; 2=Checkered; 3=Spiral; 4=ZStripe; 5=ZnMirrorZStripe; 6=coreman; 7=Kaler2Scan; 8=ZStripeUneven; 9=P10-128x4-Z; 10=QiangLiQ8; 11=InversedZStripe; 12=P10Outdoor1R1G1-1; 13=P10Outdoor1R1G1-2; 14=P10Outdoor1R1G1-3; 15=P10CoremanMapper; 16=P8Outdoor1R1G1; 17=FlippedStripe (Default: 0)
--led-pixel-mapper : Semicolon-separated list of pixel-mappers to arrange pixels.
Optional params after a colon e.g. "U-mapper;Rotate:90"
Available: "Mirror", "Rotate", "U-mapper", "V-mapper". Default: ""
--led-pwm-bits=<1..11> : PWM bits (Default: 11).
--led-brightness=<percent>: Brightness in percent (Default: 100).
--led-scan-mode=<0..1> : 0 = progressive; 1 = interlaced (Default: 0).
--led-row-addr-type=<0..4>: 0 = default; 1 = AB-addressed panels; 2 = direct row select; 3 = ABC-addressed panels; 4 = ABC Shift + DE direct (Default: 0).
--led-show-refresh : Show refresh rate.
--led-limit-refresh=<Hz> : Limit refresh rate to this frequency in Hz. Useful to keep a
constant refresh rate on loaded system. 0=no limit. Default: 0
--led-inverse : Switch if your matrix has inverse colors on.
--led-rgb-sequence : Switch if your matrix has led colors swapped (Default: "RGB")
--led-pwm-lsb-nanoseconds : PWM Nanoseconds for LSB (Default: 130)
--led-pwm-dither-bits=<0..2> : Time dithering of lower bits (Default: 0)
--led-no-hardware-pulse : Don't use hardware pin-pulse generation.
--led-panel-type=<name> : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'
--led-slowdown-gpio=<0..4>: Slowdown GPIO. Needed for faster Pis/slower panels (Default: 1).
--led-daemon : Make the process run in the background as daemon.
--led-no-drop-privs : Don't drop privileges from 'root' after initializing the hardware.
Demos, choosen with -D
0 - some rotating square
1 - forward scrolling an image (-m <scroll-ms>)
2 - backward scrolling an image (-m <scroll-ms>)
3 - test image: a square
4 - Pulsing color
5 - Grayscale Block
6 - Abelian sandpile model (-m <time-step-ms>)
7 - Conway's game of life (-m <time-step-ms>)
8 - Langton's ant (-m <time-step-ms>)
9 - Volume bars (-m <time-step-ms>)
10 - Evolution of color (-m <time-step-ms>)
11 - Brightness pulse generator
Example:
./demo -D 1 runtext.ppm
Scrolls the runtext until Ctrl-C is pressed
```
To run the actual demos, you need to run this as root so that the
GPIO pins can be accessed; as soon as that established, the program will drop
the privileges.
Here is how demo '1' looks. It requires a ppm (type raw) with a height of
32 pixel - it is infinitely scrolled over the screen; for
convenience, there is a little runtext.ppm example included:
$ sudo ./demo -D 1 runtext.ppm
Here is a video of how it looks
[![Runtext][run-vid]](http://youtu.be/OJvEWyvO4ro)
Other Examples
--------------
There are a few other examples that you can use as starting point for your
own exploration of the API. If you just type `make` in this directory, the
Makefile will build all of these, so they are ready to use. Some examples
need BDF fonts, of which there are a few provided in [../fonts](../fonts).
Some of these example programs are described in more detail further down this
page.
* [minimal-example](./minimal-example.cc) Good to get started with the API
* [image-example](./image-example.cc) How to show an image (requires to install the graphics magic library, see in the header of that demo)
* [text-example](./text-example.cc) Reads text from stdin and displays it.
* [scrolling-text-example](./scrolling-text-example.cc) Scrolls a text
given on the command-line.
* [clock](./clock.cc) Shows a clock with choosable date format string in
one or multiple lines.
* [input-example](./input-example.cc) Example how to use the LED-Matrix but
also read inputs from free GPIO-pins. Needed if you build some interactive
piece.
* [ledcat](./ledcat.cc) LED-cat compatible reading of pixels from stdin.
* [pixel-mover](./pixel-mover.cc) Displays pixel on the display
and it's expected position on the terminal. Helpful for testing panels and
figuring out new multiplexing mappings.
Shows single dot or leaves a trail with length passed with `-t` option
(think of 'snake').
Can move around the pixel with W=Up, S=Down, A=Left, D=Right keys.
Using the API
-------------
While there is the demo program and the [utilities](../utils), this code can
be used independently as a library to be used in your own programs.
The includes are in `include/`, the library to link is built
in `lib/`. This is a C++ also with C bindings. There is also a
[Python](../bindings/python) and [c#](../bindings/c%23) bindings.
Getting started
---------------
The relevant part to start with is to look at
[led-matrix.h](../include/led-matrix.h).
You can would typically use the `RGBMatrix::CreateFromFlags()` factory to
create an RGBMatrix and then go from there.
```C++
#include "led-matrix.h"
using rgb_matrix::RGBMatrix;
int main(int argc, char **argv) {
// Set some defaults
RGBMatrix::Options my_defaults;
my_defaults.hardware_mapping = "regular"; // or e.g. "adafruit-hat" or "adafruit-hat-pwm"
my_defaults.chain_length = 3;
my_defaults.show_refresh_rate = true;
rgb_matrix::RuntimeOptions runtime_defaults;
// If you drop privileges, the root user you start the program with
// to be able to initialize the hardware will be switched to an unprivileged
// user to minimize a potential security attack surface.
runtime_defaults.drop_privileges = 1;
RGBMatrix *matrix = RGBMatrix::CreateFromFlags(&argc, &argv,
&my_defaults,
&runtime_defaults);
if (matrix == NULL) {
PrintMatrixFlags(stderr, my_defaults, runtime_defaults);
return 1;
}
// matrix->ApplyPixelMapper(...); // Optional
// Do your own command line handling with the remaining options.
// .. now use matrix
delete matrix; // Make sure to delete it in the end.
}
```
The `RGBMatrix` is essentially a canvas, it provides some basic functionality
such as `SetPixel()`, `Fill()` or `Clear()`. If you want to do more, you
might be interested in functions provided in the
[graphics.h](../include/graphics.h) header.
If you have animations, you might be interested in double-buffering. There is
a way to create new canvases with `CreateFrameCanvas()`, and then use
`SwapOnVSync()` to change the content atomically. See API documentation for
details.
Start with the [minimal-example.cc](./minimal-example.cc) to start.
If you are interested in drawing text and the font drawing functions in
graphics.h, have a look at the [text example](./text-example.cc):
```
sudo ./text-example -f ../fonts/8x13.bdf
hello
```
<img src="../img/text-no-ghosting.jpg" height="100px">
How about a clock ?
```
sudo ./clock -f ../fonts/7x13.bdf --led-chain=2 -d "%A" -d "%H:%M:%S"
```
<img src="../img/time-display.jpg" height="100px">
Fonts are in a human readable and editbable `*.bdf` format. There are some
public domain fonts available in the [`../fonts/`](../fonts) directory. Any
other fonts you might want to use or scale to the size you need can be
converted to a BDF format (either with a font editor or the [otf2bdf] tool).
Here is an example how you could create a 30pixel high BDF font from some
TrueType font:
```bash
otf2bdf -v -o myfont.bdf -r 72 -p 30 /path/to/font-Bold.ttf
```
Integrating in your own application
-----------------------------------
Until this library shows up in your favorite Linux distribution, you can just
include the library via github; it is pretty easy to be up-to-date.
I suggest to add this code as a sub-module in your git repository. That way
you can use that particular version and easily update it if there are changes:
git submodule add https://github.com/hzeller/rpi-rgb-led-matrix.git matrix
(Read more about how to use [submodules in git][git-submodules])
This will check out the repository in a subdirectory `matrix/`.
The library to build would be in directory `matrix/lib`, so let's hook that
into your toplevel Makefile.
I suggest to set up some variables like this; you only need to change the
location `RGB_LIB_DISTRIBUTION` is pointing to; in the sub-module example, this
was the `matrix` directory:
RGB_LIB_DISTRIBUTION=matrix
RGB_INCDIR=$(RGB_LIB_DISTRIBUTION)/include
RGB_LIBDIR=$(RGB_LIB_DISTRIBUTION)/lib
RGB_LIBRARY_NAME=rgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) -lrt -lm -lpthread
Also, you want to add a target to build the libary in your sub-module
# (FYI: Make sure, there is a TAB-character in front of the $(MAKE))
$(RGB_LIBRARY):
$(MAKE) -C $(RGB_LIBDIR)
Now, your final binary needs to depend on your objects and also the
`$(RGB_LIBRARY)`
my-binary : $(OBJECTS) $(RGB_LIBRARY)
$(CXX) $(CXXFLAGS) $(OBJECTS) -o $@ $(LDFLAGS)
As an example, see the [PixelPusher implementation][pixelpush] which is using
this library in a git sub-module.
If you are writing your own Makefile, make sure to pass the `-O3` option to
the compiler to make sure to generate fast code.
Note, all the types provided are in the `rgb_matrix` namespace. That way, they
won't clash with other types you might use in your code; in particular pretty
common names such as `GPIO` or `Canvas` might run into clashing trouble.
Anyway, for convenience you just might add using-declarations in your
code:
// Types exported by the RGB-Matrix library.
using rgb_matrix::Canvas;
using rgb_matrix::GPIO;
using rgb_matrix::RGBMatrix;
Or, if you are lazy, just import the whole namespace:
using namespace rgb_matrix;
Read the [`minimal-example.cc`](./minimal-example.cc) to get started, then
have a look into [`demo-main.cc`](./demo-main.cc).
## Remapping coordinates ##
You might choose a different physical layout than the wiring provides.
There is an option `--led-pixel-mapper` that allows you to choose between
some re-mapping options, and also programmatic ways to do so.
### Standard mappers
#### U-mapper (U-shape connection)
Say you have 4 displays with 32x32 and only a single output
like with a Raspberry Pi 1 or the Adafruit HAT -- if we chain
them, we get a display 32 pixel high, (4*32)=128 pixel long. If we arrange
the boards in a U-shape so that they form a square, we get a logical display
of 64x64 pixels:
<img src="../img/chained-64x64.jpg" width="400px"> In action:
[![PixelPusher video][pp-vid]](http://youtu.be/ZglGuMaKvpY)
```
So the following chain (Viewed looking at the LED-side of the panels)
[<][<][<][<] }- Raspbery Pi connector
is arranged in this U-shape (on its side)
[<][<] }----- Raspberry Pi connector
[>][>]
```
Now we need to internally map pixels the pixels so that the 'folded' 128x32
screen behaves like a 64x64 screen.
There is a pixel-mapper that can help with this "U-Arrangement", you choose
it with `--led-pixel-mapper=U-mapper`. So in this particular case,
```
./demo --led-chain=4 --led-pixel-mapper="U-mapper"
```
This works for longer and more than one chain as well. Here an arrangement with
two chains with 8 panels each
```
[<][<][<][<] }--- Pi connector #1
[>][>][>][>]
[<][<][<][<] }--- Pi connector #2
[>][>][>][>]
```
(`--led-chain=8 --led-parallel=2 --led-pixel-mapper="U-mapper"`).
#### V-mapper and Vmapper:Z (Vertical arrangement)
By default, when you add panels on a chain, they are added horizontally.
If you have 2 panels of 64x32, you get 128x32.
The V-mapper allows the stacking to be vertical and not horizontal and
get the 64x64 you might want.
By default, all the panels are correct side up, and you need more cable length
as you need to cross back to the start of the next panel.
If you wish to use shorter cables, you can add use Vmapper:Z which will give
you serpentine cabling and every other panel will be upside down (see below
for an example).
It is compatible with parallel chains, so you can have multiple stacks
of panels all building a coherent overall display.
Here an example with 3 chains of 4 panels (128x64) for a total of about
98k display pixels.
```
./demo --led-rows=64 --led-cols=128 --led-chain=4 -led-parallel=3 --led-pixel-mapper=V-mapper -D0
```
Viewed looking the LED-side of the panels:
```
Vmapper Vmapper:Z
[O < I] [O < I] [O < I] [I > O] [I > O] [I > O]
,---^ ,---^ ,---^ ^ ^ ^
[O < I] [O < I] [O < I] [O < I] [O < I] [O < I]
,---^ ,---^ ,---^ ^ ^ ^
[O < I] [O < I] [O < I] [I > O] [I > O] [I > O]
,---^ ,---^ ,---^ ^ ^ ^
[O < I] [O < I] [O < I] [O < I] [O < I] [O < I]
^ ^ ^ ^ ^ ^
#1 #2 #3 #1 #2 #3
Pi connector (three parallel chains of len 4)
```
(This is also a good time to notice that 384x256 with 12 128x64 panels, is probably an
upper limit of what you can reasonably output without having an unusable fresh
rate (Try these options to help: --led-pwm-bits=7 --led-pwm-dither-bits=1 and get about 100Hz)).
This shows the wiring of a 3x5 Vmapper:Z array built by Marc MERLIN, using 15x 64x32 panels:
![Vmapper_Z_192x160_3x5.jpg](../img/Vmapper_Z_192x160_3x5.jpg)
With --led-pwm-bits=7 --led-pwm-dither-bits=1, it gets a better 300Hz refresh
but only offers around 31K pixels instead of 98K pixels in the previous example.
Please note that Vmapper can also be used to improve the refresh rate of a long
display even if it is only one panel high (e.g. for a text running output) by
splitting the load into multiple parallel chains.
```
[O < I] [O < I] [O < I]
^ ^ ^
#1 #2 #3 Pi connector (three parallel chains of len 1)
```
#### Rotate
The "Rotate" mapper allows you to rotate your screen. It takes an angle
as parameter after a colon:
```
./demo --led-pixel-mapper="Rotate:90"
```
#### Mirror
The 'Mirror' mapper allows to mirror the output horizontally or vertically.
Without parameter, it mirrors horizontally. The parameter is a single character
'H' or 'V' for horizontal or vertical mirroring.
```
./demo --led-pixel-mapper="Mirror:H"
```
#### Combining Mappers
You can chain multiple mappers in the configuration, by separating them
with a semicolon. The mappers are applied in the sequence you give them, so
if you want to arrange a couple of panels with the U-arrangement, and then
rotate the resulting screen, use
```
./demo --led-chain=8 --led-parallel=3 --led-pixel-mapper="U-mapper;Rotate:90"
```
Here, we first create a 128x192 screen (4 panels wide (`4*32=128`),
with three folded chains (`6*32=192`)) and then rotate it by 90 degrees to
get a 192x128 screen.
#### Programmatic access
If you want to choose these mappers programmatically from your program and
not via the flags, you can do this by setting the `pixel_mapper_config` option
in the options struct in C++ or Python.
```
options.pixel_mapper_config = "Rotate:90";
```
### Writing your own mappers
If you want to write your own mappers, e.g. if you have a fancy panel
arrangement, you can do so using the API provided.
In the API, there is an interface to implement,
a [`PixelMapper`](../include/pixel-mapper.h) that allows to program
re-arrangements of pixels in any way. You can plug such an implementation of
a `PixelMapper` into the RGBMatrix to use it:
```
bool RGBMatrix::ApplyPixelMapper(const PixelMapper *mapper);
```
If you want, you can also register your PixelMapper globally before you
parse the command line options; then this pixel-mapper is automatically
provided in the `--led-pixel-mapper` command line option:
```
RegisterPixelMapper(new MyOwnPixelMapper());
RGBMatrix *matrix = RGBMatrix::CreateFromFlags(...);
```
Now your mapper can be used alongside (and combined with) the standard
mappers already there (e.g. "U-mapper" or "Rotate"). Your mapper can have
parameters: In the command-line flag, parameters provided after `:` are passed
as-is to your `SetParameters()` implementation
(e.g. using `--led-pixel-mapper="Rotate:90"`, the `Rotate` mapper
gets a parameter string `"90"` as parameter).
#### Multiplex Mappers
Sometimes you even need this for the panel itself: In some panels
(typically the 'outdoor panels', often with 1:4 multiplexing) the pixels
are not mapped in a straight-forward way, but in a snake arrangement for
instance.
There are simplified pixel mappers for this purpose, the
[multiplex mappers](../lib/multiplex-mappers.cc). These are defined there
and then can be accessed via the command line flag `--led-multiplexing=...`.
If you find that whatever parameter you give to `--led-multiplexing=` doesn't
work, you might need to write your own mapper (extend `MultiplexMapperBase`
and implement the one method `MapSinglePanel()`). Then register them with
the `CreateMultiplexMapperList()` function in that file. When you do this,
this will automatically become available in the `--led-multiplexing=` command
line option in C++ and Python.
[run-vid]: ../img/running-vid.jpg
[git-submodules]: http://git-scm.com/book/en/Git-Tools-Submodules
[pixelpush]: https://github.com/hzeller/rpi-matrix-pixelpusher
[pp-vid]: ../img/pixelpusher-vid.jpg
[otf2bdf]: https://github.com/jirutka/otf2bdf

View File

@@ -0,0 +1,61 @@
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
*
* Using the C-API of this library.
*
*/
#include "led-matrix-c.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct RGBLedMatrixOptions options;
struct RGBLedMatrix *matrix;
struct LedCanvas *offscreen_canvas;
int width, height;
int x, y, i;
memset(&options, 0, sizeof(options));
options.rows = 32;
options.chain_length = 1;
/* This supports all the led commandline options. Try --led-help */
matrix = led_matrix_create_from_options(&options, &argc, &argv);
if (matrix == NULL)
return 1;
/* Let's do an example with double-buffering. We create one extra
* buffer onto which we draw, which is then swapped on each refresh.
* This is typically a good aproach for animations and such.
*/
offscreen_canvas = led_matrix_create_offscreen_canvas(matrix);
led_canvas_get_size(offscreen_canvas, &width, &height);
fprintf(stderr, "Size: %dx%d. Hardware gpio mapping: %s\n",
width, height, options.hardware_mapping);
for (i = 0; i < 1000; ++i) {
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
led_canvas_set_pixel(offscreen_canvas, x, y, i & 0xff, x, y);
}
}
/* Now, we swap the canvas. We give swap_on_vsync the buffer we
* just have drawn into, and wait until the next vsync happens.
* we get back the unused buffer to which we'll draw in the next
* iteration.
*/
offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
}
/*
* Make sure to always call led_matrix_delete() in the end to reset the
* display. Installing signal handlers for defined exit is a good idea.
*/
led_matrix_delete(matrix);
return 0;
}

View File

@@ -0,0 +1,198 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Example of a clock. This is very similar to the text-example,
// except that it shows the time :)
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include "graphics.h"
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <vector>
#include <string>
using namespace rgb_matrix;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
static int usage(const char *progname) {
fprintf(stderr, "usage: %s [options]\n", progname);
fprintf(stderr, "Reads text from stdin and displays it. "
"Empty string: clear screen\n");
fprintf(stderr, "Options:\n");
fprintf(stderr,
"\t-d <time-format> : Default '%%H:%%M'. See strftime()\n"
"\t Can be provided multiple times for multiple "
"lines\n"
"\t-f <font-file> : Use given font.\n"
"\t-x <x-origin> : X-Origin of displaying text (Default: 0)\n"
"\t-y <y-origin> : Y-Origin of displaying text (Default: 0)\n"
"\t-s <line-spacing> : Extra spacing between lines when multiple -d given\n"
"\t-S <spacing> : Extra spacing between letters (Default: 0)\n"
"\t-C <r,g,b> : Color. Default 255,255,0\n"
"\t-B <r,g,b> : Background-Color. Default 0,0,0\n"
"\t-O <r,g,b> : Outline-Color, e.g. to increase contrast.\n"
"\n"
);
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
static bool parseColor(Color *c, const char *str) {
return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}
static bool FullSaturation(const Color &c) {
return (c.r == 0 || c.r == 255)
&& (c.g == 0 || c.g == 255)
&& (c.b == 0 || c.b == 255);
}
int main(int argc, char *argv[]) {
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
}
// We accept multiple format lines
std::vector<std::string> format_lines;
Color color(255, 255, 0);
Color bg_color(0, 0, 0);
Color outline_color(0,0,0);
bool with_outline = false;
const char *bdf_font_file = NULL;
int x_orig = 0;
int y_orig = 0;
int letter_spacing = 0;
int line_spacing = 0;
int opt;
while ((opt = getopt(argc, argv, "x:y:f:C:B:O:s:S:d:")) != -1) {
switch (opt) {
case 'd': format_lines.push_back(optarg); break;
case 'x': x_orig = atoi(optarg); break;
case 'y': y_orig = atoi(optarg); break;
case 'f': bdf_font_file = strdup(optarg); break;
case 's': line_spacing = atoi(optarg); break;
case 'S': letter_spacing = atoi(optarg); break;
case 'C':
if (!parseColor(&color, optarg)) {
fprintf(stderr, "Invalid color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'B':
if (!parseColor(&bg_color, optarg)) {
fprintf(stderr, "Invalid background color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'O':
if (!parseColor(&outline_color, optarg)) {
fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
return usage(argv[0]);
}
with_outline = true;
break;
default:
return usage(argv[0]);
}
}
if (format_lines.empty()) {
format_lines.push_back("%H:%M");
}
if (bdf_font_file == NULL) {
fprintf(stderr, "Need to specify BDF font-file with -f\n");
return usage(argv[0]);
}
/*
* Load font. This needs to be a filename with a bdf bitmap font.
*/
rgb_matrix::Font font;
if (!font.LoadFont(bdf_font_file)) {
fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
return 1;
}
rgb_matrix::Font *outline_font = NULL;
if (with_outline) {
outline_font = font.CreateOutlineFont();
}
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (matrix == NULL)
return 1;
const bool all_extreme_colors = (matrix_options.brightness == 100)
&& FullSaturation(color)
&& FullSaturation(bg_color)
&& FullSaturation(outline_color);
if (all_extreme_colors)
matrix->SetPWMBits(1);
const int x = x_orig;
int y = y_orig;
FrameCanvas *offscreen = matrix->CreateFrameCanvas();
char text_buffer[256];
struct timespec next_time;
next_time.tv_sec = time(NULL);
next_time.tv_nsec = 0;
struct tm tm;
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
while (!interrupt_received) {
offscreen->Fill(bg_color.r, bg_color.g, bg_color.b);
localtime_r(&next_time.tv_sec, &tm);
int line_offset = 0;
for (const std::string &line : format_lines) {
strftime(text_buffer, sizeof(text_buffer), line.c_str(), &tm);
if (outline_font) {
rgb_matrix::DrawText(offscreen, *outline_font,
x - 1, y + font.baseline() + line_offset,
outline_color, NULL, text_buffer,
letter_spacing - 2);
}
rgb_matrix::DrawText(offscreen, font,
x, y + font.baseline() + line_offset,
color, NULL, text_buffer,
letter_spacing);
line_offset += font.height() + line_spacing;
}
// Wait until we're ready to show it.
clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next_time, NULL);
// Atomic swap with double buffer
offscreen = matrix->SwapOnVSync(offscreen);
next_time.tv_sec += 1;
}
// Finished. Shut down the RGB matrix.
delete matrix;
write(STDOUT_FILENO, "\n", 1); // Create a fresh new line after ^C on screen
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
//
// Example how to display an image, including animated images using
// ImageMagick. For a full utility that does a few more things, have a look
// at the led-image-viewer in ../utils
//
// Showing an image is not so complicated, essentially just copy all the
// pixels to the canvas. How to get the pixels ? In this example we're using
// the graphicsmagick library as universal image loader library that
// can also deal with animated images.
// You can of course do your own image loading or use some other library.
//
// This requires an external dependency, so install these first before you
// can call `make image-example`
// sudo apt-get update
// sudo apt-get install libgraphicsmagick++-dev libwebp-dev -y
// make image-example
#include "led-matrix.h"
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <exception>
#include <Magick++.h>
#include <magick/image.h>
using rgb_matrix::Canvas;
using rgb_matrix::RGBMatrix;
using rgb_matrix::FrameCanvas;
// Make sure we can exit gracefully when Ctrl-C is pressed.
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
using ImageVector = std::vector<Magick::Image>;
// Given the filename, load the image and scale to the size of the
// matrix.
// // If this is an animated image, the resutlting vector will contain multiple.
static ImageVector LoadImageAndScaleImage(const char *filename,
int target_width,
int target_height) {
ImageVector result;
ImageVector frames;
try {
readImages(&frames, filename);
} catch (std::exception &e) {
if (e.what())
fprintf(stderr, "%s\n", e.what());
return result;
}
if (frames.empty()) {
fprintf(stderr, "No image found.");
return result;
}
// Animated images have partial frames that need to be put together
if (frames.size() > 1) {
Magick::coalesceImages(&result, frames.begin(), frames.end());
} else {
result.push_back(frames[0]); // just a single still image.
}
for (Magick::Image &image : result) {
image.scale(Magick::Geometry(target_width, target_height));
}
return result;
}
// Copy an image to a Canvas. Note, the RGBMatrix is implementing the Canvas
// interface as well as the FrameCanvas we use in the double-buffering of the
// animted image.
void CopyImageToCanvas(const Magick::Image &image, Canvas *canvas) {
const int offset_x = 0, offset_y = 0; // If you want to move the image.
// Copy all the pixels to the canvas.
for (size_t y = 0; y < image.rows(); ++y) {
for (size_t x = 0; x < image.columns(); ++x) {
const Magick::Color &c = image.pixelColor(x, y);
if (c.alphaQuantum() < 256) {
canvas->SetPixel(x + offset_x, y + offset_y,
ScaleQuantumToChar(c.redQuantum()),
ScaleQuantumToChar(c.greenQuantum()),
ScaleQuantumToChar(c.blueQuantum()));
}
}
}
}
// An animated image has to constantly swap to the next frame.
// We're using double-buffering and fill an offscreen buffer first, then show.
void ShowAnimatedImage(const ImageVector &images, RGBMatrix *matrix) {
FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas();
while (!interrupt_received) {
for (const auto &image : images) {
if (interrupt_received) break;
CopyImageToCanvas(image, offscreen_canvas);
offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas);
usleep(image.animationDelay() * 10000); // 1/100s converted to usec
}
}
}
int usage(const char *progname) {
fprintf(stderr, "Usage: %s [led-matrix-options] <image-filename>\n",
progname);
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
int main(int argc, char *argv[]) {
Magick::InitializeMagick(*argv);
// Initialize the RGB matrix with
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
}
if (argc != 2)
return usage(argv[0]);
const char *filename = argv[1];
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (matrix == NULL)
return 1;
ImageVector images = LoadImageAndScaleImage(filename,
matrix->width(),
matrix->height());
switch (images.size()) {
case 0: // failed to load image.
break;
case 1: // Simple example: one image to show
CopyImageToCanvas(images[0], matrix);
while (!interrupt_received) sleep(1000); // Until Ctrl-C is pressed
break;
default: // More than one image: this is an animation.
ShowAnimatedImage(images, matrix);
break;
}
matrix->Clear();
delete matrix;
return 0;
}

View File

@@ -0,0 +1,64 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how to use the input bits
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include <unistd.h>
#include <math.h>
#include <stdio.h>
#include <signal.h>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
int main(int argc, char *argv[]) {
RGBMatrix::Options defaults;
defaults.hardware_mapping = "regular"; // or e.g. "adafruit-hat"
defaults.rows = 32;
defaults.chain_length = 1;
defaults.parallel = 1;
RGBMatrix *matrix = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults);
if (matrix == NULL)
return 1;
// It is always good to set up a signal handler to cleanly exit when we
// receive a CTRL-C for instance.
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
// Let's request all input bits and see which are actually available.
// This will differ depending on which hardware mapping you use and how
// many parallel chains you have.
const uint64_t available_inputs = matrix->RequestInputs(0xffffffff);
fprintf(stderr, "Available GPIO-bits: ");
for (int b = 0; b < 32; ++b) {
if (available_inputs & (1<<b))
fprintf(stderr, "%d ", b);
}
fprintf(stderr, "\n");
while (!interrupt_received) {
// Block and wait until any input bit changed or 100ms passed
uint32_t inputs = matrix->AwaitInputChange(100);
// Minimal output: let's show the bits with LEDs in the first row
for (int b = 0; b < 32; ++b) {
uint8_t col = (inputs & (1<<b)) ? 255 : 0;
matrix->SetPixel(32-b, 0, col, col, col);
}
}
fprintf(stderr, "Exiting.\n");
matrix->Clear();
delete matrix;
return 0;
}

View File

@@ -0,0 +1,82 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// A program that reads frames form STDIN as RGB24, much like
// https://github.com/polyfloyd/ledcat does.
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define FPS 60
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
int main(int argc, char *argv[]) {
RGBMatrix::Options defaults;
defaults.hardware_mapping = "regular"; // or e.g. "adafruit-hat"
defaults.rows = 32;
defaults.chain_length = 1;
defaults.parallel = 1;
Canvas *canvas = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults);
if (canvas == NULL) {
return 1;
}
// It is always good to set up a signal handler to cleanly exit when we
// receive a CTRL-C for instance. The DrawOnCanvas() routine is looking
// for that.
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
ssize_t frame_size = canvas->width() * canvas->height() * 3;
uint8_t buf[frame_size];
while (1) {
struct timespec start;
timespec_get(&start, TIME_UTC);
ssize_t nread;
ssize_t total_nread = 0;
while ((nread = read(STDIN_FILENO, &buf[total_nread], frame_size - total_nread)) > 0) {
if (interrupt_received) {
return 1;
}
total_nread += nread;
}
if (total_nread < frame_size){
break;
}
for (int y = 0; y < canvas->height(); y++) {
for (int x = 0; x < canvas->width(); x++) {
uint8_t *p = &buf[(y * canvas->width() + x) * 3];
uint8_t r = *(p+0), g = *(p+1), b = *(p+2);
canvas->SetPixel(x, y, r, g, b);
}
}
struct timespec end;
timespec_get(&end, TIME_UTC);
long tudiff = (end.tv_nsec / 1000 + end.tv_sec * 1000000) - (start.tv_nsec / 1000 + start.tv_sec * 1000000);
if (tudiff < 1000000l / FPS) {
usleep(1000000l / FPS - tudiff);
}
}
// Animation finished. Shut down the RGB matrix.
canvas->Clear();
delete canvas;
return 0;
}

View File

@@ -0,0 +1,69 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how to use the library.
// For more examples, look at demo-main.cc
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include <unistd.h>
#include <math.h>
#include <stdio.h>
#include <signal.h>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
static void DrawOnCanvas(Canvas *canvas) {
/*
* Let's create a simple animation. We use the canvas to draw
* pixels. We wait between each step to have a slower animation.
*/
canvas->Fill(0, 0, 255);
int center_x = canvas->width() / 2;
int center_y = canvas->height() / 2;
float radius_max = canvas->width() / 2;
float angle_step = 1.0 / 360;
for (float a = 0, r = 0; r < radius_max; a += angle_step, r += angle_step) {
if (interrupt_received)
return;
float dot_x = cos(a * 2 * M_PI) * r;
float dot_y = sin(a * 2 * M_PI) * r;
canvas->SetPixel(center_x + dot_x, center_y + dot_y,
255, 0, 0);
usleep(1 * 1000); // wait a little to slow down things.
}
}
int main(int argc, char *argv[]) {
RGBMatrix::Options defaults;
defaults.hardware_mapping = "regular"; // or e.g. "adafruit-hat"
defaults.rows = 32;
defaults.chain_length = 1;
defaults.parallel = 1;
defaults.show_refresh_rate = true;
Canvas *canvas = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults);
if (canvas == NULL)
return 1;
// It is always good to set up a signal handler to cleanly exit when we
// receive a CTRL-C for instance. The DrawOnCanvas() routine is looking
// for that.
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
DrawOnCanvas(canvas); // Using the canvas.
// Animation finished. Shut down the RGB matrix.
canvas->Clear();
delete canvas;
return 0;
}

View File

@@ -0,0 +1,210 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how to use the input bits
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include "graphics.h"
#include <ctype.h>
#include <getopt.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <deque>
using namespace rgb_matrix;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
static void InteractiveUseMessage() {
fprintf(stderr,
"Move around with common movement keysets \n"
" W,A,S,D (gaming move style) or\n"
" H,J,K,L (vi console move style)\n"
" Quit with 'q' or <ESC>\n"
"The pixel position cannot be less than 0 or greater than the "
"display height and width.\n");
}
static int usage(const char *progname) {
fprintf(stderr, "usage: %s [options]\n", progname);
fprintf(stderr, "Display single pixel with any colour.\n");
InteractiveUseMessage();
fprintf(stderr, "Options:\n\n");
fprintf(stderr,
"\t-C <r,g,b> : Color at front of trail. Default 255,255,0\n"
"\t-t <trail-len> : Length of trail behind dot (default:0)\n"
"\t-c <r,g,b> : Color at end of trail. Default 0,0,255\n\n"
);
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
static bool parseColor(Color *c, const char *str) {
return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}
static char getch() {
static bool is_terminal = isatty(STDIN_FILENO);
struct termios old;
if (is_terminal) {
if (tcgetattr(0, &old) < 0)
perror("tcsetattr()");
// Set to unbuffered mode
struct termios no_echo = old;
no_echo.c_lflag &= ~ICANON;
no_echo.c_lflag &= ~ECHO;
no_echo.c_cc[VMIN] = 1;
no_echo.c_cc[VTIME] = 0;
if (tcsetattr(0, TCSANOW, &no_echo) < 0)
perror("tcsetattr ICANON");
}
char buf = 0;
if (read(STDIN_FILENO, &buf, 1) < 0)
perror ("read()");
if (is_terminal) {
// Back to original terminal settings.
if (tcsetattr(0, TCSADRAIN, &old) < 0)
perror ("tcsetattr ~ICANON");
}
return buf;
}
// Interpolation of color between head and tail of trail.
static uint8_t quantize(float c) {
return c < 0 ? 0 : c > 255 ? 255 : roundf(c);
}
static Color interpolate(const Color &c1, const Color &c2, float fraction) {
float c2_fraction = 1 - fraction;
return { quantize(c1.r * fraction + c2.r * c2_fraction),
quantize(c1.g * fraction + c2.g * c2_fraction),
quantize(c1.b * fraction + c2.b * c2_fraction)};
}
int main(int argc, char *argv[]) {
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
}
Color front_color(255, 255, 0);
Color back_color(0, 0, 255);
int trail_len = 0;
int opt;
while ((opt = getopt(argc, argv, "C:c:t:")) != -1) {
switch (opt) {
case 't':
trail_len = std::max(0, atoi(optarg));
break;
case 'C':
if (!parseColor(&front_color, optarg)) {
fprintf(stderr, "Invalid color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'c':
if (!parseColor(&back_color, optarg)) {
fprintf(stderr, "Invalid color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
default:
return usage(argv[0]);
}
}
RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (matrix == NULL)
return usage(argv[0]);
rgb_matrix::FrameCanvas *canvas = matrix->CreateFrameCanvas();
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
std::deque<std::pair<int, int>> trail;
int x_pos = 0;
int y_pos = 0;
trail.push_back({x_pos, y_pos});
InteractiveUseMessage();
const bool output_is_terminal = isatty(STDOUT_FILENO);
bool running = true;
while (!interrupt_received && running) {
canvas->Clear();
int distance_from_head = trail.size();
for (const auto &pos : trail) { // Draw from tail -> head
distance_from_head--;
Color c = interpolate(front_color, back_color,
1.0 - 1.0f * distance_from_head / trail.size());
canvas->SetPixel(pos.first, pos.second, c.r, c.g, c.b);
}
canvas = matrix->SwapOnVSync(canvas);
printf("%sX,Y = %d,%d%s",
output_is_terminal ? "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" : "",
x_pos, y_pos,
output_is_terminal ? " " : "\n");
fflush(stdout);
const char c = tolower(getch());
switch (c) {
case 'w': case 'k': // Up
if (y_pos > 0) {
y_pos--;
trail.push_back({x_pos, y_pos});
}
break;
case 's': case 'j': // Down
if (y_pos < canvas->height() - 1) {
y_pos++;
trail.push_back({x_pos, y_pos});
}
break;
case 'a': case 'h': // Left
if (x_pos > 0) {
x_pos--;
trail.push_back({x_pos, y_pos});
}
break;
case 'd': case 'l': // Right
if (x_pos < canvas->width() - 1) {
x_pos++;
trail.push_back({x_pos, y_pos});
}
break;
// All kinds of conditions which we use to exit
case 0x1B: // Escape
case 'q': // 'Q'uit
case 0x04: // End of file
case 0x00: // Other issue from getch()
running = false;
break;
}
while ((int)trail.size() > trail_len + 1)
trail.pop_front(); // items on front are the oldest dots
}
// Finished. Shut down the RGB matrix.
delete matrix;
printf("\n");
return 0;
}

View File

@@ -0,0 +1,184 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how to scroll text.
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
// For a utility with a few more features see
// ../utils/text-scroller.cc
#include "led-matrix.h"
#include "graphics.h"
#include <string>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
using namespace rgb_matrix;
volatile bool interrupt_received = false;
static void InterruptHandler(int signo) {
interrupt_received = true;
}
static int usage(const char *progname) {
fprintf(stderr, "usage: %s [options] <text>\n", progname);
fprintf(stderr, "Takes text and scrolls it with speed -s\n");
fprintf(stderr, "Options:\n");
fprintf(stderr,
"\t-s <speed> : Approximate letters per second. "
"(Zero for no scrolling)\n"
"\t-l <loop-count> : Number of loops through the text. "
"-1 for endless (default)\n"
"\t-f <font-file> : Use given font.\n"
"\t-x <x-origin> : X-Origin of displaying text (Default: 0)\n"
"\t-y <y-origin> : Y-Origin of displaying text (Default: 0)\n"
"\t-t <track=spacing>: Spacing pixels between letters (Default: 0)\n"
"\n"
"\t-C <r,g,b> : Text-Color. Default 255,255,0\n"
"\t-B <r,g,b> : Background-Color. Default 0,0,0\n"
"\n"
);
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
static bool parseColor(Color *c, const char *str) {
return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}
static bool FullSaturation(const Color &c) {
return (c.r == 0 || c.r == 255)
&& (c.g == 0 || c.g == 255)
&& (c.b == 0 || c.b == 255);
}
int main(int argc, char *argv[]) {
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
}
Color color(255, 255, 0);
Color bg_color(0, 0, 0);
const char *bdf_font_file = NULL;
std::string line;
/* x_origin is set by default just right of the screen */
const int x_default_start = (matrix_options.chain_length
* matrix_options.cols) + 5;
int x_orig = x_default_start;
int y_orig = 0;
int letter_spacing = 0;
float speed = 7.0f;
int loops = -1;
int opt;
while ((opt = getopt(argc, argv, "x:y:f:C:B:t:s:l:")) != -1) {
switch (opt) {
case 's': speed = atof(optarg); break;
case 'l': loops = atoi(optarg); break;
case 'x': x_orig = atoi(optarg); break;
case 'y': y_orig = atoi(optarg); break;
case 'f': bdf_font_file = strdup(optarg); break;
case 't': letter_spacing = atoi(optarg); break;
case 'C':
if (!parseColor(&color, optarg)) {
fprintf(stderr, "Invalid color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'B':
if (!parseColor(&bg_color, optarg)) {
fprintf(stderr, "Invalid background color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
default:
return usage(argv[0]);
}
}
for (int i = optind; i < argc; ++i) {
line.append(argv[i]).append(" ");
}
if (line.empty()) {
fprintf(stderr, "Add the text you want to print on the command-line.\n");
return usage(argv[0]);
}
if (bdf_font_file == NULL) {
fprintf(stderr, "Need to specify BDF font-file with -f\n");
return usage(argv[0]);
}
/*
* Load font. This needs to be a filename with a bdf bitmap font.
*/
rgb_matrix::Font font;
if (!font.LoadFont(bdf_font_file)) {
fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
return 1;
}
RGBMatrix *canvas = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (canvas == NULL)
return 1;
const bool all_extreme_colors = (matrix_options.brightness == 100)
&& FullSaturation(color)
&& FullSaturation(bg_color);
if (all_extreme_colors)
canvas->SetPWMBits(1);
signal(SIGTERM, InterruptHandler);
signal(SIGINT, InterruptHandler);
printf("CTRL-C for exit.\n");
// Create a new canvas to be used with led_matrix_swap_on_vsync
FrameCanvas *offscreen_canvas = canvas->CreateFrameCanvas();
int delay_speed_usec = 1000000;
if (speed > 0) {
delay_speed_usec = 1000000 / speed / font.CharacterWidth('W');
} else if (x_orig == x_default_start) {
// There would be no scrolling, so text would never appear. Move to front.
x_orig = 0;
}
int x = x_orig;
int y = y_orig;
int length = 0;
while (!interrupt_received && loops != 0) {
offscreen_canvas->Fill(bg_color.r, bg_color.g, bg_color.b);
// length = holds how many pixels our text takes up
length = rgb_matrix::DrawText(offscreen_canvas, font,
x, y + font.baseline(),
color, nullptr,
line.c_str(), letter_spacing);
if (speed > 0 && --x + length < 0) {
x = x_orig;
if (loops > 0) --loops;
}
// Swap the offscreen_canvas with canvas on vsync, avoids flickering
offscreen_canvas = canvas->SwapOnVSync(offscreen_canvas);
usleep(delay_speed_usec);
}
// Finished. Shut down the RGB matrix.
delete canvas;
return 0;
}

View File

@@ -0,0 +1,179 @@
// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
// Small example how write text.
//
// This code is public domain
// (but note, that the led-matrix library this depends on is GPL v2)
#include "led-matrix.h"
#include "graphics.h"
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
using namespace rgb_matrix;
static int usage(const char *progname) {
fprintf(stderr, "usage: %s [options]\n", progname);
fprintf(stderr, "Reads text from stdin and displays it. "
"Empty string: clear screen\n");
fprintf(stderr, "Options:\n");
fprintf(stderr,
"\t-f <font-file> : Use given font.\n"
"\t-x <x-origin> : X-Origin of displaying text (Default: 0)\n"
"\t-y <y-origin> : Y-Origin of displaying text (Default: 0)\n"
"\t-S <spacing> : Spacing pixels between letters (Default: 0)\n"
"\t-C <r,g,b> : Color. Default 255,255,0\n"
"\t-B <r,g,b> : Font Background-Color. Default 0,0,0\n"
"\t-O <r,g,b> : Outline-Color, e.g. to increase contrast.\n"
"\t-F <r,g,b> : Panel flooding-background color. Default 0,0,0\n"
"\n"
);
rgb_matrix::PrintMatrixFlags(stderr);
return 1;
}
static bool parseColor(Color *c, const char *str) {
return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
}
static bool FullSaturation(const Color &c) {
return (c.r == 0 || c.r == 255)
&& (c.g == 0 || c.g == 255)
&& (c.b == 0 || c.b == 255);
}
int main(int argc, char *argv[]) {
RGBMatrix::Options matrix_options;
rgb_matrix::RuntimeOptions runtime_opt;
if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
&matrix_options, &runtime_opt)) {
return usage(argv[0]);
}
Color color(255, 255, 0);
Color bg_color(0, 0, 0);
Color flood_color(0, 0, 0);
Color outline_color(0,0,0);
bool with_outline = false;
const char *bdf_font_file = NULL;
int x_orig = 0;
int y_orig = 0;
int letter_spacing = 0;
int opt;
while ((opt = getopt(argc, argv, "x:y:f:C:B:O:S:F:")) != -1) {
switch (opt) {
case 'x': x_orig = atoi(optarg); break;
case 'y': y_orig = atoi(optarg); break;
case 'f': bdf_font_file = strdup(optarg); break;
case 'S': letter_spacing = atoi(optarg); break;
case 'C':
if (!parseColor(&color, optarg)) {
fprintf(stderr, "Invalid color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'B':
if (!parseColor(&bg_color, optarg)) {
fprintf(stderr, "Invalid background color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
case 'O':
if (!parseColor(&outline_color, optarg)) {
fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
return usage(argv[0]);
}
with_outline = true;
break;
case 'F':
if (!parseColor(&flood_color, optarg)) {
fprintf(stderr, "Invalid background color spec: %s\n", optarg);
return usage(argv[0]);
}
break;
default:
return usage(argv[0]);
}
}
if (bdf_font_file == NULL) {
fprintf(stderr, "Need to specify BDF font-file with -f\n");
return usage(argv[0]);
}
/*
* Load font. This needs to be a filename with a bdf bitmap font.
*/
rgb_matrix::Font font;
if (!font.LoadFont(bdf_font_file)) {
fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
return 1;
}
/*
* If we want an outline around the font, we create a new font with
* the original font as a template that is just an outline font.
*/
rgb_matrix::Font *outline_font = NULL;
if (with_outline) {
outline_font = font.CreateOutlineFont();
}
RGBMatrix *canvas = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
if (canvas == NULL)
return 1;
const bool all_extreme_colors = (matrix_options.brightness == 100)
&& FullSaturation(color)
&& FullSaturation(bg_color)
&& FullSaturation(outline_color);
if (all_extreme_colors)
canvas->SetPWMBits(1);
const int x = x_orig;
int y = y_orig;
if (isatty(STDIN_FILENO)) {
// Only give a message if we are interactive. If connected via pipe, be quiet
printf("Enter lines. Full screen or empty line clears screen.\n"
"Supports UTF-8. CTRL-D for exit.\n");
}
canvas->Fill(flood_color.r, flood_color.g, flood_color.b);
char line[1024];
while (fgets(line, sizeof(line), stdin)) {
const size_t last = strlen(line);
if (last > 0) line[last - 1] = '\0'; // remove newline.
bool line_empty = strlen(line) == 0;
if ((y + font.height() > canvas->height()) || line_empty) {
canvas->Fill(flood_color.r, flood_color.g, flood_color.b);
y = y_orig;
}
if (line_empty)
continue;
if (outline_font) {
// The outline font, we need to write with a negative (-2) text-spacing,
// as we want to have the same letter pitch as the regular text that
// we then write on top.
rgb_matrix::DrawText(canvas, *outline_font,
x - 1, y + font.baseline(),
outline_color, &bg_color, line, letter_spacing - 2);
}
// The regular text. Unless we already have filled the background with
// the outline font, we also fill the background here.
rgb_matrix::DrawText(canvas, font, x, y + font.baseline(),
color, outline_font ? NULL : &bg_color, line,
letter_spacing);
y += font.height();
}
// Finished. Shut down the RGB matrix.
delete canvas;
return 0;
}