Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions KeyLighting/CPUImageProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Newtonsoft.Json;
using OpenRGB.NET;


public class CPUImageProcessor : IDisposable
{
private byte[]? pixelBuffer;
Expand All @@ -26,7 +25,7 @@

private double fadeSpeed;

public CPUImageProcessor(LightingConfig config)

Check warning on line 28 in KeyLighting/CPUImageProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'resultBuffer' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 28 in KeyLighting/CPUImageProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'rawColors' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 28 in KeyLighting/CPUImageProcessor.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'previousFrame' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
{
fadeSpeed = config.FadeFactor;
}
Expand Down Expand Up @@ -109,7 +108,6 @@
{
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);
Expand All @@ -134,8 +132,6 @@
}
}



[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool AreSettingsCached(double brightness, double contrast)
{
Expand Down Expand Up @@ -179,8 +175,6 @@
[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);
Expand All @@ -193,38 +187,33 @@
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);

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 &&
lastSolidB == processedColor.B);

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);
});
Expand Down
117 changes: 74 additions & 43 deletions KeyLighting/Program.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,6 +13,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Numerics;
using System.Text.Unicode;

namespace KeyboardLighting
{
Expand All @@ -20,14 +22,12 @@

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;
Expand Down Expand Up @@ -75,7 +75,7 @@

prevColors = new ORGBColor[ledCount];
ledColorsBuffer = new ORGBColor[ledCount];
// Removed fadeProgress

targetColors = new ORGBColor[ledCount];

var processor = new CPUImageProcessor(config);
Expand Down Expand Up @@ -183,7 +183,30 @@

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);
Expand All @@ -192,7 +215,12 @@

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 &&
Expand All @@ -217,7 +245,7 @@
[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;
Expand All @@ -238,27 +266,26 @@
{
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);
Expand All @@ -267,57 +294,61 @@
ledColorsBuffer[i] = new ORGBColor(r, g, b);
}

// Store current color for next frame
prevColors[i] = ledColorsBuffer[i];
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void WriteFormattedToConsole<T>(T value) where T : IUtf8SpanFormattable
{
Span<byte> 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<byte[]> _threadLocalBuffer =
new ThreadLocal<byte[]>(() => new byte[2048]);

public static byte[] GetBuffer() => _threadLocalBuffer.Value;
}
static void SaveDebugImages(Bitmap frame, ORGBColor[] columnColors, LightingConfig config)
{
try
{
string folder = "images";
Directory.CreateDirectory(folder);

using (var debugBmp = new Bitmap(columnColors.Length, 50))
{
Span<byte> 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));
}
}
}
}
Expand All @@ -330,7 +361,7 @@
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SDColor ToDrawingColor(this JToken colorToken)
{
if (colorToken == null || !colorToken["R"].HasValues || !colorToken["G"].HasValues || !colorToken["B"].HasValues)

Check warning on line 364 in KeyLighting/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 364 in KeyLighting/Program.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
return SDColor.Black;

return SDColor.FromArgb(
Expand Down
Loading
Loading