11using System ;
2- using System . Collections . Concurrent ;
32using System . Drawing ;
43using System . Drawing . Drawing2D ;
54using System . Drawing . Imaging ;
65using System . Runtime . CompilerServices ;
76using System . Runtime . InteropServices ;
87using System . Threading . Tasks ;
8+ using KeyboardLighting ;
9+ using Newtonsoft . Json ;
910using OpenRGB . NET ;
1011
12+
1113public 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}
0 commit comments