Skip to content

Commit 339a296

Browse files
authored
Merge pull request #497 from Hikaru11747/feature/ImprovedVisualizer
Improved visualizer by reducing processing/drawing time by ~2.2x
2 parents b9c436c + 7673b63 commit 339a296

2 files changed

Lines changed: 209 additions & 82 deletions

File tree

‎FluentFlyoutWPF/Classes/Visualizer.cs‎

Lines changed: 207 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33

44
using FluentFlyout.Classes.Settings;
55
using FluentFlyout.Classes.Utils;
6+
using Microsoft.Win32;
67
using NAudio.CoreAudioApi;
78
using NAudio.Dsp;
89
using NAudio.Wave;
9-
using Microsoft.Win32;
10-
using System.Threading;
1110
using System.Windows;
1211
using System.Windows.Media;
1312
using 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)

‎FluentFlyoutWPF/ViewModels/AboutViewModel.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public partial class AboutViewModel : ObservableObject
2121
"xsm2",
2222
"Simnico99",
2323
"neegool",
24-
"mak7im01"
24+
"mak7im01",
25+
"Hikaru11747"
2526
];
2627

2728
public ObservableCollection<string> Translators { get; } =

0 commit comments

Comments
 (0)