mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 19:24:55 +08:00
chores: Support Multiple Palette Generation Method
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:enums="using:BetterLyrics.WinUI3.Enums"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:uc="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
@@ -115,6 +116,13 @@
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<controls:SettingsCard x:Uid="SettingsPagePaletteGeneratorType" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsBackgroundSettings.PaletteGeneratorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem Content="MedianCut" />
|
||||
<ComboBoxItem Content="OctTree" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageSpectrumLayer"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum PaletteGeneratorType
|
||||
{
|
||||
MedianCut,
|
||||
OctTree
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Impressionist.Abstractions;
|
||||
using Impressionist.Implementations;
|
||||
@@ -91,26 +93,24 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static async Task<PaletteResult> GetAccentColorsFromByteAsync(byte[] bytes, int count, bool? isDark = null)
|
||||
|
||||
public static Task<ThemeColorResult> GetAccentColorFromByteAsync(byte[] bytes, PaletteGeneratorType generatorType)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var palette = await PaletteGenerators.OctTreePaletteGenerator.CreatePalette(colors, count, false, isDark);
|
||||
return palette;
|
||||
return generatorType switch
|
||||
{
|
||||
PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorFromByteAsync(bytes),
|
||||
PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorFromByteAsync(bytes),
|
||||
_ => throw new ArgumentOutOfRangeException("generatorType"),
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<ThemeColorResult> GetAccentColorFromByteAsync(byte[] bytes)
|
||||
public static Task<PaletteResult> GetAccentColorsFromByteAsync(byte[] bytes, int count, PaletteGeneratorType generatorType, bool? isDark = null)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var theme = await PaletteGenerators.OctTreePaletteGenerator.CreateThemeColor(colors, false);
|
||||
return theme;
|
||||
return generatorType switch
|
||||
{
|
||||
PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorsFromByteAsync(bytes, count, isDark),
|
||||
PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorsFromByteAsync(bytes, count, isDark),
|
||||
_ => throw new ArgumentOutOfRangeException("generatorType"),
|
||||
};
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
@@ -193,7 +193,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return (double)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static async Task<byte[]> MakeSquareWithThemeColor(byte[] imageBytes)
|
||||
public static async Task<byte[]> MakeSquareWithThemeColor(byte[] imageBytes, PaletteGeneratorType generatorType)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(imageBytes);
|
||||
|
||||
@@ -205,7 +205,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
|
||||
var result = await GetAccentColorFromByteAsync(imageBytes);
|
||||
var result = await GetAccentColorFromByteAsync(imageBytes, generatorType);
|
||||
var color = Windows.UI.Color.FromArgb(255, (byte)result.Color.X, (byte)result.Color.Y, (byte)result.Color.Z);
|
||||
var themeColor = Rgba32.ParseHex(color.ToHex());
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using Impressionist.Abstractions;
|
||||
using Impressionist.Implementations;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PaletteHelper
|
||||
{
|
||||
public static async Task<PaletteResult> OctTreeGetAccentColorsFromByteAsync(byte[] bytes, int count, bool? isDark = null)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var palette = await PaletteGenerators.OctTreePaletteGenerator.CreatePalette(colors, count, false, isDark);
|
||||
return palette;
|
||||
}
|
||||
|
||||
public static async Task<ThemeColorResult> OctTreeGetAccentColorFromByteAsync(byte[] bytes)
|
||||
{
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
stream.Seek(0);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var theme = await PaletteGenerators.OctTreePaletteGenerator.CreateThemeColor(colors, false);
|
||||
return theme;
|
||||
}
|
||||
|
||||
public static Task<ThemeColorResult> MedianCutGetAccentColorFromByteAsync(byte[] bytes)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(bytes);
|
||||
var colorThief = new ColorThief.ImageSharp.ColorThief();
|
||||
var mainColor = colorThief.GetColor(image, 10, false);
|
||||
var theme = new ThemeColorResult(new Vector3(mainColor.Color.R, mainColor.Color.G, mainColor.Color.B), mainColor.IsDark);
|
||||
return Task.FromResult(theme);
|
||||
}
|
||||
|
||||
public static Task<PaletteResult> MedianCutGetAccentColorsFromByteAsync(byte[] bytes, int count, bool? isDark = null)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(bytes);
|
||||
var colorThief = new ColorThief.ImageSharp.ColorThief();
|
||||
var mainColor = colorThief.GetColor(image, 10, false);
|
||||
var theme = new ThemeColorResult(new Vector3(mainColor.Color.R, mainColor.Color.G, mainColor.Color.B), mainColor.IsDark);
|
||||
var palette = colorThief.GetPalette(image, 255, 10, false);
|
||||
var topColors = palette
|
||||
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
|
||||
.OrderByDescending(x => x.Population)
|
||||
.Select(x => new Vector3(x.Color.R, x.Color.G, x.Color.B))
|
||||
.Take(count)
|
||||
.ToList();
|
||||
var paletteResult = new PaletteResult(topColors, mainColor.IsDark, theme);
|
||||
|
||||
return Task.FromResult(paletteResult);
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
{
|
||||
var pixelDataProvider = await bitmapDecoder.GetPixelDataAsync();
|
||||
var pixels = pixelDataProvider.DetachPixelData();
|
||||
var count = bitmapDecoder.PixelWidth * bitmapDecoder.PixelHeight;
|
||||
var vector = new Dictionary<Vector3, int>();
|
||||
for (int i = 0; i < count; i += 10)
|
||||
{
|
||||
var offset = i * 4;
|
||||
var b = pixels[offset];
|
||||
var g = pixels[offset + 1];
|
||||
var r = pixels[offset + 2];
|
||||
var a = pixels[offset + 3];
|
||||
if (a == 0) continue;
|
||||
var color = new Vector3(r, g, b);
|
||||
if (vector.ContainsKey(color))
|
||||
{
|
||||
vector[color]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector[color] = 1;
|
||||
}
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -27,6 +28,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSpectrumOverlayEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PaletteGeneratorType PaletteGeneratorType { get; set; } = PaletteGeneratorType.OctTree;
|
||||
|
||||
public LyricsBackgroundSettings() { }
|
||||
|
||||
public object Clone()
|
||||
@@ -39,6 +42,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
PureColorOverlayOpacity = this.PureColorOverlayOpacity,
|
||||
CoverOverlaySpeed = this.CoverOverlaySpeed,
|
||||
CoverAcrylicEffectAmount = this.CoverAcrylicEffectAmount,
|
||||
PaletteGeneratorType = this.PaletteGeneratorType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
bytes = await ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
bytes = await ImageHelper.MakeSquareWithThemeColor(bytes, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
@@ -62,9 +62,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
albumArtSwBitmap = SoftwareBitmap.Copy(albumArtSwBitmap);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtLightAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(bytes, 4, false);
|
||||
var albumArtLightAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(bytes, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
|
||||
var lightColorBytes = albumArtLightAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
|
||||
var albumArtDarkAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(bytes, 4, true);
|
||||
var albumArtDarkAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(bytes, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
|
||||
var darkColorBytes = albumArtDarkAccentColors.Palette.Select(t => Windows.UI.Color.FromArgb(255, (byte)t.X, (byte)t.Y, (byte)t.Z)).ToList();
|
||||
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, lightColorBytes, darkColorBytes));
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>Open in file explorer</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>Palette Generator Type</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>This folder is already included in the existing folder and does not need to be added again</value>
|
||||
</data>
|
||||
|
||||
@@ -1012,6 +1012,9 @@
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>ファイルエクスプローラーで開きます</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>パレット生成器タイプ</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>このフォルダーは既存のフォルダーに既に含まれており、再度追加する必要はありません</value>
|
||||
</data>
|
||||
|
||||
@@ -1012,6 +1012,9 @@
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>파일 탐색기에서 열립니다</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>팔레트 생성기 유형</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>이 폴더는 이미 기존 폴더에 포함되어 있으며 다시 추가 할 필요가 없습니다.</value>
|
||||
</data>
|
||||
|
||||
@@ -1012,6 +1012,9 @@
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>在文件资源管理器中打开</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>取色算法</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>该文件夹已包含在已有文件夹中,无需再次添加</value>
|
||||
</data>
|
||||
|
||||
@@ -1012,6 +1012,9 @@
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>在檔案總管中開啟</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>取色算法</value>
|
||||
</data>
|
||||
<data name="SettingsPagePathBeIncludedInfo" xml:space="preserve">
|
||||
<value>該資料夾已包含在已有資料夾中,無需再次添加</value>
|
||||
</data>
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Impressionist.Abstractions
|
||||
public List<Vector3> Palette { get; } = new List<Vector3>();
|
||||
public bool PaletteIsDark { get; }
|
||||
public ThemeColorResult ThemeColor { get; }
|
||||
internal PaletteResult(List<Vector3> palette, bool paletteIsDark, ThemeColorResult themeColor)
|
||||
public PaletteResult(List<Vector3> palette, bool paletteIsDark, ThemeColorResult themeColor)
|
||||
{
|
||||
Palette = palette;
|
||||
PaletteIsDark = paletteIsDark;
|
||||
@@ -19,7 +19,7 @@ namespace Impressionist.Abstractions
|
||||
{
|
||||
public Vector3 Color { get; }
|
||||
public bool ColorIsDark { get; }
|
||||
internal ThemeColorResult(Vector3 color, bool colorIsDark)
|
||||
public ThemeColorResult(Vector3 color, bool colorIsDark)
|
||||
{
|
||||
Color = color;
|
||||
ColorIsDark = colorIsDark;
|
||||
|
||||
Reference in New Issue
Block a user