diff --git a/Makefile b/Makefile index 96da79c4e..d698aa8fe 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ clean: $(MAKE) -C $(PYTHON_LIB_DIR) clean build-csharp: + $(MAKE) -C $(CSHARP_LIB_DIR) nuget $(MAKE) -C $(CSHARP_LIB_DIR) build build-python: $(RGB_LIBRARY) diff --git a/bindings/c#/.gitignore b/bindings/c#/.gitignore index 8620bdd74..6e1427506 100644 --- a/bindings/c#/.gitignore +++ b/bindings/c#/.gitignore @@ -1,2 +1,5 @@ -*.dll -*.exe +**/.vs +**/bin +**/obj +**/*.sln +**/*.csproj.user \ No newline at end of file diff --git a/bindings/c#/Bindings.cs b/bindings/c#/Bindings.cs new file mode 100644 index 000000000..a4957fd07 --- /dev/null +++ b/bindings/c#/Bindings.cs @@ -0,0 +1,85 @@ +global using static RPiRgbLEDMatrix.Bindings; + +using System.Runtime.InteropServices; + +namespace RPiRgbLEDMatrix; + +/* +Some of the extern methods listed below are marked with [SuppressGCTransition]. +This disables some GC checks that may take a long time. But such methods should +be fast and trivial, otherwise the managed code may become unstable (see docs). +Keep this in mind when changing the C/C++ side. + +https://learn.microsoft.com/dotnet/api/system.runtime.interopservices.suppressgctransitionattribute +*/ +internal static class Bindings +{ + private const string Lib = "librgbmatrix.so.1"; + + [DllImport(Lib)] + public static extern IntPtr led_matrix_create(int rows, int chained, int parallel); + + [DllImport(Lib, CharSet = CharSet.Ansi)] + public static extern IntPtr led_matrix_create_from_options_const_argv( + ref InternalRGBLedMatrixOptions options, + int argc, + string[] argv); + + [DllImport(Lib)] + public static extern void led_matrix_delete(IntPtr matrix); + + [DllImport(Lib)] + public static extern IntPtr led_matrix_create_offscreen_canvas(IntPtr matrix); + + [DllImport(Lib)] + public static extern IntPtr led_matrix_swap_on_vsync(IntPtr matrix, IntPtr canvas); + + [DllImport(Lib)] + public static extern IntPtr led_matrix_get_canvas(IntPtr matrix); + + [DllImport(Lib)] + [SuppressGCTransition] + public static extern byte led_matrix_get_brightness(IntPtr matrix); + + [DllImport(Lib)] + [SuppressGCTransition] + public static extern void led_matrix_set_brightness(IntPtr matrix, byte brightness); + + [DllImport(Lib, CharSet = CharSet.Ansi)] + public static extern IntPtr load_font(string bdf_font_file); + + [DllImport(Lib, CharSet = CharSet.Ansi)] + public 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(Lib, CharSet = CharSet.Ansi)] + public 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(Lib, CharSet = CharSet.Ansi)] + public static extern void delete_font(IntPtr font); + + [DllImport(Lib)] + [SuppressGCTransition] + public static extern void led_canvas_get_size(IntPtr canvas, out int width, out int height); + + [DllImport(Lib)] + [SuppressGCTransition] + public static extern void led_canvas_set_pixel(IntPtr canvas, int x, int y, byte r, byte g, byte b); + + [DllImport(Lib)] + public static extern void led_canvas_set_pixels(IntPtr canvas, int x, int y, int width, int height, + ref Color colors); + + [DllImport(Lib)] + public static extern void led_canvas_clear(IntPtr canvas); + + [DllImport(Lib)] + public static extern void led_canvas_fill(IntPtr canvas, byte r, byte g, byte b); + + [DllImport(Lib)] + public static extern void draw_circle(IntPtr canvas, int xx, int y, int radius, byte r, byte g, byte b); + + [DllImport(Lib)] + public static extern void draw_line(IntPtr canvas, int x0, int y0, int x1, int y1, byte r, byte g, byte b); +} diff --git a/bindings/c#/Color.cs b/bindings/c#/Color.cs new file mode 100644 index 000000000..80a9a4fe2 --- /dev/null +++ b/bindings/c#/Color.cs @@ -0,0 +1,38 @@ +namespace RPiRgbLEDMatrix; + +/// +/// Represents an RGB (red, green, blue) color +/// +public struct Color +{ + /// + /// The red component value of this instance. + /// + public byte R; + + /// + /// The green component value of this instance. + /// + public byte G; + + /// + /// The blue component value of this instance. + /// + public byte B; + + /// + /// Creates a new color from the specified color values (red, green, and blue). + /// + /// The red component value. + /// The green component value. + /// The blue component value. + public Color(int r, int g, int b) : this((byte)r, (byte)g, (byte)b) { } + + /// + /// Creates a new color from the specified color values (red, green, and blue). + /// + /// The red component value. + /// The green component value. + /// The blue component value. + public Color(byte r, byte g, byte b) => (R, G, B) = (r, g, b); +} diff --git a/bindings/c#/InternalRGBLedMatrixOptions.cs b/bindings/c#/InternalRGBLedMatrixOptions.cs new file mode 100644 index 000000000..04859bd54 --- /dev/null +++ b/bindings/c#/InternalRGBLedMatrixOptions.cs @@ -0,0 +1,50 @@ +using System.Runtime.InteropServices; + +namespace RPiRgbLEDMatrix; + +[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; + + public InternalRGBLedMatrixOptions(RGBLedMatrixOptions opt) + { + chain_length = opt.ChainLength; + rows = opt.Rows; + cols = opt.Cols; + hardware_mapping = Marshal.StringToHGlobalAnsi(opt.HardwareMapping); + inverse_colors = (byte)(opt.InverseColors ? 1 : 0); + led_rgb_sequence = Marshal.StringToHGlobalAnsi(opt.LedRgbSequence); + pixel_mapper_config = Marshal.StringToHGlobalAnsi(opt.PixelMapperConfig); + panel_type = Marshal.StringToHGlobalAnsi(opt.PanelType); + parallel = opt.Parallel; + multiplexing = (int)opt.Multiplexing; + pwm_bits = opt.PwmBits; + pwm_lsb_nanoseconds = opt.PwmLsbNanoseconds; + pwm_dither_bits = opt.PwmDitherBits; + scan_mode = (int)opt.ScanMode; + show_refresh_rate = (byte)(opt.ShowRefreshRate ? 1 : 0); + limit_refresh_rate_hz = opt.LimitRefreshRateHz; + brightness = opt.Brightness; + disable_hardware_pulsing = (byte)(opt.DisableHardwarePulsing ? 1 : 0); + row_address_type = opt.RowAddressType; + } +}; diff --git a/bindings/c#/Makefile b/bindings/c#/Makefile index 2e55fb630..5df1f7368 100644 --- a/bindings/c#/Makefile +++ b/bindings/c#/Makefile @@ -1,21 +1,34 @@ -CSHARP_LIB=RGBLedMatrix.dll -SOURCES=RGBLedCanvas.cs RGBLedMatrix.cs RGBLedFont.cs -CSHARP_COMPILER=mcs +# This Makefile is intended to be used only by the toplevel Makefile. +# For any other purposes, use .NET SDK build tools directly +# Don't forget to synchronize these variables with the 'RPiRgbLEDMatrix.csproj' file RGB_LIBDIR=../../lib RGB_LIBRARY_NAME=rgbmatrix RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).so.1 -EXAMPLES_DIR=examples +NUGET_VERSION = 1.0.0 +NUGET_ID = HZeller.RPiRgbLEDMatrix +NUGET_CONFIG = Release -$(CSHARP_LIB) : $(SOURCES) $(RGB_LIBRARY) - $(CSHARP_COMPILER) -target:library -out:$@ $(SOURCES) +NUGET_PACKAGE = /bin/$(NUGET_CONFIG)/$(NUGET_ID).$(NUGET_VERSION).nupkg + +$(NUGET_PACKAGE): $(RGB_LIBRARY) + dotnet pack -c $(NUGET_CONFIG) -p:SkipNative=false -p:PackageId=$(NUGET_ID) -p:Version=$(NUGET_VERSION) + +# The examples also depend on the 'RPiRgbLEDMatrix.csproj', but this will be handled by 'dotnet' +build: $(RGB_LIBRARY) + dotnet build examples/FontExample/FontExample.csproj -p:SkipNative=false + dotnet build examples/MatrixRain/MatrixRain.csproj -p:SkipNative=false + dotnet build examples/MinimalExample/MinimalExample.csproj -p:SkipNative=false + dotnet build examples/PulsingBrightness/PulsingBrightness.csproj -p:SkipNative=false + dotnet build examples/Rotating3DCube/Rotating3DCube.csproj -p:SkipNative=false + dotnet build examples/PlayGIF/PlayGIF.csproj -p:SkipNative=false $(RGB_LIBRARY): $(MAKE) -C $(RGB_LIBDIR) -build: $(CSHARP_LIB) - $(MAKE) -C $(EXAMPLES_DIR) all +# Used by toplevel Makefile +nuget: $(NUGET_PACKAGE) -clean: - rm -f $(CSHARP_LIB) +# Used in 'RPiRgbLEDMatrix.csproj' +library: $(RGB_LIBRARY) diff --git a/bindings/c#/Multiplexing.cs b/bindings/c#/Multiplexing.cs new file mode 100644 index 000000000..b974e38cf --- /dev/null +++ b/bindings/c#/Multiplexing.cs @@ -0,0 +1,11 @@ +namespace RPiRgbLEDMatrix; + +/// +/// Type of multiplexing. +/// +public enum Multiplexing : int +{ + Direct = 0, + Stripe = 1, + Checker = 2 +} diff --git a/bindings/c#/README.md b/bindings/c#/README.md index 10e9cb6aa..bb54c1154 100644 --- a/bindings/c#/README.md +++ b/bindings/c#/README.md @@ -1,31 +1,16 @@ -C# bindings for RGB Matrix library +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. +To build the C# wrapper for the RGB Matrix C library you need to first have __.NET SDK__ installed. -### Install Mono +### Install .NET SDK -```shell -$ sudo apt-get update -$ sudo apt-get install mono-complete -``` +`sudo apt install dotnet6` should work in most cases. +For some old distributions, read [docs](https://learn.microsoft.com/dotnet/core/install/linux) -Then, in the root directory for the matrix library type +Then, in the `bindings/c#` directory type: `dotnet build` -```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. +To run the example applications in the c#\examples\EXAMPLE folder: `sudo dotnet run` diff --git a/bindings/c#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 74c1548be..fa030b966 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -1,100 +1,100 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +namespace RPiRgbLEDMatrix; -namespace rpi_rgb_led_matrix_sharp +/// +/// Represents a canvas whose pixels can be manipulated. +/// +public class RGBLedCanvas { - 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; - } + // 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; - 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); - } + // this is not called directly by the consumer code, + // consumer uses factory methods in RGBLedMatrix + internal RGBLedCanvas(IntPtr canvas) + { + _canvas = canvas; + led_canvas_get_size(_canvas, out var width, out var height); + Width = width; + Height = height; } - public struct Color + /// + /// The width of the canvas in pixels. + /// + public int Width { get; private set; } + + /// + /// The height of the canvas in pixels. + /// + public int Height { get; private set; } + + /// + /// Sets the color of a specific pixel. + /// + /// The X coordinate of the pixel. + /// The Y coordinate of the pixel. + /// New pixel color. + public void SetPixel(int x, int y, Color color) => led_canvas_set_pixel(_canvas, x, y, color.R, color.G, color.B); + + /// + /// Copies the colors from the specified buffer to a rectangle on the canvas. + /// + /// The X coordinate of the top-left pixel of the rectangle. + /// The Y coordinate of the top-left pixel of the rectangle. + /// Width of the rectangle. + /// Height of the rectangle. + /// Buffer containing the colors to copy. + public void SetPixels(int x, int y, int width, int height, Span colors) { - 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; + if (colors.Length < width * height) + throw new ArgumentOutOfRangeException(nameof(colors)); + led_canvas_set_pixels(_canvas, x, y, width, height, ref colors[0]); } + + /// + /// Sets the color of the entire canvas. + /// + /// New canvas color. + public void Fill(Color color) => led_canvas_fill(_canvas, color.R, color.G, color.B); + + /// + /// Cleans the entire canvas. + /// + public void Clear() => led_canvas_clear(_canvas); + + /// + /// Draws a circle of the specified color. + /// + /// The X coordinate of the center. + /// The Y coordinate of the center. + /// The radius of the circle, in pixels. + /// The color of the circle. + public void DrawCircle(int x, int y, int radius, Color color) => + draw_circle(_canvas, x, y, radius, color.R, color.G, color.B); + + /// + /// Draws a line of the specified color. + /// + /// The X coordinate of the first point. + /// The Y coordinate of the first point. + /// The X coordinate of the second point. + /// The Y coordinate of the second point. + /// The color of the line. + 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); + + /// + /// Draws the text with the specified color. + /// + /// Font to draw text with. + /// The X coordinate of the starting point. + /// The Y coordinate of the starting point. + /// The color of the text. + /// Text to draw. + /// Additional spacing between characters. + /// Whether to draw the text vertically. + /// How many pixels was advanced on the screen. + public int DrawText(RGBLedFont font, int x, int y, Color color, string text, int spacing = 0, bool vertical = false) => + font.DrawText(_canvas, x, y, color, text, spacing, vertical); } diff --git a/bindings/c#/RGBLedFont.cs b/bindings/c#/RGBLedFont.cs index 4e3b7f828..02f3854a4 100644 --- a/bindings/c#/RGBLedFont.cs +++ b/bindings/c#/RGBLedFont.cs @@ -1,60 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; +namespace RPiRgbLEDMatrix; -namespace rpi_rgb_led_matrix_sharp +/// +/// Represents a .BDF font. +/// +public class RGBLedFont : IDisposable { - 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); + internal IntPtr _font; + private bool disposedValue = false; - [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); + /// + /// Loads the BDF font from the specified file. + /// + /// The path to the BDF file to load. + public RGBLedFont(string bdfFontPath) => _font = load_font(bdfFontPath); - [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); + } - 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); - } + protected virtual void Dispose(bool disposing) + { + if (disposedValue) return; + delete_font(_font); + disposedValue = true; + } - #region IDisposable Support - private bool disposedValue = false; + ~RGBLedFont() => Dispose(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 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } } diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 59135d075..768fa953a 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -1,299 +1,112 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Buffers; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -namespace rpi_rgb_led_matrix_sharp +namespace RPiRgbLEDMatrix; + +/// +/// Represents a RGB matrix. +/// +public class RGBLedMatrix : IDisposable { - public class RGBLedMatrix : IDisposable + private IntPtr matrix; + private bool disposedValue = false; + + /// + /// Initializes a new matrix. + /// + /// Size of a single module. Can be 32, 16 or 8. + /// How many modules are connected in a chain. + /// How many modules are connected in a parallel. + public RGBLedMatrix(int rows, int chained, int parallel) { - #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; - } + matrix = led_matrix_create(rows, chained, parallel); + if (matrix == (IntPtr)0) + throw new ArgumentException("Could not initialize a new matrix"); + } - public byte Brightness + /// + /// Initializes a new matrix. + /// + /// A configuration of a matrix. + public RGBLedMatrix(RGBLedMatrixOptions options) + { + InternalRGBLedMatrixOptions opt = default; + try { - get { return led_matrix_get_brightness(matrix); } - set { led_matrix_set_brightness(matrix, value); } + opt = new(options); + var 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]. + var argv = new string[args.Length + 1]; + argv[0] = args[0]; + argv[1] = $"--led-slowdown-gpio={options.GpioSlowdown}"; + Array.Copy(args, 1, argv, 2, args.Length - 1); + + matrix = led_matrix_create_from_options_const_argv(ref opt, argv.Length, argv); + if (matrix == (IntPtr)0) + throw new ArgumentException("Could not initialize a new matrix"); } - - #region IDisposable Support - private bool disposedValue = false; - - protected virtual void Dispose(bool disposing) + finally { - if (!disposedValue) - { - led_matrix_delete(matrix); - disposedValue = true; - } + if(options.HardwareMapping is not null) Marshal.FreeHGlobal(opt.hardware_mapping); + if(options.LedRgbSequence is not null) Marshal.FreeHGlobal(opt.led_rgb_sequence); + if(options.PixelMapperConfig is not null) Marshal.FreeHGlobal(opt.pixel_mapper_config); + if(options.PanelType is not null) Marshal.FreeHGlobal(opt.panel_type); } - ~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 + /// + /// Creates a new backbuffer canvas for drawing on. + /// + /// An instance of representing the canvas. + public RGBLedCanvas CreateOffscreenCanvas() => new(led_matrix_create_offscreen_canvas(matrix)); + + /// + /// Returns a canvas representing the current frame buffer. + /// + /// An instance of representing the canvas. + /// Consider using instead. + public RGBLedCanvas GetCanvas() => new(led_matrix_get_canvas(matrix)); + + /// + /// Swaps this canvas with the currently active canvas. The active canvas + /// becomes a backbuffer and is mapped to instance. + ///
+ /// This operation guarantees vertical synchronization. + ///
+ /// Backbuffer canvas to swap. + public void SwapOnVsync(RGBLedCanvas canvas) => + canvas._canvas = led_matrix_swap_on_vsync(matrix, canvas._canvas); + + /// + /// The general brightness of the matrix. + /// + public byte Brightness { - /// - /// Name of the hardware mapping used. If passed NULL here, the default is used. - /// - public string HardwareMapping; - - /// - /// The "rows" are the number of rows supported by the display, so 32 or 16. - /// Default: 32. - /// - public int Rows; - - /// - /// 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. - /// - public int Cols; - - /// - /// The chain_length is the number of displays daisy-chained together - /// (output of one connected to input of next). Default: 1 - /// - public int ChainLength; - - /// - /// 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 - /// - public int Parallel; - - /// - /// 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. - /// - public int PwmBits; - - /// - /// 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. - /// - public int PwmLsbNanoseconds; - - /// - /// The lower bits can be time-dithered for higher refresh rate. - /// - public int PwmDitherBits; - - /// - /// The initial brightness of the panel in percent. Valid range is 1..100 - /// - public int Brightness; - - /// - /// Scan mode: 0=progressive, 1=interlaced - /// - public int ScanMode; - - /// - /// 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 - /// - public int RowAddressType; - - /// - /// Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8) - /// - public int Multiplexing; - - /// - /// In case the internal sequence of mapping is not "RGB", this contains the real mapping. Some panels mix up these colors. - /// - public string LedRgbSequence; - - /// - /// 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; + get => led_matrix_get_brightness(matrix); + set => led_matrix_set_brightness(matrix, value); + } - /// - /// Panel type. Typically just empty, but certain panels (FM6126) - /// requie an initialization sequence - /// - public string PanelType; + protected virtual void Dispose(bool disposing) + { + if (disposedValue) return; - /// - /// Allow to use the hardware subsystem to create pulses. This won't do anything if output enable is not connected to GPIO 18. - /// - public bool DisableHardwarePulsing; - public bool ShowRefreshRate; - public bool InverseColors; + led_matrix_delete(matrix); + disposedValue = true; + } - /// - /// Limit refresh rate of LED panel. This will help on a loaded system - // to keep a constant refresh rate. <= 0 for no limit. - /// - public int LimitRefreshRateHz; + ~RGBLedMatrix() => Dispose(false); - /// - /// Slowdown GPIO. Needed for faster Pis/slower panels. - /// - public int GpioSlowdown; - }; + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } diff --git a/bindings/c#/RGBLedMatrixOptions.cs b/bindings/c#/RGBLedMatrixOptions.cs new file mode 100644 index 000000000..2ea9a6614 --- /dev/null +++ b/bindings/c#/RGBLedMatrixOptions.cs @@ -0,0 +1,124 @@ +namespace RPiRgbLEDMatrix; + +/// +/// Represents the matrix settings. +/// +public struct RGBLedMatrixOptions +{ + /// + /// Name of the hardware mapping used. If passed + /// here, the default is used. + /// + public string? HardwareMapping = null; + + /// + /// The "rows" are the number of rows supported by the display, so 32 or 16. + /// Default: 32. + /// + public int Rows = 32; + + /// + /// 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. + /// + public int Cols = 32; + + /// + /// The chain_length is the number of displays daisy-chained together + /// (output of one connected to input of next). Default: 1 + /// + public int ChainLength = 1; + + /// + /// 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 + /// + public int Parallel = 1; + + /// + /// 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. + /// + public int PwmBits = 11; + + /// + /// 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. + /// + public int PwmLsbNanoseconds = 130; + + /// + /// The lower bits can be time-dithered for higher refresh rate. + /// + public int PwmDitherBits = 0; + + /// + /// The initial brightness of the panel in percent. Valid range is 1..100 + /// + public int Brightness = 100; + + /// + /// Scan mode. + /// + public ScanModes ScanMode = ScanModes.Progressive; + + /// + /// 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 + /// + public int RowAddressType = 0; + + /// + /// Type of multiplexing. + /// + public Multiplexing Multiplexing = Multiplexing.Direct; + + /// + /// In case the internal sequence of mapping is not "RGB", this + /// contains the real mapping. Some panels mix up these colors. + /// + public string? LedRgbSequence = null; + + /// + /// 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 = null; + + /// + /// Panel type. Typically just empty, but certain panels (FM6126) + /// requie an initialization sequence + /// + public string? PanelType = null; + + /// + /// Allow to use the hardware subsystem to create pulses. This won't do + /// anything if output enable is not connected to GPIO 18. + /// + public bool DisableHardwarePulsing = false; + public bool ShowRefreshRate = false; + public bool InverseColors = false; + + /// + /// Limit refresh rate of LED panel. This will help on a loaded system + /// to keep a constant refresh rate. <= 0 for no limit. + /// + public int LimitRefreshRateHz = 0; + + /// + /// Slowdown GPIO. Needed for faster Pis/slower panels. + /// + public int GpioSlowdown = 1; + + /// + /// Creates default matrix settings. + /// + public RGBLedMatrixOptions() { } +} diff --git a/bindings/c#/RPiRgbLEDMatrix.csproj b/bindings/c#/RPiRgbLEDMatrix.csproj new file mode 100644 index 000000000..26055fa5b --- /dev/null +++ b/bindings/c#/RPiRgbLEDMatrix.csproj @@ -0,0 +1,27 @@ + + + net6.0 + enable + enable + + + + + true + + + + + + + Always + + + + + + + + diff --git a/bindings/c#/ScanModes.cs b/bindings/c#/ScanModes.cs new file mode 100644 index 000000000..4281ec750 --- /dev/null +++ b/bindings/c#/ScanModes.cs @@ -0,0 +1,10 @@ +namespace RPiRgbLEDMatrix; + +/// +/// Scan modes. +/// +public enum ScanModes +{ + Progressive = 0, + Interlaced = 1 +} diff --git a/bindings/c#/examples/FontExample/FontExample.csproj b/bindings/c#/examples/FontExample/FontExample.csproj new file mode 100644 index 000000000..7be8960c6 --- /dev/null +++ b/bindings/c#/examples/FontExample/FontExample.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + enable + enable + + + + + diff --git a/bindings/c#/examples/FontExample/Program.cs b/bindings/c#/examples/FontExample/Program.cs new file mode 100644 index 000000000..42af63d81 --- /dev/null +++ b/bindings/c#/examples/FontExample/Program.cs @@ -0,0 +1,29 @@ +using RPiRgbLEDMatrix; + +if (args.Length < 1) +{ + Console.WriteLine("font-example.exe [font_path] "); + return -1; +} +string text = "Hello World!"; +if (args.Length > 1) + text = args[1]; + + +using var matrix = new RGBLedMatrix(32, 2, 1); +var canvas = matrix.CreateOffscreenCanvas(); +using var font = new RGBLedFont(args[0]); + +canvas.DrawText(font, 1, 6, new Color(0, 255, 0), text); +matrix.SwapOnVsync(canvas); + +// run until user presses Ctrl+C +var running = true; +Console.CancelKeyPress += (_, e) => +{ + running = false; + e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose +}; +while (running) Thread.Yield(); + +return 0; diff --git a/bindings/c#/examples/Makefile b/bindings/c#/examples/Makefile deleted file mode 100644 index 49808ecfa..000000000 --- a/bindings/c#/examples/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -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 diff --git a/bindings/c#/examples/MatrixRain/MatrixRain.csproj b/bindings/c#/examples/MatrixRain/MatrixRain.csproj new file mode 100644 index 000000000..7be8960c6 --- /dev/null +++ b/bindings/c#/examples/MatrixRain/MatrixRain.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + enable + enable + + + + + diff --git a/bindings/c#/examples/MatrixRain/Program.cs b/bindings/c#/examples/MatrixRain/Program.cs new file mode 100644 index 000000000..ac465f522 --- /dev/null +++ b/bindings/c#/examples/MatrixRain/Program.cs @@ -0,0 +1,74 @@ +using RPiRgbLEDMatrix; + +const int MaxHeight = 16; +const int ColorStep = 15; +const int FrameStep = 1; + +using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { ChainLength = 2 }); +var canvas = matrix.CreateOffscreenCanvas(); + +var rnd = new Random(); +var points = new List(); +var recycled = new Stack(); +var frame = 0; + +var running = true; +Console.CancelKeyPress += (s, e) => +{ + running = false; + e.Cancel = true; // don't terminate, we need to dispose +}; + +// run until user presses Ctrl+C +while (running) +{ + var frameStart = Environment.TickCount64; + frame++; + + if (frame % FrameStep == 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) continue; + point.Y++; + + if (point.Y - MaxHeight > canvas.Height) + { + point.Recycled = true; + recycled.Push(point); + } + + for (var i = 0; i < MaxHeight; i++) + { + canvas.SetPixel(point.X, point.Y - i, new Color(0, 255 - i * ColorStep, 0)); + } + } + + matrix.SwapOnVsync(canvas); + + // force 30 FPS + var elapsed = Environment.TickCount64 - frameStart; + if (elapsed < 33) Thread.Sleep(33 - (int)elapsed); +} + +class Point +{ + public int X; + public int Y; + public bool Recycled = false; + + public Point(int x, int y) => (X, Y) = (x, y); +} diff --git a/bindings/c#/examples/MinimalExample/MinimalExample.csproj b/bindings/c#/examples/MinimalExample/MinimalExample.csproj new file mode 100644 index 000000000..7be8960c6 --- /dev/null +++ b/bindings/c#/examples/MinimalExample/MinimalExample.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + enable + enable + + + + + diff --git a/bindings/c#/examples/MinimalExample/Program.cs b/bindings/c#/examples/MinimalExample/Program.cs new file mode 100644 index 000000000..7446ef0df --- /dev/null +++ b/bindings/c#/examples/MinimalExample/Program.cs @@ -0,0 +1,19 @@ +using RPiRgbLEDMatrix; + +using var matrix = new RGBLedMatrix(32, 2, 1); +var canvas = matrix.CreateOffscreenCanvas(); + +var centerX = canvas.Width / 2; +var centerY = canvas.Height / 2; +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(centerX, centerY, 6, new Color(0, 0, 255)); + canvas.DrawLine(centerX - 3, centerY - 3, centerX + 3, centerY + 3, new Color(0, 0, 255)); + canvas.DrawLine(centerX - 3, centerY + 3, centerX + 3, centerY - 3, new Color(0, 0, 255)); + + matrix.SwapOnVsync(canvas); +} diff --git a/bindings/c#/examples/PlayGIF/PlayGIF.csproj b/bindings/c#/examples/PlayGIF/PlayGIF.csproj new file mode 100644 index 000000000..5c72aafd7 --- /dev/null +++ b/bindings/c#/examples/PlayGIF/PlayGIF.csproj @@ -0,0 +1,12 @@ + + + Exe + net6.0 + enable + enable + + + + + + diff --git a/bindings/c#/examples/PlayGIF/Program.cs b/bindings/c#/examples/PlayGIF/Program.cs new file mode 100644 index 000000000..f44ee08ce --- /dev/null +++ b/bindings/c#/examples/PlayGIF/Program.cs @@ -0,0 +1,40 @@ +using RPiRgbLEDMatrix; +using System.Runtime.InteropServices; +using Color = RPiRgbLEDMatrix.Color; + +Console.Write("GIF path: "); +var path = Console.ReadLine()!; + +using var matrix = new RGBLedMatrix(32, 2, 1); +var canvas = matrix.CreateOffscreenCanvas(); + +Configuration.Default.PreferContiguousImageBuffers = true; +using var image = Image.Load(path); +image.Mutate(o => o.Resize(canvas.Width, canvas.Height)); + +var running = true; +Console.CancelKeyPress += (s, e) => +{ + running = false; + e.Cancel = true; // don't terminate, we need to dispose +}; + +var frame = -1; +// preprocess frames to get delays and pixel buffers +var frames = image.Frames + .Select(f => ( + Pixels: f.DangerousTryGetSinglePixelMemory(out var memory) ? memory : throw new("Could not get pixel buffer"), + Delay: f.Metadata.GetGifMetadata().FrameDelay * 10 + )).ToArray(); + +// run until user presses Ctrl+C +while (running) +{ + frame = (frame + 1) % frames.Length; + + var data = MemoryMarshal.Cast(frames[frame].Pixels.Span); + canvas.SetPixels(0, 0, canvas.Width, canvas.Height, data); + + matrix.SwapOnVsync(canvas); + Thread.Sleep(frames[frame].Delay); +} diff --git a/bindings/c#/examples/PulsingBrightness/Program.cs b/bindings/c#/examples/PulsingBrightness/Program.cs new file mode 100644 index 000000000..5ebb05848 --- /dev/null +++ b/bindings/c#/examples/PulsingBrightness/Program.cs @@ -0,0 +1,33 @@ +using RPiRgbLEDMatrix; + +using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { Rows = 32, Cols = 64 }); +var canvas = matrix.CreateOffscreenCanvas(); + +var maxBrightness = matrix.Brightness; +var rnd = new Random(); + +// run until user presses Ctrl+C +var running = true; +Console.CancelKeyPress += (_, e) => +{ + running = false; + e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose +}; + +var color = new Color(rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256)); +while (running) +{ + if (matrix.Brightness < 1) + { + matrix.Brightness = maxBrightness; + color = new Color(rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256)); + } + else + { + matrix.Brightness--; + } + + canvas.Fill(color); + matrix.SwapOnVsync(canvas); + Thread.Sleep(20); +} diff --git a/bindings/c#/examples/PulsingBrightness/PulsingBrightness.csproj b/bindings/c#/examples/PulsingBrightness/PulsingBrightness.csproj new file mode 100644 index 000000000..7be8960c6 --- /dev/null +++ b/bindings/c#/examples/PulsingBrightness/PulsingBrightness.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + enable + enable + + + + + diff --git a/bindings/c#/examples/Rotating3DCube/Program.cs b/bindings/c#/examples/Rotating3DCube/Program.cs new file mode 100644 index 000000000..f7d572fb7 --- /dev/null +++ b/bindings/c#/examples/Rotating3DCube/Program.cs @@ -0,0 +1,90 @@ +using RPiRgbLEDMatrix; +using System.Numerics; + +const float MaxModuleSpeed = 0.1f; +const float FOV = 60f; +const float Scale = 1.1f; +const float LerpPow = 0.002f; +const int ChangePerFrames = 50; + +using var leds = new RGBLedMatrix(32, 1, 1); +var canvas = leds.CreateOffscreenCanvas(); + +var (centerX, centerY) = (canvas.Width / 2, canvas.Height / 2); + +var rnd = new Random(); +var angleSpeed = new Vector3(); +var nextAngleSpeed = new Vector3(); +var frame = -1; + +var rotateMatrix = Matrix4x4.Identity; +var scaleMatrix = Matrix4x4.CreateScale(Scale); +var projectMatrix = Matrix4x4.CreatePerspectiveFieldOfView(FOV / 180 * MathF.PI, 1, 0.1f, 100f); +var cameraMatrix = Matrix4x4.CreateLookAt(new(0, 0, 4), new(0, 0, 0), new(0, 1, 0)); + +// run until user presses Ctrl+C +var running = true; +Console.CancelKeyPress += (_, e) => +{ + running = false; + e.Cancel = true; // do not terminate program with Ctrl+C, we need to dispose +}; +while (running) +{ + var frameStart = Environment.TickCount64; + + // update angle speed + frame = (frame + 1) % ChangePerFrames; + if(frame == 0) + nextAngleSpeed = new Vector3( + (rnd.NextSingle() * 2 - 1) * MaxModuleSpeed, + (rnd.NextSingle() * 2 - 1) * MaxModuleSpeed, + (rnd.NextSingle() * 2 - 1) * MaxModuleSpeed + ); + + angleSpeed = Vector3.Lerp(angleSpeed, nextAngleSpeed, LerpPow); + + // update matrices + rotateMatrix *= Matrix4x4.CreateRotationX(angleSpeed.X); + rotateMatrix *= Matrix4x4.CreateRotationY(angleSpeed.Y); + rotateMatrix *= Matrix4x4.CreateRotationZ(angleSpeed.Z); + var matrix = scaleMatrix * rotateMatrix * cameraMatrix * projectMatrix; + + // calculate points + var top1 = Vector4.Transform(new Vector3( 1, 1, 1), matrix); + var top2 = Vector4.Transform(new Vector3(-1, 1, 1), matrix); + var top3 = Vector4.Transform(new Vector3(-1, 1, -1), matrix); + var top4 = Vector4.Transform(new Vector3( 1, 1, -1), matrix); + + var bot1 = Vector4.Transform(new Vector3( 1, -1, 1), matrix); + var bot2 = Vector4.Transform(new Vector3(-1, -1, 1), matrix); + var bot3 = Vector4.Transform(new Vector3(-1, -1, -1), matrix); + var bot4 = Vector4.Transform(new Vector3( 1, -1, -1), matrix); + + // draw + canvas.Fill(new(0, 0, 0)); + DrawLine(top1, top2); + DrawLine(top2, top3); + DrawLine(top3, top4); + DrawLine(top4, top1); + + DrawLine(bot1, bot2); + DrawLine(bot2, bot3); + DrawLine(bot3, bot4); + DrawLine(bot4, bot1); + + DrawLine(top1, bot1); + DrawLine(top2, bot2); + DrawLine(top3, bot3); + DrawLine(top4, bot4); + + leds.SwapOnVsync(canvas); + // force 30 FPS + var elapsed = Environment.TickCount64 - frameStart; + if (elapsed < 33) Thread.Sleep(33 - (int)elapsed); +} + +void DrawLine(Vector4 a, Vector4 b) => canvas.DrawLine( + (int)(a.X * a.W + centerX), (int)(a.Y * a.W + centerY), + (int)(b.X * b.W + centerX), (int)(b.Y * b.W + centerY), + new(255, 255, 255)); diff --git a/bindings/c#/examples/Rotating3DCube/Rotating3DCube.csproj b/bindings/c#/examples/Rotating3DCube/Rotating3DCube.csproj new file mode 100644 index 000000000..7be8960c6 --- /dev/null +++ b/bindings/c#/examples/Rotating3DCube/Rotating3DCube.csproj @@ -0,0 +1,11 @@ + + + Exe + net6.0 + enable + enable + + + + + diff --git a/bindings/c#/examples/font-example.cs b/bindings/c#/examples/font-example.cs deleted file mode 100644 index 463831424..000000000 --- a/bindings/c#/examples/font-example.cs +++ /dev/null @@ -1,36 +0,0 @@ -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] "); - 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; - } - } -} diff --git a/bindings/c#/examples/matrix-rain.cs b/bindings/c#/examples/matrix-rain.cs deleted file mode 100644 index fe8d6b7d0..000000000 --- a/bindings/c#/examples/matrix-rain.cs +++ /dev/null @@ -1,90 +0,0 @@ -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(); - var recycled = new Stack(); - 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; - } - } -} diff --git a/bindings/c#/examples/minimal-example.cs b/bindings/c#/examples/minimal-example.cs deleted file mode 100644 index 90a447cc8..000000000 --- a/bindings/c#/examples/minimal-example.cs +++ /dev/null @@ -1,32 +0,0 @@ -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; - } - } -} diff --git a/bindings/c#/examples/pulsing-brightness.cs b/bindings/c#/examples/pulsing-brightness.cs deleted file mode 100644 index c5ef8b492..000000000 --- a/bindings/c#/examples/pulsing-brightness.cs +++ /dev/null @@ -1,54 +0,0 @@ -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; - } - } -} - diff --git a/include/led-matrix-c.h b/include/led-matrix-c.h index 55325c23b..e6907311b 100644 --- a/include/led-matrix-c.h +++ b/include/led-matrix-c.h @@ -196,6 +196,15 @@ struct RGBLedRuntimeOptions { const char *drop_priv_group; }; +/** + * 24-bit RGB color. + */ +struct Color { + uint8_t r; + uint8_t g; + uint8_t b; +}; + /** * Universal way to create and initialize a matrix. * The "options" struct (if not NULL) contains all default configuration values @@ -308,6 +317,10 @@ void led_canvas_get_size(const struct LedCanvas *canvas, void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y, uint8_t r, uint8_t g, uint8_t b); +/** Copies pixels to rectangle at (x, y) with size (width, height). */ +void led_canvas_set_pixels(struct LedCanvas *canvas, int x, int y, + int width, int height, struct Color *colors); + /** Clear screen (black). */ void led_canvas_clear(struct LedCanvas *canvas); diff --git a/include/led-matrix.h b/include/led-matrix.h index 084089a27..5e401fe9c 100644 --- a/include/led-matrix.h +++ b/include/led-matrix.h @@ -29,6 +29,7 @@ #include "canvas.h" #include "thread.h" #include "pixel-mapper.h" +#include "graphics.h" namespace rgb_matrix { class RGBMatrix; @@ -377,6 +378,8 @@ class FrameCanvas : public Canvas { virtual int height() const; virtual void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue); + virtual void SetPixels(int x, int y, int width, int height, + Color *colors); virtual void Clear(); virtual void Fill(uint8_t red, uint8_t green, uint8_t blue); diff --git a/lib/framebuffer-internal.h b/lib/framebuffer-internal.h index 2a5e3efc9..a3408865a 100644 --- a/lib/framebuffer-internal.h +++ b/lib/framebuffer-internal.h @@ -19,6 +19,7 @@ #include #include "hardware-mapping.h" +#include "../include/graphics.h" namespace rgb_matrix { class GPIO; @@ -126,6 +127,7 @@ class Framebuffer { int width() const; int height() const; void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue); + void SetPixels(int x, int y, int width, int height, Color *colors); void Clear(); void Fill(uint8_t red, uint8_t green, uint8_t blue); diff --git a/lib/framebuffer.cc b/lib/framebuffer.cc index ab73e6237..14ee41006 100644 --- a/lib/framebuffer.cc +++ b/lib/framebuffer.cc @@ -30,6 +30,7 @@ #include #include "gpio.h" +#include "../include/graphics.h" namespace rgb_matrix { namespace internal { @@ -693,6 +694,14 @@ void Framebuffer::SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) { } } +void Framebuffer::SetPixels(int x, int y, int width, int height, Color *colors) { + for (int iy = 0; iy < height; ++iy) { + for (int ix = 0; ix < width; ++ix) { + SetPixel(x + ix, y + iy, colors->r, colors->g, colors->b); + ++colors; + } + } +} // Strange LED-mappings such as RBG or so are handled here. gpio_bits_t Framebuffer::GetGpioFromLedSequence(char col, const char *led_sequence, diff --git a/lib/led-matrix-c.cc b/lib/led-matrix-c.cc index ebe882b6c..74df44e03 100644 --- a/lib/led-matrix-c.cc +++ b/lib/led-matrix-c.cc @@ -52,6 +52,9 @@ static rgb_matrix::Font *to_font(struct LedFont *font) { static struct LedFont *from_font(rgb_matrix::Font *font) { return reinterpret_cast(font); } +static rgb_matrix::Color* to_color(struct Color* color) { + return reinterpret_cast(color); +} static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit( @@ -224,6 +227,11 @@ void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y, to_canvas(canvas)->SetPixel(x, y, r, g, b); } +void led_canvas_set_pixels(struct LedCanvas *canvas, int x, int y, + int width, int height, struct Color *colors) { + to_canvas(canvas)->SetPixels(x, y, width, height, to_color(colors)); +} + void led_canvas_clear(struct LedCanvas *canvas) { to_canvas(canvas)->Clear(); } diff --git a/lib/led-matrix.cc b/lib/led-matrix.cc index 921d90089..3740da7e1 100644 --- a/lib/led-matrix.cc +++ b/lib/led-matrix.cc @@ -764,6 +764,10 @@ void FrameCanvas::SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue) { frame_->SetPixel(x, y, red, green, blue); } +void FrameCanvas::SetPixels(int x, int y, int width, int height, + Color *colors) { + frame_->SetPixels(x, y, width, height, colors); +} void FrameCanvas::Clear() { return frame_->Clear(); } void FrameCanvas::Fill(uint8_t red, uint8_t green, uint8_t blue) { frame_->Fill(red, green, blue);