33
44using FluentFlyout . Classes . Settings ;
55using FluentFlyout . Classes . Utils ;
6+ using Microsoft . Win32 ;
67using NAudio . CoreAudioApi ;
78using NAudio . Dsp ;
89using NAudio . Wave ;
9- using Microsoft . Win32 ;
10- using System . Threading ;
1110using System . Windows ;
1211using System . Windows . Media ;
1312using System . Windows . Media . Imaging ;
@@ -41,6 +40,25 @@ public class Visualizer : IDisposable
4140 private DateTime _lastDataAvailableUtc = DateTime . MinValue ;
4241 private int _restartInProgress ; // 0=false, 1=true (Interlocked)
4342
43+ private readonly struct BarGeometry
44+ {
45+ public readonly float Left , Right , Top , Bottom ;
46+ public readonly float InnerLeft , InnerRight , InnerTop , InnerBottom ;
47+
48+ public BarGeometry ( int x , int width , int y , int endY , float radius )
49+ {
50+ Left = x ;
51+ Right = x + width ;
52+ Top = y ;
53+ Bottom = endY ;
54+
55+ InnerLeft = Left + radius ;
56+ InnerRight = Right - radius ;
57+ InnerTop = Top + radius ;
58+ InnerBottom = Bottom - radius ;
59+ }
60+ }
61+
4462 public WriteableBitmap ? Bitmap
4563 {
4664 get
@@ -431,85 +449,7 @@ private void UpdateBitmap()
431449
432450 buffer . Clear ( ) ;
433451
434- // Draw bars
435- int barWidth = ( ImageWidth - ( BarCount - 1 ) * BarSpacing ) / BarCount ;
436-
437- // get dominant color for bars
438- SolidColorBrush brush = BitmapHelper . SavedDominantColors . Count > 0 ?
439- BitmapHelper . SavedDominantColors . Last ( )
440- : ( SolidColorBrush ) Application . Current . TryFindResource ( "MicaWPF.Brushes.SystemAccentColorTertiary" ) ;
441-
442- bool centeredBars = SettingsManager . Current . TaskbarVisualizerCenteredBars ;
443- int barBaseline = SettingsManager . Current . TaskbarVisualizerBaseline ? 4 : 0 ;
444-
445- int centerY = ImageHeight / 2 ;
446- float cornerRadius = 6f / MathF . Max ( 1f , SettingsManager . Current . TaskbarVisualizerBarCount / 10f ) ;
447-
448- for ( int i = 0 ; i < BarCount ; i ++ )
449- {
450- float normalizedValue = Math . Clamp ( _barValues [ i ] , 0f , 1f ) ;
451- int barHeight = Math . Max ( ( int ) ( normalizedValue * ImageHeight ) , barBaseline ) ;
452- int barX = i * ( barWidth + BarSpacing ) ;
453-
454- int barY , barEndY ;
455- if ( centeredBars )
456- {
457- // Center the bars - expand up and down from middle
458- int halfHeight = barHeight / 2 ;
459- barY = centerY - halfHeight ;
460- barEndY = centerY + halfHeight ;
461- }
462- else
463- {
464- // Original behavior - bars rise from bottom
465- barY = ImageHeight - barHeight ;
466- barEndY = ImageHeight ;
467- }
468-
469- for ( int y = barY ; y < barEndY && y < ImageHeight && y >= 0 ; y ++ )
470- {
471- for ( int x = barX ; x < barX + barWidth && x < ImageWidth ; x ++ )
472- {
473- float alpha = 1f ;
474-
475- // Calculate relative position within the bar
476- int relativeY = y - barY ;
477-
478- // Check corners
479- bool inTopLeftCorner = ( relativeY < cornerRadius ) && ( x - barX < cornerRadius ) ;
480- bool inTopRightCorner = ( relativeY < cornerRadius ) && ( barX + barWidth - x <= cornerRadius ) ;
481- bool inBottomLeftCorner = centeredBars && ( barEndY - y <= cornerRadius ) && ( x - barX < cornerRadius ) ;
482- bool inBottomRightCorner = centeredBars && ( barEndY - y <= cornerRadius ) && ( barX + barWidth - x <= cornerRadius ) ;
483-
484- if ( inTopLeftCorner || inTopRightCorner || inBottomLeftCorner || inBottomRightCorner )
485- {
486- float dx = ( inTopLeftCorner || inBottomLeftCorner ) ?
487- ( cornerRadius - 0.5f - ( x - barX ) ) :
488- ( cornerRadius - 0.5f - ( barX + barWidth - x ) ) ;
489- float dy = ( inTopLeftCorner || inTopRightCorner ) ?
490- ( cornerRadius - 0.5f - relativeY ) :
491- ( cornerRadius - 0.5f - ( barEndY - y ) ) ;
492- float distance = MathF . Sqrt ( dx * dx + dy * dy ) ;
493-
494- if ( distance > cornerRadius )
495- continue ; // Outside corner
496-
497- // Anti-aliasing: fade at edge
498- if ( distance > cornerRadius - 1f )
499- alpha = cornerRadius - distance ;
500- }
501-
502- int index = y * stride + x * 4 ;
503- if ( index + 3 < buffer . Length )
504- {
505- buffer [ index ] = brush . Color . B ;
506- buffer [ index + 1 ] = brush . Color . G ;
507- buffer [ index + 2 ] = brush . Color . R ;
508- buffer [ index + 3 ] = ( byte ) ( 255 * alpha ) ;
509- }
510- }
511- }
512- }
452+ DrawBars ( stride , buffer ) ;
513453 }
514454
515455 _bitmap . AddDirtyRect ( new Int32Rect ( 0 , 0 , ImageWidth , ImageHeight ) ) ;
@@ -522,6 +462,192 @@ private void UpdateBitmap()
522462 } , System . Windows . Threading . DispatcherPriority . Render ) ;
523463 }
524464
465+ private unsafe void DrawBars ( int stride , Span < byte > buffer )
466+ {
467+ // Resolve brush once
468+ SolidColorBrush brush = BitmapHelper . SavedDominantColors . Count > 0
469+ ? BitmapHelper . SavedDominantColors . Last ( )
470+ : ( SolidColorBrush ) Application . Current . TryFindResource ( "MicaWPF.Brushes.SystemAccentColorTertiary" ) ;
471+
472+ byte b = brush . Color . B ;
473+ byte g = brush . Color . G ;
474+ byte r = brush . Color . R ;
475+
476+ bool centeredBars = SettingsManager . Current . TaskbarVisualizerCenteredBars ;
477+ int barBaseline = SettingsManager . Current . TaskbarVisualizerBaseline ? 4 : 0 ;
478+
479+ int centerY = ImageHeight / 2 ;
480+
481+ // Horizontal layout
482+ ComputeLayout ( ImageWidth , BarCount , BarSpacing ,
483+ out int barWidth ,
484+ out int offsetX ) ;
485+
486+ // Radius
487+ float baseRadius = GetCornerRadius ( ) ;
488+
489+ // AA constants
490+ const float aa = 1.25f ;
491+ float invAA = 1f / aa ;
492+
493+ for ( int i = 0 ; i < BarCount ; i ++ )
494+ {
495+ int barX = offsetX + i * ( barWidth + BarSpacing ) ;
496+
497+ int barHeight = GetBarHeight ( _barValues [ i ] , barBaseline ) ;
498+
499+ if ( barHeight <= 0 )
500+ continue ;
501+
502+ ComputeVertical ( centeredBars , centerY , barHeight , out int barY , out int barEndY ) ;
503+
504+ // Clamp radius per bar
505+ float radius = ClampRadius ( baseRadius , barWidth , barHeight ) ;
506+ float radiusSq = radius * radius ;
507+
508+ RasterizeBar (
509+ buffer , stride ,
510+ barX , barWidth ,
511+ barY , barEndY ,
512+ centeredBars ,
513+ radius , radiusSq , invAA ,
514+ b , g , r ) ;
515+ }
516+ }
517+
518+ private static void ComputeLayout (
519+ int imageWidth ,
520+ int barCount ,
521+ int spacing ,
522+ out int barWidth ,
523+ out int offsetX )
524+ {
525+ int totalSpacing = ( barCount - 1 ) * spacing ;
526+
527+ int availableWidth = imageWidth - totalSpacing - 1 ;
528+
529+ barWidth = availableWidth / barCount ;
530+
531+ int usedWidth = barWidth * barCount + totalSpacing ;
532+
533+ // Center safely
534+ offsetX = ( imageWidth - usedWidth ) >> 1 ;
535+ }
536+
537+ private void ComputeVertical ( bool centered , int centerY , int height , out int y , out int endY )
538+ {
539+ if ( centered )
540+ {
541+ int half = height >> 1 ; // faster than /2
542+ y = centerY - half ;
543+ endY = centerY + half ;
544+ }
545+ else
546+ {
547+ y = ImageHeight - height ;
548+ endY = ImageHeight ;
549+ }
550+ }
551+
552+ private int GetBarHeight ( float value , int baseline )
553+ {
554+ return Math . Max ( ( int ) ( Math . Clamp ( value , 0f , 1f ) * ImageHeight ) , baseline ) ;
555+ }
556+ private static float GetCornerRadius ( )
557+ {
558+ return 6f / MathF . Max ( 1f , SettingsManager . Current . TaskbarVisualizerBarCount / 10f ) ;
559+ }
560+
561+ private static float ClampRadius ( float r , int width , int height )
562+ {
563+ float max = MathF . Min ( width , height ) * 0.5f ;
564+ return r > max ? max : r ;
565+ }
566+
567+ private unsafe void RasterizeBar (
568+ Span < byte > buffer ,
569+ int stride ,
570+ int barX ,
571+ int barWidth ,
572+ int barY ,
573+ int barEndY ,
574+ bool centeredBars ,
575+ float radius ,
576+ float radiusSq ,
577+ float invAA ,
578+ byte b , byte g , byte r )
579+ {
580+ float left = barX ;
581+ float right = barX + barWidth ;
582+ float top = barY ;
583+ float bottom = barEndY ;
584+
585+ float innerLeft = left + radius ;
586+ float innerRight = right - radius ;
587+ float innerTop = top + radius ;
588+ float innerBottom = bottom - radius ;
589+
590+ for ( int y = barY ; y < barEndY && y < ImageHeight && y >= 0 ; y ++ )
591+ {
592+ int row = y * stride ;
593+
594+ for ( int x = barX ; x < barX + barWidth && x < ImageWidth ; x ++ )
595+ {
596+ int index = row + ( x << 2 ) ; // x * 4 (bitshift faster)
597+ if ( index + 3 >= buffer . Length )
598+ continue ;
599+
600+ // CENTER
601+ if ( x >= innerLeft && x <= innerRight )
602+ {
603+ WritePixel ( buffer , index , b , g , r , 255 ) ;
604+ continue ;
605+ }
606+
607+ // SIDES
608+ if ( y >= innerTop && y <= innerBottom )
609+ {
610+ WritePixel ( buffer , index , b , g , r , 255 ) ;
611+ continue ;
612+ }
613+
614+ // FLAT BOTTOM
615+ if ( ! centeredBars && y >= innerBottom )
616+ {
617+ WritePixel ( buffer , index , b , g , r , 255 ) ;
618+ continue ;
619+ }
620+
621+ // CORNERS
622+ float cx = x < innerLeft ? innerLeft : ( x > innerRight ? innerRight : x ) ;
623+ float cy = y < innerTop ? innerTop : ( y > innerBottom ? innerBottom : y ) ;
624+
625+ float dx = x - cx ;
626+ float dy = y - cy ;
627+
628+ float distSq = dx * dx + dy * dy ;
629+ float sdf = ( distSq - radiusSq ) / ( 2f * radius ) ;
630+
631+ float alpha = 0.5f - sdf * invAA ;
632+
633+ if ( alpha <= 0f )
634+ continue ;
635+
636+ if ( alpha > 1f ) alpha = 1f ;
637+
638+ WritePixel ( buffer , index , b , g , r , ( byte ) ( 255 * alpha ) ) ;
639+ }
640+ }
641+ }
642+
643+ private static void WritePixel ( Span < byte > buffer , int index , byte b , byte g , byte r , byte a )
644+ {
645+ buffer [ index ] = b ;
646+ buffer [ index + 1 ] = g ;
647+ buffer [ index + 2 ] = r ;
648+ buffer [ index + 3 ] = a ;
649+ }
650+
525651 private void OnRecordingStopped ( object ? sender , StoppedEventArgs e )
526652 {
527653 if ( e . Exception != null )
0 commit comments