fix: sometimes incorrect album art background scale

This commit is contained in:
Zhe Fang
2025-06-13 19:22:56 -04:00
parent 63c0577e73
commit def2c9820a
13 changed files with 394 additions and 45 deletions

View File

@@ -113,8 +113,7 @@ namespace BetterLyrics.WinUI3
var overlayWindow = new OverlayWindow();
overlayWindow.Navigate(typeof(DesktopLyricsPage));
overlayWindow.Hide();
App.Current.OverlayWindow = overlayWindow;
Current.OverlayWindow = overlayWindow;
// Activate the window
MainWindow = new HostWindow();

View File

@@ -39,6 +39,7 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="WinUIEx" Version="2.5.1" />
@@ -85,7 +86,7 @@
<InheritWinAppVersionFrom>AssemblyVersion</InheritWinAppVersionFrom>
<PackageVersionSettings>AssemblyVersion.None.None</PackageVersionSettings>
<Version>2025.6.0</Version>
<AssemblyVersion>2025.6.13.2027</AssemblyVersion>
<FileVersion>2025.6.13.2027</FileVersion>
<AssemblyVersion>2025.6.13.2318</AssemblyVersion>
<FileVersion>2025.6.13.2318</FileVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using WinRT.Interop;
namespace BetterLyrics.WinUI3.Helper
{
public static class TransparentAppBarHelper
{
// 记录哪些 HWND 已经注册 AppBar
private static readonly HashSet<IntPtr> _registered = new();
/// <summary>
/// 启用透明 AppBar 功能。
/// </summary>
/// <param name="window">目标 WinUI 3 Window</param>
/// <param name="height">刘海高度(像素)</param>
/// <param name="clickThrough">是否点击穿透</param>
public static void Enable(Window window, int height = 50, bool clickThrough = true)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
// 设置扩展样式:透明 (LAYERED) + 置顶 + 可选穿透
// int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
//exStyle |= WS_EX_LAYERED; // 允许透明
//if (clickThrough)
// exStyle |= WS_EX_TRANSPARENT; // 点击穿透
// SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
// 透明背景(完全不影响视觉;不改变 Alpha 也行)
//SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);
// 注册 AppBar
RegisterAppBar(hwnd, height);
// 调整窗口位置与大小(示例:屏幕宽度 × 指定高度)
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
SetWindowPos(
hwnd,
HWND_TOPMOST,
0,
0,
screenWidth,
height,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW
);
}
/// <summary>
/// 关闭并注销 AppBar占位恢复。
/// </summary>
public static void Disable(Window window)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
UnregisterAppBar(hwnd);
// 移除 WS_EX_TRANSPARENT可根据需求恢复其他样式
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
exStyle &= ~WS_EX_TRANSPARENT;
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
}
/// <summary>
/// 切换点击穿透开关。
/// </summary>
public static void SetClickThrough(Window window, bool enable)
{
IntPtr hwnd = WindowNative.GetWindowHandle(window);
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
if (enable)
exStyle |= WS_EX_TRANSPARENT;
else
exStyle &= ~WS_EX_TRANSPARENT;
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
}
#region AppBar
private const uint ABM_NEW = 0x00000000;
private const uint ABM_REMOVE = 0x00000001;
private const uint ABM_SETPOS = 0x00000003;
private const int ABE_TOP = 1;
[StructLayout(LayoutKind.Sequential)]
private struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public uint uCallbackMessage;
public uint uEdge;
public RECT rc;
public int lParam;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left,
top,
right,
bottom;
}
[DllImport("shell32.dll", SetLastError = true)]
private static extern uint SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);
private static void RegisterAppBar(IntPtr hwnd, int height)
{
if (_registered.Contains(hwnd))
return;
APPBARDATA abd = new()
{
cbSize = Marshal.SizeOf<APPBARDATA>(),
hWnd = hwnd,
uEdge = ABE_TOP,
rc = new RECT
{
left = 0,
top = 0,
right = GetSystemMetrics(SM_CXSCREEN),
bottom = height,
},
};
SHAppBarMessage(ABM_NEW, ref abd);
SHAppBarMessage(ABM_SETPOS, ref abd);
_registered.Add(hwnd);
}
private static void UnregisterAppBar(IntPtr hwnd)
{
if (!_registered.Contains(hwnd))
return;
APPBARDATA abd = new() { cbSize = Marshal.SizeOf<APPBARDATA>(), hWnd = hwnd };
SHAppBarMessage(ABM_REMOVE, ref abd);
_registered.Remove(hwnd);
}
#endregion
#region Win32 Helper &
private const int GWL_EXSTYLE = -20;
private const int WS_EX_LAYERED = 0x80000;
private const int WS_EX_TRANSPARENT = 0x20;
private const int LWA_ALPHA = 0x2;
private const int SWP_NOACTIVATE = 0x0010;
private const int SWP_NOOWNERZORDER = 0x0200;
private const int SWP_SHOWWINDOW = 0x0040;
private static readonly IntPtr HWND_TOPMOST = new(-1);
private const int SM_CXSCREEN = 0;
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags
);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetLayeredWindowAttributes(
IntPtr hwnd,
uint crKey,
byte bAlpha,
uint dwFlags
);
#endregion
}
}

