mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-13 03:34:55 +08:00
format c# code
This commit is contained in:
@@ -1,33 +1,34 @@
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.ApplicationModel.Resources;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Serilog.Core;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Windows.ApplicationModel.Core;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3 {
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
public partial class App : Application {
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly ILogger<App> _logger;
|
||||
|
||||
public static new App Current => (App)Application.Current;
|
||||
@@ -42,7 +43,8 @@ namespace BetterLyrics.WinUI3 {
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App() {
|
||||
public App()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
App.ResourceLoader = new ResourceLoader();
|
||||
@@ -55,8 +57,8 @@ namespace BetterLyrics.WinUI3 {
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
private static void ConfigureServices() {
|
||||
|
||||
private static void ConfigureServices()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.File(Helper.AppInfo.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
@@ -65,21 +67,27 @@ namespace BetterLyrics.WinUI3 {
|
||||
// Register services
|
||||
Ioc.Default.ConfigureServices(
|
||||
new ServiceCollection()
|
||||
.AddSingleton(DispatcherQueue.GetForCurrentThread())
|
||||
.AddLogging(loggingBuilder => {
|
||||
loggingBuilder.ClearProviders();
|
||||
loggingBuilder.AddSerilog();
|
||||
})
|
||||
// Services
|
||||
.AddSingleton<SettingsService>()
|
||||
.AddSingleton<DatabaseService>()
|
||||
// ViewModels
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.BuildServiceProvider());
|
||||
.AddSingleton(DispatcherQueue.GetForCurrentThread())
|
||||
.AddLogging(loggingBuilder =>
|
||||
{
|
||||
loggingBuilder.ClearProviders();
|
||||
loggingBuilder.AddSerilog();
|
||||
})
|
||||
// Services
|
||||
.AddSingleton<SettingsService>()
|
||||
.AddSingleton<DatabaseService>()
|
||||
// ViewModels
|
||||
.AddSingleton<MainViewModel>()
|
||||
.AddSingleton<SettingsViewModel>()
|
||||
.BuildServiceProvider()
|
||||
);
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) {
|
||||
private void App_UnhandledException(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.UnhandledExceptionEventArgs e
|
||||
)
|
||||
{
|
||||
_logger.LogError(e.Exception, "App_UnhandledException");
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -88,8 +96,8 @@ namespace BetterLyrics.WinUI3 {
|
||||
/// Invoked when the application is launched.
|
||||
/// </summary>
|
||||
/// <param name="args">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) {
|
||||
|
||||
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
||||
{
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
|
||||
// Activate the window
|
||||
@@ -97,6 +105,5 @@ namespace BetterLyrics.WinUI3 {
|
||||
MainWindow!.Navigate(typeof(MainPage));
|
||||
MainWindow.Activate();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="DevWinUI" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter {
|
||||
public class ColorToBrushConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, string language) {
|
||||
if (value is Color color) {
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class ColorToBrushConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Color color)
|
||||
{
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
return new SolidColorBrush();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter {
|
||||
internal class ThemeTypeToElementThemeConverter : IValueConverter {
|
||||
public object Convert(object value, Type targetType, object parameter, string language) {
|
||||
if (value is int themeType) {
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
internal class ThemeTypeToElementThemeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int themeType)
|
||||
{
|
||||
return (ElementTheme)themeType;
|
||||
}
|
||||
return ElementTheme.Default;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) {
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
|
||||
public static class AnimationHelper {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class AnimationHelper
|
||||
{
|
||||
public const int StackedNotificationsShowingDuration = 3900;
|
||||
public const int StoryboardDefaultDuration = 200;
|
||||
public const int DebounceDefaultDuration = 200;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,20 +4,24 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Storage;
|
||||
|
||||
public static class AppInfo {
|
||||
public static class AppInfo
|
||||
{
|
||||
// App Metadata
|
||||
public const string AppName = "BetterLyrics";
|
||||
public const string AppDisplayName = "Better Lyrics";
|
||||
public const string AppAuthor = "Zhe Fang";
|
||||
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public static string AppVersion {
|
||||
get {
|
||||
public static string AppVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = Package.Current.Id.Version;
|
||||
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
|
||||
}
|
||||
@@ -28,7 +32,7 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
#if DEBUG
|
||||
true;
|
||||
#else
|
||||
false;
|
||||
false;
|
||||
#endif
|
||||
|
||||
// Base Folders
|
||||
@@ -46,10 +50,10 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
|
||||
|
||||
public static void EnsureDirectories() {
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public static class CollectionHelper {
|
||||
public static T? SafeGet<T>(this IList<T> list, int index) {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class CollectionHelper
|
||||
{
|
||||
public static T? SafeGet<T>(this IList<T> list, int index)
|
||||
{
|
||||
if (list == null || index < 0 || index >= list.Count)
|
||||
return default;
|
||||
return list[index];
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class ColorHelper {
|
||||
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t) {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ColorHelper
|
||||
{
|
||||
public static Windows.UI.Color LerpColor(Windows.UI.Color a, Windows.UI.Color b, double t)
|
||||
{
|
||||
byte A = (byte)(a.A + (b.A - a.A) * t);
|
||||
byte R = (byte)(a.R + (b.R - a.R) * t);
|
||||
byte G = (byte)(a.G + (b.G - a.G) * t);
|
||||
byte B = (byte)(a.B + (b.B - a.B) * t);
|
||||
return Windows.UI.Color.FromArgb(A, R, G, B);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,51 +4,66 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Color map
|
||||
/// </summary>
|
||||
internal class CMap {
|
||||
internal class CMap
|
||||
{
|
||||
private readonly List<VBox> vboxes = new List<VBox>();
|
||||
private List<QuantizedColor>? palette;
|
||||
|
||||
public void Push(VBox box) {
|
||||
public void Push(VBox box)
|
||||
{
|
||||
palette = null;
|
||||
vboxes.Add(box);
|
||||
}
|
||||
|
||||
public List<QuantizedColor> GeneratePalette() {
|
||||
if (palette == null) {
|
||||
palette = (from vBox in vboxes
|
||||
let rgb = vBox.Avg(false)
|
||||
let color = FromRgb(rgb[0], rgb[1], rgb[2])
|
||||
select new QuantizedColor(color, vBox.Count(false))).ToList();
|
||||
public List<QuantizedColor> GeneratePalette()
|
||||
{
|
||||
if (palette == null)
|
||||
{
|
||||
palette = (
|
||||
from vBox in vboxes
|
||||
let rgb = vBox.Avg(false)
|
||||
let color = FromRgb(rgb[0], rgb[1], rgb[2])
|
||||
select new QuantizedColor(color, vBox.Count(false))
|
||||
).ToList();
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
public int Size() {
|
||||
public int Size()
|
||||
{
|
||||
return vboxes.Count;
|
||||
}
|
||||
|
||||
public int[]? Map(int[] color) {
|
||||
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color))) {
|
||||
public int[]? Map(int[] color)
|
||||
{
|
||||
foreach (var vbox in vboxes.Where(vbox => vbox.Contains(color)))
|
||||
{
|
||||
return vbox.Avg(false);
|
||||
}
|
||||
return Nearest(color);
|
||||
}
|
||||
|
||||
public int[]? Nearest(int[] color) {
|
||||
public int[]? Nearest(int[] color)
|
||||
{
|
||||
var d1 = double.MaxValue;
|
||||
int[]? pColor = null;
|
||||
|
||||
foreach (var t in vboxes) {
|
||||
foreach (var t in vboxes)
|
||||
{
|
||||
var vbColor = t.Avg(false);
|
||||
var d2 = Math.Sqrt(Math.Pow(color[0] - vbColor[0], 2)
|
||||
+ Math.Pow(color[1] - vbColor[1], 2)
|
||||
+ Math.Pow(color[2] - vbColor[2], 2));
|
||||
if (d2 < d1) {
|
||||
var d2 = Math.Sqrt(
|
||||
Math.Pow(color[0] - vbColor[0], 2)
|
||||
+ Math.Pow(color[1] - vbColor[1], 2)
|
||||
+ Math.Pow(color[2] - vbColor[2], 2)
|
||||
);
|
||||
if (d2 < d1)
|
||||
{
|
||||
d1 = d2;
|
||||
pColor = vbColor;
|
||||
}
|
||||
@@ -56,23 +71,44 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return pColor;
|
||||
}
|
||||
|
||||
public VBox FindColor(double targetLuma, double minLuma, double maxLuma, double targetSaturation, double minSaturation, double maxSaturation) {
|
||||
public VBox FindColor(
|
||||
double targetLuma,
|
||||
double minLuma,
|
||||
double maxLuma,
|
||||
double targetSaturation,
|
||||
double minSaturation,
|
||||
double maxSaturation
|
||||
)
|
||||
{
|
||||
VBox? max = null;
|
||||
double maxValue = 0;
|
||||
var highestPopulation = vboxes.Select(p => p.Count(false)).Max();
|
||||
|
||||
foreach (var swatch in vboxes) {
|
||||
foreach (var swatch in vboxes)
|
||||
{
|
||||
var avg = swatch.Avg(false);
|
||||
var hsl = FromRgb(avg[0], avg[1], avg[2]).ToHsl();
|
||||
var sat = hsl.S;
|
||||
var luma = hsl.L;
|
||||
|
||||
if (sat >= minSaturation && sat <= maxSaturation &&
|
||||
luma >= minLuma && luma <= maxLuma) {
|
||||
var thisValue = Mmcq.CreateComparisonValue(sat, targetSaturation, luma, targetLuma,
|
||||
swatch.Count(false), highestPopulation);
|
||||
if (
|
||||
sat >= minSaturation
|
||||
&& sat <= maxSaturation
|
||||
&& luma >= minLuma
|
||||
&& luma <= maxLuma
|
||||
)
|
||||
{
|
||||
var thisValue = Mmcq.CreateComparisonValue(
|
||||
sat,
|
||||
targetSaturation,
|
||||
luma,
|
||||
targetLuma,
|
||||
swatch.Count(false),
|
||||
highestPopulation
|
||||
);
|
||||
|
||||
if (max == null || thisValue > maxValue) {
|
||||
if (max == null || thisValue > maxValue)
|
||||
{
|
||||
max = swatch;
|
||||
maxValue = thisValue;
|
||||
}
|
||||
@@ -82,12 +118,14 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return max;
|
||||
}
|
||||
|
||||
public Color FromRgb(int red, int green, int blue) {
|
||||
var color = new Color {
|
||||
public Color FromRgb(int red, int green, int blue)
|
||||
{
|
||||
var color = new Color
|
||||
{
|
||||
A = 255,
|
||||
R = (byte)red,
|
||||
G = (byte)green,
|
||||
B = (byte)blue
|
||||
B = (byte)blue,
|
||||
};
|
||||
|
||||
return color;
|
||||
@@ -97,7 +135,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <summary>
|
||||
/// Defines a color in RGB space.
|
||||
/// </summary>
|
||||
public struct Color {
|
||||
public struct Color
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or Set the Alpha component value for sRGB.
|
||||
/// </summary>
|
||||
@@ -122,7 +161,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// Get HSL color.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public HslColor ToHsl() {
|
||||
public HslColor ToHsl()
|
||||
{
|
||||
const double toDouble = 1.0 / 255;
|
||||
var r = toDouble * R;
|
||||
var g = toDouble * G;
|
||||
@@ -133,14 +173,20 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
double h1;
|
||||
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
if (chroma == 0) {
|
||||
if (chroma == 0)
|
||||
{
|
||||
h1 = 0;
|
||||
} else if (max == r) {
|
||||
}
|
||||
else if (max == r)
|
||||
{
|
||||
h1 = (g - b) / chroma % 6;
|
||||
} else if (max == g) {
|
||||
}
|
||||
else if (max == g)
|
||||
{
|
||||
h1 = 2 + (b - r) / chroma;
|
||||
} else //if (max == b)
|
||||
{
|
||||
}
|
||||
else //if (max == b)
|
||||
{
|
||||
h1 = 4 + (r - g) / chroma;
|
||||
}
|
||||
|
||||
@@ -155,16 +201,20 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
// ReSharper restore CompareOfFloatsByEqualityOperator
|
||||
}
|
||||
|
||||
public string ToHexString() {
|
||||
public string ToHexString()
|
||||
{
|
||||
return "#" + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
|
||||
}
|
||||
|
||||
public string ToHexAlphaString() {
|
||||
public string ToHexAlphaString()
|
||||
{
|
||||
return "#" + A.ToString("X2") + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
if (A == 255) {
|
||||
public override string ToString()
|
||||
{
|
||||
if (A == 255)
|
||||
{
|
||||
return ToHexString();
|
||||
}
|
||||
|
||||
@@ -175,7 +225,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <summary>
|
||||
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
|
||||
/// </summary>
|
||||
public struct HslColor {
|
||||
public struct HslColor
|
||||
{
|
||||
/// <summary>
|
||||
/// The Alpha/opacity in 0..1 range.
|
||||
/// </summary>
|
||||
@@ -197,7 +248,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
public double S;
|
||||
}
|
||||
|
||||
internal static class Mmcq {
|
||||
internal static class Mmcq
|
||||
{
|
||||
public const int Sigbits = 5;
|
||||
public const int Rshift = 8 - Sigbits;
|
||||
public const int Mult = 1 << Rshift;
|
||||
@@ -211,7 +263,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
private static readonly VBoxComparer ComparatorProduct = new VBoxComparer();
|
||||
private static readonly VBoxCountComparer ComparatorCount = new VBoxCountComparer();
|
||||
|
||||
public static int GetColorIndex(int r, int g, int b) {
|
||||
public static int GetColorIndex(int r, int g, int b)
|
||||
{
|
||||
return (r << (2 * Sigbits)) + (g << Sigbits) + b;
|
||||
}
|
||||
|
||||
@@ -220,10 +273,12 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// </summary>
|
||||
/// <param name="pixels">The pixels.</param>
|
||||
/// <returns>Histo (1-d array, giving the number of pixels in each quantized region of color space), or null on error.</returns>
|
||||
private static int[] GetHisto(IEnumerable<byte[]> pixels) {
|
||||
private static int[] GetHisto(IEnumerable<byte[]> pixels)
|
||||
{
|
||||
var histo = new int[Histosize];
|
||||
|
||||
foreach (var pixel in pixels) {
|
||||
foreach (var pixel in pixels)
|
||||
{
|
||||
var rval = pixel[0] >> Rshift;
|
||||
var gval = pixel[1] >> Rshift;
|
||||
var bval = pixel[2] >> Rshift;
|
||||
@@ -233,34 +288,48 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return histo;
|
||||
}
|
||||
|
||||
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo) {
|
||||
int rmin = 1000000, rmax = 0;
|
||||
int gmin = 1000000, gmax = 0;
|
||||
int bmin = 1000000, bmax = 0;
|
||||
private static VBox VboxFromPixels(IList<byte[]> pixels, int[] histo)
|
||||
{
|
||||
int rmin = 1000000,
|
||||
rmax = 0;
|
||||
int gmin = 1000000,
|
||||
gmax = 0;
|
||||
int bmin = 1000000,
|
||||
bmax = 0;
|
||||
|
||||
// find min/max
|
||||
var numPixels = pixels.Count;
|
||||
for (var i = 0; i < numPixels; i++) {
|
||||
for (var i = 0; i < numPixels; i++)
|
||||
{
|
||||
var pixel = pixels[i];
|
||||
var rval = pixel[0] >> Rshift;
|
||||
var gval = pixel[1] >> Rshift;
|
||||
var bval = pixel[2] >> Rshift;
|
||||
|
||||
if (rval < rmin) {
|
||||
if (rval < rmin)
|
||||
{
|
||||
rmin = rval;
|
||||
} else if (rval > rmax) {
|
||||
}
|
||||
else if (rval > rmax)
|
||||
{
|
||||
rmax = rval;
|
||||
}
|
||||
|
||||
if (gval < gmin) {
|
||||
if (gval < gmin)
|
||||
{
|
||||
gmin = gval;
|
||||
} else if (gval > gmax) {
|
||||
}
|
||||
else if (gval > gmax)
|
||||
{
|
||||
gmax = gval;
|
||||
}
|
||||
|
||||
if (bval < bmin) {
|
||||
if (bval < bmin)
|
||||
{
|
||||
bmin = bval;
|
||||
} else if (bval > bmax) {
|
||||
}
|
||||
else if (bval > bmax)
|
||||
{
|
||||
bmax = bval;
|
||||
}
|
||||
}
|
||||
@@ -268,11 +337,19 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
|
||||
}
|
||||
|
||||
private static VBox[] DoCut(char color, VBox vbox, IList<int> partialsum, IList<int> lookaheadsum, int total) {
|
||||
private static VBox[] DoCut(
|
||||
char color,
|
||||
VBox vbox,
|
||||
IList<int> partialsum,
|
||||
IList<int> lookaheadsum,
|
||||
int total
|
||||
)
|
||||
{
|
||||
int vboxDim1;
|
||||
int vboxDim2;
|
||||
|
||||
switch (color) {
|
||||
switch (color)
|
||||
{
|
||||
case 'r':
|
||||
vboxDim1 = vbox.R1;
|
||||
vboxDim2 = vbox.R2;
|
||||
@@ -287,29 +364,35 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = vboxDim1; i <= vboxDim2; i++) {
|
||||
if (partialsum[i] > total / 2) {
|
||||
for (var i = vboxDim1; i <= vboxDim2; i++)
|
||||
{
|
||||
if (partialsum[i] > total / 2)
|
||||
{
|
||||
var vbox1 = vbox.Clone();
|
||||
var vbox2 = vbox.Clone();
|
||||
|
||||
var left = i - vboxDim1;
|
||||
var right = vboxDim2 - i;
|
||||
|
||||
var d2 = left <= right
|
||||
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
|
||||
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
|
||||
var d2 =
|
||||
left <= right
|
||||
? Math.Min(vboxDim2 - 1, Math.Abs(i + right / 2))
|
||||
: Math.Max(vboxDim1, Math.Abs(Convert.ToInt32(i - 1 - left / 2.0)));
|
||||
|
||||
// avoid 0-count boxes
|
||||
while (d2 < 0 || partialsum[d2] <= 0) {
|
||||
while (d2 < 0 || partialsum[d2] <= 0)
|
||||
{
|
||||
d2++;
|
||||
}
|
||||
var count2 = lookaheadsum[d2];
|
||||
while (count2 == 0 && d2 > 0 && partialsum[d2 - 1] > 0) {
|
||||
while (count2 == 0 && d2 > 0 && partialsum[d2 - 1] > 0)
|
||||
{
|
||||
count2 = lookaheadsum[--d2];
|
||||
}
|
||||
|
||||
// set dimensions
|
||||
switch (color) {
|
||||
switch (color)
|
||||
{
|
||||
case 'r':
|
||||
vbox1.R2 = d2;
|
||||
vbox2.R1 = d2 + 1;
|
||||
@@ -331,11 +414,14 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
throw new Exception("VBox can't be cut");
|
||||
}
|
||||
|
||||
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox) {
|
||||
if (vbox.Count(false) == 0) {
|
||||
private static VBox?[]? MedianCutApply(IList<int> histo, VBox vbox)
|
||||
{
|
||||
if (vbox.Count(false) == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (vbox.Count(false) == 1) {
|
||||
if (vbox.Count(false) == 1)
|
||||
{
|
||||
return [vbox.Clone(), null];
|
||||
}
|
||||
|
||||
@@ -350,23 +436,33 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
var total = 0;
|
||||
var partialsum = new int[VboxLength];
|
||||
// -1 = not set / 0 = 0
|
||||
for (var l = 0; l < partialsum.Length; l++) {
|
||||
for (var l = 0; l < partialsum.Length; l++)
|
||||
{
|
||||
partialsum[l] = -1;
|
||||
}
|
||||
|
||||
// -1 = not set / 0 = 0
|
||||
var lookaheadsum = new int[VboxLength];
|
||||
for (var l = 0; l < lookaheadsum.Length; l++) {
|
||||
for (var l = 0; l < lookaheadsum.Length; l++)
|
||||
{
|
||||
lookaheadsum[l] = -1;
|
||||
}
|
||||
|
||||
int i, j, k, sum, index;
|
||||
int i,
|
||||
j,
|
||||
k,
|
||||
sum,
|
||||
index;
|
||||
|
||||
if (maxw == rw) {
|
||||
for (i = vbox.R1; i <= vbox.R2; i++) {
|
||||
if (maxw == rw)
|
||||
{
|
||||
for (i = vbox.R1; i <= vbox.R2; i++)
|
||||
{
|
||||
sum = 0;
|
||||
for (j = vbox.G1; j <= vbox.G2; j++) {
|
||||
for (k = vbox.B1; k <= vbox.B2; k++) {
|
||||
for (j = vbox.G1; j <= vbox.G2; j++)
|
||||
{
|
||||
for (k = vbox.B1; k <= vbox.B2; k++)
|
||||
{
|
||||
index = GetColorIndex(i, j, k);
|
||||
sum += histo[index];
|
||||
}
|
||||
@@ -374,11 +470,16 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
total += sum;
|
||||
partialsum[i] = total;
|
||||
}
|
||||
} else if (maxw == gw) {
|
||||
for (i = vbox.G1; i <= vbox.G2; i++) {
|
||||
}
|
||||
else if (maxw == gw)
|
||||
{
|
||||
for (i = vbox.G1; i <= vbox.G2; i++)
|
||||
{
|
||||
sum = 0;
|
||||
for (j = vbox.R1; j <= vbox.R2; j++) {
|
||||
for (k = vbox.B1; k <= vbox.B2; k++) {
|
||||
for (j = vbox.R1; j <= vbox.R2; j++)
|
||||
{
|
||||
for (k = vbox.B1; k <= vbox.B2; k++)
|
||||
{
|
||||
index = GetColorIndex(j, i, k);
|
||||
sum += histo[index];
|
||||
}
|
||||
@@ -386,12 +487,16 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
total += sum;
|
||||
partialsum[i] = total;
|
||||
}
|
||||
} else /* maxw == bw */
|
||||
{
|
||||
for (i = vbox.B1; i <= vbox.B2; i++) {
|
||||
}
|
||||
else /* maxw == bw */
|
||||
{
|
||||
for (i = vbox.B1; i <= vbox.B2; i++)
|
||||
{
|
||||
sum = 0;
|
||||
for (j = vbox.R1; j <= vbox.R2; j++) {
|
||||
for (k = vbox.G1; k <= vbox.G2; k++) {
|
||||
for (j = vbox.R1; j <= vbox.R2; j++)
|
||||
{
|
||||
for (k = vbox.G1; k <= vbox.G2; k++)
|
||||
{
|
||||
index = GetColorIndex(j, k, i);
|
||||
sum += histo[index];
|
||||
}
|
||||
@@ -401,15 +506,18 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < VboxLength; i++) {
|
||||
if (partialsum[i] != -1) {
|
||||
for (i = 0; i < VboxLength; i++)
|
||||
{
|
||||
if (partialsum[i] != -1)
|
||||
{
|
||||
lookaheadsum[i] = total - partialsum[i];
|
||||
}
|
||||
}
|
||||
|
||||
// determine the cut planes
|
||||
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total) : maxw == gw
|
||||
? DoCut('g', vbox, partialsum, lookaheadsum, total) : DoCut('b', vbox, partialsum, lookaheadsum, total);
|
||||
return maxw == rw ? DoCut('r', vbox, partialsum, lookaheadsum, total)
|
||||
: maxw == gw ? DoCut('g', vbox, partialsum, lookaheadsum, total)
|
||||
: DoCut('b', vbox, partialsum, lookaheadsum, total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -420,13 +528,21 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="histo">The histo.</param>
|
||||
/// <exception cref="System.Exception">vbox1 not defined; shouldn't happen!</exception>
|
||||
private static void Iter(List<VBox> lh, IComparer<VBox> comparator, int target, IList<int> histo) {
|
||||
private static void Iter(
|
||||
List<VBox> lh,
|
||||
IComparer<VBox> comparator,
|
||||
int target,
|
||||
IList<int> histo
|
||||
)
|
||||
{
|
||||
var ncolors = 1;
|
||||
var niters = 0;
|
||||
|
||||
while (niters < MaxIterations) {
|
||||
while (niters < MaxIterations)
|
||||
{
|
||||
var vbox = lh[lh.Count - 1];
|
||||
if (vbox.Count(false) == 0) {
|
||||
if (vbox.Count(false) == 0)
|
||||
{
|
||||
lh.Sort(comparator);
|
||||
niters++;
|
||||
continue;
|
||||
@@ -439,30 +555,35 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
var vbox1 = vboxes?[0];
|
||||
var vbox2 = vboxes?[1];
|
||||
|
||||
if (vbox1 == null) {
|
||||
throw new Exception(
|
||||
"vbox1 not defined; shouldn't happen!");
|
||||
if (vbox1 == null)
|
||||
{
|
||||
throw new Exception("vbox1 not defined; shouldn't happen!");
|
||||
}
|
||||
|
||||
lh.Add(vbox1);
|
||||
if (vbox2 != null) {
|
||||
if (vbox2 != null)
|
||||
{
|
||||
lh.Add(vbox2);
|
||||
ncolors++;
|
||||
}
|
||||
lh.Sort(comparator);
|
||||
|
||||
if (ncolors >= target) {
|
||||
if (ncolors >= target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (niters++ > MaxIterations) {
|
||||
if (niters++ > MaxIterations)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CMap Quantize(byte[][] pixels, int maxcolors) {
|
||||
public static CMap Quantize(byte[][] pixels, int maxcolors)
|
||||
{
|
||||
// short-circuit
|
||||
if (pixels.Length == 0 || maxcolors < 2 || maxcolors > 256) {
|
||||
if (pixels.Length == 0 || maxcolors < 2 || maxcolors > 256)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -490,24 +611,40 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
|
||||
// calculate the actual colors
|
||||
var cmap = new CMap();
|
||||
foreach (var vb in pq) {
|
||||
foreach (var vb in pq)
|
||||
{
|
||||
cmap.Push(vb);
|
||||
}
|
||||
|
||||
return cmap;
|
||||
}
|
||||
|
||||
public static double CreateComparisonValue(double saturation, double targetSaturation, double luma, double targetLuma, int population, int highestPopulation) {
|
||||
return WeightedMean(InvertDiff(saturation, targetSaturation), WeightSaturation,
|
||||
InvertDiff(luma, targetLuma), WeightLuma,
|
||||
population / (double)highestPopulation, WeightPopulation);
|
||||
public static double CreateComparisonValue(
|
||||
double saturation,
|
||||
double targetSaturation,
|
||||
double luma,
|
||||
double targetLuma,
|
||||
int population,
|
||||
int highestPopulation
|
||||
)
|
||||
{
|
||||
return WeightedMean(
|
||||
InvertDiff(saturation, targetSaturation),
|
||||
WeightSaturation,
|
||||
InvertDiff(luma, targetLuma),
|
||||
WeightLuma,
|
||||
population / (double)highestPopulation,
|
||||
WeightPopulation
|
||||
);
|
||||
}
|
||||
|
||||
private static double WeightedMean(params double[] values) {
|
||||
private static double WeightedMean(params double[] values)
|
||||
{
|
||||
double sum = 0;
|
||||
double sumWeight = 0;
|
||||
|
||||
for (var i = 0; i < values.Length; i += 2) {
|
||||
for (var i = 0; i < values.Length; i += 2)
|
||||
{
|
||||
var value = values[i];
|
||||
var weight = values[i + 1];
|
||||
|
||||
@@ -518,13 +655,16 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return sum / sumWeight;
|
||||
}
|
||||
|
||||
private static double InvertDiff(double value, double targetValue) {
|
||||
private static double InvertDiff(double value, double targetValue)
|
||||
{
|
||||
return 1 - Math.Abs(value - targetValue);
|
||||
}
|
||||
}
|
||||
|
||||
public class QuantizedColor {
|
||||
public QuantizedColor(Color color, int population) {
|
||||
public class QuantizedColor
|
||||
{
|
||||
public QuantizedColor(Color color, int population)
|
||||
{
|
||||
Color = color;
|
||||
Population = population;
|
||||
IsDark = CalculateYiqLuma(color) < 128;
|
||||
@@ -534,15 +674,19 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
public int Population { get; private set; }
|
||||
public bool IsDark { get; private set; }
|
||||
|
||||
public int CalculateYiqLuma(Color color) {
|
||||
return Convert.ToInt32(Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f));
|
||||
public int CalculateYiqLuma(Color color)
|
||||
{
|
||||
return Convert.ToInt32(
|
||||
Math.Round((299 * color.R + 587 * color.G + 114 * color.B) / 1000f)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3D color space box.
|
||||
/// </summary>
|
||||
internal class VBox {
|
||||
internal class VBox
|
||||
{
|
||||
private readonly int[] histo;
|
||||
private int[] avg;
|
||||
public int B1;
|
||||
@@ -554,7 +698,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
public int R2;
|
||||
private int? volume;
|
||||
|
||||
public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo) {
|
||||
public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo)
|
||||
{
|
||||
R1 = r1;
|
||||
R2 = r2;
|
||||
G1 = g1;
|
||||
@@ -565,24 +710,31 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
this.histo = histo;
|
||||
}
|
||||
|
||||
public int Volume(bool force) {
|
||||
if (volume == null || force) {
|
||||
public int Volume(bool force)
|
||||
{
|
||||
if (volume == null || force)
|
||||
{
|
||||
volume = (R2 - R1 + 1) * (G2 - G1 + 1) * (B2 - B1 + 1);
|
||||
}
|
||||
|
||||
return volume.Value;
|
||||
}
|
||||
|
||||
public int Count(bool force) {
|
||||
if (count == null || force) {
|
||||
public int Count(bool force)
|
||||
{
|
||||
if (count == null || force)
|
||||
{
|
||||
var npix = 0;
|
||||
int i;
|
||||
|
||||
for (i = R1; i <= R2; i++) {
|
||||
for (i = R1; i <= R2; i++)
|
||||
{
|
||||
int j;
|
||||
for (j = G1; j <= G2; j++) {
|
||||
for (j = G1; j <= G2; j++)
|
||||
{
|
||||
int k;
|
||||
for (k = B1; k <= B2; k++) {
|
||||
for (k = B1; k <= B2; k++)
|
||||
{
|
||||
var index = Mmcq.GetColorIndex(i, j, k);
|
||||
npix += histo[index];
|
||||
}
|
||||
@@ -595,12 +747,15 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return count.Value;
|
||||
}
|
||||
|
||||
public VBox Clone() {
|
||||
public VBox Clone()
|
||||
{
|
||||
return new VBox(R1, R2, G1, G2, B1, B2, histo);
|
||||
}
|
||||
|
||||
public int[] Avg(bool force) {
|
||||
if (avg == null || force) {
|
||||
public int[] Avg(bool force)
|
||||
{
|
||||
if (avg == null || force)
|
||||
{
|
||||
var ntot = 0;
|
||||
|
||||
var rsum = 0;
|
||||
@@ -609,11 +764,14 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
|
||||
int i;
|
||||
|
||||
for (i = R1; i <= R2; i++) {
|
||||
for (i = R1; i <= R2; i++)
|
||||
{
|
||||
int j;
|
||||
for (j = G1; j <= G2; j++) {
|
||||
for (j = G1; j <= G2; j++)
|
||||
{
|
||||
int k;
|
||||
for (k = B1; k <= B2; k++) {
|
||||
for (k = B1; k <= B2; k++)
|
||||
{
|
||||
var histoindex = Mmcq.GetColorIndex(i, j, k);
|
||||
var hval = histo[histoindex];
|
||||
ntot += hval;
|
||||
@@ -624,18 +782,22 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
}
|
||||
}
|
||||
|
||||
if (ntot > 0) {
|
||||
if (ntot > 0)
|
||||
{
|
||||
avg = new[]
|
||||
{
|
||||
Math.Abs(rsum / ntot), Math.Abs(gsum / ntot),
|
||||
Math.Abs(bsum / ntot)
|
||||
Math.Abs(rsum / ntot),
|
||||
Math.Abs(gsum / ntot),
|
||||
Math.Abs(bsum / ntot),
|
||||
};
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
avg = new[]
|
||||
{
|
||||
Math.Abs(Mmcq.Mult * (R1 + R2 + 1) / 2),
|
||||
Math.Abs(Mmcq.Mult * (G1 + G2 + 1) / 2),
|
||||
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2)
|
||||
Math.Abs(Mmcq.Mult * (B1 + B2 + 1) / 2),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -643,7 +805,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return avg;
|
||||
}
|
||||
|
||||
public bool Contains(int[] pixel) {
|
||||
public bool Contains(int[] pixel)
|
||||
{
|
||||
var rval = pixel[0] >> Mmcq.Rshift;
|
||||
var gval = pixel[1] >> Mmcq.Rshift;
|
||||
var bval = pixel[2] >> Mmcq.Rshift;
|
||||
@@ -652,16 +815,20 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
}
|
||||
}
|
||||
|
||||
internal class VBoxCountComparer : IComparer<VBox> {
|
||||
public int Compare(VBox x, VBox y) {
|
||||
internal class VBoxCountComparer : IComparer<VBox>
|
||||
{
|
||||
public int Compare(VBox x, VBox y)
|
||||
{
|
||||
var a = x.Count(false);
|
||||
var b = y.Count(false);
|
||||
return a < b ? -1 : (a > b ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
internal class VBoxComparer : IComparer<VBox> {
|
||||
public int Compare(VBox x, VBox y) {
|
||||
internal class VBoxComparer : IComparer<VBox>
|
||||
{
|
||||
public int Compare(VBox x, VBox y)
|
||||
{
|
||||
var aCount = x.Count(false);
|
||||
var bCount = y.Count(false);
|
||||
var aVolume = x.Volume(false);
|
||||
@@ -674,17 +841,20 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
}
|
||||
}
|
||||
|
||||
public class ColorThief {
|
||||
public class ColorThief
|
||||
{
|
||||
public const int DefaultColorCount = 5;
|
||||
public const int DefaultQuality = 10;
|
||||
public const bool DefaultIgnoreWhite = true;
|
||||
public const int ColorDepth = 4;
|
||||
|
||||
private CMap GetColorMap(byte[][] pixelArray, int colorCount) {
|
||||
private CMap GetColorMap(byte[][] pixelArray, int colorCount)
|
||||
{
|
||||
// Send array to quantize function which clusters values using median
|
||||
// cut algorithm
|
||||
|
||||
if (colorCount > 0) {
|
||||
if (colorCount > 0)
|
||||
{
|
||||
--colorCount;
|
||||
}
|
||||
|
||||
@@ -692,14 +862,18 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
return cmap;
|
||||
}
|
||||
|
||||
private byte[][] ConvertPixels(byte[] pixels, int pixelCount, int quality, bool ignoreWhite) {
|
||||
|
||||
|
||||
private byte[][] ConvertPixels(byte[] pixels, int pixelCount, int quality, bool ignoreWhite)
|
||||
{
|
||||
var expectedDataLength = pixelCount * ColorDepth;
|
||||
if (expectedDataLength != pixels.Length) {
|
||||
throw new ArgumentException("(expectedDataLength = "
|
||||
+ expectedDataLength + ") != (pixels.length = "
|
||||
+ pixels.Length + ")");
|
||||
if (expectedDataLength != pixels.Length)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"(expectedDataLength = "
|
||||
+ expectedDataLength
|
||||
+ ") != (pixels.length = "
|
||||
+ pixels.Length
|
||||
+ ")"
|
||||
);
|
||||
}
|
||||
|
||||
// Store the RGB values in an array format suitable for quantize
|
||||
@@ -713,7 +887,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
var numUsedPixels = 0;
|
||||
var pixelArray = new byte[numRegardedPixels][];
|
||||
|
||||
for (var i = 0; i < pixelCount; i += quality) {
|
||||
for (var i = 0; i < pixelCount; i += quality)
|
||||
{
|
||||
var offset = i * ColorDepth;
|
||||
var b = pixels[offset];
|
||||
var g = pixels[offset + 1];
|
||||
@@ -721,7 +896,8 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
var a = pixels[offset + 3];
|
||||
|
||||
// If pixel is mostly opaque and not white
|
||||
if (a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250)) {
|
||||
if (a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250))
|
||||
{
|
||||
pixelArray[numUsedPixels] = new[] { r, g, b };
|
||||
numUsedPixels++;
|
||||
}
|
||||
@@ -745,15 +921,24 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// </param>
|
||||
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
|
||||
/// <returns></returns>
|
||||
public async Task<QuantizedColor> GetColor(BitmapDecoder sourceImage, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) {
|
||||
public async Task<QuantizedColor> GetColor(
|
||||
BitmapDecoder sourceImage,
|
||||
int quality = DefaultQuality,
|
||||
bool ignoreWhite = DefaultIgnoreWhite
|
||||
)
|
||||
{
|
||||
var palette = await GetPalette(sourceImage, 3, quality, ignoreWhite);
|
||||
|
||||
var dominantColor = new QuantizedColor(new Color {
|
||||
A = Convert.ToByte(palette.Average(a => a.Color.A)),
|
||||
R = Convert.ToByte(palette.Average(a => a.Color.R)),
|
||||
G = Convert.ToByte(palette.Average(a => a.Color.G)),
|
||||
B = Convert.ToByte(palette.Average(a => a.Color.B))
|
||||
}, Convert.ToInt32(palette.Average(a => a.Population)));
|
||||
var dominantColor = new QuantizedColor(
|
||||
new Color
|
||||
{
|
||||
A = Convert.ToByte(palette.Average(a => a.Color.A)),
|
||||
R = Convert.ToByte(palette.Average(a => a.Color.R)),
|
||||
G = Convert.ToByte(palette.Average(a => a.Color.G)),
|
||||
B = Convert.ToByte(palette.Average(a => a.Color.B)),
|
||||
},
|
||||
Convert.ToInt32(palette.Average(a => a.Population))
|
||||
);
|
||||
|
||||
return dominantColor;
|
||||
}
|
||||
@@ -772,24 +957,38 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <param name="ignoreWhite">if set to <c>true</c> [ignore white].</param>
|
||||
/// <returns></returns>
|
||||
/// <code>true</code>
|
||||
public async Task<List<QuantizedColor>> GetPalette(BitmapDecoder sourceImage, int colorCount = DefaultColorCount, int quality = DefaultQuality, bool ignoreWhite = DefaultIgnoreWhite) {
|
||||
public async Task<List<QuantizedColor>> GetPalette(
|
||||
BitmapDecoder sourceImage,
|
||||
int colorCount = DefaultColorCount,
|
||||
int quality = DefaultQuality,
|
||||
bool ignoreWhite = DefaultIgnoreWhite
|
||||
)
|
||||
{
|
||||
var pixelArray = await GetPixelsFast(sourceImage, quality, ignoreWhite);
|
||||
var cmap = GetColorMap(pixelArray, colorCount);
|
||||
if (cmap != null) {
|
||||
if (cmap != null)
|
||||
{
|
||||
var colors = cmap.GeneratePalette();
|
||||
return colors;
|
||||
}
|
||||
return new List<QuantizedColor>();
|
||||
}
|
||||
|
||||
private async Task<byte[]> GetIntFromPixel(BitmapDecoder decoder) {
|
||||
private async Task<byte[]> GetIntFromPixel(BitmapDecoder decoder)
|
||||
{
|
||||
var pixelsData = await decoder.GetPixelDataAsync();
|
||||
var pixels = pixelsData.DetachPixelData();
|
||||
return pixels;
|
||||
}
|
||||
|
||||
private async Task<byte[][]> GetPixelsFast(BitmapDecoder sourceImage, int quality, bool ignoreWhite) {
|
||||
if (quality < 1) {
|
||||
private async Task<byte[][]> GetPixelsFast(
|
||||
BitmapDecoder sourceImage,
|
||||
int quality,
|
||||
bool ignoreWhite
|
||||
)
|
||||
{
|
||||
if (quality < 1)
|
||||
{
|
||||
quality = DefaultQuality;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class EasingHelper {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class EasingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// No easing
|
||||
/// </summary>
|
||||
@@ -25,23 +26,24 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
/// <summary>
|
||||
/// Acceleration until halfway then deceleration
|
||||
/// </summary>
|
||||
public static float EaseInOutQuad(float t) {
|
||||
return t < 0.5f
|
||||
? 2 * t * t
|
||||
: -1 + (4 - 2 * t) * t;
|
||||
public static float EaseInOutQuad(float t)
|
||||
{
|
||||
return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoother transition than linear
|
||||
/// </summary>
|
||||
public static float SmoothStep(float t) {
|
||||
public static float SmoothStep(float t)
|
||||
{
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Even smoother transition with continuous first and second derivatives
|
||||
/// </summary>
|
||||
public static float SmootherStep(float t) {
|
||||
public static float SmootherStep(float t)
|
||||
{
|
||||
return t * t * t * (t * (6 * t - 15) + 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -11,7 +12,9 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class ImageHelper
|
||||
{
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(byte[] imageBytes)
|
||||
public static async Task<InMemoryRandomAccessStream> GetStreamFromBytesAsync(
|
||||
byte[] imageBytes
|
||||
)
|
||||
{
|
||||
if (imageBytes == null || imageBytes.Length == 0)
|
||||
return null;
|
||||
@@ -22,5 +25,13 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
{
|
||||
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await stream.AsStreamForRead().CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class MathHelper {
|
||||
public static List<int> GetAllFactors(int n) {
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class MathHelper
|
||||
{
|
||||
public static List<int> GetAllFactors(int n)
|
||||
{
|
||||
var result = new SortedSet<int>();
|
||||
|
||||
for (int i = 1; i <= Math.Sqrt(n); i++) {
|
||||
if (n % i == 0) {
|
||||
for (int i = 1; i <= Math.Sqrt(n); i++)
|
||||
{
|
||||
if (n % i == 0)
|
||||
{
|
||||
result.Add(i);
|
||||
result.Add(n / i);
|
||||
}
|
||||
@@ -18,6 +23,5 @@ namespace BetterLyrics.WinUI3.Helper {
|
||||
|
||||
return [.. result];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class SystemBackdropHelper
|
||||
{
|
||||
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType) {
|
||||
return backdropType switch {
|
||||
public static SystemBackdrop? CreateSystemBackdrop(BackdropType backdropType)
|
||||
{
|
||||
return backdropType switch
|
||||
{
|
||||
BackdropType.None => null,
|
||||
BackdropType.Mica => new MicaSystemBackdrop(MicaKind.Base),
|
||||
BackdropType.MicaAlt => new MicaSystemBackdrop(MicaKind.BaseAlt),
|
||||
@@ -18,6 +20,5 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper {
|
||||
public class VisualHelper {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class VisualHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Source: https://stackoverflow.com/a/61626933/11048731
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="depObj"></param>
|
||||
/// <returns></returns>
|
||||
public static List<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject {
|
||||
public static List<T> FindVisualChildren<T>(DependencyObject depObj)
|
||||
where T : DependencyObject
|
||||
{
|
||||
List<T> list = [];
|
||||
if (depObj != null) {
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) {
|
||||
if (depObj != null)
|
||||
{
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
|
||||
if (child != null && child is T t) {
|
||||
if (child != null && child is T t)
|
||||
{
|
||||
list.Add(t);
|
||||
}
|
||||
|
||||
List<T> childItems = FindVisualChildren<T>(child);
|
||||
if (childItems != null && childItems.Count > 0) {
|
||||
foreach (var item in childItems) {
|
||||
if (childItems != null && childItems.Count > 0)
|
||||
{
|
||||
foreach (var item in childItems)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
@@ -12,39 +13,54 @@ using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3 {
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty window that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MainWindow : Window {
|
||||
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
private readonly OverlappedPresenter _presenter;
|
||||
|
||||
private readonly SettingsService _settingsService;
|
||||
|
||||
public static StackedNotificationsBehavior? StackedNotificationsBehavior { get; private set; }
|
||||
public static StackedNotificationsBehavior? StackedNotificationsBehavior
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public MainWindow() {
|
||||
public MainWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
_settingsService = Ioc.Default.GetService<SettingsService>()!;
|
||||
|
||||
RootGrid.RequestedTheme = (ElementTheme)_settingsService.Theme;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop((BackdropType)_settingsService.BackdropType);
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(
|
||||
(BackdropType)_settingsService.BackdropType
|
||||
);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(this, (r, m) => {
|
||||
RootGrid.RequestedTheme = m.Value;
|
||||
});
|
||||
WeakReferenceMessenger.Default.Register<ThemeChangedMessage>(
|
||||
this,
|
||||
(r, m) =>
|
||||
{
|
||||
RootGrid.RequestedTheme = m.Value;
|
||||
}
|
||||
);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(this, (r, m) => {
|
||||
SystemBackdrop = null;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
|
||||
});
|
||||
WeakReferenceMessenger.Default.Register<SystemBackdropChangedMessage>(
|
||||
this,
|
||||
(r, m) =>
|
||||
{
|
||||
SystemBackdrop = null;
|
||||
SystemBackdrop = SystemBackdropHelper.CreateSystemBackdrop(m.Value);
|
||||
}
|
||||
);
|
||||
|
||||
// AppWindow.SetIcon("white_round.ico");
|
||||
StackedNotificationsBehavior = NotificationQueue;
|
||||
@@ -56,50 +72,67 @@ namespace BetterLyrics.WinUI3 {
|
||||
SetTitleBar(TopCommandGrid);
|
||||
}
|
||||
|
||||
public void Navigate(Type type) {
|
||||
public void Navigate(Type type)
|
||||
{
|
||||
RootFrame.Navigate(type);
|
||||
}
|
||||
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) {
|
||||
private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e) {
|
||||
if (RootFrame.CurrentSourcePageType == typeof(MainPage)) {
|
||||
((RootFrame.Content as MainPage)!.FindChild("LyricsCanvas") as CanvasAnimatedControl)!.Paused = true;
|
||||
private void CloseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (RootFrame.CurrentSourcePageType == typeof(MainPage))
|
||||
{
|
||||
(
|
||||
(RootFrame.Content as MainPage)!.FindChild("LyricsCanvas")
|
||||
as CanvasAnimatedControl
|
||||
)!.Paused = true;
|
||||
App.Current.Exit();
|
||||
} else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage)) {
|
||||
}
|
||||
else if (RootFrame.CurrentSourcePageType == typeof(SettingsPage))
|
||||
{
|
||||
App.Current.SettingsWindow!.AppWindow.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void MaximiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Maximize();
|
||||
//MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
//RestoreButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void MinimiseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Minimize();
|
||||
}
|
||||
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void RestoreButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.Restore();
|
||||
//MaximiseButton.Visibility = Visibility.Visible;
|
||||
//RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args) {
|
||||
if (_presenter.State == OverlappedPresenterState.Maximized) {
|
||||
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
|
||||
{
|
||||
if (_presenter.State == OverlappedPresenterState.Maximized)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Collapsed;
|
||||
RestoreButton.Visibility = Visibility.Visible;
|
||||
} else if (_presenter.State == OverlappedPresenterState.Restored) {
|
||||
}
|
||||
else if (_presenter.State == OverlappedPresenterState.Restored)
|
||||
{
|
||||
MaximiseButton.Visibility = Visibility.Visible;
|
||||
RestoreButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void MiniButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void MiniButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppWindow.Resize(new Windows.Graphics.SizeInt32(144, 48));
|
||||
MiniButton.Visibility = Visibility.Collapsed;
|
||||
UnminiButton.Visibility = Visibility.Visible;
|
||||
@@ -109,7 +142,8 @@ namespace BetterLyrics.WinUI3 {
|
||||
CloseButton.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UnminiButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void UnminiButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AppWindow.Resize(new Windows.Graphics.SizeInt32(800, 600));
|
||||
MiniButton.Visibility = Visibility.Visible;
|
||||
UnminiButton.Visibility = Visibility.Collapsed;
|
||||
@@ -119,21 +153,26 @@ namespace BetterLyrics.WinUI3 {
|
||||
CloseButton.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void RootFrame_Navigated(object sender, NavigationEventArgs e) {
|
||||
AppWindow.Title = Title = App.ResourceLoader!.GetString($"{e.SourcePageType.Name}Title");
|
||||
private void RootFrame_Navigated(object sender, NavigationEventArgs e)
|
||||
{
|
||||
AppWindow.Title = Title = App.ResourceLoader!.GetString(
|
||||
$"{e.SourcePageType.Name}Title"
|
||||
);
|
||||
}
|
||||
|
||||
private void AOTButton_Click(object sender, RoutedEventArgs e) {
|
||||
private void AOTButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_presenter.IsAlwaysOnTop = !_presenter.IsAlwaysOnTop;
|
||||
string prefix;
|
||||
if (_presenter.IsAlwaysOnTop) {
|
||||
if (_presenter.IsAlwaysOnTop)
|
||||
{
|
||||
prefix = "Show";
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = "Hide";
|
||||
}
|
||||
(PinnedFontIcon.Resources[$"{prefix}PinnedFontIconStoryboard"] as Storyboard)!.Begin();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using DevWinUI;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using DevWinUI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages {
|
||||
public class SystemBackdropChangedMessage(BackdropType value) : ValueChangedMessage<BackdropType>(value) {
|
||||
}
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class SystemBackdropChangedMessage(BackdropType value)
|
||||
: ValueChangedMessage<BackdropType>(value) { }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Messages {
|
||||
public class ThemeChangedMessage(ElementTheme value) : ValueChangedMessage<ElementTheme>(value) {
|
||||
}
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class ThemeChangedMessage(ElementTheme value)
|
||||
: ValueChangedMessage<ElementTheme>(value) { }
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum Language {
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum Language
|
||||
{
|
||||
FollowSystem,
|
||||
English,
|
||||
SimplifiedChinese,
|
||||
|
||||
@@ -4,8 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum LyricsAlignmentType {
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum LyricsAlignmentType
|
||||
{
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
|
||||
@@ -4,9 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum LyricsFontColorType {
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum LyricsFontColorType
|
||||
{
|
||||
Default,
|
||||
Dominant
|
||||
Dominant,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Graphics.Canvas.Text;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public class LyricsLine {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsLine
|
||||
{
|
||||
public List<string> Texts { get; set; } = [];
|
||||
|
||||
public int LanguageIndex { get; set; } = 0;
|
||||
@@ -37,6 +38,5 @@ namespace BetterLyrics.WinUI3.Models {
|
||||
public float Opacity { get; set; }
|
||||
|
||||
public CanvasTextLayout TextLayout { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,23 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public enum LyricsPlayingState {
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public enum LyricsPlayingState
|
||||
{
|
||||
/// <summary>
|
||||
/// Not played yet, will be playing in the future
|
||||
/// </summary>
|
||||
NotPlayed,
|
||||
|
||||
/// <summary>
|
||||
/// Playing
|
||||
/// </summary>
|
||||
Playing,
|
||||
|
||||
/// <summary>
|
||||
/// Has already played
|
||||
/// </summary>
|
||||
Played
|
||||
Played,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using SQLite;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models {
|
||||
public class MetadataIndex {
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class MetadataIndex
|
||||
{
|
||||
[PrimaryKey]
|
||||
public string? Path { get; set; }
|
||||
public string? Title { get; set; }
|
||||
|
||||
@@ -7,34 +7,46 @@ using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
|
||||
namespace BetterLyrics.WinUI3 {
|
||||
public class Program {
|
||||
namespace BetterLyrics.WinUI3
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
[STAThread]
|
||||
static int Main(string[] args) {
|
||||
static int Main(string[] args)
|
||||
{
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
bool isRedirect = DecideRedirection();
|
||||
|
||||
if (!isRedirect) {
|
||||
Application.Start((p) => {
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
});
|
||||
if (!isRedirect)
|
||||
{
|
||||
Application.Start(
|
||||
(p) =>
|
||||
{
|
||||
var context = new DispatcherQueueSynchronizationContext(
|
||||
DispatcherQueue.GetForCurrentThread()
|
||||
);
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool DecideRedirection() {
|
||||
private static bool DecideRedirection()
|
||||
{
|
||||
bool isRedirect = false;
|
||||
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
|
||||
ExtendedActivationKind kind = args.Kind;
|
||||
AppInstance keyInstance = AppInstance.FindOrRegisterForKey(Helper.AppInfo.AppName);
|
||||
|
||||
if (keyInstance.IsCurrent) {
|
||||
if (keyInstance.IsCurrent)
|
||||
{
|
||||
keyInstance.Activated += OnActivated;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
isRedirect = true;
|
||||
RedirectActivationTo(args, keyInstance);
|
||||
}
|
||||
@@ -44,16 +56,23 @@ namespace BetterLyrics.WinUI3 {
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateEvent(
|
||||
IntPtr lpEventAttributes, bool bManualReset,
|
||||
bool bInitialState, string lpName);
|
||||
IntPtr lpEventAttributes,
|
||||
bool bManualReset,
|
||||
bool bInitialState,
|
||||
string lpName
|
||||
);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetEvent(IntPtr hEvent);
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern uint CoWaitForMultipleObjects(
|
||||
uint dwFlags, uint dwMilliseconds, ulong nHandles,
|
||||
IntPtr[] pHandles, out uint dwIndex);
|
||||
uint dwFlags,
|
||||
uint dwMilliseconds,
|
||||
ulong nHandles,
|
||||
IntPtr[] pHandles,
|
||||
out uint dwIndex
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
@@ -62,10 +81,14 @@ namespace BetterLyrics.WinUI3 {
|
||||
|
||||
// Do the redirection on another thread, and use a non-blocking
|
||||
// wait method to wait for the redirection to complete.
|
||||
public static void RedirectActivationTo(AppActivationArguments args,
|
||||
AppInstance keyInstance) {
|
||||
public static void RedirectActivationTo(
|
||||
AppActivationArguments args,
|
||||
AppInstance keyInstance
|
||||
)
|
||||
{
|
||||
redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null);
|
||||
Task.Run(() => {
|
||||
Task.Run(() =>
|
||||
{
|
||||
keyInstance.RedirectActivationToAsync(args).AsTask().Wait();
|
||||
SetEvent(redirectEventHandle);
|
||||
});
|
||||
@@ -73,17 +96,21 @@ namespace BetterLyrics.WinUI3 {
|
||||
uint CWMO_DEFAULT = 0;
|
||||
uint INFINITE = 0xFFFFFFFF;
|
||||
_ = CoWaitForMultipleObjects(
|
||||
CWMO_DEFAULT, INFINITE, 1,
|
||||
[redirectEventHandle], out uint handleIndex);
|
||||
CWMO_DEFAULT,
|
||||
INFINITE,
|
||||
1,
|
||||
[redirectEventHandle],
|
||||
out uint handleIndex
|
||||
);
|
||||
|
||||
// Bring the window to the foreground
|
||||
Process process = Process.GetProcessById((int)keyInstance.ProcessId);
|
||||
SetForegroundWindow(process.MainWindowHandle);
|
||||
}
|
||||
|
||||
private static void OnActivated(object sender, AppActivationArguments args) {
|
||||
private static void OnActivated(object sender, AppActivationArguments args)
|
||||
{
|
||||
ExtendedActivationKind kind = args.Kind;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +1,57 @@
|
||||
using ATL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SQLite;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Ude;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Database {
|
||||
public class DatabaseService {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Database
|
||||
{
|
||||
public class DatabaseService
|
||||
{
|
||||
private readonly SQLiteConnection _connection;
|
||||
private readonly CharsetDetector _charsetDetector = new();
|
||||
|
||||
public DatabaseService() {
|
||||
public DatabaseService()
|
||||
{
|
||||
_connection = new SQLiteConnection(Helper.AppInfo.DatabasePath);
|
||||
if (_connection.GetTableInfo("MetadataIndex").Count == 0) {
|
||||
if (_connection.GetTableInfo("MetadataIndex").Count == 0)
|
||||
{
|
||||
_connection.CreateTable<MetadataIndex>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<string> paths) {
|
||||
await Task.Run(() => {
|
||||
public async Task RebuildMusicMetadataIndexDatabaseAsync(IList<string> paths)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
_connection.DeleteAll<MetadataIndex>();
|
||||
foreach (var path in paths) {
|
||||
if (Directory.Exists(path)) {
|
||||
foreach (var file in Directory.GetFiles(path)) {
|
||||
foreach (var path in paths)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (var file in Directory.GetFiles(path))
|
||||
{
|
||||
var fileExtension = Path.GetExtension(file);
|
||||
var track = new Track(file);
|
||||
_connection.Insert(new MetadataIndex {
|
||||
Path = file,
|
||||
Title = track.Title,
|
||||
Artist = track.Artist,
|
||||
});
|
||||
_connection.Insert(
|
||||
new MetadataIndex
|
||||
{
|
||||
Path = file,
|
||||
Title = track.Title,
|
||||
Artist = track.Artist,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Track? GetMusicMetadata(string? title, string? artist) {
|
||||
var founds = _connection.Table<MetadataIndex>()
|
||||
// Look up by Title and Artist (these two props were fetched by reading metadata in music file befoe) first
|
||||
// then by Path (music file name usually contains song name and artist so this can be a second way to look up for)
|
||||
// Please note for .lrc file, only the second way works for it
|
||||
.Where(m => (m.Title.Contains(title) && m.Artist.Contains(artist)) || (m.Path.Contains(title) && m.Path.Contains(artist))).ToList();
|
||||
if (founds == null || founds.Count == 0) {
|
||||
return null;
|
||||
} else {
|
||||
var first = new Track(founds[0].Path);
|
||||
if (founds.Count == 1) {
|
||||
return first;
|
||||
} else {
|
||||
if (first.Lyrics.Exists()) {
|
||||
return first;
|
||||
} else {
|
||||
foreach (var found in founds) {
|
||||
if (found.Path.EndsWith(".lrc")) {
|
||||
using (FileStream fs = File.OpenRead(found.Path)) {
|
||||
_charsetDetector.Feed(fs);
|
||||
_charsetDetector.DataEnd();
|
||||
}
|
||||
|
||||
string content;
|
||||
if (_charsetDetector.Charset != null) {
|
||||
Encoding encoding = Encoding.GetEncoding(_charsetDetector.Charset);
|
||||
content = File.ReadAllText(found.Path, encoding);
|
||||
} else {
|
||||
content = File.ReadAllText(found.Path, Encoding.UTF8);
|
||||
}
|
||||
first.Lyrics.ParseLRC(content);
|
||||
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
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 {
|
||||
public static class SettingsDefaultValues {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings
|
||||
{
|
||||
public static class SettingsDefaultValues
|
||||
{
|
||||
public const bool IsFirstRun = true;
|
||||
|
||||
// Theme
|
||||
public const int ThemeType = 0; // Follow system
|
||||
|
||||
// Language
|
||||
public const int Language = 0; // Default
|
||||
|
||||
// Music
|
||||
public const string MusicLibraries = "[]";
|
||||
|
||||
// Backdrop
|
||||
public const int BackdropType = 5; // Acrylic Base
|
||||
public const bool IsCoverOverlayEnabled = true;
|
||||
public const bool IsDynamicCoverOverlay = true;
|
||||
public const int CoverOverlayOpacity = 100; // 1.0
|
||||
public const int CoverOverlayBlurAmount = 200;
|
||||
|
||||
// Lyrics
|
||||
public const int LyricsAlignmentType = 1; // Center
|
||||
public const int LyricsBlurAmount = 0;
|
||||
|
||||
@@ -4,22 +4,28 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings {
|
||||
public static class SettingsKeys {
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.Settings
|
||||
{
|
||||
public static class SettingsKeys
|
||||
{
|
||||
public const string IsFirstRun = "IsFirstRun";
|
||||
|
||||
// Theme
|
||||
public const string ThemeType = "ThemeType";
|
||||
|
||||
// Language
|
||||
public const string Language = "Language";
|
||||
|
||||
// Music
|
||||
public const string MusicLibraries = "MusicLibraries";
|
||||
|
||||
// Backdrop
|
||||
public const string BackdropType = "BackdropType";
|
||||
public const string IsCoverOverlayEnabled = "IsCoverOverlayEnabled";
|
||||
public const string IsDynamicCoverOverlay = "IsDynamicCoverOverlay";
|
||||
public const string CoverOverlayOpacity = "CoverOverlayOpacity";
|
||||
public const string CoverOverlayBlurAmount = "CoverOverlayBlurAmount";
|
||||
|
||||
// Lyrics
|
||||
public const string LyricsAlignmentType = "LyricsAlignmentType";
|
||||
public const string LyricsBlurAmount = "LyricsBlurAmount";
|
||||
@@ -30,5 +36,4 @@ namespace BetterLyrics.WinUI3.Services.Settings {
|
||||
public const string IsLyricsDynamicGlowEffectEnabled = "IsLyricsDynamicGlowEffectEnabled";
|
||||
public const string LyricsFontColorType = "LyricsFontColorType";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Globalization;
|
||||
@@ -15,6 +16,38 @@ using Windows.Storage;
|
||||
namespace BetterLyrics.WinUI3.Services.Settings {
|
||||
public partial class SettingsService : ObservableObject {
|
||||
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService() {
|
||||
|
||||
_localSettings = ApplicationData.Current.LocalSettings;
|
||||
|
||||
_musicLibraries = [.. JsonConvert.DeserializeObject<List<string>>(
|
||||
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!)!];
|
||||
|
||||
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
|
||||
}
|
||||
|
||||
private void WatchMultipleDirectories(IEnumerable<string> directories) {
|
||||
foreach (var dir in directories) {
|
||||
if (!Directory.Exists(dir)) continue;
|
||||
|
||||
var watcher = new FileSystemWatcher {
|
||||
Path = dir,
|
||||
Filter = "*.*",
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFileCreated(object sender, FileSystemEventArgs e) {
|
||||
App.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Debug.WriteLine($"[Created] {e.FullPath}");
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsFirstRun {
|
||||
get => Get(SettingsKeys.IsFirstRun, SettingsDefaultValues.IsFirstRun);
|
||||
set => Set(SettingsKeys.IsFirstRun, value);
|
||||
@@ -139,17 +172,6 @@ namespace BetterLyrics.WinUI3.Services.Settings {
|
||||
set => Set(SettingsKeys.LyricsFontColorType, value);
|
||||
}
|
||||
|
||||
private readonly ApplicationDataContainer _localSettings;
|
||||
|
||||
public SettingsService() {
|
||||
_localSettings = ApplicationData.Current.LocalSettings;
|
||||
|
||||
_musicLibraries = [.. JsonConvert.DeserializeObject<List<string>>(
|
||||
Get(SettingsKeys.MusicLibraries, SettingsDefaultValues.MusicLibraries)!)!];
|
||||
|
||||
_musicLibraries.CollectionChanged += (_, _) => SaveMusicLibraries();
|
||||
}
|
||||
|
||||
private T? Get<T>(string key, T? defaultValue = default) {
|
||||
if (_localSettings.Values.TryGetValue(key, out object? value)) {
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
using ATL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DevWinUI;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
using Microsoft.Graphics.Canvas.UI.Xaml;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Ude;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
using static ATL.LyricsInfo;
|
||||
using static CommunityToolkit.WinUI.Animations.Expressions.ExpressionValues;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels {
|
||||
public partial class MainViewModel(DatabaseService databaseService) : ObservableObject {
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class MainViewModel(SettingsService settingsService) : ObservableObject
|
||||
{
|
||||
private readonly CharsetDetector _charsetDetector = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isAnyMusicSessionExisted = false;
|
||||
@@ -31,7 +42,12 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
private string? _artist;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<Color> _coverImageDominantColors = [Colors.Transparent, Colors.Transparent, Colors.Transparent];
|
||||
private ObservableCollection<Windows.UI.Color> _coverImageDominantColors =
|
||||
[
|
||||
Colors.Transparent,
|
||||
Colors.Transparent,
|
||||
Colors.Transparent,
|
||||
];
|
||||
|
||||
[ObservableProperty]
|
||||
private BitmapImage? _coverImage;
|
||||
@@ -50,15 +66,18 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
|
||||
private readonly Helper.ColorThief _colorThief = new();
|
||||
|
||||
private readonly DatabaseService _databaseService = databaseService;
|
||||
private readonly SettingsService _settingsService = settingsService;
|
||||
|
||||
public List<LyricsLine> GetLyrics(Track? track) {
|
||||
public List<LyricsLine> GetLyrics(Track? track)
|
||||
{
|
||||
List<LyricsLine> result = [];
|
||||
|
||||
var lyricsPhrases = track?.Lyrics.SynchronizedLyrics;
|
||||
|
||||
if (lyricsPhrases?.Count > 0) {
|
||||
if (lyricsPhrases[0].TimestampMs > 0) {
|
||||
if (lyricsPhrases?.Count > 0)
|
||||
{
|
||||
if (lyricsPhrases[0].TimestampMs > 0)
|
||||
{
|
||||
var placeholder = new LyricsPhrase(0, " ");
|
||||
lyricsPhrases.Insert(0, placeholder);
|
||||
lyricsPhrases.Insert(0, placeholder);
|
||||
@@ -67,44 +86,135 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
|
||||
LyricsLine? lyricsLine = null;
|
||||
|
||||
for (int i = 0; i < lyricsPhrases?.Count; i++) {
|
||||
for (int i = 0; i < lyricsPhrases?.Count; i++)
|
||||
{
|
||||
var lyricsPhrase = lyricsPhrases[i];
|
||||
int startTimestampMs = lyricsPhrase.TimestampMs;
|
||||
int endTimestampMs;
|
||||
|
||||
if (i + 1 < lyricsPhrases.Count) {
|
||||
if (i + 1 < lyricsPhrases.Count)
|
||||
{
|
||||
endTimestampMs = lyricsPhrases[i + 1].TimestampMs;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimestampMs = (int)track.DurationMs;
|
||||
}
|
||||
|
||||
lyricsLine ??= new LyricsLine {
|
||||
StartPlayingTimestampMs = startTimestampMs,
|
||||
};
|
||||
lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs };
|
||||
|
||||
lyricsLine.Texts.Add(lyricsPhrase.Text);
|
||||
|
||||
if (endTimestampMs == startTimestampMs) {
|
||||
if (endTimestampMs == startTimestampMs)
|
||||
{
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
lyricsLine.EndPlayingTimestampMs = endTimestampMs;
|
||||
result.Add(lyricsLine);
|
||||
lyricsLine = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LyricsExisted = result.Count != 0;
|
||||
if (!LyricsExisted) {
|
||||
if (!LyricsExisted)
|
||||
{
|
||||
ShowLyricsOnly = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps) {
|
||||
public async Task<Track?> FindTrackFromDirectories(
|
||||
IEnumerable<string> directories,
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
|
||||
)
|
||||
{
|
||||
Track? finalResult = null;
|
||||
|
||||
if (mediaProps == null || mediaProps.Title == null || mediaProps.Artist == null)
|
||||
return finalResult;
|
||||
|
||||
finalResult = new() { Title = mediaProps.Title, Artist = mediaProps.Artist };
|
||||
|
||||
bool coverFound = false;
|
||||
bool lyricsFound = false;
|
||||
|
||||
if (mediaProps.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
coverFound = true;
|
||||
PictureInfo pictureInfo = PictureInfo.fromBinaryData(
|
||||
await ImageHelper.ToByteArrayAsync(streamReference)
|
||||
);
|
||||
finalResult.EmbeddedPictures.Add(pictureInfo);
|
||||
}
|
||||
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
continue;
|
||||
|
||||
foreach (
|
||||
var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories)
|
||||
)
|
||||
{
|
||||
string content = File.ReadAllText(file);
|
||||
var result = new Track(content);
|
||||
|
||||
if (
|
||||
result.Title.Contains(mediaProps.Title)
|
||||
&& result.Artist.Contains(mediaProps.Artist)
|
||||
)
|
||||
{
|
||||
if (!coverFound && result.EmbeddedPictures.Count > 0)
|
||||
{
|
||||
coverFound = true;
|
||||
finalResult.EmbeddedPictures.AddRange(result.EmbeddedPictures);
|
||||
}
|
||||
|
||||
if (!lyricsFound && result.Lyrics != null)
|
||||
{
|
||||
lyricsFound = true;
|
||||
finalResult.Lyrics = result.Lyrics;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lyricsFound && file.EndsWith(".lrc"))
|
||||
{
|
||||
using (FileStream fs = File.OpenRead(file))
|
||||
{
|
||||
_charsetDetector.Feed(fs);
|
||||
_charsetDetector.DataEnd();
|
||||
}
|
||||
|
||||
if (_charsetDetector.Charset != null)
|
||||
{
|
||||
Encoding encoding = Encoding.GetEncoding(_charsetDetector.Charset);
|
||||
content = File.ReadAllText(file, encoding);
|
||||
}
|
||||
else
|
||||
{
|
||||
content = File.ReadAllText(file, Encoding.UTF8);
|
||||
}
|
||||
|
||||
lyricsFound = true;
|
||||
finalResult.Lyrics = new();
|
||||
finalResult.Lyrics.ParseLRC(content);
|
||||
}
|
||||
|
||||
if (coverFound && lyricsFound)
|
||||
return finalResult;
|
||||
}
|
||||
}
|
||||
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
|
||||
)
|
||||
{
|
||||
SoftwareBitmap? coverSoftwareBitmap = null;
|
||||
uint coverImagePixelWidth = 0;
|
||||
uint coverImagePixelHeight = 0;
|
||||
@@ -114,26 +224,22 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
|
||||
IRandomAccessStream? stream = null;
|
||||
|
||||
var track = _databaseService.GetMusicMetadata(Title, Artist);
|
||||
var track = await FindTrackFromDirectories(_settingsService.MusicLibraries, mediaProps);
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference reference) {
|
||||
stream = await reference.OpenReadAsync();
|
||||
} else {
|
||||
if (track?.EmbeddedPictures.Count > 0) {
|
||||
var bytes = track.EmbeddedPictures[0].PictureData;
|
||||
if (bytes != null) {
|
||||
stream = await Helper.ImageHelper.GetStreamFromBytesAsync(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (track?.EmbeddedPictures?[0].PictureData is byte[] bytes)
|
||||
stream = await ImageHelper.GetStreamFromBytesAsync(bytes);
|
||||
|
||||
// Set cover image and dominant colors
|
||||
if (stream == null) {
|
||||
if (stream == null)
|
||||
{
|
||||
CoverImage = null;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
CoverImageDominantColors[i] = Colors.Transparent;
|
||||
}
|
||||
} else {
|
||||
CoverImageDominantColors =
|
||||
[
|
||||
.. Enumerable.Repeat(Microsoft.UI.Colors.Transparent, 3),
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
CoverImage = new BitmapImage();
|
||||
await CoverImage.SetSourceAsync(stream);
|
||||
stream.Seek(0);
|
||||
@@ -147,19 +253,27 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
BitmapAlphaMode.Premultiplied
|
||||
);
|
||||
|
||||
var quantizedColors = await _colorThief.GetPalette(decoder, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Helper.QuantizedColor quantizedColor = quantizedColors[i];
|
||||
CoverImageDominantColors[i] = Color.FromArgb(
|
||||
quantizedColor.Color.A, quantizedColor.Color.R, quantizedColor.Color.G, quantizedColor.Color.B);
|
||||
}
|
||||
CoverImageDominantColors =
|
||||
[
|
||||
.. (await _colorThief.GetPalette(decoder, 3)).Select(quantizedColor =>
|
||||
Windows.UI.Color.FromArgb(
|
||||
quantizedColor.Color.A,
|
||||
quantizedColor.Color.R,
|
||||
quantizedColor.Color.G,
|
||||
quantizedColor.Color.B
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
return (GetLyrics(track), coverSoftwareBitmap, coverImagePixelWidth, coverImagePixelHeight);
|
||||
|
||||
return (
|
||||
GetLyrics(track),
|
||||
coverSoftwareBitmap,
|
||||
coverImagePixelWidth,
|
||||
coverImagePixelHeight
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.Database;
|
||||
using BetterLyrics.WinUI3.Services.Settings;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Playback;
|
||||
@@ -14,9 +14,14 @@ using Windows.Storage.Pickers;
|
||||
using Windows.System;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels {
|
||||
public partial class SettingsViewModel(DatabaseService databaseService, SettingsService settingsService, MainViewModel mainViewModel) : ObservableObject {
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsViewModel(
|
||||
DatabaseService databaseService,
|
||||
SettingsService settingsService,
|
||||
MainViewModel mainViewModel
|
||||
) : ObservableObject
|
||||
{
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
|
||||
private readonly DatabaseService _databaseService = databaseService;
|
||||
@@ -28,19 +33,24 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
public string Version => Helper.AppInfo.AppVersion;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task RebuildLyricsIndexDatabaseAsync() {
|
||||
private async Task RebuildLyricsIndexDatabaseAsync()
|
||||
{
|
||||
SettingsService.IsRebuildingLyricsIndexDatabase = true;
|
||||
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(SettingsService.MusicLibraries);
|
||||
await _databaseService.RebuildMusicMetadataIndexDatabaseAsync(
|
||||
SettingsService.MusicLibraries
|
||||
);
|
||||
SettingsService.IsRebuildingLyricsIndexDatabase = false;
|
||||
}
|
||||
|
||||
public async Task RemoveFolderAsync(string path) {
|
||||
public async Task RemoveFolderAsync(string path)
|
||||
{
|
||||
SettingsService.MusicLibraries.Remove(path);
|
||||
await RebuildLyricsIndexDatabaseAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectAndAddFolderAsync() {
|
||||
private async Task SelectAndAddFolderAsync()
|
||||
{
|
||||
var picker = new FolderPicker();
|
||||
|
||||
picker.FileTypeFilter.Add("*");
|
||||
@@ -52,48 +62,62 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
|
||||
App.Current.SettingsWindow!.AppWindow.MoveInZOrderAtTop();
|
||||
|
||||
if (folder != null) {
|
||||
if (folder != null)
|
||||
{
|
||||
await AddFolderAsync(folder.Path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task AddFolderAsync(string path) {
|
||||
private async Task AddFolderAsync(string path)
|
||||
{
|
||||
bool existed = SettingsService.MusicLibraries.Any((x) => x == path);
|
||||
if (existed) {
|
||||
MainWindow.StackedNotificationsBehavior?.Show(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
|
||||
Helper.AnimationHelper.StackedNotificationsShowingDuration);
|
||||
} else {
|
||||
if (existed)
|
||||
{
|
||||
MainWindow.StackedNotificationsBehavior?.Show(
|
||||
App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"),
|
||||
Helper.AnimationHelper.StackedNotificationsShowingDuration
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsService.MusicLibraries.Add(path);
|
||||
await RebuildLyricsIndexDatabaseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static async Task LaunchProjectGitHubPageAsync() {
|
||||
private static async Task LaunchProjectGitHubPageAsync()
|
||||
{
|
||||
await Launcher.LaunchUriAsync(new Uri(Helper.AppInfo.GithubUrl));
|
||||
}
|
||||
|
||||
private static void OpenFolderInFileExplorer(string path) {
|
||||
Process.Start(new ProcessStartInfo {
|
||||
FileName = "explorer.exe",
|
||||
Arguments = path,
|
||||
UseShellExecute = true
|
||||
});
|
||||
private static void OpenFolderInFileExplorer(string path)
|
||||
{
|
||||
Process.Start(
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = path,
|
||||
UseShellExecute = true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void OpenMusicFolder(string path) {
|
||||
public static void OpenMusicFolder(string path)
|
||||
{
|
||||
OpenFolderInFileExplorer(path);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void RestartApp() {
|
||||
private static void RestartApp()
|
||||
{
|
||||
// The restart will be executed immediately.
|
||||
AppRestartFailureReason failureReason =
|
||||
Microsoft.Windows.AppLifecycle.AppInstance.Restart("");
|
||||
|
||||
// If the restart fails, handle it here.
|
||||
switch (failureReason) {
|
||||
switch (failureReason)
|
||||
{
|
||||
case AppRestartFailureReason.RestartPending:
|
||||
break;
|
||||
case AppRestartFailureReason.NotInForeground:
|
||||
@@ -106,16 +130,17 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task PlayTestingMusicTask() {
|
||||
private async Task PlayTestingMusicTask()
|
||||
{
|
||||
await AddFolderAsync(Helper.AppInfo.AssetsFolder);
|
||||
_mediaPlayer.SetUriSource(new Uri(Helper.AppInfo.TestMusicPath));
|
||||
_mediaPlayer.Play();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenLogFolder() {
|
||||
private static void OpenLogFolder()
|
||||
{
|
||||
OpenFolderInFileExplorer(Helper.AppInfo.LogDirectory);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -207,12 +207,40 @@
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander x:Uid="SettingsPageLyricsFontColor" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="False" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="1">
|
||||
<interactivity:ChangePropertyAction PropertyName="IsExpanded" Value="True" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDefault" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsFontColorDominant" />
|
||||
</ComboBox>
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Vertical">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SettingsService.LyricsFontColorType, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="1">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<GridView ItemsSource="{x:Bind ViewModel.MainViewModel.CoverImageDominantColors, Mode=OneWay}" SelectedIndex="0">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Views {
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class SettingsPage : Page {
|
||||
|
||||
public sealed partial class SettingsPage : Page
|
||||
{
|
||||
public SettingsViewModel ViewModel => (SettingsViewModel)DataContext;
|
||||
|
||||
public SettingsPage() {
|
||||
public SettingsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
DataContext = Ioc.Default.GetService<SettingsViewModel>();
|
||||
}
|
||||
|
||||
private void SettingsPageOpenPathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) {
|
||||
private void SettingsPageOpenPathButton_Click(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.RoutedEventArgs e
|
||||
)
|
||||
{
|
||||
SettingsViewModel.OpenMusicFolder((string)(sender as HyperlinkButton)!.Tag);
|
||||
}
|
||||
|
||||
private async void SettingsPageRemovePathButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) {
|
||||
private async void SettingsPageRemovePathButton_Click(
|
||||
object sender,
|
||||
Microsoft.UI.Xaml.RoutedEventArgs e
|
||||
)
|
||||
{
|
||||
await ViewModel.RemoveFolderAsync((string)(sender as HyperlinkButton)!.Tag);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user