From 3f6efe2e247c9f956a5906d511e02f60513d86c7 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Tue, 6 Feb 2024 17:31:19 +0800 Subject: [PATCH] custom background image --- .../Snap.Hutao/Control/Media/Bgra32.cs | 17 +++++ .../Control/Media/SoftwareBitmapExtension.cs | 39 +++++++++++ .../Core/RuntimeOptionsExtension.cs | 5 ++ .../Snap.Hutao/Resource/Localization/SH.resx | 6 ++ .../BackgroundImage/BackgroundImage.cs | 21 ++++++ .../BackgroundImage/BackgroundImageService.cs | 66 +++++++++++++++++++ .../IBackgroundImageService.cs | 10 +++ src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj | 2 +- src/Snap.Hutao/Snap.Hutao/View/MainView.xaml | 11 ++-- .../Snap.Hutao/View/MainView.xaml.cs | 37 +++++++++++ .../Snap.Hutao/View/Page/SettingPage.xaml | 9 ++- .../ViewModel/Setting/SettingViewModel.cs | 6 ++ src/Snap.Hutao/Snap.Hutao/Web/ApiEndpoints.cs | 8 ++- src/Snap.Hutao/Snap.Hutao/app.manifest | 11 ++-- 14 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImage.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs index eda7c14c..7fb629c8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/Bgra32.cs @@ -33,6 +33,16 @@ internal struct Bgra32 /// public byte A; + public Bgra32(byte b, byte g, byte r, byte a) + { + B = b; + G = g; + R = r; + A = a; + } + + public readonly double Luminance { get => ((0.299 * R) + (0.587 * G) + (0.114 * B)) / 255; } + /// /// 从 Color 转换 /// @@ -44,4 +54,11 @@ internal struct Bgra32 *(uint*)&bgra8 = BinaryPrimitives.ReverseEndianness(*(uint*)&color); return bgra8; } + + public static unsafe implicit operator Color(Bgra32 bgra8) + { + Unsafe.SkipInit(out Color color); + *(uint*)&color = BinaryPrimitives.ReverseEndianness(*(uint*)&bgra8); + return color; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Media/SoftwareBitmapExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Media/SoftwareBitmapExtension.cs index 214d8028..1b2dd428 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Media/SoftwareBitmapExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Media/SoftwareBitmapExtension.cs @@ -38,4 +38,43 @@ internal static class SoftwareBitmapExtension } } } + + public static unsafe double Luminance(this SoftwareBitmap softwareBitmap) + { + using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read)) + { + using (IMemoryBufferReference reference = buffer.CreateReference()) + { + reference.As().GetBuffer(out Span bytes); + double sum = 0; + foreach (ref readonly Bgra32 pixel in bytes) + { + sum += pixel.Luminance; + } + + return sum / bytes.Length; + } + } + } + + public static unsafe Bgra32 GetAccentColor(this SoftwareBitmap softwareBitmap) + { + using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read)) + { + using (IMemoryBufferReference reference = buffer.CreateReference()) + { + reference.As().GetBuffer(out Span bytes); + double b = 0, g = 0, r = 0, a = 0; + foreach (ref readonly Bgra32 pixel in bytes) + { + b += pixel.B; + g += pixel.G; + r += pixel.R; + a += pixel.A; + } + + return new((byte)(b / bytes.Length), (byte)(g / bytes.Length), (byte)(r / bytes.Length), (byte)(a / bytes.Length)); + } + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs index 4a206b0d..71fb4726 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/RuntimeOptionsExtension.cs @@ -18,4 +18,9 @@ internal static class RuntimeOptionsExtension { return Path.Combine(options.DataFolder, "ServerCache"); } + + public static string GetDataFolderBackgroundFolder(this RuntimeOptions options) + { + return Path.Combine(options.DataFolder, "Background"); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 99a8d0d9..845c5372 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -2528,6 +2528,12 @@ 前往官网 + + 自定义背景图片,支持 bmp / gif / ico / jpg / jpeg / png / tiff / webp 格式 + + + 打开背景图片文件夹 + 重置 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImage.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImage.cs new file mode 100644 index 00000000..e2e50aef --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImage.cs @@ -0,0 +1,21 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml.Media.Imaging; +using Snap.Hutao.Control.Media; +using Snap.Hutao.Core; +using System.IO; +using System.Runtime.InteropServices; +using Windows.Graphics.Imaging; +using Windows.UI; + +namespace Snap.Hutao.Service.BackgroundImage; + +internal sealed class BackgroundImage +{ + public BitmapImage ImageSource { get; set; } = default!; + + public Color AccentColor { get; set; } + + public double Luminance { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs new file mode 100644 index 00000000..074c2628 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/BackgroundImageService.cs @@ -0,0 +1,66 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Control.Media; +using Snap.Hutao.Core; +using System.IO; +using Windows.Graphics.Imaging; + +namespace Snap.Hutao.Service.BackgroundImage; + +[ConstructorGenerated] +[Injection(InjectAs.Singleton, typeof(IBackgroundImageService))] +internal sealed partial class BackgroundImageService : IBackgroundImageService +{ + private static readonly HashSet AllowedFormats = [".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".tiff", ".webp"]; + + private readonly ITaskContext taskContext; + private readonly RuntimeOptions runtimeOptions; + + private HashSet backgroundPathMap; + + public async ValueTask> GetNextBackgroundImageAsync() + { + HashSet backgroundSet = SkipOrInitBackground(); + + if (backgroundSet.Count <= 0) + { + return new(false, default!); + } + + string path = System.Random.Shared.GetItems(backgroundSet.ToArray(), 1)[0]; + backgroundSet.Remove(path); + using (FileStream fileStream = File.OpenRead(path)) + { + BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream.AsRandomAccessStream()); + SoftwareBitmap softwareBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight); + Bgra32 accentColor = softwareBitmap.GetAccentColor(); + + await taskContext.SwitchToMainThreadAsync(); + + BackgroundImage background = new() + { + ImageSource = new(path.ToUri()), + AccentColor = accentColor, + Luminance = accentColor.Luminance, + }; + + return new(true, background); + } + } + + private HashSet SkipOrInitBackground() + { + if (backgroundPathMap is null || backgroundPathMap.Count <= 0) + { + string backgroundFolder = runtimeOptions.GetDataFolderBackgroundFolder(); + Directory.CreateDirectory(backgroundFolder); + backgroundPathMap = Directory + .GetFiles(backgroundFolder, "*.*", SearchOption.AllDirectories) + .Where(path => AllowedFormats.Contains(Path.GetExtension(path))) + .ToHashSet(); + } + + return backgroundPathMap; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs new file mode 100644 index 00000000..36670b10 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/BackgroundImage/IBackgroundImageService.cs @@ -0,0 +1,10 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + + +namespace Snap.Hutao.Service.BackgroundImage; + +internal interface IBackgroundImageService +{ + ValueTask> GetNextBackgroundImageAsync(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index d09132b6..fc52d0c1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -321,7 +321,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml index 5f805db4..20339069 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml @@ -12,14 +12,15 @@ mc:Ignorable="d"> 0,44,0,0 + 0,1,0,0 24 - 0,1,0,0 - + - + \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs index 63ba33c4..942bb556 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs @@ -1,7 +1,9 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.Animations; using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Service.BackgroundImage; using Snap.Hutao.Service.Navigation; using Snap.Hutao.View.Page; @@ -14,6 +16,7 @@ namespace Snap.Hutao.View; internal sealed partial class MainView : UserControl { private readonly INavigationService navigationService; + private readonly IBackgroundImageService backgroundImageService; /// /// 构造一个新的主视图 @@ -24,6 +27,9 @@ internal sealed partial class MainView : UserControl IServiceProvider serviceProvider = Ioc.Default; + backgroundImageService = serviceProvider.GetRequiredService(); + RunBackgroundImageLoopAsync(serviceProvider.GetRequiredService()).SafeForget(); + navigationService = serviceProvider.GetRequiredService(); navigationService .As()? @@ -31,4 +37,35 @@ internal sealed partial class MainView : UserControl navigationService.Navigate(INavigationAwaiter.Default, true); } + + private async ValueTask RunBackgroundImageLoopAsync(ITaskContext taskContext) + { + using (PeriodicTimer timer = new(TimeSpan.FromMinutes(5))) + { + do + { + (bool isOk, BackgroundImage backgroundImage) = await backgroundImageService.GetNextBackgroundImageAsync().ConfigureAwait(false); + + if (isOk) + { + await taskContext.SwitchToMainThreadAsync(); + + await AnimationBuilder + .Create() + .Opacity(to: 0D, duration: TimeSpan.FromMilliseconds(300), easingType: EasingType.Sine) + .StartAsync(BackdroundImagePresenter) + .ConfigureAwait(true); + + BackdroundImagePresenter.Source = backgroundImage.ImageSource; + double targetOpacity = (1 - backgroundImage.Luminance) * 0.8; + + await AnimationBuilder + .Create() + .Opacity(to: targetOpacity, duration: TimeSpan.FromMilliseconds(300), easingType: EasingType.Sine) + .StartAsync(BackdroundImagePresenter) + .ConfigureAwait(true); + } + } while (await timer.WaitForNextTickAsync().ConfigureAwait(false)); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml index 5348c1c3..f6023be1 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/SettingPage.xaml @@ -455,7 +455,7 @@ - + - + + - - - PerMonitorV2 - + + + PerMonitorV2 + true + - + \ No newline at end of file