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