View File

@@ -0,0 +1,125 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace BetterLyrics.WinUI3.Helper
{
public static class WindowColorHelper
{
public static Color GetDominantColorBelow(
IntPtr myHwnd,
int sampleWidth = 64,
int sampleHeight = 64
)
{
// 获取屏幕坐标中,在窗口下方的某个点
if (!GetWindowRect(myHwnd, out RECT myRect))
return Color.Transparent;
POINT pt = new()
{
x = (myRect.Left + myRect.Right) / 2,
y = myRect.Bottom + 1, // 紧贴窗口底部
};
IntPtr hwndBelow = WindowFromPoint(pt);
if (hwndBelow == myHwnd || hwndBelow == IntPtr.Zero)
return Color.Transparent;
return GetAverageColorFromWindow(hwndBelow, sampleWidth, sampleHeight);
}
private static Color GetAverageColorFromWindow(IntPtr hwnd, int width, int height)
{
if (!GetWindowRect(hwnd, out RECT rect))
return Color.Transparent;
int w = Math.Min(width, rect.Right - rect.Left);
int h = Math.Min(height, rect.Bottom - rect.Top);
using Bitmap bmp = new(w, h, PixelFormat.Format32bppArgb);
using Graphics gDest = Graphics.FromImage(bmp);
IntPtr hdcDest = gDest.GetHdc();
IntPtr hdcSrc = GetWindowDC(hwnd);
BitBlt(hdcDest, 0, 0, w, h, hdcSrc, rect.Left, rect.Top, SRCCOPY);
gDest.ReleaseHdc(hdcDest);
ReleaseDC(hwnd, hdcSrc);
return ComputeAverageColor(bmp);
}
private static Color ComputeAverageColor(Bitmap bmp)
{
long r = 0,
g = 0,
b = 0;
int count = 0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color pixel = bmp.GetPixel(x, y);
r += pixel.R;
g += pixel.G;
b += pixel.B;
count++;
}
}
if (count == 0)
return Color.Transparent;
return Color.FromArgb((int)(r / count), (int)(g / count), (int)(b / count));
}
#region Win32 Imports & Structs
private const int SRCCOPY = 0x00CC0020;
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT Point);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
IntPtr hdcSrc,
int nXSrc,
int nYSrc,
int dwRop
);
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#endregion
}
}

View File

@@ -1,17 +1,12 @@
using System;
using System.Linq;
using System.Numerics;
using System.Text;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.Settings;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Windows.Graphics.Imaging;
@@ -120,8 +115,10 @@ namespace BetterLyrics.WinUI3.Rendering
float opacity
)
{
float imageWidth = (float)(softwareBitmap.PixelWidth * 96f / control.Dpi);
float imageHeight = (float)(softwareBitmap.PixelHeight * 96f / control.Dpi);
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap);
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
var scaleFactor =
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
/ Math.Min(imageWidth, imageHeight);
@@ -134,7 +131,7 @@ namespace BetterLyrics.WinUI3.Rendering
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
BorderMode = EffectBorderMode.Hard,
Scale = new Vector2(scaleFactor),
Source = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap),
Source = canvasBitmap,
},
Opacity = opacity,
},

View File

