Skip to content

Commit 99f5a8a

Browse files
authored
Merge pull request #1 from ModMaker101/dev
optimization and change some properties
2 parents dd9c1ae + 06617da commit 99f5a8a

File tree

4 files changed

+217
-46
lines changed

4 files changed

+217
-46
lines changed

KeyLighting/CPUImageProcessor.cs

Lines changed: 209 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,62 @@
11
using System;
2-
using System.Collections.Concurrent;
32
using System.Drawing;
43
using System.Drawing.Drawing2D;
54
using System.Drawing.Imaging;
65
using System.Runtime.CompilerServices;
76
using System.Runtime.InteropServices;
87
using System.Threading.Tasks;
8+
using KeyboardLighting;
9+
using Newtonsoft.Json;
910
using OpenRGB.NET;
1011

12+
1113
public class CPUImageProcessor : IDisposable
1214
{
13-
1415
private byte[]? pixelBuffer;
15-
private int lastWidth;
16-
private int lastHeight;
17-
private readonly object bufferLock = new object();
1816

19-
private byte[] brightnessLut;
20-
private byte[] contrastLut;
21-
private bool lutsInitialized = false;
17+
private byte[] brightnessLut = new byte[256];
18+
private byte[] contrastLut = new byte[256];
2219
private double lastBrightness = -1;
2320
private double lastContrast = -1;
2421

25-
public OpenRGB.NET.Color[] ProcessImage(Bitmap image, int targetWidth, int targetHeight, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
26-
{
22+
private OpenRGB.NET.Color[] previousFrame;
23+
private OpenRGB.NET.Color[] rawColors;
24+
private OpenRGB.NET.Color[] resultBuffer;
25+
private bool hasPreviousFrame = false;
26+
27+
private double fadeSpeed;
2728

28-
return ProcessImageOnCPU(image, targetWidth, targetHeight, brightness, vibrance, contrast, darkThreshold, darkFactor);
29+
public CPUImageProcessor(LightingConfig config)
30+
{
31+
fadeSpeed = config.FadeFactor;
2932
}
3033

31-
private OpenRGB.NET.Color[] resultBuffer;
34+
private bool lastFrameWasSolid = false;
35+
private int lastSolidR, lastSolidG, lastSolidB;
36+
37+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
38+
public void SetFadeSpeed(double speed)
39+
{
40+
fadeSpeed = Math.Clamp(speed, 0.0, 1.0);
41+
}
3242

33-
private OpenRGB.NET.Color[] ProcessImageOnCPU(Bitmap image, int targetWidth, int targetHeight, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
43+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
44+
public OpenRGB.NET.Color[] ProcessImage(Bitmap image, int targetWidth, int targetHeight, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
3445
{
3546

3647
if (resultBuffer == null || resultBuffer.Length != targetWidth)
3748
{
3849
resultBuffer = new OpenRGB.NET.Color[targetWidth];
50+
rawColors = new OpenRGB.NET.Color[targetWidth];
51+
previousFrame = new OpenRGB.NET.Color[targetWidth];
52+
hasPreviousFrame = false;
53+
}
54+
55+
if (!AreSettingsCached(brightness, contrast))
56+
{
57+
InitializeLuts(brightness, contrast);
58+
lastBrightness = brightness;
59+
lastContrast = contrast;
3960
}
4061

4162
Bitmap bitmapToProcess = image;
@@ -74,15 +95,29 @@ private OpenRGB.NET.Color[] ProcessImageOnCPU(Bitmap image, int targetWidth, int
7495

7596
Marshal.Copy(bmpData.Scan0, pixelBuffer, 0, byteCount);
7697

77-
if (bytesPerPixel == 4)
98+
ExtractColumns(bmpData.Stride, targetWidth, targetHeight, bytesPerPixel);
99+
100+
bool isSolidColor = IsSolidColorFrame();
101+
102+
if (isSolidColor)
78103
{
79-
ProcessColumns32Bpp(bmpData.Stride, targetWidth, targetHeight, brightness, vibrance, contrast, darkThreshold, darkFactor);
104+
ProcessSolidColor(rawColors[0].R, rawColors[0].G, rawColors[0].B,
105+
targetWidth, brightness, vibrance, contrast,
106+
darkThreshold, darkFactor);
80107
}
81-
else if (bytesPerPixel == 3)
108+
else
82109
{
83-
ProcessColumns24Bpp(bmpData.Stride, targetWidth, targetHeight, brightness, vibrance, contrast, darkThreshold, darkFactor);
110+
111+
ProcessColumnsWithEffects(targetWidth, brightness, vibrance, contrast, darkThreshold, darkFactor);
112+
113+
if (hasPreviousFrame)
114+
{
115+
ApplyFading(targetWidth, lastFrameWasSolid ? 0.95 : fadeSpeed);
116+
}
84117
}
85118

119+
StoreFrameState(targetWidth, isSolidColor);
120+
86121
return resultBuffer;
87122
}
88123
finally
@@ -100,7 +135,112 @@ private OpenRGB.NET.Color[] ProcessImageOnCPU(Bitmap image, int targetWidth, int
100135
}
101136

102137
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
103-
private void ProcessColumns24Bpp(int stride, int width, int height, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
138+
private void ProcessSolidColor(byte r, byte g, byte b, int width, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
139+
{
140+
141+
OpenRGB.NET.Color processedColor = FastApplyEffects(r, g, b, brightness, vibrance, contrast, darkThreshold, darkFactor);
142+
143+
bool needsFade = hasPreviousFrame && !(lastFrameWasSolid &&
144+
lastSolidR == processedColor.R &&
145+
lastSolidG == processedColor.G &&
146+
lastSolidB == processedColor.B);
147+
148+
if (needsFade)
149+
{
150+
151+
double fadeFactor = 0.95;
152+
153+
Parallel.For(0, width, i => {
154+
resultBuffer[i] = FastBlendColors(previousFrame[i], processedColor, fadeFactor);
155+
});
156+
}
157+
else
158+
{
159+
160+
for (int i = 0; i < width; i++)
161+
{
162+
resultBuffer[i] = processedColor;
163+
}
164+
}
165+
166+
lastSolidR = processedColor.R;
167+
lastSolidG = processedColor.G;
168+
lastSolidB = processedColor.B;
169+
}
170+
171+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
172+
private bool AreSettingsCached(double brightness, double contrast)
173+
{
174+
return Math.Abs(lastBrightness - brightness) <= 0.001 && Math.Abs(lastContrast - contrast) <= 0.001;
175+
}
176+
177+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
178+
private void StoreFrameState(int width, bool isSolidColor)
179+
{
180+
181+
Array.Copy(resultBuffer, previousFrame, width);
182+
hasPreviousFrame = true;
183+
lastFrameWasSolid = isSolidColor;
184+
}
185+
186+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
187+
private bool IsSolidColorFrame()
188+
{
189+
if (rawColors.Length < 2) return true;
190+
191+
OpenRGB.NET.Color first = rawColors[0];
192+
const int tolerance = 5;
193+
194+
int[] samplePoints = { 0, rawColors.Length / 3, rawColors.Length / 2, (rawColors.Length * 2) / 3, rawColors.Length - 1 };
195+
196+
foreach (int i in samplePoints)
197+
{
198+
if (i == 0) continue;
199+
200+
if (Math.Abs(first.R - rawColors[i].R) > tolerance ||
201+
Math.Abs(first.G - rawColors[i].G) > tolerance ||
202+
Math.Abs(first.B - rawColors[i].B) > tolerance)
203+
{
204+
return false;
205+
}
206+
}
207+
208+
return true;
209+
}
210+
211+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
212+
private void ApplyFading(int width, double fadeFactor)
213+
{
214+
Parallel.For(0, width, i => {
215+
resultBuffer[i] = FastBlendColors(previousFrame[i], resultBuffer[i], fadeFactor);
216+
});
217+
}
218+
219+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
220+
private OpenRGB.NET.Color FastBlendColors(OpenRGB.NET.Color color1, OpenRGB.NET.Color color2, double factor)
221+
{
222+
223+
factor = Math.Clamp(factor, 0.0, 1.0);
224+
double inverseFactor = 1.0 - factor;
225+
226+
byte r = (byte)(color1.R * inverseFactor + color2.R * factor);
227+
byte g = (byte)(color1.G * inverseFactor + color2.G * factor);
228+
byte b = (byte)(color1.B * inverseFactor + color2.B * factor);
229+
230+
return new OpenRGB.NET.Color(r, g, b);
231+
}
232+
233+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
234+
private void ExtractColumns(int stride, int width, int height, int bytesPerPixel)
235+
{
236+
if (bytesPerPixel == 4)
237+
ExtractColumns32Bpp(stride, width, height);
238+
else
239+
ExtractColumns24Bpp(stride, width, height);
240+
}
241+
242+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
243+
private void ExtractColumns24Bpp(int stride, int width, int height)
104244
{
105245
Parallel.For(0, width, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, x =>
106246
{
@@ -110,7 +250,20 @@ private void ProcessColumns24Bpp(int stride, int width, int height, double brigh
110250
int pixelCount = height;
111251
int columnOffset = x * 3;
112252

113-
for (int y = 0; y < height; y++)
253+
int y = 0;
254+
for (; y < height - 3; y += 4)
255+
{
256+
int offset1 = y * stride + columnOffset;
257+
int offset2 = (y + 1) * stride + columnOffset;
258+
int offset3 = (y + 2) * stride + columnOffset;
259+
int offset4 = (y + 3) * stride + columnOffset;
260+
261+
totalB += (uint)pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4];
262+
totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1];
263+
totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2];
264+
}
265+
266+
for (; y < height; y++)
114267
{
115268
int offset = y * stride + columnOffset;
116269
totalB += pixelBuffer[offset];
@@ -122,13 +275,13 @@ private void ProcessColumns24Bpp(int stride, int width, int height, double brigh
122275
byte avgG = (byte)(totalG / pixelCount);
123276
byte avgB = (byte)(totalB / pixelCount);
124277

125-
resultBuffer[x] = FastApplyEffects(avgR, avgG, avgB, brightness, vibrance, contrast, darkThreshold, darkFactor);
278+
rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB);
126279
}
127280
});
128281
}
129282

130283
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
131-
private void ProcessColumns32Bpp(int stride, int width, int height, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
284+
private void ExtractColumns32Bpp(int stride, int width, int height)
132285
{
133286
Parallel.For(0, width, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, x =>
134287
{
@@ -138,7 +291,20 @@ private void ProcessColumns32Bpp(int stride, int width, int height, double brigh
138291
int pixelCount = height;
139292
int columnOffset = x * 4;
140293

141-
for (int y = 0; y < height; y++)
294+
int y = 0;
295+
for (; y < height - 3; y += 4)
296+
{
297+
int offset1 = y * stride + columnOffset;
298+
int offset2 = (y + 1) * stride + columnOffset;
299+
int offset3 = (y + 2) * stride + columnOffset;
300+
int offset4 = (y + 3) * stride + columnOffset;
301+
302+
totalB += (uint)(pixelBuffer[offset1] + pixelBuffer[offset2] + pixelBuffer[offset3] + pixelBuffer[offset4]);
303+
totalG += (uint)pixelBuffer[offset1 + 1] + pixelBuffer[offset2 + 1] + pixelBuffer[offset3 + 1] + pixelBuffer[offset4 + 1];
304+
totalR += (uint)pixelBuffer[offset1 + 2] + pixelBuffer[offset2 + 2] + pixelBuffer[offset3 + 2] + pixelBuffer[offset4 + 2];
305+
}
306+
307+
for (; y < height; y++)
142308
{
143309
int offset = y * stride + columnOffset;
144310
totalB += pixelBuffer[offset];
@@ -150,23 +316,24 @@ private void ProcessColumns32Bpp(int stride, int width, int height, double brigh
150316
byte avgG = (byte)(totalG / pixelCount);
151317
byte avgB = (byte)(totalB / pixelCount);
152318

153-
resultBuffer[x] = FastApplyEffects(avgR, avgG, avgB, brightness, vibrance, contrast, darkThreshold, darkFactor);
319+
rawColors[x] = new OpenRGB.NET.Color(avgR, avgG, avgB);
154320
}
155321
});
156322
}
157323

158324
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
159-
private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
325+
private void ProcessColumnsWithEffects(int width, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
160326
{
161-
162-
if (!lutsInitialized || Math.Abs(lastBrightness - brightness) > 0.001 || Math.Abs(lastContrast - contrast) > 0.001)
327+
Parallel.For(0, width, x =>
163328
{
164-
InitializeLuts(brightness, contrast);
165-
lastBrightness = brightness;
166-
lastContrast = contrast;
167-
lutsInitialized = true;
168-
}
329+
resultBuffer[x] = FastApplyEffects(rawColors[x].R, rawColors[x].G, rawColors[x].B,
330+
brightness, vibrance, contrast, darkThreshold, darkFactor);
331+
});
332+
}
169333

334+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
335+
private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double brightness, double vibrance, double contrast, int darkThreshold, double darkFactor)
336+
{
170337
unchecked
171338
{
172339

@@ -217,29 +384,28 @@ private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double bright
217384
bVal = bVal < darkThreshold ? bDark : bVal;
218385
}
219386

220-
return new OpenRGB.NET.Color((byte)rVal, (byte)gVal, (byte)bVal);
387+
return new OpenRGB.NET.Color(
388+
(byte)Math.Min(Math.Max(rVal, 0), 255),
389+
(byte)Math.Min(Math.Max(gVal, 0), 255),
390+
(byte)Math.Min(Math.Max(bVal, 0), 255)
391+
);
221392
}
222393
}
223394

395+
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
224396
private void InitializeLuts(double brightness, double contrast)
225397
{
226-
if (brightnessLut == null)
227-
{
228-
brightnessLut = new byte[256];
229-
contrastLut = new byte[256];
230-
}
231-
232398
for (int i = 0; i < 256; i++)
233399
{
234400

235401
int brightVal = (int)(i * brightness);
236-
brightnessLut[i] = (byte)Math.Min(brightVal, 255);
402+
brightnessLut[i] = (byte)Math.Min(Math.Max(brightVal, 0), 255);
237403

238404
if (Math.Abs(contrast - 1.0) > 0.001)
239405
{
240406
double normalized = i / 255.0;
241-
int contrastVal = (int)(Math.Pow(normalized, contrast) * 255.0);
242-
contrastLut[i] = (byte)Math.Min(Math.Max(contrastVal, 0), 255);
407+
double adjusted = Math.Pow(normalized, contrast) * 255.0;
408+
contrastLut[i] = (byte)Math.Min(Math.Max((int)adjusted, 0), 255);
243409
}
244410
else
245411
{
@@ -252,9 +418,9 @@ public void Dispose()
252418
{
253419
pixelBuffer = null;
254420
resultBuffer = null;
421+
rawColors = null;
422+
previousFrame = null;
255423
brightnessLut = null;
256424
contrastLut = null;
257-
GC.Collect();
258-
GC.WaitForPendingFinalizers();
259425
}
260426
}

KeyLighting/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ static void Main(string[] args)
7777
fadeProgress = new float[ledCount];
7878
targetColors = new ORGBColor[ledCount];
7979

80-
using var processor = new CPUImageProcessor();
80+
var processor = new CPUImageProcessor(config);
81+
8182
using var capturer = ConfigureScreenCapturer(config);
8283

8384
var updateTimer = new System.Timers.Timer(config.UpdateDelayMs);

KeyLighting/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"brightnessMultiplier": 1.8,
77
"vibranceFactor": 1,
88
"contrastPower": 1.8,
9-
"fadeFactor": 3,
9+
"fadeFactor": 0.9,
1010
"darkenThreshold": 65,
1111
"darkenFactor": 0.4,
1212
"wasdEnabled": false,

0 commit comments

Comments
 (0)