1
1
using System ;
2
- using System . Collections . Concurrent ;
3
2
using System . Drawing ;
4
3
using System . Drawing . Drawing2D ;
5
4
using System . Drawing . Imaging ;
6
5
using System . Runtime . CompilerServices ;
7
6
using System . Runtime . InteropServices ;
8
7
using System . Threading . Tasks ;
8
+ using KeyboardLighting ;
9
+ using Newtonsoft . Json ;
9
10
using OpenRGB . NET ;
10
11
12
+
11
13
public class CPUImageProcessor : IDisposable
12
14
{
13
-
14
15
private byte [ ] ? pixelBuffer ;
15
- private int lastWidth ;
16
- private int lastHeight ;
17
- private readonly object bufferLock = new object ( ) ;
18
16
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 ] ;
22
19
private double lastBrightness = - 1 ;
23
20
private double lastContrast = - 1 ;
24
21
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 ;
27
28
28
- return ProcessImageOnCPU ( image , targetWidth , targetHeight , brightness , vibrance , contrast , darkThreshold , darkFactor ) ;
29
+ public CPUImageProcessor ( LightingConfig config )
30
+ {
31
+ fadeSpeed = config . FadeFactor ;
29
32
}
30
33
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
+ }
32
42
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 )
34
45
{
35
46
36
47
if ( resultBuffer == null || resultBuffer . Length != targetWidth )
37
48
{
38
49
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 ;
39
60
}
40
61
41
62
Bitmap bitmapToProcess = image ;
@@ -74,15 +95,29 @@ private OpenRGB.NET.Color[] ProcessImageOnCPU(Bitmap image, int targetWidth, int
74
95
75
96
Marshal . Copy ( bmpData . Scan0 , pixelBuffer , 0 , byteCount ) ;
76
97
77
- if ( bytesPerPixel == 4 )
98
+ ExtractColumns ( bmpData . Stride , targetWidth , targetHeight , bytesPerPixel ) ;
99
+
100
+ bool isSolidColor = IsSolidColorFrame ( ) ;
101
+
102
+ if ( isSolidColor )
78
103
{
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 ) ;
80
107
}
81
- else if ( bytesPerPixel == 3 )
108
+ else
82
109
{
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
+ }
84
117
}
85
118
119
+ StoreFrameState ( targetWidth , isSolidColor ) ;
120
+
86
121
return resultBuffer ;
87
122
}
88
123
finally
@@ -100,7 +135,112 @@ private OpenRGB.NET.Color[] ProcessImageOnCPU(Bitmap image, int targetWidth, int
100
135
}
101
136
102
137
[ 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 )
104
244
{
105
245
Parallel . For ( 0 , width , new ParallelOptions { MaxDegreeOfParallelism = Environment . ProcessorCount } , x =>
106
246
{
@@ -110,7 +250,20 @@ private void ProcessColumns24Bpp(int stride, int width, int height, double brigh
110
250
int pixelCount = height ;
111
251
int columnOffset = x * 3 ;
112
252
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 ++ )
114
267
{
115
268
int offset = y * stride + columnOffset ;
116
269
totalB += pixelBuffer [ offset ] ;
@@ -122,13 +275,13 @@ private void ProcessColumns24Bpp(int stride, int width, int height, double brigh
122
275
byte avgG = ( byte ) ( totalG / pixelCount ) ;
123
276
byte avgB = ( byte ) ( totalB / pixelCount ) ;
124
277
125
- resultBuffer [ x ] = FastApplyEffects ( avgR , avgG , avgB , brightness , vibrance , contrast , darkThreshold , darkFactor ) ;
278
+ rawColors [ x ] = new OpenRGB . NET . Color ( avgR , avgG , avgB ) ;
126
279
}
127
280
} ) ;
128
281
}
129
282
130
283
[ 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 )
132
285
{
133
286
Parallel . For ( 0 , width , new ParallelOptions { MaxDegreeOfParallelism = Environment . ProcessorCount } , x =>
134
287
{
@@ -138,7 +291,20 @@ private void ProcessColumns32Bpp(int stride, int width, int height, double brigh
138
291
int pixelCount = height ;
139
292
int columnOffset = x * 4 ;
140
293
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 ++ )
142
308
{
143
309
int offset = y * stride + columnOffset ;
144
310
totalB += pixelBuffer [ offset ] ;
@@ -150,23 +316,24 @@ private void ProcessColumns32Bpp(int stride, int width, int height, double brigh
150
316
byte avgG = ( byte ) ( totalG / pixelCount ) ;
151
317
byte avgB = ( byte ) ( totalB / pixelCount ) ;
152
318
153
- resultBuffer [ x ] = FastApplyEffects ( avgR , avgG , avgB , brightness , vibrance , contrast , darkThreshold , darkFactor ) ;
319
+ rawColors [ x ] = new OpenRGB . NET . Color ( avgR , avgG , avgB ) ;
154
320
}
155
321
} ) ;
156
322
}
157
323
158
324
[ 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 )
160
326
{
161
-
162
- if ( ! lutsInitialized || Math . Abs ( lastBrightness - brightness ) > 0.001 || Math . Abs ( lastContrast - contrast ) > 0.001 )
327
+ Parallel . For ( 0 , width , x =>
163
328
{
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
+ }
169
333
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
+ {
170
337
unchecked
171
338
{
172
339
@@ -217,29 +384,28 @@ private OpenRGB.NET.Color FastApplyEffects(byte r, byte g, byte b, double bright
217
384
bVal = bVal < darkThreshold ? bDark : bVal ;
218
385
}
219
386
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
+ ) ;
221
392
}
222
393
}
223
394
395
+ [ MethodImpl ( MethodImplOptions . AggressiveOptimization ) ]
224
396
private void InitializeLuts ( double brightness , double contrast )
225
397
{
226
- if ( brightnessLut == null )
227
- {
228
- brightnessLut = new byte [ 256 ] ;
229
- contrastLut = new byte [ 256 ] ;
230
- }
231
-
232
398
for ( int i = 0 ; i < 256 ; i ++ )
233
399
{
234
400
235
401
int brightVal = ( int ) ( i * brightness ) ;
236
- brightnessLut [ i ] = ( byte ) Math . Min ( brightVal , 255 ) ;
402
+ brightnessLut [ i ] = ( byte ) Math . Min ( Math . Max ( brightVal , 0 ) , 255 ) ;
237
403
238
404
if ( Math . Abs ( contrast - 1.0 ) > 0.001 )
239
405
{
240
406
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 ) ;
243
409
}
244
410
else
245
411
{
@@ -252,9 +418,9 @@ public void Dispose()
252
418
{
253
419
pixelBuffer = null ;
254
420
resultBuffer = null ;
421
+ rawColors = null ;
422
+ previousFrame = null ;
255
423
brightnessLut = null ;
256
424
contrastLut = null ;
257
- GC . Collect ( ) ;
258
- GC . WaitForPendingFinalizers ( ) ;
259
425
}
260
426
}
0 commit comments