includes go-rpi-rgb-led-matrix as a dep

This commit is contained in:
2023-12-21 11:06:27 -05:00
parent d1d6e2d9f9
commit 4dcea50309
187 changed files with 1174372 additions and 0 deletions
@@ -0,0 +1,3 @@
Various bindings to other programming languages.
Typically these are wrapping the [C-binding](../include/led-matrix-c.h) that
comes with rpi-rgb-led-matrix
@@ -0,0 +1,2 @@
*.dll
*.exe
@@ -0,0 +1,21 @@
CSHARP_LIB=RGBLedMatrix.dll
SOURCES=RGBLedCanvas.cs RGBLedMatrix.cs RGBLedFont.cs
CSHARP_COMPILER=mcs
RGB_LIBDIR=../../lib
RGB_LIBRARY_NAME=rgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).so.1
EXAMPLES_DIR=examples
$(CSHARP_LIB) : $(SOURCES) $(RGB_LIBRARY)
$(CSHARP_COMPILER) -target:library -out:$@ $(SOURCES)
$(RGB_LIBRARY):
$(MAKE) -C $(RGB_LIBDIR)
build: $(CSHARP_LIB)
$(MAKE) -C $(EXAMPLES_DIR) all
clean:
rm -f $(CSHARP_LIB)
@@ -0,0 +1,31 @@
C# bindings for RGB Matrix library
======================================
Building
--------
To build the C# wrapper for the RGB Matrix C library you need to first have mono installed.
### Install Mono
```shell
$ sudo apt-get update
$ sudo apt-get install mono-complete
```
Then, in the root directory for the matrix library type
```shell
make build-csharp
```
To run the example applications in the c#\examples folder
```shell
sudo mono minimal-example.exe
```
Notes
--------
C# applications look for libraries in the working directory of the application. To use this library for your own projects you will need to ensure you have RGBLedMatrix.dll and librgbmatrix.so in the same folder as your exe.
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace rpi_rgb_led_matrix_sharp
{
public class RGBLedCanvas
{
#region DLLImports
[DllImport("librgbmatrix.so")]
internal static extern void led_canvas_get_size(IntPtr canvas, out int width, out int height);
[DllImport("librgbmatrix.so")]
internal static extern void led_canvas_set_pixel(IntPtr canvas, int x, int y, byte r, byte g, byte b);
[DllImport("librgbmatrix.so")]
internal static extern void led_canvas_clear(IntPtr canvas);
[DllImport("librgbmatrix.so")]
internal static extern void led_canvas_fill(IntPtr canvas, byte r, byte g, byte b);
[DllImport("librgbmatrix.so")]
internal static extern void draw_circle(IntPtr canvas, int xx, int y, int radius, byte r, byte g, byte b);
[DllImport("librgbmatrix.so")]
internal static extern void draw_line(IntPtr canvas, int x0, int y0, int x1, int y1, byte r, byte g, byte b);
#endregion
// This is a wrapper for canvas no need to implement IDisposable here
// because RGBLedMatrix has ownership and takes care of disposing canvases
internal IntPtr _canvas;
// this is not called directly by the consumer code,
// consumer uses factory methods in RGBLedMatrix
internal RGBLedCanvas(IntPtr canvas)
{
_canvas = canvas;
int width;
int height;
led_canvas_get_size(_canvas, out width, out height);
Width = width;
Height = height;
}
public int Width {get; private set; }
public int Height { get; private set; }
public void SetPixel(int x, int y, Color color)
{
led_canvas_set_pixel(_canvas, x, y, color.R, color.G, color.B);
}
public void Fill(Color color)
{
led_canvas_fill(_canvas, color.R, color.G, color.B);
}
public void Clear()
{
led_canvas_clear(_canvas);
}
public void DrawCircle(int x0, int y0, int radius, Color color)
{
draw_circle(_canvas, x0, y0, radius, color.R, color.G, color.B);
}
public void DrawLine (int x0, int y0, int x1, int y1, Color color)
{
draw_line(_canvas, x0, y0, x1, y1, color.R, color.G, color.B);
}
public int DrawText(RGBLedFont font, int x, int y, Color color, string text, int spacing=0, bool vertical=false)
{
return font.DrawText(_canvas, x, y, color, text, spacing, vertical);
}
}
public struct Color
{
public Color (int r, int g, int b)
{
R = (byte)r;
G = (byte)g;
B = (byte)b;
}
public Color(byte r, byte g, byte b)
{
R = r;
G = g;
B = b;
}
public byte R;
public byte G;
public byte B;
}
}
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace rpi_rgb_led_matrix_sharp
{
public class RGBLedFont : IDisposable
{
[DllImport("librgbmatrix.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern IntPtr load_font(string bdf_font_file);
[DllImport("librgbmatrix.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern int draw_text(IntPtr canvas, IntPtr font, int x, int y, byte r, byte g, byte b, string utf8_text, int extra_spacing);
[DllImport("librgbmatrix.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern int vertical_draw_text(IntPtr canvas, IntPtr font, int x, int y, byte r, byte g, byte b, string utf8_text, int kerning_offset);
[DllImport("librgbmatrix.so", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern void delete_font(IntPtr font);
public RGBLedFont(string bdf_font_file_path)
{
_font = load_font(bdf_font_file_path);
}
internal IntPtr _font;
internal int DrawText(IntPtr canvas, int x, int y, Color color, string text, int spacing=0, bool vertical=false)
{
if (!vertical)
return draw_text(canvas, _font, x, y, color.R, color.G, color.B, text, spacing);
else
return vertical_draw_text(canvas, _font, x, y, color.R, color.G, color.B, text, spacing);
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
delete_font(_font);
disposedValue = true;
}
}
~RGBLedFont()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
@@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace rpi_rgb_led_matrix_sharp
{
public class RGBLedMatrix : IDisposable
{
#region DLLImports
[DllImport("librgbmatrix.so")]
internal static extern IntPtr led_matrix_create(int rows, int chained, int parallel);
[DllImport("librgbmatrix.so", CallingConvention= CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal static extern IntPtr led_matrix_create_from_options_const_argv(
ref InternalRGBLedMatrixOptions options,
int argc,
string[] argv);
[DllImport("librgbmatrix.so")]
internal static extern void led_matrix_delete(IntPtr matrix);
[DllImport("librgbmatrix.so")]
internal static extern IntPtr led_matrix_create_offscreen_canvas(IntPtr matrix);
[DllImport("librgbmatrix.so")]
internal static extern IntPtr led_matrix_swap_on_vsync(IntPtr matrix, IntPtr canvas);
[DllImport("librgbmatrix.so")]
internal static extern IntPtr led_matrix_get_canvas(IntPtr matrix);
[DllImport("librgbmatrix.so")]
internal static extern byte led_matrix_get_brightness(IntPtr matrix);
[DllImport("librgbmatrix.so")]
internal static extern void led_matrix_set_brightness(IntPtr matrix, byte brightness);
#endregion
public RGBLedMatrix(int rows, int chained, int parallel)
{
matrix= led_matrix_create(rows, chained, parallel);
}
public RGBLedMatrix(RGBLedMatrixOptions options)
{
var opt = new InternalRGBLedMatrixOptions();
try {
// pass in options to internal data structure
opt.chain_length = options.ChainLength;
opt.rows = options.Rows;
opt.cols = options.Cols;
opt.hardware_mapping = options.HardwareMapping != null ? Marshal.StringToHGlobalAnsi(options.HardwareMapping) : IntPtr.Zero;
opt.inverse_colors = (byte)(options.InverseColors ? 1 : 0);
opt.led_rgb_sequence = options.LedRgbSequence != null ? Marshal.StringToHGlobalAnsi(options.LedRgbSequence) : IntPtr.Zero;
opt.pixel_mapper_config = options.PixelMapperConfig != null ? Marshal.StringToHGlobalAnsi(options.PixelMapperConfig) : IntPtr.Zero;
opt.panel_type = options.PanelType != null ? Marshal.StringToHGlobalAnsi(options.PanelType) : IntPtr.Zero;
opt.parallel = options.Parallel;
opt.multiplexing = options.Multiplexing;
opt.pwm_bits = options.PwmBits;
opt.pwm_lsb_nanoseconds = options.PwmLsbNanoseconds;
opt.pwm_dither_bits = options.PwmDitherBits;
opt.scan_mode = options.ScanMode;
opt.show_refresh_rate = (byte)(options.ShowRefreshRate ? 1 : 0);
opt.limit_refresh_rate_hz = options.LimitRefreshRateHz;
opt.brightness = options.Brightness;
opt.disable_hardware_pulsing = (byte)(options.DisableHardwarePulsing ? 1 : 0);
opt.row_address_type = options.RowAddressType;
string[] cmdline_args = Environment.GetCommandLineArgs();
// Because gpio-slowdown is not provided in the options struct,
// we manually add it.
// Let's add it first to the command-line we pass to the
// matrix constructor, so that it can be overridden with the
// users' commandline.
// As always, as the _very_ first, we need to provide the
// program name argv[0], so this is why our slowdown_arg
// array will have these two elements.
//
// Given that we can't initialize the C# struct with a slowdown
// that is not 0, we just override it here with 1 if we see 0
// (zero only really is usable on super-slow vey old Rpi1,
// but for everyone else, it would be a nuisance. So we use
// 0 as our sentinel).
string[] slowdown_arg = new string[] {cmdline_args[0], "--led-slowdown-gpio="+(options.GpioSlowdown == 0 ? 1 : options.GpioSlowdown) };
string[] argv = new string[ 2 + cmdline_args.Length-1];
// Progname + slowdown arg first
slowdown_arg.CopyTo(argv, 0);
// Remaining args (excluding program name) then. This allows
// the user to not only provide any of the other --led-*
// options, but also override the --led-slowdown-gpio arg on
// the commandline.
Array.Copy(cmdline_args, 1, argv, 2, cmdline_args.Length-1);
int argc = argv.Length;
matrix = led_matrix_create_from_options_const_argv(ref opt, argc, argv);
}
finally
{
if (options.HardwareMapping != null) Marshal.FreeHGlobal(opt.hardware_mapping);
if (options.LedRgbSequence != null) Marshal.FreeHGlobal(opt.led_rgb_sequence);
if (options.PixelMapperConfig != null) Marshal.FreeHGlobal(opt.pixel_mapper_config);
if (options.PanelType != null) Marshal.FreeHGlobal(opt.panel_type);
}
}
private IntPtr matrix;
public RGBLedCanvas CreateOffscreenCanvas()
{
var canvas=led_matrix_create_offscreen_canvas(matrix);
return new RGBLedCanvas(canvas);
}
public RGBLedCanvas GetCanvas()
{
var canvas = led_matrix_get_canvas(matrix);
return new RGBLedCanvas(canvas);
}
public RGBLedCanvas SwapOnVsync(RGBLedCanvas canvas)
{
canvas._canvas = led_matrix_swap_on_vsync(matrix, canvas._canvas);
return canvas;
}
public byte Brightness
{
get { return led_matrix_get_brightness(matrix); }
set { led_matrix_set_brightness(matrix, value); }
}
#region IDisposable Support
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
led_matrix_delete(matrix);
disposedValue = true;
}
}
~RGBLedMatrix() {
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region RGBLedMatrixOptions struct
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct InternalRGBLedMatrixOptions
{
public IntPtr hardware_mapping;
public int rows;
public int cols;
public int chain_length;
public int parallel;
public int pwm_bits;
public int pwm_lsb_nanoseconds;
public int pwm_dither_bits;
public int brightness;
public int scan_mode;
public int row_address_type;
public int multiplexing;
public IntPtr led_rgb_sequence;
public IntPtr pixel_mapper_config;
public IntPtr panel_type;
public byte disable_hardware_pulsing;
public byte show_refresh_rate;
public byte inverse_colors;
public int limit_refresh_rate_hz;
};
#endregion
}
public struct RGBLedMatrixOptions
{
/// <summary>
/// Name of the hardware mapping used. If passed NULL here, the default is used.
/// </summary>
public string HardwareMapping;
/// <summary>
/// The "rows" are the number of rows supported by the display, so 32 or 16.
/// Default: 32.
/// </summary>
public int Rows;
/// <summary>
/// The "cols" are the number of columns per panel. Typically something
/// like 32, but also 64 is possible. Sometimes even 40.
/// cols * chain_length is the total length of the display, so you can
/// represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
/// same thing, but more convenient to think of.
/// </summary>
public int Cols;
/// <summary>
/// The chain_length is the number of displays daisy-chained together
/// (output of one connected to input of next). Default: 1
/// </summary>
public int ChainLength;
/// <summary>
/// The number of parallel chains connected to the Pi; in old Pis with 26
/// GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can also
/// be 2 or 3. The effective number of pixels in vertical direction is then
/// thus rows * parallel. Default: 1
/// </summary>
public int Parallel;
/// <summary>
/// Set PWM bits used for output. Default is 11, but if you only deal with limited
/// comic-colors, 1 might be sufficient. Lower require less CPU and increases refresh-rate.
/// </summary>
public int PwmBits;
/// <summary>
/// Change the base time-unit for the on-time in the lowest significant bit in
/// nanoseconds. Higher numbers provide better quality (more accurate color, less
/// ghosting), but have a negative impact on the frame rate.
/// </summary>
public int PwmLsbNanoseconds;
/// <summary>
/// The lower bits can be time-dithered for higher refresh rate.
/// </summary>
public int PwmDitherBits;
/// <summary>
/// The initial brightness of the panel in percent. Valid range is 1..100
/// </summary>
public int Brightness;
/// <summary>
/// Scan mode: 0=progressive, 1=interlaced
/// </summary>
public int ScanMode;
/// <summary>
/// Default row address type is 0, corresponding to direct setting of the
/// row, while row address type 1 is used for panels that only have A/B,
/// typically some 64x64 panels
/// </summary>
public int RowAddressType;
/// <summary>
/// Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8)
/// </summary>
public int Multiplexing;
/// <summary>
/// In case the internal sequence of mapping is not "RGB", this contains the real mapping. Some panels mix up these colors.
/// </summary>
public string LedRgbSequence;
/// <summary>
/// A string describing a sequence of pixel mappers that should be applied
/// to this matrix. A semicolon-separated list of pixel-mappers with optional
/// parameter.
public string PixelMapperConfig;
/// <summary>
/// Panel type. Typically just empty, but certain panels (FM6126)
/// requie an initialization sequence
/// </summary>
public string PanelType;
/// <summary>
/// Allow to use the hardware subsystem to create pulses. This won't do anything if output enable is not connected to GPIO 18.
/// </summary>
public bool DisableHardwarePulsing;
public bool ShowRefreshRate;
public bool InverseColors;
/// <summary>
/// Limit refresh rate of LED panel. This will help on a loaded system
// to keep a constant refresh rate. <= 0 for no limit.
/// </summary>
public int LimitRefreshRateHz;
/// <summary>
/// Slowdown GPIO. Needed for faster Pis/slower panels.
/// </summary>
public int GpioSlowdown;
};
}
@@ -0,0 +1,41 @@
CSHARP_LIB=RGBLedMatrix.dll
CSHARP_COMPILER=mcs
CSHARP_LIBDIR=..
CSHARP_LIBRARY=$(CSHARP_LIBDIR)/$(CSHARP_LIB)
RGB_LIBDIR=../../../lib
RGB_LIBRARY_NAME=librgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/$(RGB_LIBRARY_NAME).so.1
all: $(CSHARP_LIB)
cp $(RGB_LIBRARY) $(RGB_LIBRARY_NAME).so
cp $(CSHARP_LIBRARY) $(CSHARP_LIB)
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:minimal-example.exe minimal-example.cs
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:matrix-rain.exe matrix-rain.cs
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:font-example.exe font-example.cs
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:pulsing-brightness.exe pulsing-brightness.cs
minimal-example.exe: $(CSHARP_LIB)
cp $(RGB_LIBRARY) $(RGB_LIBRARY_NAME).so
cp $(CSHARP_LIBRARY) $(CSHARP_LIB)
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:minimal-example.exe minimal-example.cs
matrix-rain.exe: $(CSHARP_LIB)
cp $(RGB_LIBRARY) $(RGB_LIBRARY_NAME).so
cp $(CSHARP_LIBRARY) $(CSHARP_LIB)
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:matrix-rain.exe matrix-rain.cs
font-example.exe: $(CSHARP_LIB)
cp $(RGB_LIBRARY) $(RGB_LIBRARY_NAME).so
cp $(CSHARP_LIBRARY) $(CSHARP_LIB)
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:font-example.exe font-example.cs
pulsing-brightness.exe: $(CSHARP_LIB)
cp $(RGB_LIBRARY) $(RGB_LIBRARY_NAME).so
cp $(CSHARP_LIBRARY) $(CSHARP_LIB)
$(CSHARP_COMPILER) -r:$(CSHARP_LIB) -out:pulsing-brightness.exe pulsing-brightness.cs
$(CSHARP_LIB) :
$(MAKE) -C $(CSHARP_LIBDIR)
.PHONY : all
@@ -0,0 +1,36 @@
using rpi_rgb_led_matrix_sharp;
using System;
using System.Threading;
namespace font_example
{
class Program
{
static int Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("font-example.exe [font_path] <text>");
return -1;
}
string text = "Hello World!";
if (args.Length > 1)
text = args[1];
var matrix = new RGBLedMatrix(32, 2, 1);
var canvas = matrix.CreateOffscreenCanvas();
var font = new RGBLedFont(args[0]);
canvas.DrawText(font, 1, 6, new Color(0, 255, 0), text);
matrix.SwapOnVsync(canvas);
while (!Console.KeyAvailable)
{
Thread.Sleep(250);
}
return 0;
}
}
}
@@ -0,0 +1,90 @@
using rpi_rgb_led_matrix_sharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace matrix_rain
{
class Program
{
const int MAX_HEIGHT = 16;
const int COLOR_STEP = 15;
const int FRAME_STEP = 1;
static int Main(string[] args)
{
var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { ChainLength = 2 });
var canvas = matrix.CreateOffscreenCanvas();
var rnd = new Random();
var points = new List<Point>();
var recycled = new Stack<Point>();
int frame = 0;
var stopwatch = new Stopwatch();
while (!Console.KeyAvailable) {
stopwatch.Restart();
frame++;
if (frame % FRAME_STEP == 0)
{
if (recycled.Count == 0)
points.Add(new Point(rnd.Next(0, canvas.Width - 1), 0));
else
{
var point = recycled.Pop();
point.x = rnd.Next(0, canvas.Width - 1);
point.y = 0;
point.recycled = false;
}
}
canvas.Clear();
foreach (var point in points)
{
if (!point.recycled)
{
point.y++;
if (point.y - MAX_HEIGHT > canvas.Height)
{
point.recycled = true;
recycled.Push(point);
}
for (var i=0; i< MAX_HEIGHT; i++)
{
canvas.SetPixel(point.x, point.y - i, new Color(0, 255 - i * COLOR_STEP, 0));
}
}
}
canvas = matrix.SwapOnVsync(canvas);
// force 30 FPS
var elapsed= stopwatch.ElapsedMilliseconds;
if (elapsed < 33)
{
Thread.Sleep(33 - (int)elapsed);
}
}
return 0;
}
class Point
{
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
public int x;
public int y;
public bool recycled;
}
}
}
@@ -0,0 +1,32 @@
using rpi_rgb_led_matrix_sharp;
namespace minimal_example
{
class Program
{
static int Main(string[] args)
{
var matrix= new RGBLedMatrix(32, 2, 1);
var canvas = matrix.CreateOffscreenCanvas();
for (var i = 0; i < 1000; ++i)
{
for (var y = 0; y < canvas.Height; ++y)
{
for (var x = 0; x < canvas.Width; ++x)
{
canvas.SetPixel(x, y, new Color(i & 0xff, x, y));
}
}
canvas.DrawCircle(canvas.Width / 2, canvas.Height / 2, 6, new Color(0, 0, 255));
canvas.DrawLine(canvas.Width / 2 - 3, canvas.Height / 2 - 3, canvas.Width / 2 + 3, canvas.Height / 2 + 3, new Color(0, 0, 255));
canvas.DrawLine(canvas.Width / 2 - 3, canvas.Height / 2 + 3, canvas.Width / 2 + 3, canvas.Height / 2 - 3, new Color(0, 0, 255));
canvas = matrix.SwapOnVsync(canvas);
}
return 0;
}
}
}
@@ -0,0 +1,54 @@
using rpi_rgb_led_matrix_sharp;
using System;
using System.Threading;
namespace pulsing_brightness
{
class Program
{
static int Main(string[] args)
{
var matrix = new RGBLedMatrix(new RGBLedMatrixOptions {Rows = 32, Cols = 64});
var canvas = matrix.CreateOffscreenCanvas();
var maxBrightness = matrix.Brightness;
var count = 0;
const int c = 255;
while (!Console.KeyAvailable)
{
if (matrix.Brightness < 1)
{
matrix.Brightness = maxBrightness;
count += 1;
}
else
{
matrix.Brightness -= 1;
}
switch (count % 4)
{
case 0:
canvas.Fill(new Color(c, 0, 0));
break;
case 1:
canvas.Fill(new Color(0, c, 0));
break;
case 2:
canvas.Fill(new Color(0, 0, c));
break;
case 3:
canvas.Fill(new Color(c, c, c));
break;
}
canvas = matrix.SwapOnVsync(canvas);
Thread.Sleep(20);
}
return 0;
}
}
}
@@ -0,0 +1 @@
build/*
@@ -0,0 +1,92 @@
# TODO: This contains a lot of {c|p}ython build boilerplate, this needs cleanup.
PYTHON ?= python
SETUP := setup.py
BUILD_ARGS := build --build-lib .
INST_ARGS := install
ifdef DESTDIR
INST_ARGS += --root=$(DESTDIR)
endif
CLEAN_ARGS := clean --all
MANPAGES := $(patsubst %.txt,%,$(wildcard *.txt))
TXTTOMAN := a2x -f manpage
# Where our library resides. It is split between includes and the binary
# library in lib
RGB_LIBDIR=../../lib
RGB_LIBRARY_NAME=rgbmatrix
RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
ifneq "$(wildcard debian/changelog)" ""
PKGNAME := $(shell dpkg-parsechangelog | sed -n 's/^Source: //p')
VERSION := $(shell dpkg-parsechangelog | sed -n 's/^Version: \([^-]*\).*/\1/p')
UPSDIST := $(PKGNAME)-$(VERSION).tar.gz
DEBDIST := $(PKGNAME)_$(VERSION).orig.tar.gz
endif
all: build
build: build-python
install: install-python
clean: clean-python
find ./rgbmatrix -type f -name \*.so -delete
find . -type f -name \*.pyc -delete
$(RM) build-* install-* test-*
$(RGB_LIBRARY): FORCE
$(MAKE) -C $(RGB_LIBDIR)
test: test-python
test-python:
ifneq "$(wildcard tests/*.py)" ""
nosetests -v -w tests
else
$(info Test suite is not implemented...)
endif
ifneq "$(wildcard debian/control)" ""
PYVERS := $(shell pyversions -r -v debian/control)
PYEXEC := $(shell pyversions -d)
BUILD_ARGS += --executable=/usr/bin/$(PYEXEC)
INST_ARGS += --no-compile -O0
build-python: $(PYVERS:%=build-python-%)
build-python-%: $(RGB_LIBRARY)
$(info * Doing build for $(PYTHON)$* ...)
$(PYTHON)$* $(SETUP) $(BUILD_ARGS)
install-python: $(PYVERS:%=install-python-%)
install-python-%:
$(info * Doing install for $(PYTHON)$* ...)
$(PYTHON)$* $(SETUP) $(INST_ARGS)
clean-python: $(PYVERS:%=clean-python-%)
clean-python-%:
$(PYTHON)$* $(SETUP) $(CLEAN_ARGS)
else
build-python: $(RGB_LIBRARY)
$(PYTHON) $(SETUP) $(BUILD_ARGS)
install-python:
$(PYTHON) $(SETUP) $(INST_ARGS)
clean-python:
$(PYTHON) $(SETUP) $(CLEAN_ARGS)
endif
distclean: clean
dist: distclean
$(info * Creating ../$(UPSDIST) and ../$(DEBDIST))
@tar --exclude='.svn' \
--exclude='*.swp' \
--exclude='debian' \
-czvf ../$(UPSDIST) \
-C ../ $(notdir $(CURDIR))
@cp ../$(UPSDIST) ../$(DEBDIST)
@if test -d ../tarballs; then \
mv -v ../$(DEBDIST) ../tarballs; \
fi
FORCE:
.PHONY: FORCE
.PHONY: build install test clean dist distclean
.PHONY: build-python install-python clean-python
@@ -0,0 +1,147 @@
Python bindings for RGB Matrix library
======================================
Building
--------
If you have a different than the standard wiring (for instance if you have an
Adafruit HAT), you can edit the [../../lib/Makefile](../../lib/Makefile#L26) first to choose
the hardware in question (see below for setting it via command line argument).
Then, in the root directory for the matrix library simply type:
### Python 2
```shell
sudo apt-get update && sudo apt-get install python2.7-dev python-pillow -y
make build-python
sudo make install-python
```
### Python 3
You can also build for Python 3:
```shell
sudo apt-get update && sudo apt-get install python3-dev python3-pillow -y
make build-python PYTHON=$(which python3)
sudo make install-python PYTHON=$(which python3)
```
### PyPy
The cython binding to PyPy seems to be somewhat working but extremely slow (20x
slower even than the regular Python binding, 160x slower than C++), so this is
not recommended.
So Cython is not good together with PyPy which works best with a
[CFFI](https://cffi.readthedocs.io/) binding. @Duality4Y did an experiment here
https://github.com/Duality4Y/rgb-matrix-cffi which works well with PyPy and is
about twice as fast as running Python3+cython (but Python3+cffi is slower than
Python3+cython, so we can't just replace everything with cffi).
Of course, it would be nice to have the fastest possible binding to all kinds
of Python interpreters. If anyone wants to work on that, this would certainly
be a welcome pull request.
Performance
-----------
The simplicity of scripting comes at a price: Python is slower than C++ of
course.
If you have to do a lot of pixel updates in your demo, this can be too slow
depending on what you do. Here are some rough numbers for calling `SetPixel()`
in a tight loop:
* On a Pi-2 and Pi-3, a Python script will be about 1/8 of the speed compared
to the corresponding C++ program (pushing ~0.43 Megapixels/s Python
vs. ~3.5 Megapixels/s C++ on a Pi-3 for instance)
* On a Pi-1, the difference is even worse: 1/24 of the speed to the
corresponding C++ program. Given that the Pi-1 is already about 1/10 the
speed of a Pi-3, this almost makes Python unusable on a Pi-1
(~0.015 Megapixels/s Python vs. ~0.36 Megapixels/s C++)
* Also interesting: Python3 is a little bit slower than Python2.7.
So if you can, stick with Python2.7 for now.
* The good news is, that this is due to overhead per function call. If you
can do more per function call, then this is less problematic. For instance
if you have an image to be displayed with `SetImage()`, that will much faster
per pixel (internally this then copies the pixels natively).
The ~0.015 Megapixels/s on a Pi-1 means that you can update a 32x32 matrix
at most with ~15fps. If you have chained 5, then you barely reach 3fps.
In a Pi-3, you get about 400fps update rate (85fps for 5-chain) with a Python
program (while with C++, you can do the same thing with a comfortable 3500fps
(700fps for 5)). Keep in mind that this is if all you do is just calling
`SetPixel()`, it does not include any time of what you actually want to do in
your demo - so anything in addition to that will drop your update rate.
If you can prepare the animation you want to show, then you can either prepare
images and then use the much faster call to `SetImage()`, or can fill
entire offscreen-frames (create with `CreateFrameCanvas()`) and then
swap with `SwapOnVSync()` (this is the fastest method).
Using the library
-----------------
Be aware of the fact that using the full performance of the RGBMatrix requires root privileges.
Therefore you should run all you python scripts as using `sudo`.
You may find examples in the [samples/](./samples) subdirectory.
The examples all use the [samplebase.py](./samples/samplebase.py) that provides
some basic capabilities to all example programs, such as command-line parsing: all
sample-programs accept `--led-rows`, `--led-chain` and `--led-parallel` as
command line options to adapt to your configuration
```bash
cd samples
sudo ./runtext.py --led-chain=4
```
To use different wiring without recompiling the library to change the default,
you can use `--led-gpio-mapping` (or `-m`). For example, to use Adafruit HAT:
```bash
sudo ./runtext.py --led-gpio-mapping=adafruit-hat
```
Here is a complete example showing how to write an image viewer:
```python
#!/usr/bin/env python
import time
import sys
from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import Image
if len(sys.argv) < 2:
sys.exit("Require an image argument")
else:
image_file = sys.argv[1]
image = Image.open(image_file)
# Configuration for the matrix
options = RGBMatrixOptions()
options.rows = 32
options.chain_length = 1
options.parallel = 1
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
matrix = RGBMatrix(options = options)
# Make image fit our screen.
image.thumbnail((matrix.width, matrix.height), Image.ANTIALIAS)
matrix.SetImage(image.convert('RGB'))
try:
print("Press CTRL-C to stop.")
while True:
time.sleep(100)
except KeyboardInterrupt:
sys.exit(0)
```
## API
The source of truth for what is available in the Python bindings may be found [here](rgbmatrix/core.pyx) (RGBMatrix, FrameCanvas, RGBMatrixOptions) and [here](rgbmatrix/graphics.pyx) (graphics). The underlying implementation's ground truth documentation may be found [here](../../include), specifically for [RGBMatrix, RGBMatrixOptions, and FrameCanvas](../../include/led-matrix.h), [Canvas](../../include/canvas.h) (base class of RGBMatrix), and [graphics methods and Font](../../include/graphics.h).
### User
As noted in the Performance section above, Python programs not run as `root` will not be as high-performance as those run as `root`. When running as `root`, be aware of a potentially-unexpected behavior: to reduce the security attack surface, initializing an RGBMatrix as `root` changes the user from `root` to `daemon` (see [#1170](https://github.com/hzeller/rpi-rgb-led-matrix/issues/1170) for more information) by default. This means, for instance, that some file operations possible before initializing the RGBMatrix will not be possible after initialization. To disable this behavior, set `drop_privileges=False` in RGBMatrixOptions, but be aware that doing so will reduce security.
@@ -0,0 +1,15 @@
# The *.cpp files are included in the distribution; this is only needed when
# working on the pyx files.
#
# Please check in modified *.cpp files with distribution to not require cython
# to be installed on the users' machine.
# for python3: make PYTHON=$(which python3) CYTHON=$(which cython3)
CYTHON ?= cython
all : core.cpp graphics.cpp
%.cpp : %.pyx
$(CYTHON) --cplus -o $@ $^
clean:
rm -rf core.cpp graphics.cpp
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
__version__ = "0.0.1"
__author__ = "Christoph Friedrich <christoph.friedrich@vonaffenfels.de>"
from .core import RGBMatrix, FrameCanvas, RGBMatrixOptions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
cimport cppinc
cdef class Canvas:
cdef cppinc.Canvas *__getCanvas(self) except +
cdef class FrameCanvas(Canvas):
cdef cppinc.FrameCanvas *__canvas
cdef class RGBMatrix(Canvas):
cdef cppinc.RGBMatrix *__matrix
cdef class RGBMatrixOptions:
cdef cppinc.Options __options
cdef cppinc.RuntimeOptions __runtime_options
# Must keep a reference to the encoded bytes for the strings,
# otherwise, when the Options struct is used, it will be garbage collected
cdef bytes __py_encoded_hardware_mapping
cdef bytes __py_encoded_led_rgb_sequence
cdef bytes __py_encoded_pixel_mapper_config
cdef bytes __py_encoded_panel_type
# Local Variables:
# mode: python
# End:
@@ -0,0 +1,264 @@
# distutils: language = c++
from libcpp cimport bool
from libc.stdint cimport uint8_t, uint32_t, uintptr_t
from PIL import Image
import cython
cdef class Canvas:
cdef cppinc.Canvas* __getCanvas(self) except +:
raise Exception("Not implemented")
def SetImage(self, image, int offset_x = 0, int offset_y = 0, unsafe=True):
if (image.mode != "RGB"):
raise Exception("Currently, only RGB mode is supported for SetImage(). Please create images with mode 'RGB' or convert first with image = image.convert('RGB'). Pull requests to support more modes natively are also welcome :)")
if unsafe:
#In unsafe mode we directly access the underlying PIL image array
#in cython, which is considered unsafe pointer accecss,
#however it's super fast and seems to work fine
#https://groups.google.com/forum/#!topic/cython-users/Dc1ft5W6KM4
img_width, img_height = image.size
self.SetPixelsPillow(offset_x, offset_y, img_width, img_height, image)
else:
# First implementation of a SetImage(). OPTIMIZE_ME: A more native
# implementation that directly reads the buffer and calls the underlying
# C functions can certainly be faster.
img_width, img_height = image.size
pixels = image.load()
for x in range(max(0, -offset_x), min(img_width, self.width - offset_x)):
for y in range(max(0, -offset_y), min(img_height, self.height - offset_y)):
(r, g, b) = pixels[x, y]
self.SetPixel(x + offset_x, y + offset_y, r, g, b)
@cython.boundscheck(False)
@cython.wraparound(False)
def SetPixelsPillow(self, int xstart, int ystart, int width, int height, image):
cdef cppinc.FrameCanvas* my_canvas = <cppinc.FrameCanvas*>self.__getCanvas()
cdef int frame_width = my_canvas.width()
cdef int frame_height = my_canvas.height()
cdef int row, col
cdef uint8_t r, g, b
cdef uint32_t **image_ptr
cdef uint32_t pixel
image.load()
ptr_tmp = dict(image.im.unsafe_ptrs)['image32']
image_ptr = (<uint32_t **>(<uintptr_t>ptr_tmp))
for col in range(max(0, -xstart), min(width, frame_width - xstart)):
for row in range(max(0, -ystart), min(height, frame_height - ystart)):
pixel = image_ptr[row][col]
r = (pixel ) & 0xFF
g = (pixel >> 8) & 0xFF
b = (pixel >> 16) & 0xFF
my_canvas.SetPixel(xstart+col, ystart+row, r, g, b)
cdef class FrameCanvas(Canvas):
def __dealloc__(self):
if <void*>self.__canvas != NULL:
self.__canvas = NULL
cdef cppinc.Canvas* __getCanvas(self) except *:
if <void*>self.__canvas != NULL:
return self.__canvas
raise Exception("Canvas was destroyed or not initialized, you cannot use this object anymore")
def Fill(self, uint8_t red, uint8_t green, uint8_t blue):
(<cppinc.FrameCanvas*>self.__getCanvas()).Fill(red, green, blue)
def Clear(self):
(<cppinc.FrameCanvas*>self.__getCanvas()).Clear()
def SetPixel(self, int x, int y, uint8_t red, uint8_t green, uint8_t blue):
(<cppinc.FrameCanvas*>self.__getCanvas()).SetPixel(x, y, red, green, blue)
property width:
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).width()
property height:
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).height()
property pwmBits:
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).pwmbits()
def __set__(self, pwmBits): (<cppinc.FrameCanvas*>self.__getCanvas()).SetPWMBits(pwmBits)
property brightness:
def __get__(self): return (<cppinc.FrameCanvas*>self.__getCanvas()).brightness()
def __set__(self, val): (<cppinc.FrameCanvas*>self.__getCanvas()).SetBrightness(val)
cdef class RGBMatrixOptions:
def __cinit__(self):
self.__options = cppinc.Options()
self.__runtime_options = cppinc.RuntimeOptions()
# RGBMatrix::Options properties
property hardware_mapping:
def __get__(self): return self.__options.hardware_mapping
def __set__(self, value):
self.__py_encoded_hardware_mapping = value.encode('utf-8')
self.__options.hardware_mapping = self.__py_encoded_hardware_mapping
property rows:
def __get__(self): return self.__options.rows
def __set__(self, uint8_t value): self.__options.rows = value
property cols:
def __get__(self): return self.__options.cols
def __set__(self, uint8_t value): self.__options.cols = value
property chain_length:
def __get__(self): return self.__options.chain_length
def __set__(self, uint8_t value): self.__options.chain_length = value
property parallel:
def __get__(self): return self.__options.parallel
def __set__(self, uint8_t value): self.__options.parallel = value
property pwm_bits:
def __get__(self): return self.__options.pwm_bits
def __set__(self, uint8_t value): self.__options.pwm_bits = value
property pwm_lsb_nanoseconds:
def __get__(self): return self.__options.pwm_lsb_nanoseconds
def __set__(self, uint32_t value): self.__options.pwm_lsb_nanoseconds = value
property brightness:
def __get__(self): return self.__options.brightness
def __set__(self, uint8_t value): self.__options.brightness = value
property scan_mode:
def __get__(self): return self.__options.scan_mode
def __set__(self, uint8_t value): self.__options.scan_mode = value
property multiplexing:
def __get__(self): return self.__options.multiplexing
def __set__(self, uint8_t value): self.__options.multiplexing = value
property row_address_type:
def __get__(self): return self.__options.row_address_type
def __set__(self, uint8_t value): self.__options.row_address_type = value
property disable_hardware_pulsing:
def __get__(self): return self.__options.disable_hardware_pulsing
def __set__(self, value): self.__options.disable_hardware_pulsing = value
property show_refresh_rate:
def __get__(self): return self.__options.show_refresh_rate
def __set__(self, value): self.__options.show_refresh_rate = value
property inverse_colors:
def __get__(self): return self.__options.inverse_colors
def __set__(self, value): self.__options.inverse_colors = value
property led_rgb_sequence:
def __get__(self): return self.__options.led_rgb_sequence
def __set__(self, value):
self.__py_encoded_led_rgb_sequence = value.encode('utf-8')
self.__options.led_rgb_sequence = self.__py_encoded_led_rgb_sequence
property pixel_mapper_config:
def __get__(self): return self.__options.pixel_mapper_config
def __set__(self, value):
self.__py_encoded_pixel_mapper_config = value.encode('utf-8')
self.__options.pixel_mapper_config = self.__py_encoded_pixel_mapper_config
property panel_type:
def __get__(self): return self.__options.panel_type
def __set__(self, value):
self.__py_encoded_panel_type = value.encode('utf-8')
self.__options.panel_type = self.__py_encoded_panel_type
property pwm_dither_bits:
def __get__(self): return self.__options.pwm_dither_bits
def __set__(self, uint8_t value): self.__options.pwm_dither_bits = value
property limit_refresh_rate_hz:
def __get__(self): return self.__options.limit_refresh_rate_hz
def __set__(self, value): self.__options.limit_refresh_rate_hz = value
# RuntimeOptions properties
property gpio_slowdown:
def __get__(self): return self.__runtime_options.gpio_slowdown
def __set__(self, uint8_t value): self.__runtime_options.gpio_slowdown = value
property daemon:
def __get__(self): return self.__runtime_options.daemon
def __set__(self, uint8_t value): self.__runtime_options.daemon = value
property drop_privileges:
def __get__(self): return self.__runtime_options.drop_privileges
def __set__(self, uint8_t value): self.__runtime_options.drop_privileges = value
cdef class RGBMatrix(Canvas):
def __cinit__(self, int rows = 0, int chains = 0, int parallel = 0,
RGBMatrixOptions options = None):
# If RGBMatrixOptions not provided, create defaults and set any optional
# parameters supplied
if options == None:
options = RGBMatrixOptions()
if rows > 0:
options.rows = rows
if chains > 0:
options.chain_length = chains
if parallel > 0:
options.parallel = parallel
self.__matrix = cppinc.CreateMatrixFromOptions(options.__options,
options.__runtime_options)
def __dealloc__(self):
self.__matrix.Clear()
del self.__matrix
cdef cppinc.Canvas* __getCanvas(self) except *:
if <void*>self.__matrix != NULL:
return self.__matrix
raise Exception("Canvas was destroyed or not initialized, you cannot use this object anymore")
def Fill(self, uint8_t red, uint8_t green, uint8_t blue):
self.__matrix.Fill(red, green, blue)
def SetPixel(self, int x, int y, uint8_t red, uint8_t green, uint8_t blue):
self.__matrix.SetPixel(x, y, red, green, blue)
def Clear(self):
self.__matrix.Clear()
def CreateFrameCanvas(self):
return __createFrameCanvas(self.__matrix.CreateFrameCanvas())
def SwapOnVSync(self, FrameCanvas newFrame):
return __createFrameCanvas(self.__matrix.SwapOnVSync(newFrame.__canvas))
property luminanceCorrect:
def __get__(self): return self.__matrix.luminance_correct()
def __set__(self, luminanceCorrect): self.__matrix.set_luminance_correct(luminanceCorrect)
property pwmBits:
def __get__(self): return self.__matrix.pwmbits()
def __set__(self, pwmBits): self.__matrix.SetPWMBits(pwmBits)
property brightness:
def __get__(self): return self.__matrix.brightness()
def __set__(self, brightness): self.__matrix.SetBrightness(brightness)
property height:
def __get__(self): return self.__matrix.height()
property width:
def __get__(self): return self.__matrix.width()
cdef __createFrameCanvas(cppinc.FrameCanvas* newCanvas):
canvas = FrameCanvas()
canvas.__canvas = newCanvas
return canvas
# Local Variables:
# mode: python
# End:
@@ -0,0 +1,88 @@
from libcpp cimport bool
from libc.stdint cimport uint8_t, uint32_t
########################
### External classes ###
########################
cdef extern from "canvas.h" namespace "rgb_matrix":
cdef cppclass Canvas:
int width()
int height()
void SetPixel(int, int, uint8_t, uint8_t, uint8_t) nogil
void Clear() nogil
void Fill(uint8_t, uint8_t, uint8_t) nogil
cdef extern from "led-matrix.h" namespace "rgb_matrix":
cdef cppclass RGBMatrix(Canvas):
bool SetPWMBits(uint8_t)
uint8_t pwmbits()
void set_luminance_correct(bool)
bool luminance_correct()
void SetBrightness(uint8_t)
uint8_t brightness()
FrameCanvas *CreateFrameCanvas()
FrameCanvas *SwapOnVSync(FrameCanvas*)
cdef cppclass FrameCanvas(Canvas):
bool SetPWMBits(uint8_t)
uint8_t pwmbits()
void SetBrightness(uint8_t)
uint8_t brightness()
struct RuntimeOptions:
RuntimeOptions() except +
int gpio_slowdown
int daemon
int drop_privileges
RGBMatrix *CreateMatrixFromOptions(Options &options, RuntimeOptions runtime_options)
cdef extern from "led-matrix.h" namespace "rgb_matrix::RGBMatrix":
cdef struct Options:
Options() except +
const char *hardware_mapping
int rows
int cols
int chain_length
int parallel
int pwm_bits
int pwm_lsb_nanoseconds
int brightness
int scan_mode
int row_address_type
int multiplexing
int pwm_dither_bits
int limit_refresh_rate_hz
bool disable_hardware_pulsing
bool show_refresh_rate
bool inverse_colors
const char *led_rgb_sequence
const char *pixel_mapper_config
const char *panel_type
cdef extern from "graphics.h" namespace "rgb_matrix":
cdef struct Color:
Color(uint8_t, uint8_t, uint8_t) except +
uint8_t r
uint8_t g
uint8_t b
cdef cppclass Font:
Font() except +
bool LoadFont(const char*)
int height()
int baseline()
int CharacterWidth(uint32_t)
int DrawGlyph(Canvas*, int, int, const Color, uint32_t);
cdef int DrawText(Canvas*, const Font, int, int, const Color, const char*)
cdef void DrawCircle(Canvas*, int, int, int, const Color)
cdef void DrawLine(Canvas*, int, int, int, int, const Color)
@@ -0,0 +1,11 @@
cimport cppinc
cdef class Color:
cdef cppinc.Color __color
cdef class Font:
cdef cppinc.Font __font
# Local Variables:
# mode: python
# End:
@@ -0,0 +1,54 @@
# distutils: language = c++
from libcpp cimport bool
from libc.stdint cimport uint8_t, uint32_t
cimport core
cdef class Color:
def __init__(self, uint8_t red = 0, uint8_t green = 0, uint8_t blue = 0):
self.__color.r = red
self.__color.g = green
self.__color.b = blue
property red:
def __get__(self): return self.__color.r
def __set__(self, uint8_t value): self.__color.r = value
property green:
def __get__(self): return self.__color.g
def __set__(self, uint8_t value): self.__color.g = value
property blue:
def __get__(self): return self.__color.b
def __set__(self, uint8_t value): self.__color.b = value
cdef class Font:
def CharacterWidth(self, uint32_t char):
return self.__font.CharacterWidth(char)
def LoadFont(self, file):
if (not self.__font.LoadFont(file.encode('utf-8'))):
raise Exception("Couldn't load font " + file)
def DrawGlyph(self, core.Canvas c, int x, int y, Color color, uint32_t char):
return self.__font.DrawGlyph(c.__getCanvas(), x, y, color.__color, char)
property height:
def __get__(self): return self.__font.height()
property baseline:
def __get__(self): return self.__font.baseline()
def DrawText(core.Canvas c, Font f, int x, int y, Color color, text):
return cppinc.DrawText(c.__getCanvas(), f.__font, x, y, color.__color, text.encode('utf-8'))
def DrawCircle(core.Canvas c, int x, int y, int r, Color color):
cppinc.DrawCircle(c.__getCanvas(), x, y, r, color.__color)
def DrawLine(core.Canvas c, int x1, int y1, int x2, int y2, Color color):
cppinc.DrawLine(c.__getCanvas(), x1, y1, x2, y2, color.__color)
# Local Variables:
# mode: python
# End:
@@ -0,0 +1,32 @@
#!/usr/bin/env python
from samplebase import SampleBase
from rgbmatrix import graphics
import time
class GraphicsTest(SampleBase):
def __init__(self, *args, **kwargs):
super(GraphicsTest, self).__init__(*args, **kwargs)
def run(self):
canvas = self.matrix
font = graphics.Font()
font.LoadFont("../../../fonts/7x13.bdf")
red = graphics.Color(255, 0, 0)
graphics.DrawLine(canvas, 5, 5, 22, 13, red)
green = graphics.Color(0, 255, 0)
graphics.DrawCircle(canvas, 15, 15, 10, green)
blue = graphics.Color(0, 0, 255)
graphics.DrawText(canvas, font, 2, 10, blue, "Text")
time.sleep(10) # show display for 10 seconds before exit
# Main function
if __name__ == "__main__":
graphics_test = GraphicsTest()
if (not graphics_test.process()):
graphics_test.print_help()
@@ -0,0 +1,39 @@
#!/usr/bin/env python
from samplebase import SampleBase
import time
class GrayscaleBlock(SampleBase):
def __init__(self, *args, **kwargs):
super(GrayscaleBlock, self).__init__(*args, **kwargs)
def run(self):
sub_blocks = 16
width = self.matrix.width
height = self.matrix.height
x_step = max(1, width / sub_blocks)
y_step = max(1, height / sub_blocks)
count = 0
while True:
for y in range(0, height):
for x in range(0, width):
c = sub_blocks * int(y / y_step) + int(x / x_step)
if count % 4 == 0:
self.matrix.SetPixel(x, y, c, c, c)
elif count % 4 == 1:
self.matrix.SetPixel(x, y, c, 0, 0)
elif count % 4 == 2:
self.matrix.SetPixel(x, y, 0, c, 0)
elif count % 4 == 3:
self.matrix.SetPixel(x, y, 0, 0, c)
count += 1
time.sleep(2)
# Main function
if __name__ == "__main__":
grayscale_block = GrayscaleBlock()
if (not grayscale_block.process()):
grayscale_block.print_help()
@@ -0,0 +1,48 @@
#!/usr/bin/env python
# (This is an example similar to an example from the Adafruit fork
# to show the similarities. Most important difference currently is, that
# this library wants RGB mode.)
#
# A more complex RGBMatrix example works with the Python Imaging Library,
# demonstrating a few graphics primitives and image loading.
# Note that PIL graphics do not have an immediate effect on the display --
# image is drawn into a separate buffer, which is then copied to the matrix
# using the SetImage() function (see examples below).
# Requires rgbmatrix.so present in the same directory.
# PIL Image module (create or load images) is explained here:
# http://effbot.org/imagingbook/image.htm
# PIL ImageDraw module (draw shapes to images) explained here:
# http://effbot.org/imagingbook/imagedraw.htm
from PIL import Image
from PIL import ImageDraw
import time
from rgbmatrix import RGBMatrix, RGBMatrixOptions
# Configuration for the matrix
options = RGBMatrixOptions()
options.rows = 32
options.chain_length = 1
options.parallel = 1
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
matrix = RGBMatrix(options = options)
# RGB example w/graphics prims.
# Note, only "RGB" mode is supported currently.
image = Image.new("RGB", (32, 32)) # Can be larger than matrix if wanted!!
draw = ImageDraw.Draw(image) # Declare Draw instance before prims
# Draw some shapes into image (no immediate effect on matrix)...
draw.rectangle((0, 0, 31, 31), fill=(0, 0, 0), outline=(0, 0, 255))
draw.line((0, 0, 31, 31), fill=(255, 0, 0))
draw.line((0, 31, 31, 0), fill=(0, 255, 0))
# Then scroll image across matrix...
for n in range(-32, 33): # Start off top-left, move off bottom-right
matrix.Clear()
matrix.SetImage(image, n, n)
time.sleep(0.05)
matrix.Clear()
@@ -0,0 +1,40 @@
#!/usr/bin/env python
import time
from samplebase import SampleBase
from PIL import Image
class ImageScroller(SampleBase):
def __init__(self, *args, **kwargs):
super(ImageScroller, self).__init__(*args, **kwargs)
self.parser.add_argument("-i", "--image", help="The image to display", default="../../../examples-api-use/runtext.ppm")
def run(self):
if not 'image' in self.__dict__:
self.image = Image.open(self.args.image).convert('RGB')
self.image.resize((self.matrix.width, self.matrix.height), Image.ANTIALIAS)
double_buffer = self.matrix.CreateFrameCanvas()
img_width, img_height = self.image.size
# let's scroll
xpos = 0
while True:
xpos += 1
if (xpos > img_width):
xpos = 0
double_buffer.SetImage(self.image, -xpos)
double_buffer.SetImage(self.image, -xpos + img_width)
double_buffer = self.matrix.SwapOnVSync(double_buffer)
time.sleep(0.01)
# Main function
# e.g. call with
# sudo ./image-scroller.py --chain=4
# if you have a chain of four
if __name__ == "__main__":
image_scroller = ImageScroller()
if (not image_scroller.process()):
image_scroller.print_help()
@@ -0,0 +1,34 @@
#!/usr/bin/env python
import time
import sys
from rgbmatrix import RGBMatrix, RGBMatrixOptions
from PIL import Image
if len(sys.argv) < 2:
sys.exit("Require an image argument")
else:
image_file = sys.argv[1]
image = Image.open(image_file)
# Configuration for the matrix
options = RGBMatrixOptions()
options.rows = 32
options.chain_length = 1
options.parallel = 1
options.hardware_mapping = 'regular' # If you have an Adafruit HAT: 'adafruit-hat'
matrix = RGBMatrix(options = options)
# Make image fit our screen.
image.thumbnail((matrix.width, matrix.height), Image.ANTIALIAS)
matrix.SetImage(image.convert('RGB'))
try:
print("Press CTRL-C to stop.")
while True:
time.sleep(100)
except KeyboardInterrupt:
sys.exit(0)
@@ -0,0 +1,36 @@
#!/usr/bin/env python
from samplebase import SampleBase
class GrayscaleBlock(SampleBase):
def __init__(self, *args, **kwargs):
super(GrayscaleBlock, self).__init__(*args, **kwargs)
def run(self):
max_brightness = self.matrix.brightness
count = 0
c = 255
while (True):
if self.matrix.brightness < 1:
self.matrix.brightness = max_brightness
count += 1
else:
self.matrix.brightness -= 1
if count % 4 == 0:
self.matrix.Fill(c, 0, 0)
elif count % 4 == 1:
self.matrix.Fill(0, c, 0)
elif count % 4 == 2:
self.matrix.Fill(0, 0, c)
elif count % 4 == 3:
self.matrix.Fill(c, c, c)
self.usleep(20 * 1000)
# Main function
if __name__ == "__main__":
grayscale_block = GrayscaleBlock()
if (not grayscale_block.process()):
grayscale_block.print_help()
@@ -0,0 +1,42 @@
#!/usr/bin/env python
from samplebase import SampleBase
class PulsingColors(SampleBase):
def __init__(self, *args, **kwargs):
super(PulsingColors, self).__init__(*args, **kwargs)
def run(self):
self.offscreen_canvas = self.matrix.CreateFrameCanvas()
continuum = 0
while True:
self.usleep(5 * 1000)
continuum += 1
continuum %= 3 * 255
red = 0
green = 0
blue = 0
if continuum <= 255:
c = continuum
blue = 255 - c
red = c
elif continuum > 255 and continuum <= 511:
c = continuum - 256
red = 255 - c
green = c
else:
c = continuum - 512
green = 255 - c
blue = c
self.offscreen_canvas.Fill(red, green, blue)
self.offscreen_canvas = self.matrix.SwapOnVSync(self.offscreen_canvas)
# Main function
if __name__ == "__main__":
pulsing_colors = PulsingColors()
if (not pulsing_colors.process()):
pulsing_colors.print_help()
@@ -0,0 +1,72 @@
#!/usr/bin/env python
from samplebase import SampleBase
import math
def scale_col(val, lo, hi):
if val < lo:
return 0
if val > hi:
return 255
return 255 * (val - lo) / (hi - lo)
def rotate(x, y, sin, cos):
return x * cos - y * sin, x * sin + y * cos
class RotatingBlockGenerator(SampleBase):
def __init__(self, *args, **kwargs):
super(RotatingBlockGenerator, self).__init__(*args, **kwargs)
def run(self):
cent_x = self.matrix.width / 2
cent_y = self.matrix.height / 2
rotate_square = min(self.matrix.width, self.matrix.height) * 1.41
min_rotate = cent_x - rotate_square / 2
max_rotate = cent_x + rotate_square / 2
display_square = min(self.matrix.width, self.matrix.height) * 0.7
min_display = cent_x - display_square / 2
max_display = cent_x + display_square / 2
deg_to_rad = 2 * 3.14159265 / 360
rotation = 0
# Pre calculate colors
col_table = []
for x in range(int(min_rotate), int(max_rotate)):
col_table.insert(x, scale_col(x, min_display, max_display))
offset_canvas = self.matrix.CreateFrameCanvas()
while True:
rotation += 1
rotation %= 360
# calculate sin and cos once for each frame
angle = rotation * deg_to_rad
sin = math.sin(angle)
cos = math.cos(angle)
for x in range(int(min_rotate), int(max_rotate)):
for y in range(int(min_rotate), int(max_rotate)):
# Our rotate center is always offset by cent_x
rot_x, rot_y = rotate(x - cent_x, y - cent_x, sin, cos)
if x >= min_display and x < max_display and y >= min_display and y < max_display:
x_col = col_table[x]
y_col = col_table[y]
offset_canvas.SetPixel(rot_x + cent_x, rot_y + cent_y, x_col, 255 - y_col, y_col)
else:
offset_canvas.SetPixel(rot_x + cent_x, rot_y + cent_y, 0, 0, 0)
offset_canvas = self.matrix.SwapOnVSync(offset_canvas)
# Main function
if __name__ == "__main__":
rotating_block_generator = RotatingBlockGenerator()
if (not rotating_block_generator.process()):
rotating_block_generator.print_help()
@@ -0,0 +1,36 @@
#!/usr/bin/env python
# Display a runtext with double-buffering.
from samplebase import SampleBase
from rgbmatrix import graphics
import time
class RunText(SampleBase):
def __init__(self, *args, **kwargs):
super(RunText, self).__init__(*args, **kwargs)
self.parser.add_argument("-t", "--text", help="The text to scroll on the RGB LED panel", default="Hello world!")
def run(self):
offscreen_canvas = self.matrix.CreateFrameCanvas()
font = graphics.Font()
font.LoadFont("../../../fonts/7x13.bdf")
textColor = graphics.Color(255, 255, 0)
pos = offscreen_canvas.width
my_text = self.args.text
while True:
offscreen_canvas.Clear()
len = graphics.DrawText(offscreen_canvas, font, pos, 10, textColor, my_text)
pos -= 1
if (pos + len < 0):
pos = offscreen_canvas.width
time.sleep(0.05)
offscreen_canvas = self.matrix.SwapOnVSync(offscreen_canvas)
# Main function
if __name__ == "__main__":
run_text = RunText()
if (not run_text.process()):
run_text.print_help()
@@ -0,0 +1,81 @@
import argparse
import time
import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))
from rgbmatrix import RGBMatrix, RGBMatrixOptions
class SampleBase(object):
def __init__(self, *args, **kwargs):
self.parser = argparse.ArgumentParser()
self.parser.add_argument("-r", "--led-rows", action="store", help="Display rows. 16 for 16x32, 32 for 32x32. Default: 32", default=32, type=int)
self.parser.add_argument("--led-cols", action="store", help="Panel columns. Typically 32 or 64. (Default: 32)", default=32, type=int)
self.parser.add_argument("-c", "--led-chain", action="store", help="Daisy-chained boards. Default: 1.", default=1, type=int)
self.parser.add_argument("-P", "--led-parallel", action="store", help="For Plus-models or RPi2: parallel chains. 1..3. Default: 1", default=1, type=int)
self.parser.add_argument("-p", "--led-pwm-bits", action="store", help="Bits used for PWM. Something between 1..11. Default: 11", default=11, type=int)
self.parser.add_argument("-b", "--led-brightness", action="store", help="Sets brightness level. Default: 100. Range: 1..100", default=100, type=int)
self.parser.add_argument("-m", "--led-gpio-mapping", help="Hardware Mapping: regular, adafruit-hat, adafruit-hat-pwm" , choices=['regular', 'regular-pi1', 'adafruit-hat', 'adafruit-hat-pwm'], type=str)
self.parser.add_argument("--led-scan-mode", action="store", help="Progressive or interlaced scan. 0 Progressive, 1 Interlaced (default)", default=1, choices=range(2), type=int)
self.parser.add_argument("--led-pwm-lsb-nanoseconds", action="store", help="Base time-unit for the on-time in the lowest significant bit in nanoseconds. Default: 130", default=130, type=int)
self.parser.add_argument("--led-show-refresh", action="store_true", help="Shows the current refresh rate of the LED panel")
self.parser.add_argument("--led-slowdown-gpio", action="store", help="Slow down writing to GPIO. Range: 0..4. Default: 1", default=1, type=int)
self.parser.add_argument("--led-no-hardware-pulse", action="store", help="Don't use hardware pin-pulse generation")
self.parser.add_argument("--led-rgb-sequence", action="store", help="Switch if your matrix has led colors swapped. Default: RGB", default="RGB", type=str)
self.parser.add_argument("--led-pixel-mapper", action="store", help="Apply pixel mappers. e.g \"Rotate:90\"", default="", type=str)
self.parser.add_argument("--led-row-addr-type", action="store", help="0 = default; 1=AB-addressed panels; 2=row direct; 3=ABC-addressed panels; 4 = ABC Shift + DE direct", default=0, type=int, choices=[0,1,2,3,4])
self.parser.add_argument("--led-multiplexing", action="store", help="Multiplexing type: 0=direct; 1=strip; 2=checker; 3=spiral; 4=ZStripe; 5=ZnMirrorZStripe; 6=coreman; 7=Kaler2Scan; 8=ZStripeUneven... (Default: 0)", default=0, type=int)
self.parser.add_argument("--led-panel-type", action="store", help="Needed to initialize special panels. Supported: 'FM6126A'", default="", type=str)
self.parser.add_argument("--led-no-drop-privs", dest="drop_privileges", help="Don't drop privileges from 'root' after initializing the hardware.", action='store_false')
self.parser.set_defaults(drop_privileges=True)
def usleep(self, value):
time.sleep(value / 1000000.0)
def run(self):
print("Running")
def process(self):
self.args = self.parser.parse_args()
options = RGBMatrixOptions()
if self.args.led_gpio_mapping != None:
options.hardware_mapping = self.args.led_gpio_mapping
options.rows = self.args.led_rows
options.cols = self.args.led_cols
options.chain_length = self.args.led_chain
options.parallel = self.args.led_parallel
options.row_address_type = self.args.led_row_addr_type
options.multiplexing = self.args.led_multiplexing
options.pwm_bits = self.args.led_pwm_bits
options.brightness = self.args.led_brightness
options.pwm_lsb_nanoseconds = self.args.led_pwm_lsb_nanoseconds
options.led_rgb_sequence = self.args.led_rgb_sequence
options.pixel_mapper_config = self.args.led_pixel_mapper
options.panel_type = self.args.led_panel_type
if self.args.led_show_refresh:
options.show_refresh_rate = 1
if self.args.led_slowdown_gpio != None:
options.gpio_slowdown = self.args.led_slowdown_gpio
if self.args.led_no_hardware_pulse:
options.disable_hardware_pulsing = True
if not self.args.drop_privileges:
options.drop_privileges=False
self.matrix = RGBMatrix(options = options)
try:
# Start loop
print("Press CTRL-C to stop sample")
self.run()
except KeyboardInterrupt:
print("Exiting\n")
sys.exit(0)
return True
@@ -0,0 +1,30 @@
#!/usr/bin/env python
from samplebase import SampleBase
class SimpleSquare(SampleBase):
def __init__(self, *args, **kwargs):
super(SimpleSquare, self).__init__(*args, **kwargs)
def run(self):
offset_canvas = self.matrix.CreateFrameCanvas()
while True:
for x in range(0, self.matrix.width):
offset_canvas.SetPixel(x, x, 255, 255, 255)
offset_canvas.SetPixel(offset_canvas.height - 1 - x, x, 255, 0, 255)
for x in range(0, offset_canvas.width):
offset_canvas.SetPixel(x, 0, 255, 0, 0)
offset_canvas.SetPixel(x, offset_canvas.height - 1, 255, 255, 0)
for y in range(0, offset_canvas.height):
offset_canvas.SetPixel(0, y, 0, 0, 255)
offset_canvas.SetPixel(offset_canvas.width - 1, y, 0, 255, 0)
offset_canvas = self.matrix.SwapOnVSync(offset_canvas)
# Main function
if __name__ == "__main__":
simple_square = SimpleSquare()
if (not simple_square.process()):
simple_square.print_help()
@@ -0,0 +1,33 @@
#!/usr/bin/python
from distutils.core import setup, Extension
core_ext = Extension(
name = 'core',
sources = ['rgbmatrix/core.cpp'],
include_dirs = ['../../include'],
library_dirs = ['../../lib'],
libraries = ['rgbmatrix'],
extra_compile_args = ["-O3", "-Wall"],
language = 'c++'
)
graphics_ext = Extension(
name = 'graphics',
sources = ['rgbmatrix/graphics.cpp'],
include_dirs = ['../../include'],
library_dirs = ['../../lib'],
libraries = ['rgbmatrix'],
extra_compile_args = ["-O3", "-Wall"],
language = 'c++'
)
setup(
name = 'rgbmatrix',
version = '0.0.1',
author = 'Christoph Friedrich',
author_email = 'christoph.friedrich@vonaffenfels.de',
classifiers = ['Development Status :: 3 - Alpha'],
ext_package = 'rgbmatrix',
ext_modules = [core_ext, graphics_ext],
packages = ['rgbmatrix']
)