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)) {