@@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services.Settings
namespace BetterLyrics.WinUI3.Services.Settings
{
public static class SettingsDefaultValues
{
@@ -36,7 +29,7 @@ namespace BetterLyrics.WinUI3.Services.Settings
// Lyrics
public const int InAppLyricsAlignmentType = 1; // Center
public const int InAppLyricsBlurAmount = 0;
public const int InAppLyricsVerticalEdgeOpacity = 0; // 0.0
public const int InAppLyricsVerticalEdgeOpacity = 0; // 0 % = 0.0
public const float InAppLyricsLineSpacingFactor = 0.5f;
public const int InAppLyricsFontSize = 28;
public const bool IsInAppLyricsGlowEffectEnabled = false;
@@ -46,9 +39,9 @@ namespace BetterLyrics.WinUI3.Services.Settings
public const int DesktopLyricsAlignmentType = 1; // Center
public const int DesktopLyricsBlurAmount = 0;
public const int DesktopLyricsVerticalEdgeOpacity = 0; // 0.0
public const int DesktopLyricsVerticalEdgeOpacity = 0; // 0 % = 0.0
public const float DesktopLyricsLineSpacingFactor = 0.5f;
public const int DesktopLyricsFontSize = 28;
public const int DesktopLyricsFontSize = 24;
public const bool IsDesktopLyricsGlowEffectEnabled = false;
public const bool IsDesktopLyricsDynamicGlowEffectEnabled = false;
public const int DesktopLyricsFontColorType = 0; // Default

View File

@@ -393,4 +393,7 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>Exit picture-in-picture mode</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>Lyrics not found</value>
</data>
</root>

View File

@@ -393,4 +393,7 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出画中画模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>未找到歌词</value>
</data>
</root>

View File

@@ -393,4 +393,7 @@
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
<value>退出畫中畫模式</value>
</data>
<data name="MainPageLyricsNotFound.Text" xml:space="preserve">
<value>找不到歌詞</value>
</data>
</root>

View File

@@ -1,4 +1,6 @@
using System.Diagnostics;
using System.Drawing;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Rendering;
@@ -10,6 +12,8 @@ using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using WinRT.Interop;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -35,8 +39,6 @@ namespace BetterLyrics.WinUI3.Views
{
this.InitializeComponent();
Debug.WriteLine("hashcode for DesktopLyricsRenderer: " + _lyricsRenderer.GetHashCode());
WeakReferenceMessenger.Default.Register<
DesktopLyricsPage,
DesktopLyricsRelayoutRequestedMessage

View File

@@ -62,7 +62,7 @@
FontFamily="Segoe Fluent Icons"
FontSize="{StaticResource DisplayTextBlockFontSize}"
Glyph="&#xE90B;" />
<TextBlock FontSize="{StaticResource TitleTextBlockFontSize}" Text="Lyrics not found" />
<TextBlock x:Uid="MainPageLyricsNotFound" FontSize="{StaticResource TitleTextBlockFontSize}" />
</StackPanel>
</Grid>

View File

@@ -1,5 +1,7 @@
using System;
using System.Diagnostics;
using System.Drawing;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Messages;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Rendering;
@@ -8,9 +10,13 @@ using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using WinRT;
using WinRT.Interop;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
using DispatcherQueuePriority = Microsoft.UI.Dispatching.DispatcherQueuePriority;
@@ -65,10 +71,7 @@ namespace BetterLyrics.WinUI3.Views
}
);
WeakReferenceMessenger.Default.Register<
MainPage,
DesktopLyricsRelayoutRequestedMessage
>(
WeakReferenceMessenger.Default.Register<MainPage, InAppLyricsRelayoutRequestedMessage>(
this,
async (r, m) =>
{
@@ -218,21 +221,44 @@ namespace BetterLyrics.WinUI3.Views
private void DesktopLyricsToggleButton_Checked(object sender, RoutedEventArgs e)
{
if (App.Current.OverlayWindow is null)
{
var overlayWindow = new OverlayWindow();
overlayWindow.Navigate(typeof(DesktopLyricsPage));
App.Current.OverlayWindow = overlayWindow;
}
TransparentAppBarHelper.Enable(App.Current.OverlayWindow!, 48);
var overlayAppWindow = App.Current.OverlayWindow!.AppWindow;
overlayAppWindow.Show();
Color color = WindowColorHelper.GetDominantColorBelow(
WindowNative.GetWindowHandle(App.Current.OverlayWindow!)
);
var config = new SystemBackdropConfiguration
{
IsInputActive = true,
Theme = SystemBackdropTheme.Default,
};
var micaController = new MicaController();
micaController.TintColor = Windows.UI.Color.FromArgb(
color.A,
color.R,
color.G,
color.B
); // 指定自定义颜色
micaController.TintOpacity = 0.7f;
micaController.FallbackColor = Colors.Black;
// 配置 Backdrop假设你已经满足系统要求
micaController.AddSystemBackdropTarget(
(
App.Current.OverlayWindow!
).As<Microsoft.UI.Composition.ICompositionSupportsSystemBackdrop>()
);
micaController.SetSystemBackdropConfiguration(config);
}
private void DesktopLyricsToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
var overlayAppWindow = App.Current.OverlayWindow!.AppWindow;
overlayAppWindow.Hide();
TransparentAppBarHelper.Disable(App.Current.OverlayWindow);
}
private async void LyricsCanvas_Loaded(object sender, RoutedEventArgs e)

View File

@@ -24,22 +24,26 @@ namespace BetterLyrics.WinUI3.Views
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
// Hide border
this.SetWindowStyle(WindowStyle.Popup | WindowStyle.Visible);
this.SetWindowStyle(WindowStyle.Popup);
//this.SetExtendedWindowStyle(
// ExtendedWindowStyle.Layered | ExtendedWindowStyle.Transparent
//);
// Hide from taskbar and alt-tab
this.SetIsShownInSwitchers(false);
// Transparent window
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.Transparent);
// SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(BackdropType.DesktopAcrylic);
// Stretch to screen width
this.CenterOnScreen();
var screenWidth = AppWindow.Position.X * 2 + AppWindow.Size.Width;
AppWindow.Move(new Windows.Graphics.PointInt32(0, 0));
AppWindow.Resize(new Windows.Graphics.SizeInt32(screenWidth, 72));
//this.CenterOnScreen();
//var screenWidth = AppWindow.Position.X * 2 + AppWindow.Size.Width;
//AppWindow.Move(new Windows.Graphics.PointInt32(0, 0));
//AppWindow.Resize(new Windows.Graphics.SizeInt32(screenWidth, 72));
// Always on top
((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
// ((OverlappedPresenter)AppWindow.Presenter).IsAlwaysOnTop = true;
}
public void Navigate(Type type)