From fe2bc77371c1bdae793874f0254d6591fbe96642 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 12:30:49 +0800 Subject: [PATCH 01/16] Moved projects to .NET 6 --- bindings/c#/.gitignore | 7 +++- bindings/c#/Makefile | 21 ---------- bindings/c#/RPiRgbLEDMatrix.csproj | 11 +++++ .../examples/FontExample/FontExample.csproj | 11 +++++ .../Program.cs} | 0 bindings/c#/examples/Makefile | 41 ------------------- .../c#/examples/MatrixRain/MatrixRain.csproj | 11 +++++ .../{matrix-rain.cs => MatrixRain/Program.cs} | 0 .../MinimalExample/MinimalExample.csproj | 11 +++++ .../Program.cs} | 0 .../Program.cs} | 0 .../PulsingBrightness.csproj | 11 +++++ 12 files changed, 60 insertions(+), 64 deletions(-) delete mode 100644 bindings/c#/Makefile create mode 100644 bindings/c#/RPiRgbLEDMatrix.csproj create mode 100644 bindings/c#/examples/FontExample/FontExample.csproj rename bindings/c#/examples/{font-example.cs => FontExample/Program.cs} (100%) delete mode 100644 bindings/c#/examples/Makefile create mode 100644 bindings/c#/examples/MatrixRain/MatrixRain.csproj rename bindings/c#/examples/{matrix-rain.cs => MatrixRain/Program.cs} (100%) create mode 100644 bindings/c#/examples/MinimalExample/MinimalExample.csproj rename bindings/c#/examples/{minimal-example.cs => MinimalExample/Program.cs} (100%) rename bindings/c#/examples/{pulsing-brightness.cs => PulsingBrightness/Program.cs} (100%) create mode 100644 bindings/c#/examples/PulsingBrightness/PulsingBrightness.csproj 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#/Makefile b/bindings/c#/Makefile deleted file mode 100644 index 2e55fb630..000000000 --- a/bindings/c#/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -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) diff --git a/bindings/c#/RPiRgbLEDMatrix.csproj b/bindings/c#/RPiRgbLEDMatrix.csproj new file mode 100644 index 000000000..7df59e354 --- /dev/null +++ b/bindings/c#/RPiRgbLEDMatrix.csproj @@ -0,0 +1,11 @@ + + + net6.0 + enable + enable + + + + + + \ No newline at end of file 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/font-example.cs b/bindings/c#/examples/FontExample/Program.cs similarity index 100% rename from bindings/c#/examples/font-example.cs rename to bindings/c#/examples/FontExample/Program.cs 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/matrix-rain.cs b/bindings/c#/examples/MatrixRain/Program.cs similarity index 100% rename from bindings/c#/examples/matrix-rain.cs rename to bindings/c#/examples/MatrixRain/Program.cs 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/minimal-example.cs b/bindings/c#/examples/MinimalExample/Program.cs similarity index 100% rename from bindings/c#/examples/minimal-example.cs rename to bindings/c#/examples/MinimalExample/Program.cs diff --git a/bindings/c#/examples/pulsing-brightness.cs b/bindings/c#/examples/PulsingBrightness/Program.cs similarity index 100% rename from bindings/c#/examples/pulsing-brightness.cs rename to bindings/c#/examples/PulsingBrightness/Program.cs 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 + + + + + From a2b24ed7722cb040e69b0a7c45bc27d2ae474b25 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 13:02:08 +0800 Subject: [PATCH 02/16] Remove unused usings, rename namespaces to fit naming conventions --- bindings/c#/RGBLedCanvas.cs | 176 ++++++------ bindings/c#/RGBLedFont.cs | 86 +++--- bindings/c#/RGBLedMatrix.cs | 536 ++++++++++++++++++------------------ 3 files changed, 390 insertions(+), 408 deletions(-) diff --git a/bindings/c#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 74c1548be..6e1c01c07 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -1,100 +1,94 @@ -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 +namespace RPiRgbLEDMatrix; + +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) { - #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); - } + _canvas = canvas; + int width; + int height; + led_canvas_get_size(_canvas, out width, out height); + Width = width; + Height = height; } - public struct Color + 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) { - 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; + R = r; + G = g; + B = b; } + public byte R; + public byte G; + public byte B; } diff --git a/bindings/c#/RGBLedFont.cs b/bindings/c#/RGBLedFont.cs index 4e3b7f828..aeb8258db 100644 --- a/bindings/c#/RGBLedFont.cs +++ b/bindings/c#/RGBLedFont.cs @@ -1,60 +1,54 @@ -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 +namespace RPiRgbLEDMatrix; + +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 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 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 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); + [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; + 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); + } - #region IDisposable Support - private bool disposedValue = false; + #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() + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - Dispose(true); - GC.SuppressFinalize(this); + delete_font(_font); + disposedValue = true; } - #endregion } + ~RGBLedFont() + { + Dispose(false); + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion } diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 59135d075..910f21045 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -1,299 +1,293 @@ -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 +namespace RPiRgbLEDMatrix; + +public class RGBLedMatrix : IDisposable { - public class RGBLedMatrix : IDisposable - { - #region DLLImports - [DllImport("librgbmatrix.so")] - internal static extern IntPtr led_matrix_create(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", 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 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_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_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 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 byte led_matrix_get_brightness(IntPtr matrix); - [DllImport("librgbmatrix.so")] - internal static extern void led_matrix_set_brightness(IntPtr matrix, byte brightness); - #endregion + [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(int rows, int chained, int parallel) + { + matrix= led_matrix_create(rows, chained, parallel); + } - public RGBLedMatrix(RGBLedMatrixOptions options) + 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 { - 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); - } + 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; + private IntPtr matrix; - public RGBLedCanvas CreateOffscreenCanvas() - { - var canvas=led_matrix_create_offscreen_canvas(matrix); - return new RGBLedCanvas(canvas); - } + 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 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 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); } - } + public byte Brightness + { + get { return led_matrix_get_brightness(matrix); } + set { led_matrix_set_brightness(matrix, value); } + } - #region IDisposable Support - private bool disposedValue = false; + #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() + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) { - Dispose(true); - GC.SuppressFinalize(this); + led_matrix_delete(matrix); + disposedValue = true; } - #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 } + ~RGBLedMatrix() { + Dispose(false); + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion - public struct RGBLedMatrixOptions + #region RGBLedMatrixOptions struct + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct InternalRGBLedMatrixOptions { - /// - /// 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; - - /// - /// Panel type. Typically just empty, but certain panels (FM6126) - /// requie an initialization sequence - /// - public string PanelType; - - /// - /// 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; - - /// - /// 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; - - /// - /// Slowdown GPIO. Needed for faster Pis/slower panels. - /// - public int GpioSlowdown; + 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 +{ + /// + /// 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; + + /// + /// Panel type. Typically just empty, but certain panels (FM6126) + /// requie an initialization sequence + /// + public string PanelType; + + /// + /// 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; + + /// + /// 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; + + /// + /// Slowdown GPIO. Needed for faster Pis/slower panels. + /// + public int GpioSlowdown; +}; From 31d9ec6f941b5a1e85aab87e0e78a6259f573a06 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 13:10:55 +0800 Subject: [PATCH 03/16] Moved externs to a separate class --- bindings/c#/Bindings.cs | 68 +++++++++++++++++++++++++++++++++++++ bindings/c#/RGBLedCanvas.cs | 22 ------------ bindings/c#/RGBLedFont.cs | 14 -------- bindings/c#/RGBLedMatrix.cs | 29 ---------------- 4 files changed, 68 insertions(+), 65 deletions(-) create mode 100644 bindings/c#/Bindings.cs diff --git a/bindings/c#/Bindings.cs b/bindings/c#/Bindings.cs new file mode 100644 index 000000000..59bbabae3 --- /dev/null +++ b/bindings/c#/Bindings.cs @@ -0,0 +1,68 @@ +global using static RPiRgbLEDMatrix.Bindings; + +using static RPiRgbLEDMatrix.RGBLedMatrix; +using System.Runtime.InteropServices; + +namespace RPiRgbLEDMatrix; + +internal static class Bindings +{ + private const string Lib = "librgbmatrix.so"; + + [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)] + public static extern byte led_matrix_get_brightness(IntPtr matrix); + + [DllImport(Lib)] + 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)] + public static extern void led_canvas_get_size(IntPtr canvas, out int width, out int height); + + [DllImport(Lib)] + 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_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#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 6e1c01c07..19c62a5fb 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -1,29 +1,7 @@ -using System.Runtime.InteropServices; - namespace RPiRgbLEDMatrix; 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; diff --git a/bindings/c#/RGBLedFont.cs b/bindings/c#/RGBLedFont.cs index aeb8258db..aed82a906 100644 --- a/bindings/c#/RGBLedFont.cs +++ b/bindings/c#/RGBLedFont.cs @@ -1,21 +1,7 @@ -using System.Runtime.InteropServices; - namespace RPiRgbLEDMatrix; 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); diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 910f21045..e8bb4577d 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -4,35 +4,6 @@ namespace RPiRgbLEDMatrix; 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); From 32acb710d488ce89d85efdc0c0b3fe266620ecda Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 13:41:45 +0800 Subject: [PATCH 04/16] Refactor RGBLedMatrix.cs --- bindings/c#/InternalRGBLedMatrixOptions.cs | 50 ++++ bindings/c#/RGBLedMatrix.cs | 252 +++------------------ bindings/c#/RGBLedMatrixOptions.cs | 115 ++++++++++ 3 files changed, 197 insertions(+), 220 deletions(-) create mode 100644 bindings/c#/InternalRGBLedMatrixOptions.cs create mode 100644 bindings/c#/RGBLedMatrixOptions.cs diff --git a/bindings/c#/InternalRGBLedMatrixOptions.cs b/bindings/c#/InternalRGBLedMatrixOptions.cs new file mode 100644 index 000000000..9d7825c88 --- /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 = opt.Multiplexing; + pwm_bits = opt.PwmBits; + pwm_lsb_nanoseconds = opt.PwmLsbNanoseconds; + pwm_dither_bits = opt.PwmDitherBits; + scan_mode = 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#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index e8bb4577d..7900ce629 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -1,41 +1,23 @@ +using System.Buffers; using System.Runtime.InteropServices; namespace RPiRgbLEDMatrix; public class RGBLedMatrix : IDisposable { - public RGBLedMatrix(int rows, int chained, int parallel) - { - matrix= led_matrix_create(rows, chained, parallel); - } + private IntPtr matrix; + private bool disposedValue = false; + + 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(); + InternalRGBLedMatrixOptions opt = default; + try + { + opt = new(options); + var args = Environment.GetCommandLineArgs(); // Because gpio-slowdown is not provided in the options struct, // we manually add it. @@ -43,53 +25,26 @@ public RGBLedMatrix(RGBLedMatrixOptions options) // 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; + // 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, argc, argv); + matrix = led_matrix_create_from_options_const_argv(ref opt, argv.Length, 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); + Marshal.FreeHGlobal(opt.hardware_mapping); + Marshal.FreeHGlobal(opt.led_rgb_sequence); + Marshal.FreeHGlobal(opt.pixel_mapper_config); + 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 CreateOffscreenCanvas() => new(led_matrix_create_offscreen_canvas(matrix)); - public RGBLedCanvas GetCanvas() - { - var canvas = led_matrix_get_canvas(matrix); - return new RGBLedCanvas(canvas); - } + public RGBLedCanvas GetCanvas() => new(led_matrix_get_canvas(matrix)); public RGBLedCanvas SwapOnVsync(RGBLedCanvas canvas) { @@ -99,166 +54,23 @@ public RGBLedCanvas SwapOnVsync(RGBLedCanvas canvas) public byte Brightness { - get { return led_matrix_get_brightness(matrix); } - set { led_matrix_set_brightness(matrix, value); } + get => 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); + if (disposedValue) return; + + 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 -{ - /// - /// 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; - - /// - /// Panel type. Typically just empty, but certain panels (FM6126) - /// requie an initialization sequence - /// - public string PanelType; - - /// - /// 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; - - /// - /// 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; - - /// - /// Slowdown GPIO. Needed for faster Pis/slower panels. - /// - public int GpioSlowdown; -}; diff --git a/bindings/c#/RGBLedMatrixOptions.cs b/bindings/c#/RGBLedMatrixOptions.cs new file mode 100644 index 000000000..133e37bd7 --- /dev/null +++ b/bindings/c#/RGBLedMatrixOptions.cs @@ -0,0 +1,115 @@ +namespace RPiRgbLEDMatrix; + +public struct RGBLedMatrixOptions +{ + /// + /// Name of the hardware mapping used. If passed NULL 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: 0=progressive, 1=interlaced + /// + public int ScanMode = 0; + + /// + /// 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. 0 = direct, 1 = stripe, 2 = checker (typical 1:8) + /// + public int Multiplexing = 0; + + /// + /// 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; + + public RGBLedMatrixOptions() { } +} From 5fbd9deedd4668193b55f5731da9636b829804fc Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 14:02:08 +0800 Subject: [PATCH 05/16] Refactor the rest of a library --- bindings/c#/Color.cs | 12 +++++++++ bindings/c#/RGBLedCanvas.cs | 54 +++++++------------------------------ bindings/c#/RGBLedFont.cs | 28 +++++++------------ 3 files changed, 31 insertions(+), 63 deletions(-) create mode 100644 bindings/c#/Color.cs diff --git a/bindings/c#/Color.cs b/bindings/c#/Color.cs new file mode 100644 index 000000000..869cfbf5b --- /dev/null +++ b/bindings/c#/Color.cs @@ -0,0 +1,12 @@ +namespace RPiRgbLEDMatrix; + +public struct Color +{ + public byte R; + public byte G; + public byte B; + + public Color(int r, int g, int b) : this((byte)r, (byte)g, (byte)b) { } + + public Color(byte r, byte g, byte b) => (R, G, B) = (r, g, b); +} diff --git a/bindings/c#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 19c62a5fb..5b51b5c0a 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -11,62 +11,26 @@ public class RGBLedCanvas internal RGBLedCanvas(IntPtr canvas) { _canvas = canvas; - int width; - int height; - led_canvas_get_size(_canvas, out width, out height); + led_canvas_get_size(_canvas, out int width, out int height); Width = width; Height = height; } - public int Width {get; private set; } + 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 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 Fill(Color color) => led_canvas_fill(_canvas, color.R, color.G, color.B); - public void Clear() - { - led_canvas_clear(_canvas); - } + public void Clear() => led_canvas_clear(_canvas); - public void DrawCircle(int x0, int y0, int radius, Color color) - { + 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) - { + 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; + 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 aed82a906..85baf8661 100644 --- a/bindings/c#/RGBLedFont.cs +++ b/bindings/c#/RGBLedFont.cs @@ -2,13 +2,12 @@ namespace RPiRgbLEDMatrix; public class RGBLedFont : IDisposable { - public RGBLedFont(string bdf_font_file_path) - { - _font = load_font(bdf_font_file_path); - } internal IntPtr _font; + private bool disposedValue = false; - internal int DrawText(IntPtr canvas, int x, int y, Color color, string text, int spacing=0, bool vertical=false) + public RGBLedFont(string bdfFontPath) => _font = load_font(bdfFontPath); + + 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); @@ -16,25 +15,18 @@ internal int DrawText(IntPtr canvas, int x, int y, Color color, string text, int 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); + if (disposedValue) return; + delete_font(_font); + disposedValue = true; } + + ~RGBLedFont() => Dispose(false); + public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } - #endregion } From c3207a5a64abccfbc769fd5fab174addc5e58eb0 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 14:34:19 +0800 Subject: [PATCH 06/16] Updated examples --- bindings/c#/RGBLedMatrix.cs | 5 +- bindings/c#/examples/FontExample/Program.cs | 53 +++---- bindings/c#/examples/MatrixRain/Program.cs | 134 ++++++++---------- .../c#/examples/MinimalExample/Program.cs | 41 ++---- .../c#/examples/PulsingBrightness/Program.cs | 75 ++++------ 5 files changed, 124 insertions(+), 184 deletions(-) diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 7900ce629..92d37e986 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -46,11 +46,8 @@ public RGBLedMatrix(RGBLedMatrixOptions options) public RGBLedCanvas GetCanvas() => new(led_matrix_get_canvas(matrix)); - public RGBLedCanvas SwapOnVsync(RGBLedCanvas canvas) - { + public void SwapOnVsync(RGBLedCanvas canvas) => canvas._canvas = led_matrix_swap_on_vsync(matrix, canvas._canvas); - return canvas; - } public byte Brightness { diff --git a/bindings/c#/examples/FontExample/Program.cs b/bindings/c#/examples/FontExample/Program.cs index 463831424..42af63d81 100644 --- a/bindings/c#/examples/FontExample/Program.cs +++ b/bindings/c#/examples/FontExample/Program.cs @@ -1,36 +1,29 @@ -using rpi_rgb_led_matrix_sharp; -using System; -using System.Threading; +using RPiRgbLEDMatrix; -namespace font_example +if (args.Length < 1) { - 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]; - + 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); +using var matrix = new RGBLedMatrix(32, 2, 1); +var canvas = matrix.CreateOffscreenCanvas(); +using var font = new RGBLedFont(args[0]); - while (!Console.KeyAvailable) - { - Thread.Sleep(250); - } +canvas.DrawText(font, 1, 6, new Color(0, 255, 0), text); +matrix.SwapOnVsync(canvas); - return 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) Thread.Yield(); + +return 0; diff --git a/bindings/c#/examples/MatrixRain/Program.cs b/bindings/c#/examples/MatrixRain/Program.cs index fe8d6b7d0..ac465f522 100644 --- a/bindings/c#/examples/MatrixRain/Program.cs +++ b/bindings/c#/examples/MatrixRain/Program.cs @@ -1,90 +1,74 @@ -using rpi_rgb_led_matrix_sharp; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; +using RPiRgbLEDMatrix; -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(); +const int MaxHeight = 16; +const int ColorStep = 15; +const int FrameStep = 1; - while (!Console.KeyAvailable) { - stopwatch.Restart(); +using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { ChainLength = 2 }); +var canvas = matrix.CreateOffscreenCanvas(); - frame++; +var rnd = new Random(); +var points = new List(); +var recycled = new Stack(); +var frame = 0; - 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(); +var running = true; +Console.CancelKeyPress += (s, e) => +{ + running = false; + e.Cancel = true; // don't terminate, we need to dispose +}; - foreach (var point in points) - { - if (!point.recycled) - { - point.y++; +// run until user presses Ctrl+C +while (running) +{ + var frameStart = Environment.TickCount64; + frame++; - if (point.y - MAX_HEIGHT > canvas.Height) - { - point.recycled = true; - recycled.Push(point); - } + 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; + } + } - 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); + canvas.Clear(); - // force 30 FPS - var elapsed= stopwatch.ElapsedMilliseconds; - if (elapsed < 33) - { - Thread.Sleep(33 - (int)elapsed); - } - } + foreach (var point in points) + { + if (point.Recycled) continue; + point.Y++; - return 0; + if (point.Y - MaxHeight > canvas.Height) + { + point.Recycled = true; + recycled.Push(point); } - class Point + for (var i = 0; i < MaxHeight; i++) { - public Point(int x, int y) - { - this.x = x; - this.y = y; - } - public int x; - public int y; - public bool recycled; + 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/Program.cs b/bindings/c#/examples/MinimalExample/Program.cs index 90a447cc8..7446ef0df 100644 --- a/bindings/c#/examples/MinimalExample/Program.cs +++ b/bindings/c#/examples/MinimalExample/Program.cs @@ -1,32 +1,19 @@ -using rpi_rgb_led_matrix_sharp; +using RPiRgbLEDMatrix; -namespace minimal_example -{ - class Program - { - static int Main(string[] args) - { - - var matrix= new RGBLedMatrix(32, 2, 1); - var canvas = matrix.CreateOffscreenCanvas(); +using 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)); +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 = matrix.SwapOnVsync(canvas); - } + 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)); - return 0; - } - } + matrix.SwapOnVsync(canvas); } diff --git a/bindings/c#/examples/PulsingBrightness/Program.cs b/bindings/c#/examples/PulsingBrightness/Program.cs index c5ef8b492..5ebb05848 100644 --- a/bindings/c#/examples/PulsingBrightness/Program.cs +++ b/bindings/c#/examples/PulsingBrightness/Program.cs @@ -1,54 +1,33 @@ -using rpi_rgb_led_matrix_sharp; -using System; -using System.Threading; +using RPiRgbLEDMatrix; -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; - } +using var matrix = new RGBLedMatrix(new RGBLedMatrixOptions { Rows = 32, Cols = 64 }); +var canvas = matrix.CreateOffscreenCanvas(); - 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; - } +var maxBrightness = matrix.Brightness; +var rnd = new Random(); - canvas = matrix.SwapOnVsync(canvas); - - Thread.Sleep(20); - } +// 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 +}; - return 0; - } +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); +} From c6364349d6ffce7f93b7ad932dd7176be4370524 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 08:56:47 +0000 Subject: [PATCH 07/16] Updated Makefiles --- Makefile | 1 + bindings/c#/Bindings.cs | 3 +-- bindings/c#/Makefile | 33 ++++++++++++++++++++++++++++++ bindings/c#/RPiRgbLEDMatrix.csproj | 32 ++++++++++++++++++++--------- 4 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 bindings/c#/Makefile 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#/Bindings.cs b/bindings/c#/Bindings.cs index 59bbabae3..7e3cb536b 100644 --- a/bindings/c#/Bindings.cs +++ b/bindings/c#/Bindings.cs @@ -1,13 +1,12 @@ global using static RPiRgbLEDMatrix.Bindings; -using static RPiRgbLEDMatrix.RGBLedMatrix; using System.Runtime.InteropServices; namespace RPiRgbLEDMatrix; internal static class Bindings { - private const string Lib = "librgbmatrix.so"; + private const string Lib = "librgbmatrix.so.1"; [DllImport(Lib)] public static extern IntPtr led_matrix_create(int rows, int chained, int parallel); diff --git a/bindings/c#/Makefile b/bindings/c#/Makefile new file mode 100644 index 000000000..532f0e604 --- /dev/null +++ b/bindings/c#/Makefile @@ -0,0 +1,33 @@ +# 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 this variable with the variable in the 'RPiRgbLEDMatrix.csproj' file +RGB_LIBDIR=../../lib + +RGB_LIBRARY_NAME=rgbmatrix +RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).so.1 + +NUGET_VERSION = 1.0.0 +NUGET_ID = HZeller.RPiRgbLEDMatrix +NUGET_CONFIG = Release + +NUGET_PACKAGE = /bin/$(NUGET_CONFIG)/$(NUGET_ID).$(NUGET_VERSION).nupkg + +$(NUGET_PACKAGE): $(RGB_LIBRARY) + dotnet pack -c $(NUGET_CONFIG) -p:SkipNative=true -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=true + dotnet build examples/MatrixRain/MatrixRain.csproj -p:SkipNative=true + dotnet build examples/MinimalExample/MinimalExample.csproj -p:SkipNative=true + dotnet build examples/PulsingBrightness/PulsingBrightness.csproj -p:SkipNative=true + +$(RGB_LIBRARY): + $(MAKE) -C $(RGB_LIBDIR) + +# Used by toplevel Makefile +nuget: $(NUGET_PACKAGE) + +# Used in 'RPiRgbLEDMatrix.csproj' +library: $(RGB_LIBRARY) diff --git a/bindings/c#/RPiRgbLEDMatrix.csproj b/bindings/c#/RPiRgbLEDMatrix.csproj index 7df59e354..7bd9a0896 100644 --- a/bindings/c#/RPiRgbLEDMatrix.csproj +++ b/bindings/c#/RPiRgbLEDMatrix.csproj @@ -1,11 +1,23 @@ - - - net6.0 - enable - enable - - - - - + + + net6.0 + enable + enable + + + + + + Always + + + + + + true + + + + + \ No newline at end of file From f7fe9fe208a0c21e164d535d92b373421a1312b8 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 17:48:07 +0800 Subject: [PATCH 08/16] Fixed examples --- bindings/c#/RGBLedMatrix.cs | 8 ++++---- bindings/c#/RPiRgbLEDMatrix.csproj | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 92d37e986..571a6db5b 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -35,10 +35,10 @@ public RGBLedMatrix(RGBLedMatrixOptions options) } finally { - Marshal.FreeHGlobal(opt.hardware_mapping); - Marshal.FreeHGlobal(opt.led_rgb_sequence); - Marshal.FreeHGlobal(opt.pixel_mapper_config); - Marshal.FreeHGlobal(opt.panel_type); + 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); } } diff --git a/bindings/c#/RPiRgbLEDMatrix.csproj b/bindings/c#/RPiRgbLEDMatrix.csproj index 7bd9a0896..27c397697 100644 --- a/bindings/c#/RPiRgbLEDMatrix.csproj +++ b/bindings/c#/RPiRgbLEDMatrix.csproj @@ -4,18 +4,20 @@ enable enable + + + + true + + - + Always - - - true - From 1eb1511bc0cea1cf637bb4be221d431ec1497824 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 17:55:29 +0800 Subject: [PATCH 09/16] Update C# bindings README --- bindings/c#/README.md | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) 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` From 8c4251ab42df072c18efed9274d4257441740a58 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 12:59:04 +0000 Subject: [PATCH 10/16] Included native library in NuGet package --- bindings/c#/Makefile | 13 ++++++------- bindings/c#/RPiRgbLEDMatrix.csproj | 8 +++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bindings/c#/Makefile b/bindings/c#/Makefile index 532f0e604..68ff1bbde 100644 --- a/bindings/c#/Makefile +++ b/bindings/c#/Makefile @@ -1,9 +1,8 @@ # 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 this variable with the variable in the 'RPiRgbLEDMatrix.csproj' file +# 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 @@ -14,14 +13,14 @@ NUGET_CONFIG = Release NUGET_PACKAGE = /bin/$(NUGET_CONFIG)/$(NUGET_ID).$(NUGET_VERSION).nupkg $(NUGET_PACKAGE): $(RGB_LIBRARY) - dotnet pack -c $(NUGET_CONFIG) -p:SkipNative=true -p:PackageId=$(NUGET_ID) -p:Version=$(NUGET_VERSION) + 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=true - dotnet build examples/MatrixRain/MatrixRain.csproj -p:SkipNative=true - dotnet build examples/MinimalExample/MinimalExample.csproj -p:SkipNative=true - dotnet build examples/PulsingBrightness/PulsingBrightness.csproj -p:SkipNative=true + 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 $(RGB_LIBRARY): $(MAKE) -C $(RGB_LIBDIR) diff --git a/bindings/c#/RPiRgbLEDMatrix.csproj b/bindings/c#/RPiRgbLEDMatrix.csproj index 27c397697..26055fa5b 100644 --- a/bindings/c#/RPiRgbLEDMatrix.csproj +++ b/bindings/c#/RPiRgbLEDMatrix.csproj @@ -13,13 +13,15 @@ - + Always - + - \ No newline at end of file + From a0aad96a885271bce3aaf8c0ab40f1ea6468405c Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 22:36:38 +0800 Subject: [PATCH 11/16] Documented all public APIs --- bindings/c#/Color.cs | 26 ++++++++++++++ bindings/c#/Multiplexing.cs | 11 ++++++ bindings/c#/RGBLedCanvas.cs | 55 ++++++++++++++++++++++++++++-- bindings/c#/RGBLedFont.cs | 8 +++++ bindings/c#/RGBLedMatrix.cs | 41 +++++++++++++++++++++- bindings/c#/RGBLedMatrixOptions.cs | 29 ++++++++++------ bindings/c#/ScanModes.cs | 10 ++++++ 7 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 bindings/c#/Multiplexing.cs create mode 100644 bindings/c#/ScanModes.cs diff --git a/bindings/c#/Color.cs b/bindings/c#/Color.cs index 869cfbf5b..80a9a4fe2 100644 --- a/bindings/c#/Color.cs +++ b/bindings/c#/Color.cs @@ -1,12 +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#/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#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 5b51b5c0a..7b50b8c5f 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -1,5 +1,8 @@ namespace RPiRgbLEDMatrix; +/// +/// Represents a canvas whose pixels can be manipulated. +/// public class RGBLedCanvas { // This is a wrapper for canvas no need to implement IDisposable here @@ -11,26 +14,72 @@ public class RGBLedCanvas internal RGBLedCanvas(IntPtr canvas) { _canvas = canvas; - led_canvas_get_size(_canvas, out int width, out int height); + led_canvas_get_size(_canvas, out var width, out var height); Width = width; Height = height; } + /// + /// 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); + /// + /// 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); - public void DrawCircle(int x0, int y0, int radius, Color color) => - draw_circle(_canvas, x0, y0, radius, color.R, color.G, color.B); + /// + /// 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 85baf8661..02f3854a4 100644 --- a/bindings/c#/RGBLedFont.cs +++ b/bindings/c#/RGBLedFont.cs @@ -1,10 +1,17 @@ namespace RPiRgbLEDMatrix; +/// +/// Represents a .BDF font. +/// public class RGBLedFont : IDisposable { internal IntPtr _font; private bool disposedValue = false; + /// + /// Loads the BDF font from the specified file. + /// + /// The path to the BDF file to load. public RGBLedFont(string bdfFontPath) => _font = load_font(bdfFontPath); internal int DrawText(IntPtr canvas, int x, int y, Color color, string text, int spacing = 0, bool vertical = false) @@ -24,6 +31,7 @@ protected virtual void Dispose(bool disposing) ~RGBLedFont() => Dispose(false); + /// public void Dispose() { Dispose(true); diff --git a/bindings/c#/RGBLedMatrix.cs b/bindings/c#/RGBLedMatrix.cs index 571a6db5b..768fa953a 100644 --- a/bindings/c#/RGBLedMatrix.cs +++ b/bindings/c#/RGBLedMatrix.cs @@ -3,14 +3,31 @@ namespace RPiRgbLEDMatrix; +/// +/// Represents a RGB matrix. +/// public class RGBLedMatrix : IDisposable { private IntPtr matrix; private bool disposedValue = false; - public RGBLedMatrix(int rows, int chained, int parallel) => + /// + /// 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) + { matrix = led_matrix_create(rows, chained, parallel); + if (matrix == (IntPtr)0) + throw new ArgumentException("Could not initialize a new matrix"); + } + /// + /// Initializes a new matrix. + /// + /// A configuration of a matrix. public RGBLedMatrix(RGBLedMatrixOptions options) { InternalRGBLedMatrixOptions opt = default; @@ -32,6 +49,8 @@ public RGBLedMatrix(RGBLedMatrixOptions options) 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"); } finally { @@ -42,13 +61,32 @@ public RGBLedMatrix(RGBLedMatrixOptions options) } } + /// + /// 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 { get => led_matrix_get_brightness(matrix); @@ -65,6 +103,7 @@ protected virtual void Dispose(bool disposing) ~RGBLedMatrix() => Dispose(false); + /// public void Dispose() { Dispose(true); diff --git a/bindings/c#/RGBLedMatrixOptions.cs b/bindings/c#/RGBLedMatrixOptions.cs index 133e37bd7..2ea9a6614 100644 --- a/bindings/c#/RGBLedMatrixOptions.cs +++ b/bindings/c#/RGBLedMatrixOptions.cs @@ -1,9 +1,13 @@ namespace RPiRgbLEDMatrix; +/// +/// Represents the matrix settings. +/// public struct RGBLedMatrixOptions { /// - /// Name of the hardware mapping used. If passed NULL here, the default is used. + /// Name of the hardware mapping used. If passed + /// here, the default is used. /// public string? HardwareMapping = null; @@ -16,7 +20,7 @@ public struct RGBLedMatrixOptions /// /// 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 + /// 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. /// @@ -32,7 +36,7 @@ public struct RGBLedMatrixOptions /// 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 + /// thus rows * parallel. Default: 1 ///
public int Parallel = 1; @@ -60,9 +64,9 @@ public struct RGBLedMatrixOptions public int Brightness = 100; /// - /// Scan mode: 0=progressive, 1=interlaced + /// Scan mode. /// - public int ScanMode = 0; + public ScanModes ScanMode = ScanModes.Progressive; /// /// Default row address type is 0, corresponding to direct setting of the @@ -72,12 +76,13 @@ public struct RGBLedMatrixOptions public int RowAddressType = 0; /// - /// Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8) + /// Type of multiplexing. /// - public int Multiplexing = 0; + 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. + /// 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; @@ -94,7 +99,8 @@ public struct RGBLedMatrixOptions 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. + /// 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; @@ -102,7 +108,7 @@ public struct RGBLedMatrixOptions /// /// Limit refresh rate of LED panel. This will help on a loaded system - // to keep a constant refresh rate. <= 0 for no limit. + /// to keep a constant refresh rate. <= 0 for no limit. /// public int LimitRefreshRateHz = 0; @@ -111,5 +117,8 @@ public struct RGBLedMatrixOptions /// public int GpioSlowdown = 1; + /// + /// Creates default matrix settings. + /// public RGBLedMatrixOptions() { } } 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 +} From 4878251e8fff30d161efd0c5b13da54e9a6f6370 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Sun, 12 Feb 2023 23:35:40 +0800 Subject: [PATCH 12/16] Fixed error --- bindings/c#/InternalRGBLedMatrixOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/c#/InternalRGBLedMatrixOptions.cs b/bindings/c#/InternalRGBLedMatrixOptions.cs index 9d7825c88..04859bd54 100644 --- a/bindings/c#/InternalRGBLedMatrixOptions.cs +++ b/bindings/c#/InternalRGBLedMatrixOptions.cs @@ -36,11 +36,11 @@ public InternalRGBLedMatrixOptions(RGBLedMatrixOptions opt) pixel_mapper_config = Marshal.StringToHGlobalAnsi(opt.PixelMapperConfig); panel_type = Marshal.StringToHGlobalAnsi(opt.PanelType); parallel = opt.Parallel; - multiplexing = opt.Multiplexing; + multiplexing = (int)opt.Multiplexing; pwm_bits = opt.PwmBits; pwm_lsb_nanoseconds = opt.PwmLsbNanoseconds; pwm_dither_bits = opt.PwmDitherBits; - scan_mode = opt.ScanMode; + scan_mode = (int)opt.ScanMode; show_refresh_rate = (byte)(opt.ShowRefreshRate ? 1 : 0); limit_refresh_rate_hz = opt.LimitRefreshRateHz; brightness = opt.Brightness; From 7f8a89d1b05d6b941868a3637337fbccb158b70e Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Mon, 13 Feb 2023 00:14:14 +0800 Subject: [PATCH 13/16] Added rotating 3D cube example --- bindings/c#/Makefile | 1 + .../c#/examples/Rotating3DCube/Program.cs | 90 +++++++++++++++++++ .../Rotating3DCube/Rotating3DCube.csproj | 11 +++ 3 files changed, 102 insertions(+) create mode 100644 bindings/c#/examples/Rotating3DCube/Program.cs create mode 100644 bindings/c#/examples/Rotating3DCube/Rotating3DCube.csproj diff --git a/bindings/c#/Makefile b/bindings/c#/Makefile index 68ff1bbde..955302805 100644 --- a/bindings/c#/Makefile +++ b/bindings/c#/Makefile @@ -21,6 +21,7 @@ build: $(RGB_LIBRARY) 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 $(RGB_LIBRARY): $(MAKE) -C $(RGB_LIBDIR) 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 + + + + + From 6f92e281350e546db3ced724f07f4b3fdafd600f Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Mon, 13 Feb 2023 22:40:22 +0800 Subject: [PATCH 14/16] Implemented bulk version of 'SetPixel()' method --- bindings/c#/Bindings.cs | 10 ++++++++-- bindings/c#/RGBLedCanvas.cs | 15 +++++++++++++++ include/led-matrix-c.h | 13 +++++++++++++ include/led-matrix.h | 3 +++ lib/framebuffer-internal.h | 2 ++ lib/framebuffer.cc | 9 +++++++++ lib/led-matrix-c.cc | 8 ++++++++ lib/led-matrix.cc | 4 ++++ 8 files changed, 62 insertions(+), 2 deletions(-) diff --git a/bindings/c#/Bindings.cs b/bindings/c#/Bindings.cs index 7e3cb536b..fb9195b4e 100644 --- a/bindings/c#/Bindings.cs +++ b/bindings/c#/Bindings.cs @@ -39,10 +39,12 @@ internal static class Bindings 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); + 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); + 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); @@ -53,6 +55,10 @@ internal static class Bindings [DllImport(Lib)] 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); diff --git a/bindings/c#/RGBLedCanvas.cs b/bindings/c#/RGBLedCanvas.cs index 7b50b8c5f..fa030b966 100644 --- a/bindings/c#/RGBLedCanvas.cs +++ b/bindings/c#/RGBLedCanvas.cs @@ -37,6 +37,21 @@ internal RGBLedCanvas(IntPtr canvas) /// 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) + { + 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. /// diff --git a/include/led-matrix-c.h b/include/led-matrix-c.h index 0025e5661..84892d2dc 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); From 28fd4d4ea344aec0f2642ef33d8249ff6afc3a5d Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Mon, 29 May 2023 21:48:01 +0800 Subject: [PATCH 15/16] C#: Add [SuppressGCTransition] to trivial extern methods --- bindings/c#/Bindings.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bindings/c#/Bindings.cs b/bindings/c#/Bindings.cs index fb9195b4e..a4957fd07 100644 --- a/bindings/c#/Bindings.cs +++ b/bindings/c#/Bindings.cs @@ -4,6 +4,14 @@ 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"; @@ -30,9 +38,11 @@ internal static class Bindings 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)] @@ -50,9 +60,11 @@ internal static class Bindings 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)] From abd7d3013d735f0e0df27cb2f873a43e0dcc6e48 Mon Sep 17 00:00:00 2001 From: KirillAldashkin Date: Mon, 29 May 2023 22:47:13 +0800 Subject: [PATCH 16/16] Add play gif example (C# + SixLabors.ImageSharp) --- bindings/c#/Makefile | 1 + bindings/c#/examples/PlayGIF/PlayGIF.csproj | 12 +++++++ bindings/c#/examples/PlayGIF/Program.cs | 40 +++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 bindings/c#/examples/PlayGIF/PlayGIF.csproj create mode 100644 bindings/c#/examples/PlayGIF/Program.cs diff --git a/bindings/c#/Makefile b/bindings/c#/Makefile index 955302805..5df1f7368 100644 --- a/bindings/c#/Makefile +++ b/bindings/c#/Makefile @@ -22,6 +22,7 @@ build: $(RGB_LIBRARY) 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) 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); +}