@@ -41,6 +41,25 @@ public class Visualizer : IDisposable
4141 private DateTime _lastDataAvailableUtc = DateTime . MinValue ;
4242 private int _restartInProgress ; // 0=false, 1=true (Interlocked)
4343
44+ private readonly struct BarGeometry
45+ {
46+ public readonly float Left , Right , Top , Bottom ;
47+ public readonly float InnerLeft , InnerRight , InnerTop , InnerBottom ;
48+
49+ public BarGeometry ( int x , int width , int y , int endY , float radius )
50+ {
51+ Left = x ;
52+ Right = x + width ;
53+ Top = y ;
54+ Bottom = endY ;
55+
56+ InnerLeft = Left + radius ;
57+ InnerRight = Right - radius ;
58+ InnerTop = Top + radius ;
59+ InnerBottom = Bottom - radius ;
60+ }
61+ }
62+
4463 public WriteableBitmap ? Bitmap
4564 {
4665 get
@@ -431,85 +450,7 @@ private void UpdateBitmap()
431450
432451 buffer . Clear ( ) ;
433452
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- }
453+ DrawBars ( stride , buffer ) ;
513454 }
514455
515456 _bitmap . AddDirtyRect ( new Int32Rect ( 0 , 0 , ImageWidth , ImageHeight ) ) ;
@@ -522,6 +463,151 @@ private void UpdateBitmap()
522463 } , System . Windows . Threading . DispatcherPriority . Render ) ;
523464 }
524465
466+ private unsafe void DrawBars ( int stride , Span < byte > buffer )
467+ {
468+ // Resolve brush once (avoid repeated lookups)
469+ SolidColorBrush brush = BitmapHelper . SavedDominantColors . Count > 0
470+ ? BitmapHelper . SavedDominantColors . Last ( )
471+ : ( SolidColorBrush ) Application . Current . TryFindResource ( "MicaWPF.Brushes.SystemAccentColorTertiary" ) ;
472+
473+ byte b = brush . Color . B ;
474+ byte g = brush . Color . G ;
475+ byte r = brush . Color . R ;
476+
477+ bool centeredBars = SettingsManager . Current . TaskbarVisualizerCenteredBars ;
478+ int barBaseline = SettingsManager . Current . TaskbarVisualizerBaseline ? 4 : 0 ;
479+
480+ int centerY = ImageHeight / 2 ;
481+
482+ // Layout calculation (float-based to avoid spacing drift)
483+ float totalSpacing = ( BarCount - 1 ) * BarSpacing ;
484+ float exactBarWidth = ( ImageWidth - totalSpacing ) / ( float ) BarCount ;
485+ float totalBarsWidth = BarCount * exactBarWidth + totalSpacing ;
486+ float offsetX = ( ImageWidth - totalBarsWidth ) * 0.5f ;
487+
488+ for ( int i = 0 ; i < BarCount ; i ++ )
489+ {
490+ // Normalize and scale height
491+ float normalizedValue = Math . Clamp ( _barValues [ i ] , 0f , 1f ) ;
492+ int barHeight = Math . Max ( ( int ) ( normalizedValue * ImageHeight ) , barBaseline ) ;
493+
494+ // Compute precise X bounds (prevents cumulative rounding errors)
495+ ComputeBarBounds ( i , exactBarWidth , offsetX , out int barX , out int barWidth ) ;
496+
497+ if ( barWidth <= 0 )
498+ continue ;
499+
500+ // Compute vertical bounds
501+ ComputeBarVertical ( centeredBars , centerY , barHeight , out int barY , out int barEndY ) ;
502+
503+ // Precompute geometry
504+ float radius = MathF . Min ( barWidth , barHeight ) * 0.5f ;
505+
506+ var geom = new BarGeometry ( barX , barWidth , barY , barEndY , radius ) ;
507+
508+ // Precompute SDF constants
509+ float aa = 1.25f ;
510+ float invAA = 1f / aa ;
511+ float radiusSq = radius * radius ;
512+
513+ // Rasterize
514+ RasterizeBar ( buffer , stride , geom , centeredBars , b , g , r , invAA , radiusSq ) ;
515+ }
516+ }
517+
518+ private unsafe void RasterizeBar (
519+ Span < byte > buffer ,
520+ int stride ,
521+ BarGeometry g ,
522+ bool centeredBars ,
523+ byte b , byte gr , byte r ,
524+ float invAA ,
525+ float radiusSq )
526+ {
527+ for ( int y = ( int ) g . Top ; y < g . Bottom && y < ImageHeight && y >= 0 ; y ++ )
528+ {
529+ int row = y * stride ;
530+
531+ for ( int x = ( int ) g . Left ; x < g . Right && x < ImageWidth ; x ++ )
532+ {
533+ int index = row + x * 4 ;
534+ if ( index + 3 >= buffer . Length )
535+ continue ;
536+
537+ // Fast path: center rectangle
538+ if ( x >= g . InnerLeft && x <= g . InnerRight )
539+ {
540+ WritePixel ( buffer , index , b , gr , r , 255 ) ;
541+ continue ;
542+ }
543+
544+ // Fast path: vertical sides (no AA)
545+ if ( y >= g . InnerTop && y <= g . InnerBottom )
546+ {
547+ WritePixel ( buffer , index , b , gr , r , 255 ) ;
548+ continue ;
549+ }
550+
551+ // Flat bottom if not centered
552+ if ( ! centeredBars && y >= g . InnerBottom )
553+ {
554+ WritePixel ( buffer , index , b , gr , r , 255 ) ;
555+ continue ;
556+ }
557+
558+ // Rounded caps only
559+ float cx = x < g . InnerLeft ? g . InnerLeft : ( x > g . InnerRight ? g . InnerRight : x ) ;
560+ float cy = y < g . InnerTop ? g . InnerTop : ( y > g . InnerBottom ? g . InnerBottom : y ) ;
561+
562+ float dx = x - cx ;
563+ float dy = y - cy ;
564+
565+ float distSq = dx * dx + dy * dy ;
566+ float sdf = ( distSq - radiusSq ) / ( 2f * MathF . Sqrt ( radiusSq ) ) ; // radius = sqrt(radiusSq)
567+
568+ float alpha = Math . Clamp ( 0.5f - sdf * invAA , 0f , 1f ) ;
569+
570+ if ( alpha <= 0f )
571+ continue ;
572+
573+ WritePixel ( buffer , index , b , gr , r , ( byte ) ( 255 * alpha ) ) ;
574+ }
575+ }
576+ }
577+
578+ private void ComputeBarBounds ( int i , float width , float offsetX , out int x , out int barWidth )
579+ {
580+ float fx = offsetX + i * ( width + BarSpacing ) ;
581+ float fxNext = fx + width ;
582+
583+ x = ( int ) MathF . Round ( fx ) ;
584+ int right = ( int ) MathF . Round ( fxNext ) ;
585+ barWidth = right - x ;
586+ }
587+
588+ private void ComputeBarVertical ( bool centered , int centerY , int height , out int y , out int endY )
589+ {
590+ if ( centered )
591+ {
592+ int half = height / 2 ;
593+ y = centerY - half ;
594+ endY = centerY + half ;
595+ }
596+ else
597+ {
598+ y = ImageHeight - height ;
599+ endY = ImageHeight ;
600+ }
601+ }
602+
603+ private static void WritePixel ( Span < byte > buffer , int index , byte b , byte g , byte r , byte a )
604+ {
605+ buffer [ index ] = b ;
606+ buffer [ index + 1 ] = g ;
607+ buffer [ index + 2 ] = r ;
608+ buffer [ index + 3 ] = a ;
609+ }
610+
525611 private void OnRecordingStopped ( object ? sender , StoppedEventArgs e )
526612 {
527613 if ( e . Exception != null )
0 commit comments