From 431fd327f9d8100502b8bf90cad9ab2c59e572f5 Mon Sep 17 00:00:00 2001 From: ModMaker101 <119018978+ModMaker101@users.noreply.github.com> Date: Mon, 19 May 2025 13:19:15 -0400 Subject: [PATCH 1/3] adding better console performance --- KeyLighting/CPUImageProcessor.cs | 17 +---- KeyLighting/Program.cs | 117 +++++++++++++++++++------------ KeyLighting/ScreenCapturer.cs | 29 ++------ 3 files changed, 83 insertions(+), 80 deletions(-) diff --git a/KeyLighting/CPUImageProcessor.cs b/KeyLighting/CPUImageProcessor.cs index da2765b..b438d98 100644 --- a/KeyLighting/CPUImageProcessor.cs +++ b/KeyLighting/CPUImageProcessor.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json; using OpenRGB.NET; - public class CPUImageProcessor : IDisposable { private byte[]? pixelBuffer; @@ -109,7 +108,6 @@ public OpenRGB.NET.Color[] ProcessImage(Bitmap image, int targetWidth, int targe { ProcessColumnsWithEffects(targetWidth, brightness, vibrance, contrast, darkThreshold, darkFactor); - // Apply fading only if fade speed is less than 1.0 if (hasPreviousFrame && fadeSpeed < 1.0) { ApplyFading(targetWidth, fadeSpeed); @@ -134,8 +132,6 @@ public OpenRGB.NET.Color[] ProcessImage(Bitmap image, int targetWidth, int targe } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool AreSettingsCached(double brightness, double contrast) { @@ -179,8 +175,6 @@ private bool IsSolidColorFrame() [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ApplyFading(int width, double fadeFactor) { - // This method should only be called when fadeSpeed < 1.0 - // Simply use the provided fade factor without any brightness-based adjustments Parallel.For(0, width, i => { resultBuffer[i] = FastBlendColors(previousFrame[i], resultBuffer[i], fadeFactor); @@ -193,7 +187,6 @@ private OpenRGB.NET.Color FastBlendColors(OpenRGB.NET.Color color1, OpenRGB.NET. factor = Math.Clamp(factor, 0.0, 1.0); double inverseFactor = 1.0 - factor; - // Process each channel with adaptive blending byte r = (byte)(color1.R * inverseFactor + color2.R * factor); byte g = (byte)(color1.G * inverseFactor + color2.G * factor); byte b = (byte)(color1.B * inverseFactor + color2.B * factor); @@ -201,15 +194,13 @@ private OpenRGB.NET.Color FastBlendColors(OpenRGB.NET.Color color1, OpenRGB.NET. return new OpenRGB.NET.Color(r, g, b); } - // Also modify ProcessSolidColor method to handle brightness transitions better [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ProcessSolidColor(byte r, byte g, byte b, int width, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor) { OpenRGB.NET.Color processedColor = FastApplyEffects(r, g, b, brightness, vibrance, contrast, darkThreshold, darkFactor); - // Check if we need to apply fading bool needsFade = hasPreviousFrame && - fadeSpeed < 1.0 && // Only fade if fade speed is less than 1.0 + fadeSpeed < 1.0 && !(lastFrameWasSolid && lastSolidR == processedColor.R && lastSolidG == processedColor.G && @@ -217,14 +208,12 @@ private void ProcessSolidColor(byte r, byte g, byte b, int width, double brightn if (needsFade) { - // Calculate brightness values for current and previous frame + int prevBrightness = lastSolidR + lastSolidG + lastSolidB; int newBrightness = processedColor.R + processedColor.G + processedColor.B; - // Determine if we're brightening or darkening - double fadeFactor = fadeSpeed; // Use the configured fade speed + double fadeFactor = fadeSpeed; - // Apply transition Parallel.For(0, width, i => { resultBuffer[i] = FastBlendColors(previousFrame[i], processedColor, fadeFactor); }); diff --git a/KeyLighting/Program.cs b/KeyLighting/Program.cs index 0a71457..45a35d5 100644 --- a/KeyLighting/Program.cs +++ b/KeyLighting/Program.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Drawing; using System.Drawing.Imaging; @@ -12,6 +13,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Numerics; +using System.Text.Unicode; namespace KeyboardLighting { @@ -20,14 +22,12 @@ namespace KeyboardLighting class Program { - + static readonly byte[] debugBuffer = new byte[1024]; static readonly object colorsLock = new object(); static ORGBColor[]? prevColors; static DateTime lastUpdate = DateTime.MinValue; static DateTime lastDebugImageSave = DateTime.MinValue; - // Remove fade progress tracking - // static float[] fadeProgress; static ORGBColor[] targetColors; const int MIN_CAPTURE_INTERVAL_MS = 16; @@ -75,7 +75,7 @@ static void Main(string[] args) prevColors = new ORGBColor[ledCount]; ledColorsBuffer = new ORGBColor[ledCount]; - // Removed fadeProgress + targetColors = new ORGBColor[ledCount]; var processor = new CPUImageProcessor(config); @@ -183,7 +183,30 @@ static void ProcessFrame(ScreenCapturer capturer, CPUImageProcessor processor, O if (config.DebugStringUpdates) { - Console.WriteLine($"CPU colors: {string.Join(", ", columnColors.Take(5).Select(c => $"R{c.R},G{c.G},B{c.B}"))}"); + + var span = debugBuffer.AsSpan(); + bool success = false; + int written = 0; + + if (Utf8.TryWrite(span, $"CPU colors: ", out written)) + { + span = span.Slice(written); + for (int i = 0; i < Math.Min(5, columnColors.Length); i++) + { + var c = columnColors[i]; + if (i > 0 && Utf8.TryWrite(span, $", ", out int commaWritten)) + { + span = span.Slice(commaWritten); + } + if (Utf8.TryWrite(span, $"R{c.R},G{c.G},B{c.B}", out int colorWritten)) + { + span = span.Slice(colorWritten); + written += colorWritten; + } + else break; + } + Console.WriteLine(Encoding.UTF8.GetString(debugBuffer, 0, debugBuffer.Length - span.Length)); + } } UpdateLedColors(columnColors, config, ledCount); @@ -192,7 +215,12 @@ static void ProcessFrame(ScreenCapturer capturer, CPUImageProcessor processor, O if (config.DebugStringUpdates) { - Console.WriteLine($"Updated LEDs, first LED: R{ledColorsBuffer[0].R} G{ledColorsBuffer[0].G} B{ledColorsBuffer[0].B}"); + + var span = debugBuffer.AsSpan(); + if (Utf8.TryWrite(span, $"Updated LEDs, first LED: R{ledColorsBuffer[0].R} G{ledColorsBuffer[0].G} B{ledColorsBuffer[0].B}", out int written)) + { + Console.WriteLine(Encoding.UTF8.GetString(debugBuffer, 0, written)); + } } if (config.SaveDebugImages && @@ -217,7 +245,7 @@ static void ProcessFrame(ScreenCapturer capturer, CPUImageProcessor processor, O [MethodImpl(MethodImplOptions.AggressiveInlining)] static void UpdateLedColors(ORGBColor[] columnColors, LightingConfig config, int ledCount) { - // Check if we want instant transitions (fadeSpeed at or very near 1.0) + bool instantTransition = config.FadeFactor >= 0.99; var wasdEnabled = config.WASDEnabled; @@ -238,27 +266,26 @@ static void UpdateLedColors(ORGBColor[] columnColors, LightingConfig config, int { if (wasdEnabled && Array.IndexOf(wasdKeys, i) >= 0) { - // Handle WASD keys with special color + ledColorsBuffer[i] = new ORGBColor(wasdR, wasdG, wasdB); } else { - // Apply column colors to the keyboard + int columnIndex = Math.Min(i, columnLength - 1); if (instantTransition) { - // With instantTransition, directly apply the column color + ledColorsBuffer[i] = columnColors[columnIndex]; } else { - // For backward compatibility, keep some very minimal smoothing + ORGBColor prev = prevColors[i]; ORGBColor target = columnColors[columnIndex]; - // Simple lerp with very high weight toward target color - float t = 0.8f; // High value for quick transition but not instant + float t = 0.8f; byte r = (byte)Math.Round(prev.R * (1 - t) + target.R * t); byte g = (byte)Math.Round(prev.G * (1 - t) + target.G * t); @@ -267,12 +294,26 @@ static void UpdateLedColors(ORGBColor[] columnColors, LightingConfig config, int ledColorsBuffer[i] = new ORGBColor(r, g, b); } - // Store current color for next frame prevColors[i] = ledColorsBuffer[i]; } } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void WriteFormattedToConsole(T value) where T : IUtf8SpanFormattable + { + Span buffer = stackalloc byte[512]; + if (value.TryFormat(buffer, out int written, default, null)) + { + Console.WriteLine(Encoding.UTF8.GetString(buffer.Slice(0, written))); + } + } + static class Utf8BufferPool + { + private static readonly ThreadLocal _threadLocalBuffer = + new ThreadLocal(() => new byte[2048]); + public static byte[] GetBuffer() => _threadLocalBuffer.Value; + } static void SaveDebugImages(Bitmap frame, ORGBColor[] columnColors, LightingConfig config) { try @@ -280,44 +321,34 @@ static void SaveDebugImages(Bitmap frame, ORGBColor[] columnColors, LightingConf string folder = "images"; Directory.CreateDirectory(folder); - using (var debugBmp = new Bitmap(columnColors.Length, 50)) - { + Span filenameBuffer = stackalloc byte[256]; + DateTime now = DateTime.Now; - var bmpData = debugBmp.LockBits( - new Rectangle(0, 0, debugBmp.Width, debugBmp.Height), - ImageLockMode.WriteOnly, - PixelFormat.Format32bppArgb); - - IntPtr ptr = bmpData.Scan0; - int bytes = Math.Abs(bmpData.Stride) * debugBmp.Height; - byte[] rgbValues = new byte[bytes]; + if (Utf8.TryWrite(filenameBuffer, $"{folder}/debug_frame_{now:yyyyMMdd_HHmmss_fff}.png", out int debugWritten)) + { + string debugFilename = Encoding.UTF8.GetString(filenameBuffer.Slice(0, debugWritten)); - for (int x = 0; x < columnColors.Length; x++) + using (var debugBmp = new Bitmap(columnColors.Length, 50)) { - var color = columnColors[x]; - for (int y = 0; y < 50; y++) - { - int offset = y * bmpData.Stride + x * 4; - rgbValues[offset] = color.B; - rgbValues[offset + 1] = color.G; - rgbValues[offset + 2] = color.R; - rgbValues[offset + 3] = 255; - } - } - - Marshal.Copy(rgbValues, 0, ptr, bytes); - debugBmp.UnlockBits(bmpData); - string filename = $"{folder}/debug_frame_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png"; - debugBmp.Save(filename, ImageFormat.Png); + debugBmp.Save(debugFilename, ImageFormat.Png); + } } - string frameFilename = $"{folder}/captured_frame_{DateTime.Now:yyyyMMdd_HHmmss_fff}.png"; - frame.Save(frameFilename, ImageFormat.Png); + if (Utf8.TryWrite(filenameBuffer, $"{folder}/captured_frame_{now:yyyyMMdd_HHmmss_fff}.png", out int frameWritten)) + { + string frameFilename = Encoding.UTF8.GetString(filenameBuffer.Slice(0, frameWritten)); + frame.Save(frameFilename, ImageFormat.Png); + } } catch (Exception ex) { - Console.WriteLine($"Error saving debug images: {ex.Message}"); + + var span = debugBuffer.AsSpan(); + if (Utf8.TryWrite(span, $"Error saving debug images: {ex.Message}", out int written)) + { + Console.WriteLine(Encoding.UTF8.GetString(debugBuffer, 0, written)); + } } } } diff --git a/KeyLighting/ScreenCapturer.cs b/KeyLighting/ScreenCapturer.cs index 93385f1..1913887 100644 --- a/KeyLighting/ScreenCapturer.cs +++ b/KeyLighting/ScreenCapturer.cs @@ -7,9 +7,7 @@ namespace KeyboardLighting { - /// - /// Platform-independent screen capture utility that works without Windows Forms - /// + public class ScreenCapturer : IDisposable { #region Platform Detection @@ -70,16 +68,10 @@ private struct MONITORINFOEX #region Linux-specific imports - // These would be implemented when adding Linux support - // For example, using X11 or Wayland APIs - #endregion #region MacOS-specific imports - // These would be implemented when adding macOS support - // For example, using CoreGraphics APIs - #endregion private class Monitor @@ -127,23 +119,20 @@ private void InitializeMonitors() } else if (IsLinux) { - // Linux implementation would go here - // For now, add a fallback monitor + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); } else if (IsMacOS) { - // macOS implementation would go here - // For now, add a fallback monitor + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); } else { - // Fallback for unknown platforms + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); } - // If no monitors were detected, add a default one if (monitors.Count == 0) { monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); @@ -295,15 +284,13 @@ private void CaptureToBufferWindows(Bitmap targetBuffer) private void CaptureToBufferLinux(Bitmap targetBuffer) { - // This would use an X11 or Wayland implementation to capture the screen + Console.WriteLine("Linux screen capture not yet implemented"); - // For now, just draw a test pattern using (Graphics g = Graphics.FromImage(targetBuffer)) { g.Clear(Color.DarkGray); - // Draw a grid pattern using (Pen pen = new Pen(Color.LightGray, 1)) { for (int x = 0; x < targetBuffer.Width; x += 20) @@ -317,7 +304,6 @@ private void CaptureToBufferLinux(Bitmap targetBuffer) } } - // Draw text using (Font font = new Font("Arial", 24)) using (Brush brush = new SolidBrush(Color.White)) { @@ -328,15 +314,13 @@ private void CaptureToBufferLinux(Bitmap targetBuffer) private void CaptureToBufferMacOS(Bitmap targetBuffer) { - // This would use CoreGraphics to capture the screen + Console.WriteLine("macOS screen capture not yet implemented"); - // For now, just draw a test pattern using (Graphics g = Graphics.FromImage(targetBuffer)) { g.Clear(Color.DarkGray); - // Draw a grid pattern using (Pen pen = new Pen(Color.LightGray, 1)) { for (int x = 0; x < targetBuffer.Width; x += 20) @@ -350,7 +334,6 @@ private void CaptureToBufferMacOS(Bitmap targetBuffer) } } - // Draw text using (Font font = new Font("Arial", 24)) using (Brush brush = new SolidBrush(Color.White)) { From a4b99377157e86e37e93baa135ecaa561cb4acc7 Mon Sep 17 00:00:00 2001 From: ModMaker101 <119018978+ModMaker101@users.noreply.github.com> Date: Mon, 19 May 2025 23:17:59 -0400 Subject: [PATCH 2/3] 1.4.0 ig --- KeyLighting/CPUImageProcessor.cs | 136 ++++++++--- KeyLighting/KeyLighting.csproj | 5 - KeyLighting/Program.cs | 6 +- KeyLighting/ScreenCapturer.cs | 380 +++++++++++++++++++++++-------- KeyLighting/config.json | 2 +- 5 files changed, 392 insertions(+), 137 deletions(-) diff --git a/KeyLighting/CPUImageProcessor.cs b/KeyLighting/CPUImageProcessor.cs index b438d98..ad44625 100644 --- a/KeyLighting/CPUImageProcessor.cs +++ b/KeyLighting/CPUImageProcessor.cs @@ -2,6 +2,7 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -247,23 +248,44 @@ private void ExtractColumns24Bpp(int stride, int width, int height) { unchecked { - uint totalR = 0, totalG = 0, totalB = 0; int pixelCount = height; int columnOffset = x * 3; - int y = 0; - for (; y < height - 3; y += 4) + + Vector sumR = Vector.Zero; + Vector sumG = Vector.Zero; + Vector sumB = Vector.Zero; + int vectorSize = Vector.Count; + + // Vectorized sum + for (; y <= height - vectorSize; y += vectorSize) { - int offset1 = y * stride + columnOffset; - int offset2 = (y + 1) * stride + columnOffset; - int offset3 = (y + 2) * stride + columnOffset; - int offset4 = (y + 3) * stride + columnOffset; - - totalB += (uint)pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4]; - totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1]; - totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2]; + Span colBytes = stackalloc byte[vectorSize * 3]; + for (int v = 0; v < vectorSize; v++) + { + int offset = (y + v) * stride + columnOffset; + colBytes[v * 3 + 0] = pixelBuffer[offset]; + colBytes[v * 3 + 1] = pixelBuffer[offset + 1]; + colBytes[v * 3 + 2] = pixelBuffer[offset + 2]; + } + + var vec = new Vector(colBytes); + + // Extract R, G, B channels and sum + ulong r = 0, g = 0, b = 0; + for (int v = 0; v < vectorSize; v++) + { + b += vec[v * 3 + 0]; + g += vec[v * 3 + 1]; + r += vec[v * 3 + 2]; + } + sumR += new Vector(r); + sumG += new Vector(g); + sumB += new Vector(b); } + // Scalar sum for remaining pixels + ulong totalR = 0, totalG = 0, totalB = 0; for (; y < height; y++) { int offset = y * stride + columnOffset; @@ -272,15 +294,25 @@ private void ExtractColumns24Bpp(int stride, int width, int height) totalR += pixelBuffer[offset + 2]; } - byte avgR = (byte)(totalR / pixelCount); - byte avgG = (byte)(totalG / pixelCount); - byte avgB = (byte)(totalB / pixelCount); + // Add vectorized sums + for (int i = 0; i < Vector.Count; i++) + { + totalR += sumR[i]; + totalG += sumG[i]; + totalB += sumB[i]; + } + + byte avgR = (byte)(totalR / (ulong)pixelCount); + byte avgG = (byte)(totalG / (ulong)pixelCount); + byte avgB = (byte)(totalB / (ulong)pixelCount); rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB); } }); } + + // Move the stackalloc and Vector creation outside the vectorized loop [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ExtractColumns32Bpp(int stride, int width, int height) { @@ -288,40 +320,43 @@ private void ExtractColumns32Bpp(int stride, int width, int height) { unchecked { - uint totalR = 0, totalG = 0, totalB = 0; int pixelCount = height; int columnOffset = x * 4; + ulong totalR = 0, totalG = 0, totalB = 0; + // Process in blocks for cache efficiency + int blockSize = 32; // Tune for your CPU cache int y = 0; - for (; y < height - 3; y += 4) + for (; y <= height - blockSize; y += blockSize) { - int offset1 = y * stride + columnOffset; - int offset2 = (y + 1) * stride + columnOffset; - int offset3 = (y + 2) * stride + columnOffset; - int offset4 = (y + 3) * stride + columnOffset; - - totalB += (uint)(pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4]); - totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1]; - totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2]; + for (int b = 0; b < blockSize; b++) + { + int offset = (y + b) * stride + columnOffset; + totalB += pixelBuffer![offset]; + totalG += pixelBuffer![offset + 1]; + totalR += pixelBuffer![offset + 2]; + } } - + // Process remaining pixels for (; y < height; y++) { int offset = y * stride + columnOffset; - totalB += pixelBuffer[offset]; - totalG += pixelBuffer[offset + 1]; - totalR += pixelBuffer[offset + 2]; + totalB += pixelBuffer![offset]; + totalG += pixelBuffer![offset + 1]; + totalR += pixelBuffer![offset + 2]; } - byte avgR = (byte)(totalR / pixelCount); - byte avgG = (byte)(totalG / pixelCount); - byte avgB = (byte)(totalB / pixelCount); + byte avgR = (byte)(totalR / (ulong)pixelCount); + byte avgG = (byte)(totalG / (ulong)pixelCount); + byte avgB = (byte)(totalB / (ulong)pixelCount); rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB); } }); } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ProcessColumnsWithEffects(int width, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor) { @@ -394,19 +429,48 @@ private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double bright } [MethodImpl(MethodImplOptions.AggressiveOptimization)] + // Vectorized LUT initialization private void InitializeLuts(double brightness, double contrast) { - for (int i = 0; i < 256; i++) + // Vectorized brightness LUT + if (Vector.IsHardwareAccelerated) { + var brightnessVec = new Vector((float)brightness); + int vecSize = Vector.Count; + int i = 0; + for (; i <= 256 - vecSize; i += vecSize) + { + var indices = new Vector(Enumerable.Range(i, vecSize).Select(x => (float)x).ToArray()); + var result = Vector.Multiply(indices, brightnessVec); + for (int j = 0; j < vecSize; j++) + { + brightnessLut[i + j] = (byte)Math.Clamp((int)result[j], 0, 255); + } + } + // Handle any remaining elements + for (; i < 256; i++) + { + int brightVal = (int)(i * brightness); + brightnessLut[i] = (byte)Math.Clamp(brightVal, 0, 255); + } + } + else + { + for (int i = 0; i < 256; i++) + { + int brightVal = (int)(i * brightness); + brightnessLut[i] = (byte)Math.Clamp(brightVal, 0, 255); + } + } - int brightVal = (int)(i * brightness); - brightnessLut[i] = (byte)Math.Min(Math.Max(brightVal, 0), 255); - + // Contrast LUT (not vectorized) + for (int i = 0; i < 256; i++) + { if (Math.Abs(contrast - 1.0) > 0.001) { double normalized = i / 255.0; double adjusted = Math.Pow(normalized, contrast) * 255.0; - contrastLut[i] = (byte)Math.Min(Math.Max((int)adjusted, 0), 255); + contrastLut[i] = (byte)Math.Clamp((int)adjusted, 0, 255); } else { diff --git a/KeyLighting/KeyLighting.csproj b/KeyLighting/KeyLighting.csproj index d80a7c7..c19d1e7 100644 --- a/KeyLighting/KeyLighting.csproj +++ b/KeyLighting/KeyLighting.csproj @@ -17,11 +17,6 @@ - - - - - diff --git a/KeyLighting/Program.cs b/KeyLighting/Program.cs index 45a35d5..c5fd723 100644 --- a/KeyLighting/Program.cs +++ b/KeyLighting/Program.cs @@ -56,10 +56,14 @@ static void Main(string[] args) var devices = client.GetAllControllerData(); int keyboardIndex = -1; int ledCount = 0; + //foreach (var device in devices) + //{ + // Console.WriteLine($"Name: {device.Name}, Type: {device.Type}, LEDs: {device.Leds.Length}"); + //} for (int i = 0; i < devices.Length; i++) { - if (devices[i].Type == DeviceType.Keyboard && devices[i].Name.Contains("Scope")) + if (devices[i].Type == DeviceType.Keyboard) { keyboardIndex = i; ledCount = devices[i].Leds.Length; diff --git a/KeyLighting/ScreenCapturer.cs b/KeyLighting/ScreenCapturer.cs index 1913887..a66c3f7 100644 --- a/KeyLighting/ScreenCapturer.cs +++ b/KeyLighting/ScreenCapturer.cs @@ -7,14 +7,12 @@ namespace KeyboardLighting { - public class ScreenCapturer : IDisposable { #region Platform Detection private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + #endregion @@ -66,13 +64,7 @@ private struct MONITORINFOEX #endregion - #region Linux-specific imports - - #endregion - - #region MacOS-specific imports - - #endregion + private class Monitor { @@ -101,6 +93,10 @@ public Monitor(Rectangle bounds, bool isPrimary, string deviceName) private Bitmap backBuffer = null; private readonly object bufferSwapLock = new object(); + // Binary serialization buffers for performance + private byte[] binaryBuffer = null; + private readonly object binaryBufferLock = new object(); + public ScreenCapturer() { InitializeMonitors(); @@ -117,19 +113,8 @@ private void InitializeMonitors() { EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, EnumMonitorsCallback, IntPtr.Zero); } - else if (IsLinux) - { - - monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); - } - else if (IsMacOS) - { - - monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); - } else { - monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); } @@ -170,10 +155,24 @@ private void InitializeBuffers() frontBuffer?.Dispose(); backBuffer?.Dispose(); - frontBuffer = new Bitmap(captureRegion.Width, captureRegion.Height, PixelFormat.Format32bppRgb); - backBuffer = new Bitmap(captureRegion.Width, captureRegion.Height, PixelFormat.Format32bppRgb); + int scaledWidth = captureRegion.Width / 2; + int scaledHeight = captureRegion.Height / 2; + + frontBuffer = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppRgb); + backBuffer = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppRgb); + + // Initialize binary buffer for the new size + int requiredSize = scaledWidth * scaledHeight * 4 + 12; // 4 bytes per pixel + 12 bytes header + lock (binaryBufferLock) + { + if (binaryBuffer == null || binaryBuffer.Length < requiredSize) + { + binaryBuffer = new byte[requiredSize]; + } + } } + public void SetMonitorIndex(int index) { if (index < 0 || index >= monitors.Count) @@ -235,14 +234,6 @@ private void CaptureToBuffer(Bitmap targetBuffer) { CaptureToBufferWindows(targetBuffer); } - else if (IsLinux) - { - CaptureToBufferLinux(targetBuffer); - } - else if (IsMacOS) - { - CaptureToBufferMacOS(targetBuffer); - } else { Console.WriteLine("Platform not supported for screen capture"); @@ -264,15 +255,24 @@ private void CaptureToBufferWindows(Bitmap targetBuffer) g.SmoothingMode = SmoothingMode.None; g.PixelOffsetMode = PixelOffsetMode.None; - IntPtr hdcDest = g.GetHdc(); - try - { - BitBlt(hdcDest, 0, 0, captureRegion.Width, captureRegion.Height, - hdcSrc, captureRegion.X, captureRegion.Y, SRCCOPY); - } - finally + int scaledWidth = captureRegion.Width / 2; + int scaledHeight = captureRegion.Height / 2; + + using (Bitmap temp = new Bitmap(captureRegion.Width, captureRegion.Height)) + using (Graphics tempG = Graphics.FromImage(temp)) { - g.ReleaseHdc(hdcDest); + IntPtr hdcDest = tempG.GetHdc(); + try + { + BitBlt(hdcDest, 0, 0, captureRegion.Width, captureRegion.Height, + hdcSrc, captureRegion.X, captureRegion.Y, SRCCOPY); + } + finally + { + tempG.ReleaseHdc(hdcDest); + } + + g.DrawImage(temp, 0, 0, scaledWidth, scaledHeight); } } } @@ -282,87 +282,78 @@ private void CaptureToBufferWindows(Bitmap targetBuffer) } } - private void CaptureToBufferLinux(Bitmap targetBuffer) - { - Console.WriteLine("Linux screen capture not yet implemented"); - using (Graphics g = Graphics.FromImage(targetBuffer)) + private void ResetBitmapCache() + { + lock (bufferSwapLock) { - g.Clear(Color.DarkGray); - - using (Pen pen = new Pen(Color.LightGray, 1)) - { - for (int x = 0; x < targetBuffer.Width; x += 20) - { - g.DrawLine(pen, x, 0, x, targetBuffer.Height); - } - - for (int y = 0; y < targetBuffer.Height; y += 20) - { - g.DrawLine(pen, 0, y, targetBuffer.Width, y); - } - } + frontBuffer?.Dispose(); + backBuffer?.Dispose(); + frontBuffer = null; + backBuffer = null; + } - using (Font font = new Font("Arial", 24)) - using (Brush brush = new SolidBrush(Color.White)) - { - g.DrawString("Linux Capture", font, brush, new PointF(targetBuffer.Width / 2 - 100, targetBuffer.Height / 2 - 20)); - } + lock (binaryBufferLock) + { + binaryBuffer = null; } } - private void CaptureToBufferMacOS(Bitmap targetBuffer) + // Original method - returns Bitmap + public Bitmap CaptureFrame() { - - Console.WriteLine("macOS screen capture not yet implemented"); - - using (Graphics g = Graphics.FromImage(targetBuffer)) + try { - g.Clear(Color.DarkGray); - - using (Pen pen = new Pen(Color.LightGray, 1)) + var timeSinceLastCapture = (DateTime.Now - lastCaptureTime).TotalMilliseconds; + if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS) { - for (int x = 0; x < targetBuffer.Width; x += 20) + lock (bufferSwapLock) { - g.DrawLine(pen, x, 0, x, targetBuffer.Height); + return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; } + } - for (int y = 0; y < targetBuffer.Height; y += 20) + lastCaptureTime = DateTime.Now; + + lock (bufferSwapLock) + { + if (frontBuffer == null || backBuffer == null || + frontBuffer.Width != captureRegion.Width || + frontBuffer.Height != captureRegion.Height) { - g.DrawLine(pen, 0, y, targetBuffer.Width, y); + InitializeBuffers(); } - } - using (Font font = new Font("Arial", 24)) - using (Brush brush = new SolidBrush(Color.White)) - { - g.DrawString("macOS Capture", font, brush, new PointF(targetBuffer.Width / 2 - 100, targetBuffer.Height / 2 - 20)); + CaptureToBuffer(backBuffer); + + var temp = frontBuffer; + frontBuffer = backBuffer; + backBuffer = temp; + + return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; } } - } - - private void ResetBitmapCache() - { - lock (bufferSwapLock) + catch (Exception ex) { - frontBuffer?.Dispose(); - backBuffer?.Dispose(); - frontBuffer = null; - backBuffer = null; + Console.WriteLine("Error during frame capture: " + ex.Message); + return null; } } - public Bitmap CaptureFrame() + // New method - returns binary data (much faster than string conversion) + public unsafe byte[] CaptureFrameAsBinary() { try { var timeSinceLastCapture = (DateTime.Now - lastCaptureTime).TotalMilliseconds; - if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS) + + lock (bufferSwapLock) { - lock (bufferSwapLock) + // If we captured recently, just serialize the existing front buffer + if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS && frontBuffer != null) { - return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; + return SerializeBitmapToBinary(frontBuffer); } } @@ -383,16 +374,155 @@ public Bitmap CaptureFrame() frontBuffer = backBuffer; backBuffer = temp; - return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; + return SerializeBitmapToBinary(frontBuffer); } } catch (Exception ex) { - Console.WriteLine("Error during frame capture: " + ex.Message); + Console.WriteLine("Error during binary frame capture: " + ex.Message); return null; } } + // High-performance binary serialization + private unsafe byte[] SerializeBitmapToBinary(Bitmap bitmap) + { + if (bitmap == null) return null; + + BitmapData bmpData = bitmap.LockBits( + new Rectangle(0, 0, bitmap.Width, bitmap.Height), + ImageLockMode.ReadOnly, + PixelFormat.Format32bppRgb); + + try + { + int stride = Math.Abs(bmpData.Stride); + int pixelDataSize = stride * bitmap.Height; + int totalSize = pixelDataSize + 12; // 12 bytes for header (width, height, stride) + + byte[] result; + lock (binaryBufferLock) + { + // Reuse buffer if it's large enough + if (binaryBuffer == null || binaryBuffer.Length < totalSize) + { + binaryBuffer = new byte[totalSize]; + } + result = new byte[totalSize]; + } + + // Write header (width, height, stride) + BitConverter.GetBytes(bitmap.Width).CopyTo(result, 0); + BitConverter.GetBytes(bitmap.Height).CopyTo(result, 4); + BitConverter.GetBytes(stride).CopyTo(result, 8); + + // Copy raw pixel data + Marshal.Copy(bmpData.Scan0, result, 12, pixelDataSize); + + return result; + } + finally + { + bitmap.UnlockBits(bmpData); + } + } + + // Deserialize binary data back to Bitmap (for receiving end) + public static unsafe Bitmap DeserializeBinaryToBitmap(byte[] binaryData) + { + if (binaryData == null || binaryData.Length < 12) + return null; + + try + { + // Read header + int width = BitConverter.ToInt32(binaryData, 0); + int height = BitConverter.ToInt32(binaryData, 4); + int stride = BitConverter.ToInt32(binaryData, 8); + + // Validate dimensions + if (width <= 0 || height <= 0 || stride <= 0) + return null; + + int expectedDataSize = stride * height; + if (binaryData.Length < expectedDataSize + 12) + return null; + + Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppRgb); + BitmapData bmpData = bitmap.LockBits( + new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, + PixelFormat.Format32bppRgb); + + try + { + // Copy pixel data back + Marshal.Copy(binaryData, 12, bmpData.Scan0, expectedDataSize); + return bitmap; + } + finally + { + bitmap.UnlockBits(bmpData); + } + } + catch (Exception ex) + { + Console.WriteLine("Error deserializing binary data to bitmap: " + ex.Message); + return null; + } + } + + // Utility method to extract dimensions from binary data without full deserialization + public static (int width, int height) GetDimensionsFromBinary(byte[] binaryData) + { + if (binaryData == null || binaryData.Length < 8) + return (0, 0); + + int width = BitConverter.ToInt32(binaryData, 0); + int height = BitConverter.ToInt32(binaryData, 4); + return (width, height); + } + + // Performance comparison method + public void ComparePerformance(int iterations = 100) + { + Console.WriteLine($"Performance comparison over {iterations} iterations:"); + + var watch = System.Diagnostics.Stopwatch.StartNew(); + + // Test binary serialization + watch.Restart(); + for (int i = 0; i < iterations; i++) + { + byte[] binaryData = CaptureFrameAsBinary(); + if (binaryData != null) + { + // Simulate sending to CPU processing + // In real scenario, you'd pass this to your image processing method + } + } + watch.Stop(); + long binaryTime = watch.ElapsedMilliseconds; + + // Test bitmap method + watch.Restart(); + for (int i = 0; i < iterations; i++) + { + Bitmap bitmap = CaptureFrame(); + if (bitmap != null) + { + // Simulate processing + bitmap.Dispose(); + } + } + watch.Stop(); + long bitmapTime = watch.ElapsedMilliseconds; + + Console.WriteLine($"Binary serialization: {binaryTime}ms"); + Console.WriteLine($"Bitmap method: {bitmapTime}ms"); + Console.WriteLine($"Binary is {(double)bitmapTime / binaryTime:F2}x faster"); + } + public int ScreenCount => monitors.Count; public void PrintAvailableMonitors() @@ -413,6 +543,68 @@ public void Dispose() frontBuffer = null; backBuffer = null; } + + lock (binaryBufferLock) + { + binaryBuffer = null; + } + } + } + + // Example usage class + public class CpuImageProcessor + { + // Example method that would receive binary data instead of string + public void ProcessImageBinary(byte[] binaryImageData) + { + if (binaryImageData == null) return; + + // Get dimensions without full deserialization (very fast) + var (width, height) = ScreenCapturer.GetDimensionsFromBinary(binaryImageData); + Console.WriteLine($"Processing image: {width}x{height}"); + + // If you need the full bitmap for processing + using (Bitmap bitmap = ScreenCapturer.DeserializeBinaryToBitmap(binaryImageData)) + { + if (bitmap != null) + { + // Your image processing logic here + // For example: calculate average color, detect brightness, etc. + Color avgColor = CalculateAverageColor(bitmap); + Console.WriteLine($"Average color: R={avgColor.R}, G={avgColor.G}, B={avgColor.B}"); + } + } + } + + private unsafe Color CalculateAverageColor(Bitmap bitmap) + { + BitmapData bmpData = bitmap.LockBits( + new Rectangle(0, 0, bitmap.Width, bitmap.Height), + ImageLockMode.ReadOnly, + PixelFormat.Format32bppRgb); + + try + { + byte* ptr = (byte*)bmpData.Scan0; + long totalR = 0, totalG = 0, totalB = 0; + int pixelCount = bitmap.Width * bitmap.Height; + + for (int i = 0; i < pixelCount; i++) + { + totalB += ptr[i * 4]; + totalG += ptr[i * 4 + 1]; + totalR += ptr[i * 4 + 2]; + } + + return Color.FromArgb( + (int)(totalR / pixelCount), + (int)(totalG / pixelCount), + (int)(totalB / pixelCount)); + } + finally + { + bitmap.UnlockBits(bmpData); + } } } } \ No newline at end of file diff --git a/KeyLighting/config.json b/KeyLighting/config.json index acd6763..2161f19 100644 --- a/KeyLighting/config.json +++ b/KeyLighting/config.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.4.0", "numKeys": 107, "downscaleHeight": 35, "updateDelayMs": 32, From c3638a9627b4bbe475acee0f2da602661d582146 Mon Sep 17 00:00:00 2001 From: ModMaker101 <119018978+ModMaker101@users.noreply.github.com> Date: Mon, 19 May 2025 23:19:59 -0400 Subject: [PATCH 3/3] Revert "1.4.0 ig" This reverts commit a4b99377157e86e37e93baa135ecaa561cb4acc7. --- KeyLighting/CPUImageProcessor.cs | 136 +++-------- KeyLighting/KeyLighting.csproj | 5 + KeyLighting/Program.cs | 6 +- KeyLighting/ScreenCapturer.cs | 380 ++++++++----------------------- KeyLighting/config.json | 2 +- 5 files changed, 137 insertions(+), 392 deletions(-) diff --git a/KeyLighting/CPUImageProcessor.cs b/KeyLighting/CPUImageProcessor.cs index ad44625..b438d98 100644 --- a/KeyLighting/CPUImageProcessor.cs +++ b/KeyLighting/CPUImageProcessor.cs @@ -2,7 +2,6 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading.Tasks; @@ -248,44 +247,23 @@ private void ExtractColumns24Bpp(int stride, int width, int height) { unchecked { + uint totalR = 0, totalG = 0, totalB = 0; int pixelCount = height; int columnOffset = x * 3; - int y = 0; - - Vector sumR = Vector.Zero; - Vector sumG = Vector.Zero; - Vector sumB = Vector.Zero; - int vectorSize = Vector.Count; - // Vectorized sum - for (; y <= height - vectorSize; y += vectorSize) + int y = 0; + for (; y < height - 3; y += 4) { - Span colBytes = stackalloc byte[vectorSize * 3]; - for (int v = 0; v < vectorSize; v++) - { - int offset = (y + v) * stride + columnOffset; - colBytes[v * 3 + 0] = pixelBuffer[offset]; - colBytes[v * 3 + 1] = pixelBuffer[offset + 1]; - colBytes[v * 3 + 2] = pixelBuffer[offset + 2]; - } - - var vec = new Vector(colBytes); - - // Extract R, G, B channels and sum - ulong r = 0, g = 0, b = 0; - for (int v = 0; v < vectorSize; v++) - { - b += vec[v * 3 + 0]; - g += vec[v * 3 + 1]; - r += vec[v * 3 + 2]; - } - sumR += new Vector(r); - sumG += new Vector(g); - sumB += new Vector(b); + int offset1 = y * stride + columnOffset; + int offset2 = (y + 1) * stride + columnOffset; + int offset3 = (y + 2) * stride + columnOffset; + int offset4 = (y + 3) * stride + columnOffset; + + totalB += (uint)pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4]; + totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1]; + totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2]; } - // Scalar sum for remaining pixels - ulong totalR = 0, totalG = 0, totalB = 0; for (; y < height; y++) { int offset = y * stride + columnOffset; @@ -294,25 +272,15 @@ private void ExtractColumns24Bpp(int stride, int width, int height) totalR += pixelBuffer[offset + 2]; } - // Add vectorized sums - for (int i = 0; i < Vector.Count; i++) - { - totalR += sumR[i]; - totalG += sumG[i]; - totalB += sumB[i]; - } - - byte avgR = (byte)(totalR / (ulong)pixelCount); - byte avgG = (byte)(totalG / (ulong)pixelCount); - byte avgB = (byte)(totalB / (ulong)pixelCount); + byte avgR = (byte)(totalR / pixelCount); + byte avgG = (byte)(totalG / pixelCount); + byte avgB = (byte)(totalB / pixelCount); rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB); } }); } - - // Move the stackalloc and Vector creation outside the vectorized loop [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ExtractColumns32Bpp(int stride, int width, int height) { @@ -320,43 +288,40 @@ private void ExtractColumns32Bpp(int stride, int width, int height) { unchecked { + uint totalR = 0, totalG = 0, totalB = 0; int pixelCount = height; int columnOffset = x * 4; - ulong totalR = 0, totalG = 0, totalB = 0; - // Process in blocks for cache efficiency - int blockSize = 32; // Tune for your CPU cache int y = 0; - for (; y <= height - blockSize; y += blockSize) + for (; y < height - 3; y += 4) { - for (int b = 0; b < blockSize; b++) - { - int offset = (y + b) * stride + columnOffset; - totalB += pixelBuffer![offset]; - totalG += pixelBuffer![offset + 1]; - totalR += pixelBuffer![offset + 2]; - } + int offset1 = y * stride + columnOffset; + int offset2 = (y + 1) * stride + columnOffset; + int offset3 = (y + 2) * stride + columnOffset; + int offset4 = (y + 3) * stride + columnOffset; + + totalB += (uint)(pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4]); + totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1]; + totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2]; } - // Process remaining pixels + for (; y < height; y++) { int offset = y * stride + columnOffset; - totalB += pixelBuffer![offset]; - totalG += pixelBuffer![offset + 1]; - totalR += pixelBuffer![offset + 2]; + totalB += pixelBuffer[offset]; + totalG += pixelBuffer[offset + 1]; + totalR += pixelBuffer[offset + 2]; } - byte avgR = (byte)(totalR / (ulong)pixelCount); - byte avgG = (byte)(totalG / (ulong)pixelCount); - byte avgB = (byte)(totalB / (ulong)pixelCount); + byte avgR = (byte)(totalR / pixelCount); + byte avgG = (byte)(totalG / pixelCount); + byte avgB = (byte)(totalB / pixelCount); rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB); } }); } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] private void ProcessColumnsWithEffects(int width, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor) { @@ -429,48 +394,19 @@ private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double bright } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - // Vectorized LUT initialization private void InitializeLuts(double brightness, double contrast) { - // Vectorized brightness LUT - if (Vector.IsHardwareAccelerated) - { - var brightnessVec = new Vector((float)brightness); - int vecSize = Vector.Count; - int i = 0; - for (; i <= 256 - vecSize; i += vecSize) - { - var indices = new Vector(Enumerable.Range(i, vecSize).Select(x => (float)x).ToArray()); - var result = Vector.Multiply(indices, brightnessVec); - for (int j = 0; j < vecSize; j++) - { - brightnessLut[i + j] = (byte)Math.Clamp((int)result[j], 0, 255); - } - } - // Handle any remaining elements - for (; i < 256; i++) - { - int brightVal = (int)(i * brightness); - brightnessLut[i] = (byte)Math.Clamp(brightVal, 0, 255); - } - } - else - { - for (int i = 0; i < 256; i++) - { - int brightVal = (int)(i * brightness); - brightnessLut[i] = (byte)Math.Clamp(brightVal, 0, 255); - } - } - - // Contrast LUT (not vectorized) for (int i = 0; i < 256; i++) { + + int brightVal = (int)(i * brightness); + brightnessLut[i] = (byte)Math.Min(Math.Max(brightVal, 0), 255); + if (Math.Abs(contrast - 1.0) > 0.001) { double normalized = i / 255.0; double adjusted = Math.Pow(normalized, contrast) * 255.0; - contrastLut[i] = (byte)Math.Clamp((int)adjusted, 0, 255); + contrastLut[i] = (byte)Math.Min(Math.Max((int)adjusted, 0), 255); } else { diff --git a/KeyLighting/KeyLighting.csproj b/KeyLighting/KeyLighting.csproj index c19d1e7..d80a7c7 100644 --- a/KeyLighting/KeyLighting.csproj +++ b/KeyLighting/KeyLighting.csproj @@ -17,6 +17,11 @@ + + + + + diff --git a/KeyLighting/Program.cs b/KeyLighting/Program.cs index c5fd723..45a35d5 100644 --- a/KeyLighting/Program.cs +++ b/KeyLighting/Program.cs @@ -56,14 +56,10 @@ static void Main(string[] args) var devices = client.GetAllControllerData(); int keyboardIndex = -1; int ledCount = 0; - //foreach (var device in devices) - //{ - // Console.WriteLine($"Name: {device.Name}, Type: {device.Type}, LEDs: {device.Leds.Length}"); - //} for (int i = 0; i < devices.Length; i++) { - if (devices[i].Type == DeviceType.Keyboard) + if (devices[i].Type == DeviceType.Keyboard && devices[i].Name.Contains("Scope")) { keyboardIndex = i; ledCount = devices[i].Leds.Length; diff --git a/KeyLighting/ScreenCapturer.cs b/KeyLighting/ScreenCapturer.cs index a66c3f7..1913887 100644 --- a/KeyLighting/ScreenCapturer.cs +++ b/KeyLighting/ScreenCapturer.cs @@ -7,12 +7,14 @@ namespace KeyboardLighting { + public class ScreenCapturer : IDisposable { #region Platform Detection private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - + private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + private static readonly bool IsMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); #endregion @@ -64,7 +66,13 @@ private struct MONITORINFOEX #endregion - + #region Linux-specific imports + + #endregion + + #region MacOS-specific imports + + #endregion private class Monitor { @@ -93,10 +101,6 @@ public Monitor(Rectangle bounds, bool isPrimary, string deviceName) private Bitmap backBuffer = null; private readonly object bufferSwapLock = new object(); - // Binary serialization buffers for performance - private byte[] binaryBuffer = null; - private readonly object binaryBufferLock = new object(); - public ScreenCapturer() { InitializeMonitors(); @@ -113,8 +117,19 @@ private void InitializeMonitors() { EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, EnumMonitorsCallback, IntPtr.Zero); } + else if (IsLinux) + { + + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); + } + else if (IsMacOS) + { + + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); + } else { + monitors.Add(new Monitor(new Rectangle(0, 0, 1920, 1080), true, "Default")); } @@ -155,24 +170,10 @@ private void InitializeBuffers() frontBuffer?.Dispose(); backBuffer?.Dispose(); - int scaledWidth = captureRegion.Width / 2; - int scaledHeight = captureRegion.Height / 2; - - frontBuffer = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppRgb); - backBuffer = new Bitmap(scaledWidth, scaledHeight, PixelFormat.Format32bppRgb); - - // Initialize binary buffer for the new size - int requiredSize = scaledWidth * scaledHeight * 4 + 12; // 4 bytes per pixel + 12 bytes header - lock (binaryBufferLock) - { - if (binaryBuffer == null || binaryBuffer.Length < requiredSize) - { - binaryBuffer = new byte[requiredSize]; - } - } + frontBuffer = new Bitmap(captureRegion.Width, captureRegion.Height, PixelFormat.Format32bppRgb); + backBuffer = new Bitmap(captureRegion.Width, captureRegion.Height, PixelFormat.Format32bppRgb); } - public void SetMonitorIndex(int index) { if (index < 0 || index >= monitors.Count) @@ -234,6 +235,14 @@ private void CaptureToBuffer(Bitmap targetBuffer) { CaptureToBufferWindows(targetBuffer); } + else if (IsLinux) + { + CaptureToBufferLinux(targetBuffer); + } + else if (IsMacOS) + { + CaptureToBufferMacOS(targetBuffer); + } else { Console.WriteLine("Platform not supported for screen capture"); @@ -255,24 +264,15 @@ private void CaptureToBufferWindows(Bitmap targetBuffer) g.SmoothingMode = SmoothingMode.None; g.PixelOffsetMode = PixelOffsetMode.None; - int scaledWidth = captureRegion.Width / 2; - int scaledHeight = captureRegion.Height / 2; - - using (Bitmap temp = new Bitmap(captureRegion.Width, captureRegion.Height)) - using (Graphics tempG = Graphics.FromImage(temp)) + IntPtr hdcDest = g.GetHdc(); + try { - IntPtr hdcDest = tempG.GetHdc(); - try - { - BitBlt(hdcDest, 0, 0, captureRegion.Width, captureRegion.Height, - hdcSrc, captureRegion.X, captureRegion.Y, SRCCOPY); - } - finally - { - tempG.ReleaseHdc(hdcDest); - } - - g.DrawImage(temp, 0, 0, scaledWidth, scaledHeight); + BitBlt(hdcDest, 0, 0, captureRegion.Width, captureRegion.Height, + hdcSrc, captureRegion.X, captureRegion.Y, SRCCOPY); + } + finally + { + g.ReleaseHdc(hdcDest); } } } @@ -282,78 +282,87 @@ private void CaptureToBufferWindows(Bitmap targetBuffer) } } + private void CaptureToBufferLinux(Bitmap targetBuffer) + { + Console.WriteLine("Linux screen capture not yet implemented"); - private void ResetBitmapCache() - { - lock (bufferSwapLock) + using (Graphics g = Graphics.FromImage(targetBuffer)) { - frontBuffer?.Dispose(); - backBuffer?.Dispose(); - frontBuffer = null; - backBuffer = null; - } + g.Clear(Color.DarkGray); - lock (binaryBufferLock) - { - binaryBuffer = null; + using (Pen pen = new Pen(Color.LightGray, 1)) + { + for (int x = 0; x < targetBuffer.Width; x += 20) + { + g.DrawLine(pen, x, 0, x, targetBuffer.Height); + } + + for (int y = 0; y < targetBuffer.Height; y += 20) + { + g.DrawLine(pen, 0, y, targetBuffer.Width, y); + } + } + + using (Font font = new Font("Arial", 24)) + using (Brush brush = new SolidBrush(Color.White)) + { + g.DrawString("Linux Capture", font, brush, new PointF(targetBuffer.Width / 2 - 100, targetBuffer.Height / 2 - 20)); + } } } - // Original method - returns Bitmap - public Bitmap CaptureFrame() + private void CaptureToBufferMacOS(Bitmap targetBuffer) { - try + + Console.WriteLine("macOS screen capture not yet implemented"); + + using (Graphics g = Graphics.FromImage(targetBuffer)) { - var timeSinceLastCapture = (DateTime.Now - lastCaptureTime).TotalMilliseconds; - if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS) + g.Clear(Color.DarkGray); + + using (Pen pen = new Pen(Color.LightGray, 1)) { - lock (bufferSwapLock) + for (int x = 0; x < targetBuffer.Width; x += 20) { - return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; + g.DrawLine(pen, x, 0, x, targetBuffer.Height); } - } - lastCaptureTime = DateTime.Now; - - lock (bufferSwapLock) - { - if (frontBuffer == null || backBuffer == null || - frontBuffer.Width != captureRegion.Width || - frontBuffer.Height != captureRegion.Height) + for (int y = 0; y < targetBuffer.Height; y += 20) { - InitializeBuffers(); + g.DrawLine(pen, 0, y, targetBuffer.Width, y); } + } - CaptureToBuffer(backBuffer); - - var temp = frontBuffer; - frontBuffer = backBuffer; - backBuffer = temp; - - return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; + using (Font font = new Font("Arial", 24)) + using (Brush brush = new SolidBrush(Color.White)) + { + g.DrawString("macOS Capture", font, brush, new PointF(targetBuffer.Width / 2 - 100, targetBuffer.Height / 2 - 20)); } } - catch (Exception ex) + } + + private void ResetBitmapCache() + { + lock (bufferSwapLock) { - Console.WriteLine("Error during frame capture: " + ex.Message); - return null; + frontBuffer?.Dispose(); + backBuffer?.Dispose(); + frontBuffer = null; + backBuffer = null; } } - // New method - returns binary data (much faster than string conversion) - public unsafe byte[] CaptureFrameAsBinary() + public Bitmap CaptureFrame() { try { var timeSinceLastCapture = (DateTime.Now - lastCaptureTime).TotalMilliseconds; - - lock (bufferSwapLock) + if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS) { - // If we captured recently, just serialize the existing front buffer - if (timeSinceLastCapture < MIN_CAPTURE_INTERVAL_MS && frontBuffer != null) + lock (bufferSwapLock) { - return SerializeBitmapToBinary(frontBuffer); + return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; } } @@ -374,155 +383,16 @@ public unsafe byte[] CaptureFrameAsBinary() frontBuffer = backBuffer; backBuffer = temp; - return SerializeBitmapToBinary(frontBuffer); - } - } - catch (Exception ex) - { - Console.WriteLine("Error during binary frame capture: " + ex.Message); - return null; - } - } - - // High-performance binary serialization - private unsafe byte[] SerializeBitmapToBinary(Bitmap bitmap) - { - if (bitmap == null) return null; - - BitmapData bmpData = bitmap.LockBits( - new Rectangle(0, 0, bitmap.Width, bitmap.Height), - ImageLockMode.ReadOnly, - PixelFormat.Format32bppRgb); - - try - { - int stride = Math.Abs(bmpData.Stride); - int pixelDataSize = stride * bitmap.Height; - int totalSize = pixelDataSize + 12; // 12 bytes for header (width, height, stride) - - byte[] result; - lock (binaryBufferLock) - { - // Reuse buffer if it's large enough - if (binaryBuffer == null || binaryBuffer.Length < totalSize) - { - binaryBuffer = new byte[totalSize]; - } - result = new byte[totalSize]; - } - - // Write header (width, height, stride) - BitConverter.GetBytes(bitmap.Width).CopyTo(result, 0); - BitConverter.GetBytes(bitmap.Height).CopyTo(result, 4); - BitConverter.GetBytes(stride).CopyTo(result, 8); - - // Copy raw pixel data - Marshal.Copy(bmpData.Scan0, result, 12, pixelDataSize); - - return result; - } - finally - { - bitmap.UnlockBits(bmpData); - } - } - - // Deserialize binary data back to Bitmap (for receiving end) - public static unsafe Bitmap DeserializeBinaryToBitmap(byte[] binaryData) - { - if (binaryData == null || binaryData.Length < 12) - return null; - - try - { - // Read header - int width = BitConverter.ToInt32(binaryData, 0); - int height = BitConverter.ToInt32(binaryData, 4); - int stride = BitConverter.ToInt32(binaryData, 8); - - // Validate dimensions - if (width <= 0 || height <= 0 || stride <= 0) - return null; - - int expectedDataSize = stride * height; - if (binaryData.Length < expectedDataSize + 12) - return null; - - Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppRgb); - BitmapData bmpData = bitmap.LockBits( - new Rectangle(0, 0, width, height), - ImageLockMode.WriteOnly, - PixelFormat.Format32bppRgb); - - try - { - // Copy pixel data back - Marshal.Copy(binaryData, 12, bmpData.Scan0, expectedDataSize); - return bitmap; - } - finally - { - bitmap.UnlockBits(bmpData); + return frontBuffer?.Clone(new Rectangle(0, 0, frontBuffer.Width, frontBuffer.Height), frontBuffer.PixelFormat) as Bitmap; } } catch (Exception ex) { - Console.WriteLine("Error deserializing binary data to bitmap: " + ex.Message); + Console.WriteLine("Error during frame capture: " + ex.Message); return null; } } - // Utility method to extract dimensions from binary data without full deserialization - public static (int width, int height) GetDimensionsFromBinary(byte[] binaryData) - { - if (binaryData == null || binaryData.Length < 8) - return (0, 0); - - int width = BitConverter.ToInt32(binaryData, 0); - int height = BitConverter.ToInt32(binaryData, 4); - return (width, height); - } - - // Performance comparison method - public void ComparePerformance(int iterations = 100) - { - Console.WriteLine($"Performance comparison over {iterations} iterations:"); - - var watch = System.Diagnostics.Stopwatch.StartNew(); - - // Test binary serialization - watch.Restart(); - for (int i = 0; i < iterations; i++) - { - byte[] binaryData = CaptureFrameAsBinary(); - if (binaryData != null) - { - // Simulate sending to CPU processing - // In real scenario, you'd pass this to your image processing method - } - } - watch.Stop(); - long binaryTime = watch.ElapsedMilliseconds; - - // Test bitmap method - watch.Restart(); - for (int i = 0; i < iterations; i++) - { - Bitmap bitmap = CaptureFrame(); - if (bitmap != null) - { - // Simulate processing - bitmap.Dispose(); - } - } - watch.Stop(); - long bitmapTime = watch.ElapsedMilliseconds; - - Console.WriteLine($"Binary serialization: {binaryTime}ms"); - Console.WriteLine($"Bitmap method: {bitmapTime}ms"); - Console.WriteLine($"Binary is {(double)bitmapTime / binaryTime:F2}x faster"); - } - public int ScreenCount => monitors.Count; public void PrintAvailableMonitors() @@ -543,68 +413,6 @@ public void Dispose() frontBuffer = null; backBuffer = null; } - - lock (binaryBufferLock) - { - binaryBuffer = null; - } - } - } - - // Example usage class - public class CpuImageProcessor - { - // Example method that would receive binary data instead of string - public void ProcessImageBinary(byte[] binaryImageData) - { - if (binaryImageData == null) return; - - // Get dimensions without full deserialization (very fast) - var (width, height) = ScreenCapturer.GetDimensionsFromBinary(binaryImageData); - Console.WriteLine($"Processing image: {width}x{height}"); - - // If you need the full bitmap for processing - using (Bitmap bitmap = ScreenCapturer.DeserializeBinaryToBitmap(binaryImageData)) - { - if (bitmap != null) - { - // Your image processing logic here - // For example: calculate average color, detect brightness, etc. - Color avgColor = CalculateAverageColor(bitmap); - Console.WriteLine($"Average color: R={avgColor.R}, G={avgColor.G}, B={avgColor.B}"); - } - } - } - - private unsafe Color CalculateAverageColor(Bitmap bitmap) - { - BitmapData bmpData = bitmap.LockBits( - new Rectangle(0, 0, bitmap.Width, bitmap.Height), - ImageLockMode.ReadOnly, - PixelFormat.Format32bppRgb); - - try - { - byte* ptr = (byte*)bmpData.Scan0; - long totalR = 0, totalG = 0, totalB = 0; - int pixelCount = bitmap.Width * bitmap.Height; - - for (int i = 0; i < pixelCount; i++) - { - totalB += ptr[i * 4]; - totalG += ptr[i * 4 + 1]; - totalR += ptr[i * 4 + 2]; - } - - return Color.FromArgb( - (int)(totalR / pixelCount), - (int)(totalG / pixelCount), - (int)(totalB / pixelCount)); - } - finally - { - bitmap.UnlockBits(bmpData); - } } } } \ No newline at end of file diff --git a/KeyLighting/config.json b/KeyLighting/config.json index 2161f19..acd6763 100644 --- a/KeyLighting/config.json +++ b/KeyLighting/config.json @@ -1,5 +1,5 @@ { - "version": "1.4.0", + "version": "1.1.0", "numKeys": 107, "downscaleHeight": 35, "updateDelayMs": 32,