diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package).wapproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package).wapproj
index 2dfc48a..8bc7a88 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package).wapproj
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package).wapproj
@@ -1,150 +1,150 @@
-
- 15.0
-
-
-
- Debug
- x86
-
-
- Release
- x86
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
- Debug
- ARM64
-
-
- Release
- ARM64
-
-
-
- $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
- BetterLyrics.WinUI3\
-
-
-
- 6576cd19-ef92-4099-b37d-e2d8ebdb6bf5
- 10.0.26100.0
- 10.0.17763.0
- net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)
- zh-CN
- True
- ..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj
- False
- SHA256
- False
- True
- x86|x64
- True
- 0
- BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx
-
-
- Always
- en-US
-
-
- Always
- en-US
-
-
- Always
- en-US
-
-
- Always
- en-US
-
-
- Always
- en-US
-
-
- Always
- en-US
-
-
-
- Designer
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-True
- Properties\PublishProfiles\win-$(Platform).pubxml
-
-
-
-
-
-
-
+
+ 15.0
+
+
+
+ Debug
+ x86
+
+
+ Release
+ x86
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+ Debug
+ ARM64
+
+
+ Release
+ ARM64
+
+
+
+ $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
+ BetterLyrics.WinUI3\
+
+
+
+ 6576cd19-ef92-4099-b37d-e2d8ebdb6bf5
+ 10.0.26100.0
+ 10.0.17763.0
+ net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)
+ zh-CN
+ True
+ ..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj
+ False
+ SHA256
+ False
+ True
+ x86|x64
+ True
+ 0
+ BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx
+
+
+ Always
+ en-US
+
+
+ Always
+ en-US
+
+
+ Always
+ en-US
+
+
+ Always
+ en-US
+
+
+ Always
+ en-US
+
+
+ Always
+ en-US
+
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ True
+ Properties\PublishProfiles\win-$(Platform).pubxml
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
index 7a44072..ffff3cf 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj
@@ -49,7 +49,6 @@
-
@@ -66,14 +65,14 @@
-
+
-
-
+
+
-
-
+
+
@@ -82,20 +81,20 @@
-
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
index 0d635ac..a4039f3 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs
@@ -9,12 +9,6 @@ using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.Formats.Jpeg;
-using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Generic;
using System.IO;
@@ -52,7 +46,7 @@ namespace BetterLyrics.WinUI3.Helper
return RandomAccessStreamReference.CreateFromStream(stream);
}
- public static async Task CreateTextPlaceholderBytesAsync(int width, int height)
+ public static async Task CreateTextPlaceholderBytesAsync(int width, int height)
{
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
@@ -82,34 +76,29 @@ namespace BetterLyrics.WinUI3.Helper
}
// 保存为 PNG 并转为 byte[]
- using var stream = new InMemoryRandomAccessStream();
+ var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
- var buffer = new byte[stream.Size];
- using (var reader = new DataReader(stream.GetInputStreamAt(0)))
- {
- await reader.LoadAsync((uint)stream.Size);
- reader.ReadBytes(buffer);
- }
- return buffer;
+ stream.Seek(0);
+ return stream;
}
- public static Task GetAccentColorFromByteAsync(byte[] bytes, PaletteGeneratorType generatorType)
+ public static Task GetAccentColorFromByteAsync(BitmapDecoder decoder, PaletteGeneratorType generatorType)
{
return generatorType switch
{
- PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorFromByteAsync(bytes),
- PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorFromByteAsync(bytes),
- _ => throw new ArgumentOutOfRangeException("generatorType"),
+ PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorFromByteAsync(decoder),
+ PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorFromByteAsync(decoder),
+ _ => throw new ArgumentOutOfRangeException(nameof(generatorType)),
};
}
- public static Task GetAccentColorsFromByteAsync(byte[] bytes, int count, PaletteGeneratorType generatorType, bool? isDark = null)
+ public static Task GetAccentColorsFromByteAsync(BitmapDecoder decoder, int count, PaletteGeneratorType generatorType, bool? isDark = null)
{
return generatorType switch
{
- PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorsFromByteAsync(bytes, count, isDark),
- PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorsFromByteAsync(bytes, count, isDark),
- _ => throw new ArgumentOutOfRangeException("generatorType"),
+ PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorsFromByteAsync(decoder, count, isDark),
+ PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorsFromByteAsync(decoder, count, isDark),
+ _ => throw new ArgumentOutOfRangeException(nameof(generatorType)),
};
}
@@ -166,13 +155,12 @@ namespace BetterLyrics.WinUI3.Helper
// return stream;
//}
- public static async Task ToByteArrayAsync(IRandomAccessStreamReference streamRef)
+ public static async Task ToBufferAsync(IRandomAccessStreamReference streamRef)
{
using IRandomAccessStream stream = await streamRef.OpenReadAsync();
- using var reader = new DataReader(stream);
- await reader.LoadAsync((uint)stream.Size);
- byte[] buffer = new byte[stream.Size];
- reader.ReadBytes(buffer);
+ stream.Seek(0);
+ var buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
+ await stream.ReadAsync(buffer, (uint)stream.Size, InputStreamOptions.None);
return buffer;
}
@@ -193,55 +181,111 @@ namespace BetterLyrics.WinUI3.Helper
return (double)(sum / (pixels.Length / 4));
}
- public static async Task MakeSquareWithThemeColor(byte[] imageBytes, PaletteGeneratorType generatorType)
+ public static async Task MakeSquareWithThemeColor(IBuffer buffer, PaletteGeneratorType generatorType)
{
- using var image = Image.Load(imageBytes);
-
- if (image.Width == image.Height)
+ try
{
- // 已经是正方形,直接返回
- return imageBytes;
+ using var stream = new InMemoryRandomAccessStream();
+ await stream.WriteAsync(buffer);
+ var decoder = await BitmapDecoder.CreateAsync(stream);
+
+ if (decoder.PixelWidth == decoder.PixelHeight)
+ {
+ // 已经是正方形,直接返回
+ return buffer;
+ }
+
+ using var device = CanvasDevice.GetSharedDevice();
+ using var canvasBitmap = await CanvasBitmap.LoadAsync(device, stream);
+ var size = Math.Max(decoder.PixelWidth, decoder.PixelHeight);
+
+ var result = await GetAccentColorFromByteAsync(decoder, generatorType);
+ var color = Windows.UI.Color.FromArgb(255, (byte)result.Color.X, (byte)result.Color.Y, (byte)result.Color.Z);
+ using var renderTarget = new CanvasRenderTarget(device, size, size, 96);
+
+ int offsetX = (int)(size - decoder.PixelWidth) / 2;
+ int offsetY = (int)(size - decoder.PixelHeight) / 2;
+ using (var ds = renderTarget.CreateDrawingSession())
+ {
+ ds.FillRectangle(0, 0, size, size, color);
+ ds.DrawImage(canvasBitmap, offsetX, offsetY);
+ }
+
+ // 保存为 PNG 并转为 byte[]
+ stream.Seek(0);
+ stream.Size = 0;
+ await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
+ var newBuffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
+
+ await stream.ReadAsync(newBuffer, (uint)stream.Size, InputStreamOptions.None);
+ return newBuffer;
+ }
+ catch(Exception e)
+ {
+ throw e;
}
-
- int size = Math.Max(image.Width, image.Height);
-
- 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());
-
- using var square = new Image(size, size, themeColor);
-
- int offsetX = (size - image.Width) / 2;
- int offsetY = (size - image.Height) / 2;
-
- square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
-
- using var ms = new MemoryStream();
- square.Save(ms, new PngEncoder());
- return ms.ToArray();
}
- public static byte[] Resize(byte[] imageBytes, int size)
+ public static async Task Resize(IBuffer buffer, int size)
{
- using (Image image = Image.Load(imageBytes))
+ using var stream = new InMemoryRandomAccessStream();
+ await stream.WriteAsync(buffer);
+ var decoder = await BitmapDecoder.CreateAsync(stream);
+
+ var factor = Math.Max((double)size / decoder.PixelWidth, (double)size / decoder.PixelHeight);
+
+ var width = (uint)(decoder.PixelWidth * factor);
+ var height = (uint)(decoder.PixelHeight * factor);
+
+ if (factor > 1)
{
- var factor = Math.Max((double)size / image.Width, (double)size / image.Height);
-
- int width = (int)(image.Width * factor);
- int height = (int)(image.Height * factor);
-
- if (factor > 1)
+ var transform = new BitmapTransform()
{
- image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
- }
- else
- {
- image.Mutate(x => x.Resize(width, height, KnownResamplers.NearestNeighbor));
- }
+ ScaledWidth = width,
+ ScaledHeight = height,
+ InterpolationMode = BitmapInterpolationMode.Fant
+ };
+ var pixelData = await decoder.GetPixelDataAsync(
+ BitmapPixelFormat.Rgba8,
+ BitmapAlphaMode.Straight,
+ transform, ExifOrientationMode.RespectExifOrientation,
+ ColorManagementMode.ColorManageToSRgb);
+ var pixels = pixelData.DetachPixelData();
- using var ms = new MemoryStream();
- image.Save(ms, new JpegEncoder());
- return ms.ToArray();
+ stream.Seek(0);
+ stream.Size = 0;
+ var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
+ encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
+ await encoder.FlushAsync();
+ var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
+ stream.Seek(0);
+ await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
+ return output;
+ }
+ else
+ {
+ var transform = new BitmapTransform()
+ {
+ ScaledWidth = (uint)width,
+ ScaledHeight = (uint)height,
+ InterpolationMode = BitmapInterpolationMode.NearestNeighbor
+ };
+ var pixelData = await decoder.GetPixelDataAsync(
+ BitmapPixelFormat.Rgba8,
+ BitmapAlphaMode.Straight,
+ transform, ExifOrientationMode.RespectExifOrientation,
+ ColorManagementMode.ColorManageToSRgb);
+ var pixels = pixelData.DetachPixelData();
+
+ stream.Seek(0);
+ stream.Size = 0;
+ var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
+ encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96, 96, pixels);
+ await encoder.FlushAsync();
+ var output = new Windows.Storage.Streams.Buffer((uint)stream.Size);
+ stream.Seek(0);
+ await stream.ReadAsync(output, (uint)stream.Size, InputStreamOptions.None);
+ return output;
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PaletteHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PaletteHelper.cs
index 179f755..8c91863 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PaletteHelper.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/PaletteHelper.cs
@@ -1,7 +1,6 @@
-using Impressionist.Abstractions;
+using ColorThiefDotNet;
+using Impressionist.Abstractions;
using Impressionist.Implementations;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -16,44 +15,33 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class PaletteHelper
{
- public static async Task OctTreeGetAccentColorsFromByteAsync(byte[] bytes, int count, bool? isDark = null)
+ private static ColorThief colorThief = new();
+ public static async Task OctTreeGetAccentColorsFromByteAsync(BitmapDecoder decoder, 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 OctTreeGetAccentColorFromByteAsync(byte[] bytes)
+ public static async Task OctTreeGetAccentColorFromByteAsync(BitmapDecoder decoder)
{
- 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 MedianCutGetAccentColorFromByteAsync(byte[] bytes)
+ public static async Task MedianCutGetAccentColorFromByteAsync(BitmapDecoder decoder)
{
- using var image = Image.Load(bytes);
- var colorThief = new ColorThief.ImageSharp.ColorThief();
- var mainColor = colorThief.GetColor(image, 10, false);
+ var mainColor = await colorThief.GetColor(decoder, 10, false);
var theme = new ThemeColorResult(new Vector3(mainColor.Color.R, mainColor.Color.G, mainColor.Color.B), mainColor.IsDark);
- return Task.FromResult(theme);
+ return theme;
}
- public static Task MedianCutGetAccentColorsFromByteAsync(byte[] bytes, int count, bool? isDark = null)
+ public static async Task MedianCutGetAccentColorsFromByteAsync(BitmapDecoder decoder, int count, bool? isDark = null)
{
- using var image = Image.Load(bytes);
- var colorThief = new ColorThief.ImageSharp.ColorThief();
- var mainColor = colorThief.GetColor(image, 10, false);
+ var mainColor = await colorThief.GetColor(decoder, 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 palette = await colorThief.GetPalette(decoder, 255, 10, false);
var topColors = palette
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
.OrderByDescending(x => x.Population)
@@ -62,7 +50,7 @@ namespace BetterLyrics.WinUI3.Helper
.ToList();
var paletteResult = new PaletteResult(topColors, mainColor.IsDark, theme);
- return Task.FromResult(paletteResult);
+ return paletteResult;
}
public static async Task> GetPixelColor(BitmapDecoder bitmapDecoder)
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/AlbumArtSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/AlbumArtSearchService.cs
index 1ac9a89..3ba6551 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/AlbumArtSearchService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/AlbumArtSearchService.cs
@@ -11,9 +11,11 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
@@ -31,9 +33,9 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
_iTunesHttpClinet = new();
}
- public async Task SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token)
+ public async Task SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token)
{
- byte[]? result = null;
+ IBuffer? result = null;
try
{
@@ -47,15 +49,16 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
switch (provider.Provider)
{
case AlbumArtSearchProvider.Local:
- result = SearchFile(artist, title);
+ result = SearchFile(artist, title)?.AsBuffer();
break;
case AlbumArtSearchProvider.SMTC:
- result = bytesFromSMTC;
+ result = bufferFromSMTC;
break;
case AlbumArtSearchProvider.iTunes:
foreach (string countryCode in new List() { "us", "cn", "jp", "kr" })
{
- result = await SearchiTunesAsync(artist, album, title, countryCode);
+ var byteArray = await SearchiTunesAsync(artist, album, title, countryCode);
+ result = byteArray?.AsBuffer();
if (token.IsCancellationRequested) return result;
if (result != null) break;
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/IAlbumArtSearchService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/IAlbumArtSearchService.cs
index faee086..a505280 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/IAlbumArtSearchService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/AlbumArtSearchService/IAlbumArtSearchService.cs
@@ -4,11 +4,12 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public interface IAlbumArtSearchService
{
- Task SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token);
+ Task SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token);
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.AlbumArtUpdater.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.AlbumArtUpdater.cs
index bfce206..4563067 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.AlbumArtUpdater.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.AlbumArtUpdater.cs
@@ -33,38 +33,41 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
return;
}
- byte[]? bytes = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
+ IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
SongInfo?.PlayerId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
- _SMTCAlbumArtBytes,
+ _SMTCAlbumArtBuffer,
token
), token);
if (token.IsCancellationRequested) return;
+ BitmapDecoder? decoder = null;
- if (bytes == null)
+ if (buffer == null)
{
- bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
+ using var placeHolderStream = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
+ var tempBuffer = new Windows.Storage.Streams.Buffer((uint)placeHolderStream.Size);
+ await placeHolderStream.ReadAsync(tempBuffer, (uint)placeHolderStream.Size, InputStreamOptions.None);
+ buffer = tempBuffer;
token.ThrowIfCancellationRequested();
}
-
- bytes = await ImageHelper.MakeSquareWithThemeColor(bytes, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
+ buffer = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
using var stream = new InMemoryRandomAccessStream();
- await stream.WriteAsync(bytes.AsBuffer());
+ await stream.WriteAsync(buffer);
token.ThrowIfCancellationRequested();
- var decoder = await BitmapDecoder.CreateAsync(stream);
+ decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
albumArtSwBitmap = SoftwareBitmap.Copy(albumArtSwBitmap);
token.ThrowIfCancellationRequested();
- var albumArtLightAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(bytes, 4, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, false);
+ var albumArtLightAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(decoder, 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, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType, true);
+ var albumArtDarkAccentColors = await ImageHelper.GetAccentColorsFromByteAsync(decoder, 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));
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.cs
index 377314f..362a1db 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/MediaSessionsService/MediaSessionsService.cs
@@ -23,6 +23,7 @@ using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json;
using System.Threading.Tasks;
using Windows.Media.Control;
@@ -58,7 +59,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private readonly MediaManager _mediaManager = new();
private SongInfo? _cachedSongInfo;
- private byte[]? _SMTCAlbumArtBytes = null;
+ private IBuffer? _SMTCAlbumArtBuffer = null;
public event EventHandler? IsPlayingChanged;
public event EventHandler? TimelineChanged;
@@ -303,7 +304,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
StopSSE();
}
- _SMTCAlbumArtBytes = null;
+ _SMTCAlbumArtBuffer = null;
}
else
{
@@ -352,15 +353,15 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (sessionId == Constants.PlayerID.LXMusic && _lxMusicAlbumArtBytes != null)
{
- _SMTCAlbumArtBytes = _lxMusicAlbumArtBytes;
+ _SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
- _SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
+ _SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
else
{
- _SMTCAlbumArtBytes = null;
+ _SMTCAlbumArtBuffer = null;
}
}
@@ -532,7 +533,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_logger.LogInformation("LX Music Album Art URL: {url}", picUrl);
_lxMusicAlbumArtBytes = await ImageHelper.GetImageBytesFromUrlAsync(picUrl);
- _SMTCAlbumArtBytes = _lxMusicAlbumArtBytes;
+ _SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
UpdateAlbumArt();
}
}
diff --git a/BetterLyrics.sln b/BetterLyrics.sln
index 1bb41bd..5bdbb14 100644
--- a/BetterLyrics.sln
+++ b/BetterLyrics.sln
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.WinUI3", "Bett
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impressionist", "Impressionist\Impressionist\Impressionist.csproj", "{A678BCA5-03DE-71E4-73C1-388B7550E4E3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorThief.WinUI3", "ColorThief.WinUI3\ColorThief.WinUI3.csproj", "{8F2FE667-2D91-428E-0630-05E6330F9625}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -61,6 +63,18 @@ Global
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x64.Build.0 = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x86.ActiveCfg = Release|Any CPU
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x86.Build.0 = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x64.Build.0 = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Debug|x86.Build.0 = Debug|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|ARM64.Build.0 = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x64.ActiveCfg = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x64.Build.0 = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x86.ActiveCfg = Release|Any CPU
+ {8F2FE667-2D91-428E-0630-05E6330F9625}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ColorThief.WinUI3/CMap.cs b/ColorThief.WinUI3/CMap.cs
new file mode 100644
index 0000000..04459bb
--- /dev/null
+++ b/ColorThief.WinUI3/CMap.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ColorThiefDotNet
+{
+ ///
+ /// Color map
+ ///
+ internal class CMap
+ {
+ private readonly List vboxes = new List();
+ private List palette;
+
+ public void Push(VBox box)
+ {
+ palette = null;
+ vboxes.Add(box);
+ }
+
+ public List 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()
+ {
+ return vboxes.Count;
+ }
+
+ 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)
+ {
+ var d1 = double.MaxValue;
+ int[] pColor = null;
+
+ 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)
+ {
+ d1 = d2;
+ pColor = vbColor;
+ }
+ }
+ return pColor;
+ }
+
+ 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)
+ {
+ 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(max == null || thisValue > maxValue)
+ {
+ max = swatch;
+ maxValue = thisValue;
+ }
+ }
+ }
+
+ return max;
+ }
+
+ public Color FromRgb(int red, int green, int blue)
+ {
+ var color = new Color
+ {
+ A = 255,
+ R = (byte)red,
+ G = (byte)green,
+ B = (byte)blue
+ };
+
+ return color;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/Color.cs b/ColorThief.WinUI3/Color.cs
new file mode 100644
index 0000000..dffd499
--- /dev/null
+++ b/ColorThief.WinUI3/Color.cs
@@ -0,0 +1,94 @@
+using System;
+
+namespace ColorThiefDotNet
+{
+ ///
+ /// Defines a color in RGB space.
+ ///
+ public struct Color
+ {
+ ///
+ /// Get or Set the Alpha component value for sRGB.
+ ///
+ public byte A;
+
+ ///
+ /// Get or Set the Blue component value for sRGB.
+ ///
+ public byte B;
+
+ ///
+ /// Get or Set the Green component value for sRGB.
+ ///
+ public byte G;
+
+ ///
+ /// Get or Set the Red component value for sRGB.
+ ///
+ public byte R;
+
+ ///
+ /// Get HSL color.
+ ///
+ ///
+ public HslColor ToHsl()
+ {
+ const double toDouble = 1.0 / 255;
+ var r = toDouble * R;
+ var g = toDouble * G;
+ var b = toDouble * B;
+ var max = Math.Max(Math.Max(r, g), b);
+ var min = Math.Min(Math.Min(r, g), b);
+ var chroma = max - min;
+ double h1;
+
+ // ReSharper disable CompareOfFloatsByEqualityOperator
+ if(chroma == 0)
+ {
+ h1 = 0;
+ }
+ else if(max == r)
+ {
+ h1 = (g - b) / chroma % 6;
+ }
+ else if(max == g)
+ {
+ h1 = 2 + (b - r) / chroma;
+ }
+ else //if (max == b)
+ {
+ h1 = 4 + (r - g)/chroma;
+ }
+
+ var lightness = 0.5 * (max - min);
+ var saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs(2*lightness - 1));
+ HslColor ret;
+ ret.H = 60 * h1;
+ ret.S = saturation;
+ ret.L = lightness;
+ ret.A = toDouble * A;
+ return ret;
+ // ReSharper restore CompareOfFloatsByEqualityOperator
+ }
+
+ public string ToHexString()
+ {
+ return "#" + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
+ }
+
+ public string ToHexAlphaString()
+ {
+ return "#" + A.ToString("X2") + R.ToString("X2") + G.ToString("X2") + B.ToString("X2");
+ }
+
+ public override string ToString()
+ {
+ if(A == 255)
+ {
+ return ToHexString();
+ }
+
+ return ToHexAlphaString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/ColorThief.WinUI3.cs b/ColorThief.WinUI3/ColorThief.WinUI3.cs
new file mode 100644
index 0000000..a2f9dcc
--- /dev/null
+++ b/ColorThief.WinUI3/ColorThief.WinUI3.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Windows.Graphics.Imaging;
+
+namespace ColorThiefDotNet
+{
+ public partial class ColorThief
+ {
+ ///
+ /// Use the median cut algorithm to cluster similar colors and return the base color from the largest cluster.
+ ///
+ /// The source image.
+ ///
+ /// 1 is the highest quality settings. 10 is the default. There is
+ /// a trade-off between quality and speed. The bigger the number,
+ /// the faster a color will be returned but the greater the
+ /// likelihood that it will not be the visually most dominant color.
+ ///
+ /// if set to true [ignore white].
+ ///
+ public async Task 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)));
+
+ return dominantColor;
+ }
+
+ ///
+ /// Use the median cut algorithm to cluster similar colors.
+ ///
+ /// The source image.
+ /// The color count.
+ ///
+ /// 1 is the highest quality settings. 10 is the default. There is
+ /// a trade-off between quality and speed. The bigger the number,
+ /// the faster a color will be returned but the greater the
+ /// likelihood that it will not be the visually most dominant color.
+ ///
+ /// if set to true [ignore white].
+ ///
+ /// true
+ public async Task> 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)
+ {
+ var colors = cmap.GeneratePalette();
+ return colors;
+ }
+ return new List();
+ }
+
+ private async Task GetIntFromPixel(BitmapDecoder decoder)
+ {
+ var pixelsData = await decoder.GetPixelDataAsync();
+ var pixels = pixelsData.DetachPixelData();
+ return pixels;
+ }
+
+ private async Task GetPixelsFast(BitmapDecoder sourceImage, int quality, bool ignoreWhite)
+ {
+ if(quality < 1)
+ {
+ quality = DefaultQuality;
+ }
+
+ var pixels = await GetIntFromPixel(sourceImage);
+ var pixelCount = sourceImage.PixelWidth*sourceImage.PixelHeight;
+
+ return ConvertPixels(pixels, Convert.ToInt32(pixelCount), quality, ignoreWhite);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/ColorThief.WinUI3.csproj b/ColorThief.WinUI3/ColorThief.WinUI3.csproj
new file mode 100644
index 0000000..14fe016
--- /dev/null
+++ b/ColorThief.WinUI3/ColorThief.WinUI3.csproj
@@ -0,0 +1,14 @@
+
+
+ net8.0-windows10.0.19041.0
+ 10.0.17763.0
+ ColorThief.WinUI3
+ win-x86;win-x64;win-arm64
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ColorThief.WinUI3/ColorThief.cs b/ColorThief.WinUI3/ColorThief.cs
new file mode 100644
index 0000000..5025fbe
--- /dev/null
+++ b/ColorThief.WinUI3/ColorThief.cs
@@ -0,0 +1,77 @@
+using System;
+
+namespace ColorThiefDotNet
+{
+ public partial class ColorThief
+ {
+ public const int DefaultColorCount = 5;
+ public const int DefaultQuality = 10;
+ public const bool DefaultIgnoreWhite = true;
+ public const int ColorDepth = 4;
+
+ ///
+ /// Use the median cut algorithm to cluster similar colors.
+ ///
+ /// Pixel array.
+ /// The color count.
+ ///
+ private CMap GetColorMap(byte[][] pixelArray, int colorCount)
+ {
+ // Send array to quantize function which clusters values using median
+ // cut algorithm
+
+ if (colorCount > 0)
+ {
+ --colorCount;
+ }
+
+ var cmap = Mmcq.Quantize(pixelArray, colorCount);
+ return cmap;
+ }
+
+ 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 + ")");
+ }
+
+ // Store the RGB values in an array format suitable for quantize
+ // function
+
+ // numRegardedPixels must be rounded up to avoid an
+ // ArrayIndexOutOfBoundsException if all pixels are good.
+
+ var numRegardedPixels = (pixelCount + quality - 1) / quality;
+
+ var numUsedPixels = 0;
+ var pixelArray = new byte[numRegardedPixels][];
+
+ for(var i = 0; i < pixelCount; i += quality)
+ {
+ var offset = i * ColorDepth;
+ var b = pixels[offset];
+ var g = pixels[offset + 1];
+ var r = pixels[offset + 2];
+ var a = pixels[offset + 3];
+
+ // If pixel is mostly opaque and not white
+ if(a >= 125 && !(ignoreWhite && r > 250 && g > 250 && b > 250))
+ {
+ pixelArray[numUsedPixels] = new[] {r, g, b};
+ numUsedPixels++;
+ }
+ }
+
+ // Remove unused pixels from the array
+ var copy = new byte[numUsedPixels][];
+ Array.Copy(pixelArray, copy, numUsedPixels);
+ return copy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/HslColor.cs b/ColorThief.WinUI3/HslColor.cs
new file mode 100644
index 0000000..db681fd
--- /dev/null
+++ b/ColorThief.WinUI3/HslColor.cs
@@ -0,0 +1,28 @@
+namespace ColorThiefDotNet
+{
+ ///
+ /// Defines a color in Hue/Saturation/Lightness (HSL) space.
+ ///
+ public struct HslColor
+ {
+ ///
+ /// The Alpha/opacity in 0..1 range.
+ ///
+ public double A;
+
+ ///
+ /// The Hue in 0..360 range.
+ ///
+ public double H;
+
+ ///
+ /// The Lightness in 0..1 range.
+ ///
+ public double L;
+
+ ///
+ /// The Saturation in 0..1 range.
+ ///
+ public double S;
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/Mmcq.cs b/ColorThief.WinUI3/Mmcq.cs
new file mode 100644
index 0000000..1294b86
--- /dev/null
+++ b/ColorThief.WinUI3/Mmcq.cs
@@ -0,0 +1,387 @@
+using System;
+using System.Collections.Generic;
+
+namespace ColorThiefDotNet
+{
+ internal static class Mmcq
+ {
+ public const int Sigbits = 5;
+ public const int Rshift = 8 - Sigbits;
+ public const int Mult = 1 << Rshift;
+ public const int Histosize = 1 << (3 * Sigbits);
+ public const int VboxLength = 1 << Sigbits;
+ public const double FractByPopulation = 0.75;
+ public const int MaxIterations = 1000;
+ public const double WeightSaturation = 3d;
+ public const double WeightLuma = 6d;
+ public const double WeightPopulation = 1d;
+ private static readonly VBoxComparer ComparatorProduct = new VBoxComparer();
+ private static readonly VBoxCountComparer ComparatorCount = new VBoxCountComparer();
+
+ public static int GetColorIndex(int r, int g, int b)
+ {
+ return (r << (2 * Sigbits)) + (g << Sigbits) + b;
+ }
+
+ ///
+ /// Gets the histo.
+ ///
+ /// The pixels.
+ /// Histo (1-d array, giving the number of pixels in each quantized region of color space), or null on error.
+ private static int[] GetHisto(IEnumerable pixels)
+ {
+ var histo = new int[Histosize];
+
+ foreach(var pixel in pixels)
+ {
+ var rval = pixel[0] >> Rshift;
+ var gval = pixel[1] >> Rshift;
+ var bval = pixel[2] >> Rshift;
+ var index = GetColorIndex(rval, gval, bval);
+ histo[index]++;
+ }
+ return histo;
+ }
+
+ private static VBox VboxFromPixels(IList 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++)
+ {
+ var pixel = pixels[i];
+ var rval = pixel[0] >> Rshift;
+ var gval = pixel[1] >> Rshift;
+ var bval = pixel[2] >> Rshift;
+
+ if(rval < rmin)
+ {
+ rmin = rval;
+ }
+ else if(rval > rmax)
+ {
+ rmax = rval;
+ }
+
+ if(gval < gmin)
+ {
+ gmin = gval;
+ }
+ else if(gval > gmax)
+ {
+ gmax = gval;
+ }
+
+ if(bval < bmin)
+ {
+ bmin = bval;
+ }
+ else if(bval > bmax)
+ {
+ bmax = bval;
+ }
+ }
+
+ return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo);
+ }
+
+ private static VBox[] DoCut(char color, VBox vbox, IList partialsum, IList lookaheadsum, int total)
+ {
+ int vboxDim1;
+ int vboxDim2;
+
+ switch(color)
+ {
+ case 'r':
+ vboxDim1 = vbox.R1;
+ vboxDim2 = vbox.R2;
+ break;
+ case 'g':
+ vboxDim1 = vbox.G1;
+ vboxDim2 = vbox.G2;
+ break;
+ default:
+ vboxDim1 = vbox.B1;
+ vboxDim2 = vbox.B2;
+ break;
+ }
+
+ 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)));
+
+ // avoid 0-count boxes
+ while(d2 < 0 || partialsum[d2] <= 0)
+ {
+ d2++;
+ }
+ var count2 = lookaheadsum[d2];
+ while(count2 == 0 && d2 > 0 && partialsum[d2 - 1] > 0)
+ {
+ count2 = lookaheadsum[--d2];
+ }
+
+ // set dimensions
+ switch(color)
+ {
+ case 'r':
+ vbox1.R2 = d2;
+ vbox2.R1 = d2 + 1;
+ break;
+ case 'g':
+ vbox1.G2 = d2;
+ vbox2.G1 = d2 + 1;
+ break;
+ default:
+ vbox1.B2 = d2;
+ vbox2.B1 = d2 + 1;
+ break;
+ }
+
+ return new[] {vbox1, vbox2};
+ }
+ }
+
+ throw new Exception("VBox can't be cut");
+ }
+
+ private static VBox[] MedianCutApply(IList histo, VBox vbox)
+ {
+ if(vbox.Count(false) == 0)
+ {
+ return null;
+ }
+ if(vbox.Count(false) == 1)
+ {
+ return new[] {vbox.Clone(), null};
+ }
+
+ // only one pixel, no split
+
+ var rw = vbox.R2 - vbox.R1 + 1;
+ var gw = vbox.G2 - vbox.G1 + 1;
+ var bw = vbox.B2 - vbox.B1 + 1;
+ var maxw = Math.Max(Math.Max(rw, gw), bw);
+
+ // Find the partial sum arrays along the selected axis.
+ var total = 0;
+ var partialsum = new int[VboxLength];
+ // -1 = not set / 0 = 0
+ 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++)
+ {
+ lookaheadsum[l] = -1;
+ }
+
+ int i, j, k, sum, index;
+
+ 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++)
+ {
+ index = GetColorIndex(i, j, k);
+ sum += histo[index];
+ }
+ }
+ total += sum;
+ partialsum[i] = total;
+ }
+ }
+ 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++)
+ {
+ index = GetColorIndex(j, i, k);
+ sum += histo[index];
+ }
+ }
+ total += sum;
+ partialsum[i] = total;
+ }
+ }
+ 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++)
+ {
+ index = GetColorIndex(j, k, i);
+ sum += histo[index];
+ }
+ }
+ total += sum;
+ partialsum[i] = total;
+ }
+ }
+
+ 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);
+ }
+
+ ///
+ /// Inner function to do the iteration.
+ ///
+ /// The lh.
+ /// The comparator.
+ /// The target.
+ /// The histo.
+ /// vbox1 not defined; shouldn't happen!
+ private static void Iter(List lh, IComparer comparator, int target, IList histo)
+ {
+ var ncolors = 1;
+ var niters = 0;
+
+ while(niters < MaxIterations)
+ {
+ var vbox = lh[lh.Count - 1];
+ if(vbox.Count(false) == 0)
+ {
+ lh.Sort(comparator);
+ niters++;
+ continue;
+ }
+
+ lh.RemoveAt(lh.Count - 1);
+
+ // do the cut
+ var vboxes = MedianCutApply(histo, vbox);
+ var vbox1 = vboxes[0];
+ var vbox2 = vboxes[1];
+
+ if(vbox1 == null)
+ {
+ throw new Exception(
+ "vbox1 not defined; shouldn't happen!");
+ }
+
+ lh.Add(vbox1);
+ if(vbox2 != null)
+ {
+ lh.Add(vbox2);
+ ncolors++;
+ }
+ lh.Sort(comparator);
+
+ if(ncolors >= target)
+ {
+ return;
+ }
+ if(niters++ > MaxIterations)
+ {
+ return;
+ }
+ }
+ }
+
+ public static CMap Quantize(byte[][] pixels, int maxcolors)
+ {
+ // short-circuit
+ if(pixels.Length == 0 || maxcolors < 2 || maxcolors > 256)
+ {
+ return null;
+ }
+
+ var histo = GetHisto(pixels);
+
+ // get the beginning vbox from the colors
+ var vbox = VboxFromPixels(pixels, histo);
+ var pq = new List {vbox};
+
+ // Round up to have the same behaviour as in JavaScript
+ var target = (int)Math.Ceiling(FractByPopulation * maxcolors);
+
+ // first set of colors, sorted by population
+ Iter(pq, ComparatorCount, target, histo);
+
+ // Re-sort by the product of pixel occupancy times the size in color
+ // space.
+ pq.Sort(ComparatorProduct);
+
+ // next set - generate the median cuts using the (npix * vol) sorting.
+ Iter(pq, ComparatorProduct, maxcolors - pq.Count, histo);
+
+ // Reverse to put the highest elements first into the color map
+ pq.Reverse();
+
+ // calculate the actual colors
+ var cmap = new CMap();
+ 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);
+ }
+
+ private static double WeightedMean(params double[] values)
+ {
+ double sum = 0;
+ double sumWeight = 0;
+
+ for(var i = 0; i < values.Length; i += 2)
+ {
+ var value = values[i];
+ var weight = values[i + 1];
+
+ sum += value * weight;
+ sumWeight += weight;
+ }
+
+ return sum / sumWeight;
+ }
+
+ private static double InvertDiff(double value, double targetValue)
+ {
+ return 1 - Math.Abs(value - targetValue);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/QuantizedColor.cs b/ColorThief.WinUI3/QuantizedColor.cs
new file mode 100644
index 0000000..7561be8
--- /dev/null
+++ b/ColorThief.WinUI3/QuantizedColor.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace ColorThiefDotNet
+{
+ public class QuantizedColor
+ {
+ public QuantizedColor(Color color, int population)
+ {
+ Color = color;
+ Population = population;
+ IsDark = CalculateYiqLuma(color) < 128;
+ }
+
+ public Color Color { get; private set; }
+ 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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/ColorThief.WinUI3/VBox.cs b/ColorThief.WinUI3/VBox.cs
new file mode 100644
index 0000000..8b0ff74
--- /dev/null
+++ b/ColorThief.WinUI3/VBox.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+
+namespace ColorThiefDotNet
+{
+ ///
+ /// 3D color space box.
+ ///
+ internal class VBox
+ {
+ private readonly int[] histo;
+ private int[] avg;
+ public int B1;
+ public int B2;
+ private int? count;
+ public int G1;
+ public int G2;
+ public int R1;
+ public int R2;
+ private int? volume;
+
+ public VBox(int r1, int r2, int g1, int g2, int b1, int b2, int[] histo)
+ {
+ R1 = r1;
+ R2 = r2;
+ G1 = g1;
+ G2 = g2;
+ B1 = b1;
+ B2 = b2;
+
+ this.histo = histo;
+ }
+
+ 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)
+ {
+ var npix = 0;
+ int i;
+
+ for(i = R1; i <= R2; i++)
+ {
+ int j;
+ for(j = G1; j <= G2; j++)
+ {
+ int k;
+ for(k = B1; k <= B2; k++)
+ {
+ var index = Mmcq.GetColorIndex(i, j, k);
+ npix += histo[index];
+ }
+ }
+ }
+
+ count = npix;
+ }
+
+ return count.Value;
+ }
+
+ public VBox Clone()
+ {
+ return new VBox(R1, R2, G1, G2, B1, B2, histo);
+ }
+
+ public int[] Avg(bool force)
+ {
+ if(avg == null || force)
+ {
+ var ntot = 0;
+
+ var rsum = 0;
+ var gsum = 0;
+ var bsum = 0;
+
+ int i;
+
+ for(i = R1; i <= R2; i++)
+ {
+ int j;
+ for(j = G1; j <= G2; j++)
+ {
+ int k;
+ for(k = B1; k <= B2; k++)
+ {
+ var histoindex = Mmcq.GetColorIndex(i, j, k);
+ var hval = histo[histoindex];
+ ntot += hval;
+ rsum += Convert.ToInt32((hval * (i + 0.5) * Mmcq.Mult));
+ gsum += Convert.ToInt32((hval * (j + 0.5) * Mmcq.Mult));
+ bsum += Convert.ToInt32((hval * (k + 0.5) * Mmcq.Mult));
+ }
+ }
+ }
+
+ if(ntot > 0)
+ {
+ avg = new[]
+ {
+ Math.Abs(rsum / ntot), Math.Abs(gsum / ntot),
+ Math.Abs(bsum / ntot)
+ };
+ }
+ 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)
+ };
+ }
+ }
+
+ return avg;
+ }
+
+ public bool Contains(int[] pixel)
+ {
+ var rval = pixel[0] >> Mmcq.Rshift;
+ var gval = pixel[1] >> Mmcq.Rshift;
+ var bval = pixel[2] >> Mmcq.Rshift;
+
+ return rval >= R1 && rval <= R2 && gval >= G1 && gval <= G2 && bval >= B1 && bval <= B2;
+ }
+ }
+
+ internal class VBoxCountComparer : IComparer
+ {
+ 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
+ {
+ public int Compare(VBox x, VBox y)
+ {
+ var aCount = x.Count(false);
+ var bCount = y.Count(false);
+ var aVolume = x.Volume(false);
+ var bVolume = y.Volume(false);
+
+ // Otherwise sort by products
+ var a = aCount * aVolume;
+ var b = bCount * bVolume;
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+ }
+}
\ No newline at end of file