Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d25920c697 | ||
|
|
639c4171dd | ||
|
|
95a1d65c50 | ||
|
|
feb4f47db9 | ||
|
|
81f2c4e34a | ||
|
|
48176cce0f | ||
|
|
77d41c4813 | ||
|
|
8d1be745ac | ||
|
|
978702b6ae | ||
|
|
0e1f487ac1 | ||
|
|
3c8775d2cd | ||
|
|
7c39214f2d | ||
|
|
b7952f5eef | ||
|
|
8c0d3667e4 | ||
|
|
e1f900a9e6 | ||
|
|
212c1fbcdd | ||
|
|
2cb04fa1b7 | ||
|
|
8ab3a53a38 | ||
|
|
3b4d5b9668 | ||
|
|
2409165b91 | ||
|
|
fe1eb2882e | ||
|
|
51dd58fc74 | ||
|
|
0f5f663b32 | ||
|
|
4815c32dba | ||
|
|
3f5a0a0d73 | ||
|
|
1b34b28fbe | ||
|
|
fe4d67c1f1 | ||
|
|
1ae00257a1 | ||
|
|
814de1a4a5 | ||
|
|
28b568e7a4 | ||
|
|
786d23b320 | ||
|
|
a394527f80 | ||
|
|
80422376c3 | ||
|
|
5114c83843 | ||
|
|
835e0d34fc | ||
|
|
7ab833a53a | ||
|
|
d348a30237 | ||
|
|
9debdc76f9 |
@@ -1,150 +1,150 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
|
||||
<Content Include="Images\LargeTile.scale-100.png" />
|
||||
<Content Include="Images\LargeTile.scale-125.png" />
|
||||
<Content Include="Images\LargeTile.scale-150.png" />
|
||||
<Content Include="Images\LargeTile.scale-200.png" />
|
||||
<Content Include="Images\LargeTile.scale-400.png" />
|
||||
<Content Include="Images\SmallTile.scale-100.png" />
|
||||
<Content Include="Images\SmallTile.scale-125.png" />
|
||||
<Content Include="Images\SmallTile.scale-150.png" />
|
||||
<Content Include="Images\SmallTile.scale-200.png" />
|
||||
<Content Include="Images\SmallTile.scale-400.png" />
|
||||
<Content Include="Images\SplashScreen.scale-100.png" />
|
||||
<Content Include="Images\SplashScreen.scale-125.png" />
|
||||
<Content Include="Images\SplashScreen.scale-150.png" />
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\SplashScreen.scale-400.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-100.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-125.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-150.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
|
||||
<Content Include="Images\StoreLogo.scale-100.png" />
|
||||
<Content Include="Images\StoreLogo.scale-125.png" />
|
||||
<Content Include="Images\StoreLogo.scale-150.png" />
|
||||
<Content Include="Images\StoreLogo.scale-200.png" />
|
||||
<Content Include="Images\StoreLogo.scale-400.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-400.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
|
||||
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' < '15.0'">
|
||||
<VisualStudioVersion>15.0</VisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x86">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x86">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
|
||||
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
|
||||
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
|
||||
<DefaultLanguage>zh-CN</DefaultLanguage>
|
||||
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
|
||||
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
|
||||
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
|
||||
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
|
||||
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
|
||||
<GenerateTestArtifacts>True</GenerateTestArtifacts>
|
||||
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
|
||||
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
|
||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<AppxBundle>Always</AppxBundle>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AppxManifest Include="Package.appxmanifest">
|
||||
<SubType>Designer</SubType>
|
||||
</AppxManifest>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
|
||||
<Content Include="Images\LargeTile.scale-100.png" />
|
||||
<Content Include="Images\LargeTile.scale-125.png" />
|
||||
<Content Include="Images\LargeTile.scale-150.png" />
|
||||
<Content Include="Images\LargeTile.scale-200.png" />
|
||||
<Content Include="Images\LargeTile.scale-400.png" />
|
||||
<Content Include="Images\SmallTile.scale-100.png" />
|
||||
<Content Include="Images\SmallTile.scale-125.png" />
|
||||
<Content Include="Images\SmallTile.scale-150.png" />
|
||||
<Content Include="Images\SmallTile.scale-200.png" />
|
||||
<Content Include="Images\SmallTile.scale-400.png" />
|
||||
<Content Include="Images\SplashScreen.scale-100.png" />
|
||||
<Content Include="Images\SplashScreen.scale-125.png" />
|
||||
<Content Include="Images\SplashScreen.scale-150.png" />
|
||||
<Content Include="Images\SplashScreen.scale-200.png" />
|
||||
<Content Include="Images\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Images\SplashScreen.scale-400.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Square150x150Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-100.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-125.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-150.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Images\Square44x44Logo.scale-400.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
|
||||
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
|
||||
<Content Include="Images\StoreLogo.scale-100.png" />
|
||||
<Content Include="Images\StoreLogo.scale-125.png" />
|
||||
<Content Include="Images\StoreLogo.scale-150.png" />
|
||||
<Content Include="Images\StoreLogo.scale-200.png" />
|
||||
<Content Include="Images\StoreLogo.scale-400.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-100.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-125.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-150.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-200.png" />
|
||||
<Content Include="Images\Wide310x150Logo.scale-400.png" />
|
||||
<None Include="Package.StoreAssociation.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
|
||||
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251003001" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
|
||||
</Project>
|
||||
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.83.0" />
|
||||
Version="1.0.85.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<LyricsSearchControlViewModel>()
|
||||
.AddSingleton<LyricsWindowSettingsControlViewModel>()
|
||||
.AddSingleton<LyricsWindowSwitchControlViewModel>()
|
||||
.AddSingleton<LyricsWindowSwitchWindowViewModel>()
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
|
||||
<PackageReference Include="ColorThief.ImageSharp" Version="1.0.0" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
|
||||
@@ -66,14 +65,14 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="csharp-pinyin" Version="1.0.1" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.2" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251003001" />
|
||||
<PackageReference Include="NAudio.Wasapi" Version="2.2.1" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
|
||||
@@ -82,18 +81,21 @@
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.10" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.1.6" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.1.6" />
|
||||
<PackageReference Include="WinUIEx" Version="2.6.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.2.0" />
|
||||
<PackageReference Include="Vanara.PInvoke.CoreAudio" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.DwmApi" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.Gdi32" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.Shell32" Version="4.2.1" />
|
||||
<PackageReference Include="Vanara.PInvoke.User32" Version="4.2.1" />
|
||||
<PackageReference Include="WinUIEx" Version="2.9.0" />
|
||||
<PackageReference Include="z440.atl.core" Version="7.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ColorThief.WinUI3\ColorThief.WinUI3.csproj" />
|
||||
<ProjectReference Include="..\..\Impressionist\Impressionist\Impressionist.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Rendering\InAppLyricsRenderer.xaml">
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace BetterLyrics.WinUI3.Constants
|
||||
public static class Link
|
||||
{
|
||||
public const string GitHubUrl = "https://github.com/jayfunc/BetterLyrics";
|
||||
public const string FAQUrl = $"{GitHubUrl}/blob/dev/FAQ/FAQ.md";
|
||||
public const string FAQUrl = $"{GitHubUrl}/blob/dev/FAQ/index.md";
|
||||
public const string QQGroupUrl = "https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info";
|
||||
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
|
||||
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoSize">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoAdjust">
|
||||
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard IsEnabled="{x:Bind AlbumArtLayoutSettings.AutoAlbumArtSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
|
||||
@@ -67,14 +67,26 @@
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsFontSize" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<local:ExtendedSlider
|
||||
Default="18"
|
||||
Frequency="2"
|
||||
Maximum="72"
|
||||
Minimum="8"
|
||||
Value="{x:Bind AlbumArtLayoutSettings.SongInfoFontSize, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsFontSize"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoAdjust">
|
||||
<ToggleSwitch IsOn="{x:Bind AlbumArtLayoutSettings.IsAutoSongInfoFontSize, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard>
|
||||
<local:ExtendedSlider
|
||||
Default="18"
|
||||
Frequency="2"
|
||||
IsEnabled="{x:Bind AlbumArtLayoutSettings.IsAutoSongInfoFontSize, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
Maximum="72"
|
||||
Minimum="8"
|
||||
Value="{x:Bind AlbumArtLayoutSettings.SongInfoFontSize, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageShowTitle"
|
||||
|
||||
@@ -21,23 +21,7 @@
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="{ThemeResource AccentAcrylicBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="4">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind LyricsWindowStatus.IsBorderless, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="BorderThickness" Value="0" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind LyricsWindowStatus.IsBorderless, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="BorderThickness" Value="1" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Grid>
|
||||
CornerRadius="4" />
|
||||
<!-- Is default -->
|
||||
<Grid
|
||||
Margin="4"
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
Margin="3,-2,3,0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="2">
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind Value, Mode=OneWay}" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind RoundedValue, Mode=OneWay}" />
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind Unit, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
|
||||
private static readonly DependencyProperty RoundedValueProperty =
|
||||
DependencyProperty.Register(nameof(Value), typeof(string), typeof(ExtendedSlider), new PropertyMetadata(default));
|
||||
public static readonly DependencyProperty DefaultProperty =
|
||||
DependencyProperty.Register(nameof(Default), typeof(double), typeof(ExtendedSlider), new PropertyMetadata(default));
|
||||
public static readonly DependencyProperty ResetButtonVisibilityProperty =
|
||||
@@ -93,7 +95,16 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
public double Value
|
||||
{
|
||||
get => (double)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
set
|
||||
{
|
||||
SetValue(ValueProperty, value);
|
||||
SetValue(RoundedValueProperty, value.ToString("F1").Replace(".0", ""));
|
||||
}
|
||||
}
|
||||
private string RoundedValue
|
||||
{
|
||||
get => (string)GetValue(RoundedValueProperty);
|
||||
set => SetValue(RoundedValueProperty, value);
|
||||
}
|
||||
public double Default
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
@@ -113,6 +114,13 @@
|
||||
Value="{x:Bind LyricsBackgroundSettings.FluidOverlayOpacity, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPagePaletteGeneratorType">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsBackgroundSettings.PaletteGeneratorType, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageMedianCut" />
|
||||
<ComboBoxItem x:Uid="SettingsPageOctTree" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageDynamicLyricsFontSize">
|
||||
<controls:SettingsCard x:Uid="SettingsPageAutoAdjust">
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard IsEnabled="{x:Bind LyricsStyleSettings.IsDynamicLyricsFontSize, Converter={StaticResource BoolNegationConverter}, Mode=OneWay}">
|
||||
|
||||
@@ -144,7 +144,10 @@
|
||||
IsExpanded="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageEnvColorSample" Header="Environment color sample mode">
|
||||
<controls:SettingsCard
|
||||
x:Uid="SettingsPageEnvColorSample"
|
||||
Header="Environment color sample mode"
|
||||
IsEnabled="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment, Mode=OneWay}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
|
||||
@@ -314,7 +317,7 @@
|
||||
<TextBlock x:Uid="LyricsWindowSettingsControlCurrentLyricsWindowConfig" Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
|
||||
<ListView
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.HorizontalScrollMode="Enabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
|
||||
@@ -16,16 +16,27 @@
|
||||
VerticalAlignment="Center"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
CornerRadius="12">
|
||||
<FontIcon
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="" />
|
||||
<Button
|
||||
Margin="12"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Click="Button_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<Button.KeyboardAccelerators>
|
||||
<KeyboardAccelerator Key="Escape" />
|
||||
</Button.KeyboardAccelerators>
|
||||
</Button>
|
||||
|
||||
<ListView
|
||||
Margin="48"
|
||||
Margin="48,56"
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.WindowBoundsRecords, Mode=OneWay}"
|
||||
SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus, Mode=TwoWay}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@@ -46,5 +57,20 @@
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<StackPanel
|
||||
Margin="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<FontIcon
|
||||
Margin="0,1,0,0"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="LyricsWindowSwitchWindowHelp" Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
@@ -32,13 +33,21 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsWindowSwitchControlViewModel>();
|
||||
}
|
||||
|
||||
private void Grid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
private async void Grid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.HideWindow<LyricsWindowSwitchWindow>();
|
||||
await HideAsync();
|
||||
}
|
||||
|
||||
private void Button_Click(object sender, RoutedEventArgs e)
|
||||
private async void Button_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await HideAsync();
|
||||
}
|
||||
|
||||
private async Task HideAsync()
|
||||
{
|
||||
var lyricsWindowSwitchWindow = WindowHelper.GetWindowByWindowType<LyricsWindowSwitchWindow>();
|
||||
lyricsWindowSwitchWindow?.ViewModel.RootGridOpacity = 0;
|
||||
await Task.Delay(300);
|
||||
WindowHelper.HideWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
CanReorderItems="True"
|
||||
DragItemsCompleted="MediaSourceProvidersListView_DragItemsCompleted"
|
||||
ItemsSource="{x:Bind ViewModel.AppSettings.MediaSourceProvidersInfo, Mode=OneWay}"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.HorizontalScrollMode="Enabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollMode="Disabled"
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
<Setter Property="MinWidth" Value="600" />
|
||||
</Style>
|
||||
</MenuFlyout.MenuFlyoutPresenterStyle>
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTraySwitch"
|
||||
Command="{x:Bind ViewModel.OpenLyricsWindowSwitchCommand}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayLyrics"
|
||||
Command="{x:Bind ViewModel.OpenLyricsCommand}"
|
||||
@@ -44,13 +49,11 @@
|
||||
Command="{x:Bind ViewModel.OpenSettingsCommand}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayResetWindowPosition"
|
||||
Command="{x:Bind ViewModel.ResetWindowPositionCommand}"
|
||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}" />
|
||||
<MenuFlyoutSeparator />
|
||||
<MenuFlyoutItem
|
||||
x:Uid="SystemTrayRestart"
|
||||
Command="{x:Bind ViewModel.RestartAppCommand}"
|
||||
|
||||
@@ -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,15 +1,14 @@
|
||||
// 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;
|
||||
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.Jpeg;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -21,6 +20,7 @@ using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
using static Vanara.PInvoke.Ole32;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
@@ -46,7 +46,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return RandomAccessStreamReference.CreateFromStream(stream);
|
||||
}
|
||||
|
||||
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
|
||||
public static async Task<IRandomAccessStream> CreateTextPlaceholderBytesAsync(int width, int height)
|
||||
{
|
||||
using var device = CanvasDevice.GetSharedDevice();
|
||||
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
|
||||
@@ -76,31 +76,58 @@ 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 List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
|
||||
|
||||
public static Task<ThemeColorResult> GetAccentColorAsync(BitmapDecoder decoder, PaletteGeneratorType generatorType)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(bytes);
|
||||
var colorThief = new ColorThief.ImageSharp.ColorThief();
|
||||
var mainColor = colorThief.GetColor(image, 10, false);
|
||||
var palette = colorThief.GetPalette(image, 255, 10, false);
|
||||
var topColors = palette
|
||||
.OrderByDescending(x => x.Population)
|
||||
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
|
||||
.Select(x => Windows.UI.Color.FromArgb(x.Color.A, x.Color.R, x.Color.G, x.Color.B))
|
||||
.Take(count)
|
||||
.ToList();
|
||||
return generatorType switch
|
||||
{
|
||||
PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorFromByteAsync(decoder),
|
||||
PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorFromByteAsync(decoder),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(generatorType)),
|
||||
};
|
||||
}
|
||||
|
||||
return topColors;
|
||||
public static Task<PaletteResult> GetAccentColorsAsync(BitmapDecoder decoder, int count, PaletteGeneratorType generatorType, bool? isDark = null)
|
||||
{
|
||||
return generatorType switch
|
||||
{
|
||||
PaletteGeneratorType.OctTree => PaletteHelper.OctTreeGetAccentColorsFromByteAsync(decoder, count, isDark),
|
||||
PaletteGeneratorType.MedianCut => PaletteHelper.MedianCutGetAccentColorsFromByteAsync(decoder, count, isDark),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(generatorType)),
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
|
||||
@@ -129,13 +156,12 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
// return stream;
|
||||
//}
|
||||
|
||||
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
|
||||
public static async Task<IBuffer> 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;
|
||||
}
|
||||
|
||||
@@ -156,53 +182,105 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return (double)(sum / (pixels.Length / 4));
|
||||
}
|
||||
|
||||
public static byte[] MakeSquareWithThemeColor(byte[] imageBytes)
|
||||
public static async Task<BitmapDecoder> MakeSquareWithThemeColor(IBuffer buffer, PaletteGeneratorType generatorType)
|
||||
{
|
||||
using var image = Image.Load<Rgba32>(imageBytes);
|
||||
|
||||
if (image.Width == image.Height)
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(buffer);
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
|
||||
if (decoder.PixelWidth == decoder.PixelHeight)
|
||||
{
|
||||
// 已经是正方形,直接返回
|
||||
return imageBytes;
|
||||
return decoder;
|
||||
}
|
||||
|
||||
int size = Math.Max(image.Width, image.Height);
|
||||
using var device = CanvasDevice.GetSharedDevice();
|
||||
using var canvasBitmap = await CanvasBitmap.LoadAsync(device, stream);
|
||||
var size = Math.Max(decoder.PixelWidth, decoder.PixelHeight);
|
||||
|
||||
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes, 1).FirstOrDefault().ToHex());
|
||||
var result = await GetAccentColorAsync(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);
|
||||
|
||||
using var square = new Image<Rgba32>(size, size, themeColor);
|
||||
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);
|
||||
}
|
||||
|
||||
int offsetX = (size - image.Width) / 2;
|
||||
int offsetY = (size - image.Height) / 2;
|
||||
// 保存为 PNG 并转为 byte[]
|
||||
stream.Seek(0);
|
||||
stream.Size = 0;
|
||||
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
|
||||
stream.Seek(0);
|
||||
var newDecoder = await BitmapDecoder.CreateAsync(stream);
|
||||
return newDecoder;
|
||||
|
||||
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<IBuffer> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using ColorThiefDotNet;
|
||||
using Impressionist.Abstractions;
|
||||
using Impressionist.Implementations;
|
||||
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
|
||||
{
|
||||
private static ColorThief colorThief = new();
|
||||
public static async Task<PaletteResult> OctTreeGetAccentColorsFromByteAsync(BitmapDecoder decoder, int count, bool? isDark = null)
|
||||
{
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var palette = await PaletteGenerators.OctTreePaletteGenerator.CreatePalette(colors, count, false, isDark);
|
||||
return palette;
|
||||
}
|
||||
|
||||
public static async Task<ThemeColorResult> OctTreeGetAccentColorFromByteAsync(BitmapDecoder decoder)
|
||||
{
|
||||
var colors = await GetPixelColor(decoder);
|
||||
var theme = await PaletteGenerators.OctTreePaletteGenerator.CreateThemeColor(colors, false);
|
||||
return theme;
|
||||
}
|
||||
|
||||
public static async Task<ThemeColorResult> MedianCutGetAccentColorFromByteAsync(BitmapDecoder decoder)
|
||||
{
|
||||
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 theme;
|
||||
}
|
||||
|
||||
public static async Task<PaletteResult> MedianCutGetAccentColorsFromByteAsync(BitmapDecoder decoder, int count, bool? isDark = null)
|
||||
{
|
||||
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 = await colorThief.GetPalette(decoder, 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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
using Windows.ApplicationModel.Core;
|
||||
using Windows.Foundation;
|
||||
@@ -33,10 +34,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static void HideWindow<T>()
|
||||
{
|
||||
var window = _activeWindows.Find(w => w is T);
|
||||
if (window is Window w)
|
||||
{
|
||||
w.Hide();
|
||||
}
|
||||
var castedWindow = window as Window;
|
||||
castedWindow?.Hide();
|
||||
}
|
||||
|
||||
public static void CloseWindow<T>()
|
||||
|
||||
@@ -199,6 +199,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
IsBorderless = true,
|
||||
IsClickThrough = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.WindowEdge,
|
||||
LyricsStyleSettings = new()
|
||||
{
|
||||
@@ -207,8 +208,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsPureColorOverlayEnabled = false,
|
||||
IsCoverOverlayEnabled = false,
|
||||
IsFluidOverlayEnabled = false,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -223,6 +223,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
IsAlwaysOnTopPolling = true,
|
||||
IsBorderless = true,
|
||||
IsAdaptToEnvironment = true,
|
||||
IsShownInSwitchers = false,
|
||||
LyricsDisplayType = LyricsDisplayType.LyricsOnly,
|
||||
EnvironmentSampleMode = WindowPixelSampleMode.BelowWindow,
|
||||
TitleBarArea = TitleBarArea.None,
|
||||
@@ -233,7 +234,8 @@ namespace BetterLyrics.WinUI3.Models
|
||||
},
|
||||
LyricsBackgroundSettings = new LyricsBackgroundSettings
|
||||
{
|
||||
IsCoverOverlayEnabled = false,
|
||||
IsFluidOverlayEnabled = false,
|
||||
IsPureColorOverlayEnabled = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -246,18 +248,19 @@ namespace BetterLyrics.WinUI3.Models
|
||||
WindowBounds = monitorBounds,
|
||||
IsAlwaysOnTop = true,
|
||||
IsBorderless = true,
|
||||
IsShownInSwitchers = false,
|
||||
TitleBarArea = Enums.TitleBarArea.None,
|
||||
LyricsLayoutOrientation = Enums.LyricsLayoutOrientation.Vertical,
|
||||
LyricsStyleSettings = new LyricsStyleSettings
|
||||
{
|
||||
LyricsFontSize = 96,
|
||||
LyricsFontSize = 72,
|
||||
LyricsAlignmentType = Enums.TextAlignmentType.Center,
|
||||
},
|
||||
AlbumArtLayoutSettings = new AlbumArtLayoutSettings
|
||||
{
|
||||
AutoAlbumArtSize = false,
|
||||
AlbumArtSize = 148,
|
||||
SongInfoFontSize = 48,
|
||||
AlbumArtSize = 128,
|
||||
SongInfoFontSize = 36,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType SongInfoAlignmentType { get; set; } = TextAlignmentType.Left;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageRadius { get; set; } = 12; // 12 % of the cover image size
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverImageShadowAmount { get; set; } = 12;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsAutoSongInfoFontSize { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int SongInfoFontSize { get; set; } = 18;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowTitle { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ShowArtists { get; set; } = true;
|
||||
@@ -28,6 +29,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
SongInfoAlignmentType = this.SongInfoAlignmentType,
|
||||
CoverImageRadius = this.CoverImageRadius,
|
||||
CoverImageShadowAmount = this.CoverImageShadowAmount,
|
||||
IsAutoSongInfoFontSize = this.IsAutoSongInfoFontSize,
|
||||
SongInfoFontSize = this.SongInfoFontSize,
|
||||
ShowTitle = this.ShowTitle,
|
||||
ShowArtists = this.ShowArtists,
|
||||
|
||||
@@ -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;
|
||||
@@ -13,20 +14,22 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial ElementTheme LyricsBackgroundTheme { get; set; } = ElementTheme.Dark;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsPureColorOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PureColorOverlayOpacity { get; set; } = 100; // 100 % = 1.0
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsCoverOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayBlurAmount { get; set; } = 100; // 100 % of the cover image size
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlayOpacity { get; set; } = 100; // 100 % = 1.0
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverOverlaySpeed { get; set; } = 50; // 50 % of the base rotate speed
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int CoverAcrylicEffectAmount { get; set; } = 0;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFluidOverlayEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int FluidOverlayOpacity { get; set; } = 100;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial PaletteGeneratorType PaletteGeneratorType { get; set; } = PaletteGeneratorType.MedianCut;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsSpectrumOverlayEnabled { get; set; } = false;
|
||||
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBlurAmount { get; set; } = 5;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsLineFadeEnabled { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsLineFadeEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsGlowEffectEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsGlowEffectScope { get; set; } = LineRenderingType.CurrentChar;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class LyricsStyleSettings : ObservableRecipient, ICloneable
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDynamicLyricsFontSize { get; set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsDynamicLyricsFontSize { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFontSize { get; set; } = 24;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TextAlignmentType LyricsAlignmentType { get; set; } = TextAlignmentType.Left;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsBgFontOpacity { get; set; } = 30; // 30% opacity
|
||||
|
||||
@@ -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<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token)
|
||||
public async Task<IBuffer?> 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<string>() { "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;
|
||||
}
|
||||
|
||||
@@ -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<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token);
|
||||
Task<IBuffer?> SearchAsync(string mediaSessionId, string title, string artist, string album, IBuffer? bufferFromSMTC, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,39 +33,38 @@ 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 = ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
decoder = await ImageHelper.MakeSquareWithThemeColor(buffer, _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.PaletteGeneratorType);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
albumArtSwBitmap = SoftwareBitmap.Copy(albumArtSwBitmap);
|
||||
albumArtSwBitmap.DpiX = 96;
|
||||
albumArtSwBitmap.DpiY = 96;
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtLightAccentColors = ImageHelper.GetAccentColorsFromByte(bytes, 4, false);
|
||||
var albumArtDarkAccentColors = ImageHelper.GetAccentColorsFromByte(bytes, 4, true);
|
||||
|
||||
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColors, albumArtDarkAccentColors));
|
||||
var albumArtLightAccentColors = await ImageHelper.GetAccentColorsAsync(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.GetAccentColorsAsync(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -35,6 +36,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsWindowStatus>>,
|
||||
IRecipient<PropertyChangedMessage<PaletteGeneratorType>>,
|
||||
IRecipient<PropertyChangedMessage<ChineseRomanization>>,
|
||||
IRecipient<PropertyChangedMessage<List<string>>>
|
||||
{
|
||||
@@ -58,7 +60,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<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
@@ -303,7 +305,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
_SMTCAlbumArtBytes = null;
|
||||
_SMTCAlbumArtBuffer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -352,15 +354,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 +534,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();
|
||||
}
|
||||
}
|
||||
@@ -684,5 +686,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<PaletteGeneratorType> message)
|
||||
{
|
||||
if (message.Sender is LyricsBackgroundSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsBackgroundSettings.PaletteGeneratorType))
|
||||
{
|
||||
UpdateAlbumArt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +303,12 @@
|
||||
<data name="LyricsWindowSettingsControlSetDefault.Text" xml:space="preserve">
|
||||
<value>Set as default</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Lyrics window switcher</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowHelp.Text" xml:space="preserve">
|
||||
<value>Go to Settings to add more modes</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>Album art area only</value>
|
||||
</data>
|
||||
@@ -331,9 +337,6 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>Welcome to BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Immersive mode</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>Add to playlist</value>
|
||||
</data>
|
||||
@@ -445,6 +448,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>Picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="SetingsPageContributors.Text" xml:space="preserve">
|
||||
<value>Contributors</value>
|
||||
</data>
|
||||
<data name="SetingsPageDonation.Text" xml:space="preserve">
|
||||
<value>Donation</value>
|
||||
</data>
|
||||
@@ -520,8 +526,8 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageApply.Content" xml:space="preserve">
|
||||
<value>Apply</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoSize.Header" xml:space="preserve">
|
||||
<value>Automatic resizing</value>
|
||||
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
|
||||
<value>Automatic adjustment</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>Automatic startup</value>
|
||||
@@ -625,9 +631,6 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageDragArea.Header" xml:space="preserve">
|
||||
<value>Draggable area</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>Automatic adjustment</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>Easing animation type</value>
|
||||
</data>
|
||||
@@ -976,6 +979,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>Media library</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>Conservative</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Monitor this playback source</value>
|
||||
</data>
|
||||
@@ -1006,12 +1012,18 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>None</value>
|
||||
</data>
|
||||
<data name="SettingsPageOctTree.Content" xml:space="preserve">
|
||||
<value>Aggressive</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpacity.Header" xml:space="preserve">
|
||||
<value>Opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpenFolderButton.Content" xml:space="preserve">
|
||||
<value>Open in file explorer</value>
|
||||
</data>
|
||||
<data name="SettingsPagePaletteGeneratorType.Header" xml:space="preserve">
|
||||
<value>Color picker style</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>
|
||||
@@ -1244,11 +1256,14 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<value>Reset window position</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>Resart</value>
|
||||
<value>Restart</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>Lyrics window switcher</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>Translate server is not set, please configure it in settings first</value>
|
||||
</data>
|
||||
|
||||
@@ -303,6 +303,12 @@
|
||||
<data name="LyricsWindowSettingsControlSetDefault.Text" xml:space="preserve">
|
||||
<value>既定のブラウザーに設定する</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌詞ウィンドウスイッチャー</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowHelp.Text" xml:space="preserve">
|
||||
<value>設定に移動して、さらにモードを追加してください</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>アルバムアートエリアのみ</value>
|
||||
</data>
|
||||
@@ -331,9 +337,6 @@
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>BetterLyrics へようこそ</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>没入モード</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>プレイリストに追加します</value>
|
||||
</data>
|
||||
@@ -445,6 +448,9 @@
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモード</value>
|
||||
</data>
|
||||
<data name="SetingsPageContributors.Text" xml:space="preserve">
|
||||
<value>投稿者</value>
|
||||
</data>
|
||||
<data name="SetingsPageDonation.Text" xml:space="preserve">
|
||||
<value>寄付</value>
|
||||
</data>
|
||||
@@ -520,8 +526,8 @@
|
||||
<data name="SettingsPageApply.Content" xml:space="preserve">
|
||||
<value>適用する</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoSize.Header" xml:space="preserve">
|
||||
<value>自動サイズ変更</value>
|
||||
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
|
||||
<value>自動調整</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自動起動</value>
|
||||
@@ -625,9 +631,6 @@
|
||||
<data name="SettingsPageDragArea.Header" xml:space="preserve">
|
||||
<value>ドラッグ可能</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>自動調整</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>アニメーションタイプを緩和します</value>
|
||||
</data>
|
||||
@@ -976,6 +979,9 @@
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>メディアライブラリ</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>保守的</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>この再生ソースを監視します</value>
|
||||
</data>
|
||||
@@ -1006,12 +1012,18 @@
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>なし</value>
|
||||
</data>
|
||||
<data name="SettingsPageOctTree.Content" xml:space="preserve">
|
||||
<value>急進的だ</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpacity.Header" xml:space="preserve">
|
||||
<value>不透明度</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -1244,11 +1256,14 @@
|
||||
<value>ウィンドウの位置をリセットします</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>再アート</value>
|
||||
<value>再起動</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>設定を開く</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>歌詞ウィンドウスイッチャー</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
|
||||
</data>
|
||||
|
||||
@@ -303,6 +303,12 @@
|
||||
<data name="LyricsWindowSettingsControlSetDefault.Text" xml:space="preserve">
|
||||
<value>기본 값으로 설정</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>가사 창 전환기</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowHelp.Text" xml:space="preserve">
|
||||
<value>설정으로 이동하여 모드를 더 추가하세요</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>앨범 아트 영역만</value>
|
||||
</data>
|
||||
@@ -331,9 +337,6 @@
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>Betterlyrics에 오신 것을 환영합니다</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>몰입 형 모드</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>재생 목록에 추가하십시오</value>
|
||||
</data>
|
||||
@@ -445,6 +448,9 @@
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>사진 인당 모드</value>
|
||||
</data>
|
||||
<data name="SetingsPageContributors.Text" xml:space="preserve">
|
||||
<value>기여자</value>
|
||||
</data>
|
||||
<data name="SetingsPageDonation.Text" xml:space="preserve">
|
||||
<value>기부</value>
|
||||
</data>
|
||||
@@ -488,7 +494,7 @@
|
||||
<value>앨범 표지 소스를 구성합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSize.Header" xml:space="preserve">
|
||||
<value>앨범아트</value>
|
||||
<value>앨범 아트 크기</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>앨범 아트 소스</value>
|
||||
@@ -520,8 +526,8 @@
|
||||
<data name="SettingsPageApply.Content" xml:space="preserve">
|
||||
<value>적용하다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoSize.Header" xml:space="preserve">
|
||||
<value>자동 크기 조정</value>
|
||||
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
|
||||
<value>자동 조정</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>자동 시작</value>
|
||||
@@ -625,9 +631,6 @@
|
||||
<data name="SettingsPageDragArea.Header" xml:space="preserve">
|
||||
<value>드래그 가능</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>자동 조정</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>애니메이션 유형 완화</value>
|
||||
</data>
|
||||
@@ -976,6 +979,9 @@
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>미디어 라이브러리</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>안전하게</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>이 재생 소스를 모니터링하십시오</value>
|
||||
</data>
|
||||
@@ -1006,12 +1012,18 @@
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>없음</value>
|
||||
</data>
|
||||
<data name="SettingsPageOctTree.Content" xml:space="preserve">
|
||||
<value>공격적인</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpacity.Header" xml:space="preserve">
|
||||
<value>투명도</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -1244,11 +1256,14 @@
|
||||
<value>창 위치를 재설정합니다</value>
|
||||
</data>
|
||||
<data name="SystemTrayRestart.Text" xml:space="preserve">
|
||||
<value>resart</value>
|
||||
<value>다시 시작</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>열기 설정</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>가사 창 전환기</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
|
||||
</data>
|
||||
|
||||
@@ -303,6 +303,12 @@
|
||||
<data name="LyricsWindowSettingsControlSetDefault.Text" xml:space="preserve">
|
||||
<value>设为默认</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌词窗口切换器</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowHelp.Text" xml:space="preserve">
|
||||
<value>转到“设置”以添加更多模式</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>仅显示专辑区域</value>
|
||||
</data>
|
||||
@@ -331,9 +337,6 @@
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>欢迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌单</value>
|
||||
</data>
|
||||
@@ -445,6 +448,9 @@
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>画中画模式</value>
|
||||
</data>
|
||||
<data name="SetingsPageContributors.Text" xml:space="preserve">
|
||||
<value>贡献者</value>
|
||||
</data>
|
||||
<data name="SetingsPageDonation.Text" xml:space="preserve">
|
||||
<value>捐贈</value>
|
||||
</data>
|
||||
@@ -488,7 +494,7 @@
|
||||
<value>配置专辑封面源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSize.Header" xml:space="preserve">
|
||||
<value>专辑封面</value>
|
||||
<value>专辑封面尺寸</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>专辑封面源</value>
|
||||
@@ -520,8 +526,8 @@
|
||||
<data name="SettingsPageApply.Content" xml:space="preserve">
|
||||
<value>应用</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoSize.Header" xml:space="preserve">
|
||||
<value>自动调整尺寸</value>
|
||||
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
|
||||
<value>自动调整</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自动启动</value>
|
||||
@@ -625,9 +631,6 @@
|
||||
<data name="SettingsPageDragArea.Header" xml:space="preserve">
|
||||
<value>可拖拽区域</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>自动调整</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>缓动动画类型</value>
|
||||
</data>
|
||||
@@ -976,6 +979,9 @@
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒体库</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>保守</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>监听此播放源</value>
|
||||
</data>
|
||||
@@ -1006,12 +1012,18 @@
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>无</value>
|
||||
</data>
|
||||
<data name="SettingsPageOctTree.Content" xml:space="preserve">
|
||||
<value>激进</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpacity.Header" xml:space="preserve">
|
||||
<value>不透明度</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -1249,6 +1261,9 @@
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>打开设置</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>歌词窗口切换器</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>未设置Translate服务器,请先在设置中进行配置</value>
|
||||
</data>
|
||||
|
||||
@@ -303,6 +303,12 @@
|
||||
<data name="LyricsWindowSettingsControlSetDefault.Text" xml:space="preserve">
|
||||
<value>設為預設</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌詞視窗切換器</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowHelp.Text" xml:space="preserve">
|
||||
<value>前往設定以新增更多模式</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>僅限相簿藝術區域</value>
|
||||
</data>
|
||||
@@ -331,9 +337,6 @@
|
||||
<data name="MainPageWelcomeTeachingTip.Title" xml:space="preserve">
|
||||
<value>歡迎使用 BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MainWindowImmersiveMode.ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>沉浸模式</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAddToCustomList.Content" xml:space="preserve">
|
||||
<value>添加到歌單</value>
|
||||
</data>
|
||||
@@ -445,6 +448,9 @@
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>畫中畫模式</value>
|
||||
</data>
|
||||
<data name="SetingsPageContributors.Text" xml:space="preserve">
|
||||
<value>貢獻者</value>
|
||||
</data>
|
||||
<data name="SetingsPageDonation.Text" xml:space="preserve">
|
||||
<value>捐贈</value>
|
||||
</data>
|
||||
@@ -488,7 +494,7 @@
|
||||
<value>配置專輯封面源</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSize.Header" xml:space="preserve">
|
||||
<value>專輯封面</value>
|
||||
<value>相簿藝術尺寸</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumLib.Content" xml:space="preserve">
|
||||
<value>專輯封面來源</value>
|
||||
@@ -520,8 +526,8 @@
|
||||
<data name="SettingsPageApply.Content" xml:space="preserve">
|
||||
<value>應用</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoSize.Header" xml:space="preserve">
|
||||
<value>自動調整大小</value>
|
||||
<data name="SettingsPageAutoAdjust.Header" xml:space="preserve">
|
||||
<value>自動調整</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自動啟動</value>
|
||||
@@ -625,9 +631,6 @@
|
||||
<data name="SettingsPageDragArea.Header" xml:space="preserve">
|
||||
<value>拖動</value>
|
||||
</data>
|
||||
<data name="SettingsPageDynamicLyricsFontSize.Header" xml:space="preserve">
|
||||
<value>自動調整</value>
|
||||
</data>
|
||||
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
|
||||
<value>緩動動畫類型</value>
|
||||
</data>
|
||||
@@ -976,6 +979,9 @@
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>媒體庫</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>保守</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
|
||||
<value>監聽此播放來源</value>
|
||||
</data>
|
||||
@@ -1006,12 +1012,18 @@
|
||||
<data name="SettingsPageNoBackdrop.Content" xml:space="preserve">
|
||||
<value>無</value>
|
||||
</data>
|
||||
<data name="SettingsPageOctTree.Content" xml:space="preserve">
|
||||
<value>激進</value>
|
||||
</data>
|
||||
<data name="SettingsPageOpacity.Header" xml:space="preserve">
|
||||
<value>專輯背景層不透明度</value>
|
||||
</data>
|
||||
<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>
|
||||
@@ -1249,6 +1261,9 @@
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>打開設置</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>歌詞視窗切換器</value>
|
||||
</data>
|
||||
<data name="TranslateServerNotSet" xml:space="preserve">
|
||||
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
|
||||
</data>
|
||||
|
||||
@@ -85,6 +85,10 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
_isAlbumArtSizeChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(AlbumArtLayoutSettings.IsAutoSongInfoFontSize))
|
||||
{
|
||||
UpdateSongInfoFontSize();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsBackgroundSettings)
|
||||
{
|
||||
|
||||
@@ -160,6 +160,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
|
||||
{
|
||||
UpdateSongInfoFontSize();
|
||||
|
||||
_isCoverAcrylicEffectAmountChanged = true;
|
||||
|
||||
_effect?.Properties["Width"] = (float)control.ConvertDipsToPixels((float)_canvasWidth, CanvasDpiRounding.Round);
|
||||
@@ -409,16 +411,16 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
_lyricsXTransition.Update(_elapsedTime);
|
||||
_lyricsYTransition.Update(_elapsedTime);
|
||||
|
||||
|
||||
_albumArtXTransition.Update(_elapsedTime);
|
||||
_albumArtYTransition.Update(_elapsedTime);
|
||||
|
||||
|
||||
_lyricsOpacityTransition.Update(_elapsedTime);
|
||||
_albumArtOpacityTransition.Update(_elapsedTime);
|
||||
|
||||
|
||||
_immersiveBgOpacityTransition.Update(_elapsedTime);
|
||||
_immersiveBgColorTransition.Update(_elapsedTime);
|
||||
|
||||
|
||||
_albumArtAccentColor1Transition.Update(_elapsedTime);
|
||||
_albumArtAccentColor2Transition.Update(_elapsedTime);
|
||||
_albumArtAccentColor3Transition.Update(_elapsedTime);
|
||||
@@ -437,7 +439,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.IsDynamicLyricsFontSize)
|
||||
{
|
||||
_lyricsTextFormat.FontSize = (float)Math.Clamp(Math.Min(_canvasHeight, _canvasWidth) / 10, 12, 72);
|
||||
_lyricsTextFormat.FontSize = (float)Math.Clamp(Math.Min(_canvasHeight, _canvasWidth) / 15, 12, 96);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -783,8 +785,16 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private void UpdateSongInfoFontSize()
|
||||
{
|
||||
_titleTextFormat.FontSize = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoFontSize;
|
||||
_artistTextFormat.FontSize = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoFontSize - 2;
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.IsAutoSongInfoFontSize)
|
||||
{
|
||||
_titleTextFormat.FontSize = (int)Math.Clamp(Math.Min(_canvasHeight, _canvasWidth) / 20, 8, 72);
|
||||
}
|
||||
else
|
||||
{
|
||||
_titleTextFormat.FontSize = _liveStatesService.LiveStates.LyricsWindowStatus.AlbumArtLayoutSettings.SongInfoFontSize;
|
||||
}
|
||||
|
||||
_artistTextFormat.FontSize = (int)(_titleTextFormat.FontSize * 0.8);
|
||||
|
||||
_isSongInfoFontSizeChanged = true;
|
||||
}
|
||||
|
||||
@@ -394,6 +394,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private void MediaSessionsService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
|
||||
{
|
||||
_lastAlbumArtCanvasBitmap?.Dispose();
|
||||
_lastAlbumArtCanvasBitmap = null;
|
||||
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_albumArtSwBitmap = e.AlbumArtSwBitmap;
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsWindowSwitchWindowViewModel : BaseViewModel
|
||||
{
|
||||
[ObservableProperty] public partial float RootGridOpacity { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
@@ -52,5 +52,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenLyricsWindowSwitch()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
|
||||
xmlns:canvas="using:Microsoft.Graphics.Canvas.UI.Xaml"
|
||||
xmlns:const="using:BetterLyrics.WinUI3.Constants"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
@@ -18,19 +19,27 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
RightTapped="RootGrid_RightTapped"
|
||||
SizeChanged="RootGrid_SizeChanged">
|
||||
<!-- Lyrics area -->
|
||||
<renderer:LyricsRenderer />
|
||||
<!--<Image Source="/Assets/Cover.jpg" />-->
|
||||
|
||||
<!-- No music playing placeholder -->
|
||||
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
|
||||
<TextBlock
|
||||
x:Uid="MainPageNoMusicPlaying"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontFamily, Mode=OneWay}"
|
||||
FontSize="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontSize, Mode=OneWay}" />
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock
|
||||
x:Uid="MainPageNoMusicPlaying"
|
||||
HorizontalAlignment="Center"
|
||||
FontFamily="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontFamily, Mode=OneWay}"
|
||||
FontSize="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsFontSize, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||
<HyperlinkButton x:Uid="SettingsPageFAQ" NavigateUri="{x:Bind const:Link.FAQUrl}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
@@ -172,5 +172,13 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
ViewModel.TimelineSliderThumbOpacity = 0f;
|
||||
}
|
||||
|
||||
private void RootGrid_RightTapped(object sender, Microsoft.UI.Xaml.Input.RightTappedRoutedEventArgs e)
|
||||
{
|
||||
if (BottomCommandFlyoutContainer.Children.Count != 0)
|
||||
{
|
||||
BottomCommandFlyout.ShowAt(BottomCommandFlyoutTrigger);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
|
||||
<!-- More -->
|
||||
<!-- Music gallery -->
|
||||
<Button Click="MusicGalleryButton_Click" Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
@@ -57,6 +57,16 @@
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
<Button Click="LyricsWindowSwitchButton_Click" Style="{StaticResource TitleBarButtonStyle}">
|
||||
<FontIcon
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
FontSize="{x:Bind ViewModel.TitleBarFontSize, Mode=OneWay}"
|
||||
Glyph="" />
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Uid="LyricsWindowSwitchButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
|
||||
private readonly WindowMessageMonitor _wmm;
|
||||
|
||||
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
|
||||
|
||||
public LyricsWindow()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
@@ -79,8 +81,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
public LyricsWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowViewModel>();
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
{
|
||||
if (args.DidPositionChange || args.DidSizeChange)
|
||||
@@ -127,5 +127,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
ViewModel.ExitOrClose();
|
||||
}
|
||||
|
||||
private void LyricsWindowSwitchButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
Title="LyricsWindowSwitchWindow"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid x:Name="RootGrid" Opacity="{x:Bind ViewModel.RootGridOpacity, Mode=OneWay}">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<Grid
|
||||
x:Name="PlaceholderGrid"
|
||||
Width="1"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
@@ -25,6 +27,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
/// </summary>
|
||||
public sealed partial class LyricsWindowSwitchWindow : Window
|
||||
{
|
||||
public LyricsWindowSwitchWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<LyricsWindowSwitchWindowViewModel>();
|
||||
|
||||
public LyricsWindowSwitchWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -37,6 +41,19 @@ namespace BetterLyrics.WinUI3.Views
|
||||
AppWindow.IsShownInSwitchers = false;
|
||||
this.SetIsAlwaysOnTop(true);
|
||||
SetTitleBar(PlaceholderGrid);
|
||||
|
||||
AppWindow.Changed += AppWindow_Changed;
|
||||
}
|
||||
|
||||
private void AppWindow_Changed(Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowChangedEventArgs args)
|
||||
{
|
||||
if (args.DidVisibilityChange)
|
||||
{
|
||||
if (sender.IsVisible)
|
||||
{
|
||||
ViewModel.RootGridOpacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +188,18 @@
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageContributors" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
|
||||
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
|
||||
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
|
||||
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
|
||||
</controls:SettingsExpander.Items>
|
||||
<controls:SettingsExpander.ItemsHeader>
|
||||
<InfoBar
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.36105.23 d17.13
|
||||
VisualStudioVersion = 17.13.36105.23
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "BetterLyrics.WinUI3 (Package)", "BetterLyrics.WinUI3\BetterLyrics.WinUI3 (Package)\BetterLyrics.WinUI3 (Package).wapproj", "{6576CD19-EF92-4099-B37D-E2D8EBDB6BF5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BetterLyrics.WinUI3", "BetterLyrics.WinUI3\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj", "{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}"
|
||||
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
|
||||
@@ -47,6 +51,30 @@ Global
|
||||
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x64.Build.0 = Release|x64
|
||||
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x86.ActiveCfg = Release|x86
|
||||
{6D26909A-9EE5-4D26-9E81-686BDE36A9D3}.Release|x86.Build.0 = Release|x86
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{A678BCA5-03DE-71E4-73C1-388B7550E4E3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{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
|
||||
|
||||
111
ColorThief.WinUI3/CMap.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ColorThiefDotNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Color map
|
||||
/// </summary>
|
||||
internal class CMap
|
||||
{
|
||||
private readonly List<VBox> vboxes = new List<VBox>();
|
||||
private List<QuantizedColor> palette;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
ColorThief.WinUI3/Color.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
|
||||
namespace ColorThiefDotNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a color in RGB space.
|
||||
/// </summary>
|
||||
public struct Color
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or Set the Alpha component value for sRGB.
|
||||
/// </summary>
|
||||
public byte A;
|
||||
|
||||
/// <summary>
|
||||
/// Get or Set the Blue component value for sRGB.
|
||||
/// </summary>
|
||||
public byte B;
|
||||
|
||||
/// <summary>
|
||||
/// Get or Set the Green component value for sRGB.
|
||||
/// </summary>
|
||||
public byte G;
|
||||
|
||||
/// <summary>
|
||||
/// Get or Set the Red component value for sRGB.
|
||||
/// </summary>
|
||||
public byte R;
|
||||
|
||||
/// <summary>
|
||||
/// Get HSL color.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
ColorThief.WinUI3/ColorThief.WinUI3.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the median cut algorithm to cluster similar colors and return the base color from the largest cluster.
|
||||
/// </summary>
|
||||
/// <param name="sourceImage">The source image.</param>
|
||||
/// <param name="quality">
|
||||
/// 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.
|
||||
/// </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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use the median cut algorithm to cluster similar colors.
|
||||
/// </summary>
|
||||
/// <param name="sourceImage">The source image.</param>
|
||||
/// <param name="colorCount">The color count.</param>
|
||||
/// <param name="quality">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <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)
|
||||
{
|
||||
var pixelArray = await GetPixelsFast(sourceImage, quality, ignoreWhite);
|
||||
var cmap = GetColorMap(pixelArray, colorCount);
|
||||
if(cmap != null)
|
||||
{
|
||||
var colors = cmap.GeneratePalette();
|
||||
return colors;
|
||||
}
|
||||
return new List<QuantizedColor>();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
quality = DefaultQuality;
|
||||
}
|
||||
|
||||
var pixels = await GetIntFromPixel(sourceImage);
|
||||
var pixelCount = sourceImage.PixelWidth*sourceImage.PixelHeight;
|
||||
|
||||
return ConvertPixels(pixels, Convert.ToInt32(pixelCount), quality, ignoreWhite);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
ColorThief.WinUI3/ColorThief.WinUI3.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<RootNamespace>ColorThief.WinUI3</RootNamespace>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251003001" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
77
ColorThief.WinUI3/ColorThief.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Use the median cut algorithm to cluster similar colors.
|
||||
/// </summary>
|
||||
/// <param name="pixelArray">Pixel array.</param>
|
||||
/// <param name="colorCount">The color count.</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
ColorThief.WinUI3/HslColor.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace ColorThiefDotNet
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a color in Hue/Saturation/Lightness (HSL) space.
|
||||
/// </summary>
|
||||
public struct HslColor
|
||||
{
|
||||
/// <summary>
|
||||
/// The Alpha/opacity in 0..1 range.
|
||||
/// </summary>
|
||||
public double A;
|
||||
|
||||
/// <summary>
|
||||
/// The Hue in 0..360 range.
|
||||
/// </summary>
|
||||
public double H;
|
||||
|
||||
/// <summary>
|
||||
/// The Lightness in 0..1 range.
|
||||
/// </summary>
|
||||
public double L;
|
||||
|
||||
/// <summary>
|
||||
/// The Saturation in 0..1 range.
|
||||
/// </summary>
|
||||
public double S;
|
||||
}
|
||||
}
|
||||
387
ColorThief.WinUI3/Mmcq.cs
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the histo.
|
||||
/// </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)
|
||||
{
|
||||
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<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++)
|
||||
{
|
||||
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<int> partialsum, IList<int> 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<int> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inner function to do the iteration.
|
||||
/// </summary>
|
||||
/// <param name="lh">The lh.</param>
|
||||
/// <param name="comparator">The comparator.</param>
|
||||
/// <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)
|
||||
{
|
||||
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> {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);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
ColorThief.WinUI3/QuantizedColor.cs
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
163
ColorThief.WinUI3/VBox.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ColorThiefDotNet
|
||||
{
|
||||
/// <summary>
|
||||
/// 3D color space box.
|
||||
/// </summary>
|
||||
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<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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
FAQ/FAQ.md
@@ -1,37 +0,0 @@
|
||||
### ? Where I can find the logs?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\logs`
|
||||
|
||||
### ? Where I can find the lyrics cache?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\lyrics`
|
||||
|
||||
### ? How to install ".msixbundle" package? (for test package only)
|
||||
[See this doc](https://github.com/jayfunc/BetterLyrics/blob/dev/How2Install/How2Install.md)
|
||||
|
||||
### ? Lyrics are moving back and forth constantly, how to fix it?
|
||||

|
||||
|
||||
Go to Settings > Playback sources > Disable "Lyrics timeline sync" or increase "Lyrics timeline sync threshold"
|
||||
|
||||
### ? Wrong lyrics are shown, how to fix it?
|
||||

|
||||
|
||||
Open search panel to manually search for the correct lyrics.
|
||||
|
||||
### ? Playback control panel is not showing in dock mode, how to fix it?
|
||||

|
||||
|
||||
Hover over the bottom of the lyrics window and click on the white line to show the playback control panel.
|
||||
|
||||
### ? How to lock/unlock the lyrics window in desktop mode?
|
||||

|
||||
|
||||

|
||||
|
||||
Alternatively, you can also use the shortcut `Ctrl+Alt+U` (default) to toggle lock/unlock.
|
||||
|
||||

|
||||
|
||||
You can change the shortcut in Settings > App appearance and behavior > Unlock and lock shortcut keys
|
||||
|
||||
### ? How to enable/disable immersive mode?
|
||||

|
||||
BIN
FAQ/PixPin_2025-10-24_18-06-32.gif
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
FAQ/PixPin_2025-10-24_18-07-45.gif
Normal file
|
After Width: | Height: | Size: 12 MiB |
BIN
FAQ/PixPin_2025-10-25_09-08-47.gif
Normal file
|
After Width: | Height: | Size: 6.3 MiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 157 KiB |
BIN
FAQ/image.png
Normal file
|
After Width: | Height: | Size: 244 KiB |
54
FAQ/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Welcome to BetterLyrics
|
||||
|
||||
### 🤔 Where can I find the logs?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\logs`
|
||||
|
||||
### 🤔 Where can I find the lyrics cache?
|
||||
`%LocalAppData%\Packages\37412.BetterLyrics_rd1g0rsrrtxw8\LocalCache\lyrics`
|
||||
|
||||
### 🤔 I cannot see any buttons.
|
||||
|
||||
By default, the top command bar and the bottom command bar (playback control panel) are automatically hidden when your mouse is out of those areas. Just hover your mouse back to those areas to show them again.
|
||||
|
||||
### 🤔 No music is playing now. What should I do?
|
||||
|
||||
Some of the players need additional config, check out **Multiple Music Players Supported** under [this](https://github.com/jayfunc/BetterLyrics/blob/dev/README.md#-highlighted-features) section.
|
||||
|
||||
### 🤔 How to add more modes?
|
||||
|
||||
If this is the first time that you use this app, only the standard mode was initially added for you. To add more modes, follow the steps below:
|
||||
|
||||
Settings -> Lyrics window manager -> Create from templates -> Fullscreen mode
|
||||
|
||||

|
||||
|
||||
### 🤔 How to switch modes?
|
||||
|
||||
You can switch modes by pressing the default shortcuts `Ctrl + Alt + S` and then choosing one of the modes displayed on your screen. (Press `Escape` to close the choosing window)
|
||||
|
||||

|
||||
|
||||
### 🤔 How to move and resize the window? I cannot touch the window.
|
||||
|
||||

|
||||
|
||||
### 🤔 How to install ".msixbundle" package? (for test package only)
|
||||
[See this doc](https://github.com/jayfunc/BetterLyrics/blob/dev/How2Install/How2Install.md)
|
||||
|
||||
### 🤔 Lyrics are moving back and forth constantly.
|
||||

|
||||
|
||||
Go to Settings > Playback sources > Disable "Lyrics timeline sync" or increase "Lyrics timeline sync threshold"
|
||||
|
||||
### 🤔 Wrong lyrics are shown.
|
||||

|
||||
|
||||
Open the search panel to manually search for the correct lyrics.
|
||||
|
||||
### 🤔 Bottom command bar (playback control panel) is hidden?
|
||||
|
||||
By default, the playback control panel at the bottom is hidden automatically when your mouse is out of that area.
|
||||
|
||||

|
||||
|
||||
But when the window size is too small to place that panel, only hovering over the bottom of the lyrics window and clicking on the white line can the playback control panel be displayed, or just right-click on the inner side of the window.
|
||||
10
Impressionist/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
bin/
|
||||
obj/
|
||||
|
||||
*.user
|
||||
|
||||
.DS_Store
|
||||
92
Impressionist/Impressionist.Benchmark/BenchMark.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Impressionist.Implementations;
|
||||
using System.Drawing;
|
||||
using System.Numerics;
|
||||
#pragma warning disable CA1416 // 验证平台兼容性
|
||||
|
||||
namespace Impressionist.Benchmark
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class BenchMark
|
||||
{
|
||||
public List<string> fileName = new List<string>()
|
||||
{
|
||||
};
|
||||
public List<Dictionary<Vector3, int>> imageData = new List<Dictionary<Vector3, int>>();
|
||||
[Benchmark]
|
||||
public async Task GetPaletteOctTree()
|
||||
{
|
||||
foreach (var item in imageData)
|
||||
{
|
||||
var result = await PaletteGenerators.OctTreePaletteGenerator.CreatePalette(item, 4);
|
||||
}
|
||||
}
|
||||
[Benchmark]
|
||||
public async Task GetPaletteKMeansPP()
|
||||
{
|
||||
foreach (var item in imageData)
|
||||
{
|
||||
var result = await PaletteGenerators.KMeansPaletteGenerator.CreatePalette(item, 4, useKMeansPP: true);
|
||||
}
|
||||
}
|
||||
[Benchmark]
|
||||
public async Task GetPaletteKMeans()
|
||||
{
|
||||
foreach (var item in imageData)
|
||||
{
|
||||
var result = await PaletteGenerators.KMeansPaletteGenerator.CreatePalette(item, 4, useKMeansPP: false);
|
||||
}
|
||||
}
|
||||
[Benchmark]
|
||||
public async Task GetPaletteKMeansPPToLab()
|
||||
{
|
||||
foreach (var item in imageData)
|
||||
{
|
||||
var result = await PaletteGenerators.KMeansPaletteGenerator.CreatePalette(item, 4, useKMeansPP: true, toLab: true);
|
||||
}
|
||||
}
|
||||
[Benchmark]
|
||||
public async Task GetPaletteKMeansToLab()
|
||||
{
|
||||
foreach (var item in imageData)
|
||||
{
|
||||
var result = await PaletteGenerators.KMeansPaletteGenerator.CreatePalette(item, 4, useKMeansPP: false, toLab: true);
|
||||
}
|
||||
}
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
foreach (var item in fileName)
|
||||
{
|
||||
using var originalImage = new Bitmap(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "/Pictures/", item));
|
||||
var result = GetColor(originalImage);
|
||||
imageData.Add(result);
|
||||
}
|
||||
}
|
||||
Dictionary<Vector3, int> GetColor(Bitmap bmp)
|
||||
{
|
||||
var result = new Dictionary<Vector3, int>();
|
||||
for (var x = 0; x < bmp.Width; x++)
|
||||
{
|
||||
for (var y = 0; y < bmp.Height; y++)
|
||||
{
|
||||
var clr = bmp.GetPixel(x, y);
|
||||
if (clr.A == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var vec = new Vector3(clr.R, clr.G, clr.B);
|
||||
if (result.ContainsKey(vec))
|
||||
{
|
||||
result[vec]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[vec] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Impressionist\Impressionist.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Pictures\1.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Pictures\2.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Pictures\3.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Pictures\4.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Pictures\5.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Pictures\6.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
12
Impressionist/Impressionist.Benchmark/Program.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Impressionist.Benchmark
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
BenchmarkRunner.Run<BenchMark>();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Impressionist/Impressionist.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34728.123
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Impressionist", "Impressionist\Impressionist.csproj", "{90BFD9F6-514F-40F1-BE87-7EAFCA55E16D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Impressionist.Benchmark", "Impressionist.Benchmark\Impressionist.Benchmark.csproj", "{14FB07AA-3DCD-4AE8-88DF-65EA68C974AF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{90BFD9F6-514F-40F1-BE87-7EAFCA55E16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{90BFD9F6-514F-40F1-BE87-7EAFCA55E16D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{90BFD9F6-514F-40F1-BE87-7EAFCA55E16D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{90BFD9F6-514F-40F1-BE87-7EAFCA55E16D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{14FB07AA-3DCD-4AE8-88DF-65EA68C974AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{14FB07AA-3DCD-4AE8-88DF-65EA68C974AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{14FB07AA-3DCD-4AE8-88DF-65EA68C974AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{14FB07AA-3DCD-4AE8-88DF-65EA68C974AF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {085FE9CD-1200-431E-909F-CE0F6ED12289}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
10
Impressionist/Impressionist/Abstractions/HSVColor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Impressionist.Abstractions
|
||||
{
|
||||
public struct HSVColor
|
||||
{
|
||||
public float H { get; set; }
|
||||
public float S { get; set; }
|
||||
public float V { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Impressionist.Abstractions
|
||||
{
|
||||
public interface IPaletteGenrator
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Impressionist.Abstractions
|
||||
{
|
||||
public interface IThemeColorGenrator
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
28
Impressionist/Impressionist/Abstractions/PaletteResult.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Impressionist.Abstractions
|
||||
{
|
||||
public class PaletteResult
|
||||
{
|
||||
public List<Vector3> Palette { get; } = new List<Vector3>();
|
||||
public bool PaletteIsDark { get; }
|
||||
public ThemeColorResult ThemeColor { get; }
|
||||
public PaletteResult(List<Vector3> palette, bool paletteIsDark, ThemeColorResult themeColor)
|
||||
{
|
||||
Palette = palette;
|
||||
PaletteIsDark = paletteIsDark;
|
||||
ThemeColor = themeColor;
|
||||
}
|
||||
}
|
||||
public class ThemeColorResult
|
||||
{
|
||||
public Vector3 Color { get; }
|
||||
public bool ColorIsDark { get; }
|
||||
public ThemeColorResult(Vector3 color, bool colorIsDark)
|
||||
{
|
||||
Color = color;
|
||||
ColorIsDark = colorIsDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
291
Impressionist/Impressionist/Implementations/ColorUtilities.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using Impressionist.Abstractions;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Impressionist.Implementations
|
||||
{
|
||||
public static class ColorUtilities
|
||||
{
|
||||
public static HSVColor RGBVectorToHSVColor(this Vector3 color)
|
||||
{
|
||||
HSVColor hsv = new HSVColor();
|
||||
|
||||
float max = Math.Max(Math.Max(color.X, color.Y), color.Z);
|
||||
float min = Math.Min(Math.Min(color.X, color.Y), color.Z);
|
||||
|
||||
hsv.V = max * 100 / 255;
|
||||
|
||||
if (max == min)
|
||||
{
|
||||
hsv.H = 0;
|
||||
hsv.S = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
hsv.S = (((max - min) / max) * 100);
|
||||
|
||||
hsv.H = 0;
|
||||
|
||||
if (max == color.X)
|
||||
{
|
||||
hsv.H = (60 * (color.Y - color.Z) / (max - min));
|
||||
if (hsv.H < 0) hsv.H += 360;
|
||||
}
|
||||
else if (max == color.Y)
|
||||
{
|
||||
hsv.H = (60 * (2 + (color.Z - color.X) / (max - min)));
|
||||
if (hsv.H < 0) hsv.H += 360;
|
||||
}
|
||||
else if (max == color.Z)
|
||||
{
|
||||
hsv.H = (60 * (4 + (color.X - color.Y) / (max - min)));
|
||||
if (hsv.H < 0) hsv.H += 360;
|
||||
}
|
||||
|
||||
}
|
||||
return hsv;
|
||||
}
|
||||
public static Vector3 HSVColorToRGBVector(this HSVColor hsv)
|
||||
{
|
||||
if (hsv.H == 360) hsv.H = 0;
|
||||
int Hi = (int)Math.Floor((float)hsv.H / 60) % 6;
|
||||
|
||||
float f = (hsv.H / 60) - Hi;
|
||||
float p = (hsv.V / 100) * (1 - (hsv.S / 100));
|
||||
float q = (hsv.V / 100) * (1 - f * (hsv.S / 100));
|
||||
float t = (hsv.V / 100) * (1 - (1 - f) * (hsv.S / 100));
|
||||
|
||||
p *= 255;
|
||||
q *= 255;
|
||||
t *= 255;
|
||||
|
||||
Vector3 rgb = Vector3.Zero;
|
||||
|
||||
switch (Hi)
|
||||
{
|
||||
case 0:
|
||||
rgb = new Vector3(hsv.V * 255 / 100, t, p);
|
||||
break;
|
||||
case 1:
|
||||
rgb = new Vector3(q, hsv.V * 255 / 100, p);
|
||||
break;
|
||||
case 2:
|
||||
rgb = new Vector3(p, hsv.V * 255 / 100, t);
|
||||
break;
|
||||
case 3:
|
||||
rgb = new Vector3(p, q, hsv.V * 255 / 100);
|
||||
break;
|
||||
case 4:
|
||||
rgb = new Vector3(t, p, hsv.V * 255 / 100);
|
||||
break;
|
||||
case 5:
|
||||
rgb = new Vector3(hsv.V * 255 / 100, p, q);
|
||||
break;
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
public static Vector3 RGBVectorToXYZVector(this Vector3 rgb)
|
||||
{
|
||||
var red = rgb.X;
|
||||
var green = rgb.Y;
|
||||
var blue = rgb.Z;
|
||||
// normalize red, green, blue values
|
||||
float rLinear = red / 255.0f;
|
||||
float gLinear = green / 255.0f;
|
||||
float bLinear = blue / 255.0f;
|
||||
|
||||
// convert to a sRGB form
|
||||
float r = (rLinear > 0.04045) ? (float)Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (float)(rLinear / 12.92);
|
||||
float g = (gLinear > 0.04045) ? (float)Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (float)(gLinear / 12.92);
|
||||
float b = (bLinear > 0.04045) ? (float)Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (float)(bLinear / 12.92);
|
||||
|
||||
// converts
|
||||
return new Vector3(
|
||||
(r * 0.4124f + g * 0.3576f + b * 0.1805f),
|
||||
(r * 0.2126f + g * 0.7152f + b * 0.0722f),
|
||||
(r * 0.0193f + g * 0.1192f + b * 0.9505f)
|
||||
);
|
||||
}
|
||||
public static Vector3 XYZVectorToRGBVector(this Vector3 xyz)
|
||||
{
|
||||
var x = xyz.X;
|
||||
var y = xyz.Y;
|
||||
var z = xyz.Z;
|
||||
float[] Clinear = new float[3];
|
||||
Clinear[0] = x * 3.2410f - y * 1.5374f - z * 0.4986f; // red
|
||||
Clinear[1] = -x * 0.9692f + y * 1.8760f - z * 0.0416f; // green
|
||||
Clinear[2] = x * 0.0556f - y * 0.2040f + z * 1.0570f; // blue
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Clinear[i] = (Clinear[i] <= 0.0031308) ? 12.92f * Clinear[i] : (float)((
|
||||
1 + 0.055) * Math.Pow(Clinear[i], (1.0 / 2.4)) - 0.055);
|
||||
}
|
||||
|
||||
return new Vector3(
|
||||
Convert.ToInt32(float.Parse(string.Format("{0:0.00}",
|
||||
Clinear[0] * 255.0))),
|
||||
Convert.ToInt32(float.Parse(string.Format("{0:0.00}",
|
||||
Clinear[1] * 255.0))),
|
||||
Convert.ToInt32(float.Parse(string.Format("{0:0.00}",
|
||||
Clinear[2] * 255.0)))
|
||||
);
|
||||
}
|
||||
private static float D65X = 0.9505f;
|
||||
private static float D65Y = 1f;
|
||||
private static float D65Z = 1.089f;
|
||||
private static float Fxyz(float t)
|
||||
{
|
||||
return ((t > 0.008856) ? (float)Math.Pow(t, (1.0 / 3.0)) : (7.787f * t + 16.0f / 116.0f));
|
||||
}
|
||||
public static Vector3 XYZVectorToLABVector(this Vector3 xyz)
|
||||
{
|
||||
Vector3 lab = new Vector3();
|
||||
var x = xyz.X;
|
||||
var y = xyz.Y;
|
||||
var z = xyz.Z;
|
||||
lab.X = 116.0f * Fxyz(y / D65Y) - 16f;
|
||||
lab.Y = 500.0f * (Fxyz(x / D65X) - Fxyz(y / D65Y));
|
||||
lab.Z = 200.0f * (Fxyz(y / D65Y) - Fxyz(z / D65Z));
|
||||
return lab;
|
||||
}
|
||||
public static Vector3 LABVectorToXYZVector(this Vector3 lab)
|
||||
{
|
||||
float delta = 6.0f / 29.0f;
|
||||
var l = lab.X;
|
||||
var a = lab.Y;
|
||||
var b = lab.Z;
|
||||
float fy = (l + 16f) / 116.0f;
|
||||
float fx = fy + (a / 500.0f);
|
||||
float fz = fy - (b / 200.0f);
|
||||
|
||||
return new Vector3(
|
||||
(fx > delta) ? D65X * (fx * fx * fx) : (fx - 16.0f / 116.0f) * 3 * (
|
||||
delta * delta) * D65X,
|
||||
(fy > delta) ? D65Y * (fy * fy * fy) : (fy - 16.0f / 116.0f) * 3 * (
|
||||
delta * delta) * D65Y,
|
||||
(fz > delta) ? D65Z * (fz * fz * fz) : (fz - 16.0f / 116.0f) * 3 * (
|
||||
delta * delta) * D65Z
|
||||
);
|
||||
}
|
||||
|
||||
public static Vector3 RGBVectorToLABVector(this Vector3 rgb)
|
||||
{
|
||||
return rgb.RGBVectorToXYZVector().XYZVectorToLABVector();
|
||||
}
|
||||
public static Vector3 LABVectorToRGBVector(this Vector3 lab)
|
||||
{
|
||||
return lab.LABVectorToXYZVector().XYZVectorToRGBVector();
|
||||
}
|
||||
|
||||
internal static float A = 0.17883277f;
|
||||
internal static float B = 0.28466892f;
|
||||
internal static float C = 0.55991073f;
|
||||
internal static float HLGGap = 1f / 12f;
|
||||
internal static float HLGFunction1(float s)
|
||||
{
|
||||
return 0.5f * (float)Math.Sqrt(12f * s);
|
||||
}
|
||||
internal static float HLGFunction2(float s)
|
||||
{
|
||||
return (float)(A * Math.Log(12f * s - B)) + C;
|
||||
}
|
||||
|
||||
public static bool HLGColorIsDark(this HSVColor color)
|
||||
{
|
||||
if (color.V < 65) return true;
|
||||
var s = color.S / 100;
|
||||
if (s <= HLGGap)
|
||||
{
|
||||
var targetV = HLGFunction1(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetV = HLGFunction2(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
}
|
||||
|
||||
internal static float BT709Gap = 0.018f;
|
||||
internal static float BT709Function1(float s)
|
||||
{
|
||||
return 4.5f * s;
|
||||
}
|
||||
internal static float BT709Function2(float s)
|
||||
{
|
||||
return (float)(1.099 * Math.Pow(s, 0.45) - 0.099);
|
||||
}
|
||||
public static bool BT709ColorIsDark(this HSVColor color)
|
||||
{
|
||||
if (color.V < 65) return true;
|
||||
var s = color.S / 100;
|
||||
if (s <= BT709Gap)
|
||||
{
|
||||
var targetV = BT709Function1(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetV = BT709Function2(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
}
|
||||
|
||||
internal static float sRGBGap = 0.0031308f;
|
||||
internal static float sRGBFunction1(float s)
|
||||
{
|
||||
return 12.92f * s;
|
||||
}
|
||||
internal static float sRGBFunction2(float s)
|
||||
{
|
||||
return (float)(1.055 * Math.Pow(s, 1 / 2.4) - 0.055);
|
||||
}
|
||||
public static bool sRGBColorIsDark(this HSVColor color)
|
||||
{
|
||||
if (color.V < 65) return true;
|
||||
var s = color.S / 100;
|
||||
if (s <= sRGBGap)
|
||||
{
|
||||
var targetV = sRGBFunction1(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetV = sRGBFunction2(s);
|
||||
return color.V / 100f < targetV;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool RGBVectorLStarIsDark(this Vector3 rgb)
|
||||
{
|
||||
var limitedColor = rgb / 255f;
|
||||
var y = 0.2126f * ChannelToLin(limitedColor.X) + 0.7152f * ChannelToLin(limitedColor.Y) + 0.0722f * ChannelToLin(limitedColor.Z);
|
||||
var lStar = YToLStar(y);
|
||||
return lStar <= 55;
|
||||
}
|
||||
public static float ChannelToLin(float value)
|
||||
{
|
||||
if (value <= 0.04045f)
|
||||
{
|
||||
return value / 12.92f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (float)Math.Pow((value + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
}
|
||||
public static float YToLStar(float y)
|
||||
{
|
||||
if (y <= (216f / 24389f))
|
||||
{ // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
|
||||
return y * (24389f / 27f); // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
|
||||
}
|
||||
else
|
||||
{
|
||||
return (float)Math.Pow(y, (1f / 3f)) * 116f - 16f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using Impressionist.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Impressionist.Implementations
|
||||
{
|
||||
// I'm really appreciate wieslawsoltes's PaletteGenerator. Which make this project possible.
|
||||
public class KMeansPaletteGenerator :
|
||||
IThemeColorGenrator,
|
||||
IPaletteGenrator
|
||||
{
|
||||
public Task<ThemeColorResult> CreateThemeColor(Dictionary<Vector3, int> sourceColor, bool ignoreWhite = false, bool toLab = false)
|
||||
{
|
||||
var builder = sourceColor.AsEnumerable();
|
||||
if (ignoreWhite && sourceColor.Count > 1)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.X <= 250 || t.Key.Y <= 250 || t.Key.Z <= 250);
|
||||
}
|
||||
if (toLab)
|
||||
{
|
||||
builder = builder.Select(t => new KeyValuePair<Vector3, int>(t.Key.RGBVectorToLABVector(), t.Value));
|
||||
}
|
||||
var targetColor = builder.ToDictionary(t => t.Key, t => t.Value);
|
||||
var clusters = KMeansCluster(targetColor, 1, false);
|
||||
var colorVector = clusters.First();
|
||||
if (toLab)
|
||||
{
|
||||
colorVector = clusters.First().LABVectorToRGBVector();
|
||||
}
|
||||
var isDark = colorVector.RGBVectorLStarIsDark();
|
||||
return Task.FromResult(new ThemeColorResult(colorVector, isDark));
|
||||
}
|
||||
|
||||
public async Task<PaletteResult> CreatePalette(Dictionary<Vector3, int> sourceColor, int clusterCount, bool ignoreWhite = false, bool toLab = false, bool useKMeansPP = false)
|
||||
{
|
||||
if (sourceColor.Count == 1)
|
||||
{
|
||||
ignoreWhite = false;
|
||||
useKMeansPP = false;
|
||||
}
|
||||
var colorResult = await CreateThemeColor(sourceColor, ignoreWhite, toLab);
|
||||
var builder = sourceColor.AsEnumerable();
|
||||
var colorIsDark = colorResult.ColorIsDark;
|
||||
if (colorIsDark)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ignoreWhite)
|
||||
{
|
||||
builder = builder.Where(t => !t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
else
|
||||
{
|
||||
builder = builder.Where(t => !t.Key.RGBVectorLStarIsDark() && (t.Key.X <= 250 || t.Key.Y <= 250 || t.Key.Z <= 250));
|
||||
}
|
||||
}
|
||||
if (toLab)
|
||||
{
|
||||
builder = builder.Select(t => new KeyValuePair<Vector3, int>(t.Key.RGBVectorToLABVector(), t.Value));
|
||||
}
|
||||
var targetColors = builder.ToDictionary(t => t.Key, t => t.Value);
|
||||
var clusters = KMeansCluster(targetColors, clusterCount, useKMeansPP);
|
||||
var dominantColors = new List<Vector3>();
|
||||
foreach (var cluster in clusters)
|
||||
{
|
||||
var representative = cluster;
|
||||
if (toLab)
|
||||
{
|
||||
representative = representative.LABVectorToRGBVector();
|
||||
}
|
||||
dominantColors.Add(representative);
|
||||
}
|
||||
var result = new List<Vector3>();
|
||||
var count = dominantColors.Count;
|
||||
for (int i = 0; i < clusterCount; i++)
|
||||
{
|
||||
// You know, it is always hard to fullfill a palette when you have no enough colors. So please forgive me when placing the same color over and over again.
|
||||
result.Add(dominantColors[i % count]);
|
||||
}
|
||||
return new PaletteResult(result, colorIsDark, colorResult);
|
||||
}
|
||||
static Vector3[] KMeansCluster(Dictionary<Vector3, int> colors, int numClusters, bool useKMeansPP)
|
||||
{
|
||||
// Initialize the clusters, reduces the total number when total colors is less than clusters
|
||||
var clusterCount = Math.Min(numClusters, colors.Count);
|
||||
var clusters = new List<Dictionary<Vector3, int>>();
|
||||
for (int i = 0; i < clusterCount; i++)
|
||||
{
|
||||
clusters.Add(new Dictionary<Vector3, int>());
|
||||
}
|
||||
|
||||
// Select the initial cluster centers randomly
|
||||
Vector3[] centers = null;
|
||||
if (!useKMeansPP)
|
||||
{
|
||||
centers = colors.Keys.OrderByDescending(t => Guid.NewGuid()).Take(clusterCount).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
centers = KMeansPlusPlusCluster(colors, clusterCount).ToArray();
|
||||
}
|
||||
// Loop until the clusters stabilize
|
||||
var changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
// Assign each color to the nearest cluster center
|
||||
foreach (var color in colors.Keys)
|
||||
{
|
||||
var nearest = FindNearestCenter(color, centers);
|
||||
var clusterIndex = Array.IndexOf(centers, nearest);
|
||||
clusters[clusterIndex][color] = colors[color];
|
||||
}
|
||||
|
||||
// Recompute the cluster centers
|
||||
for (int i = 0; i < Math.Min(numClusters, clusterCount); i++)
|
||||
{
|
||||
var sumX = 0f;
|
||||
var sumY = 0f;
|
||||
var sumZ = 0f;
|
||||
var count = 0f;
|
||||
foreach (var color in clusters[i].Keys)
|
||||
{
|
||||
sumX += color.X * colors[color];
|
||||
sumY += color.Y * colors[color];
|
||||
sumZ += color.Z * colors[color];
|
||||
count += colors[color];
|
||||
}
|
||||
|
||||
var x = (sumX / count);
|
||||
var y = (sumY / count);
|
||||
var z = (sumZ / count);
|
||||
var newCenter = new Vector3(x, y, z);
|
||||
if (!newCenter.Equals(centers[i]))
|
||||
{
|
||||
centers[i] = newCenter;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the clusters
|
||||
return centers;
|
||||
}
|
||||
|
||||
static Vector3 FindNearestCenter(Vector3 color, Vector3[] centers)
|
||||
{
|
||||
var nearest = centers[0];
|
||||
var minDist = float.MaxValue;
|
||||
|
||||
foreach (var center in centers)
|
||||
{
|
||||
var dist = Vector3.Distance(color, center); // The original version implemented a Distance method by wieslawsoltes himself, I changed that to Vector ones.
|
||||
if (dist < minDist)
|
||||
{
|
||||
nearest = center;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
static List<Vector3> KMeansPlusPlusCluster(Dictionary<Vector3, int> colors, int numClusters)
|
||||
{
|
||||
Random random = new Random();
|
||||
var clusterCount = Math.Min(numClusters, colors.Count);
|
||||
var clusters = new List<Vector3>();
|
||||
var targetColor = colors.Keys.ToList();
|
||||
var index = random.Next(targetColor.Count);
|
||||
clusters.Add(targetColor[index]);
|
||||
for (int i = 1; i < clusterCount; i++)
|
||||
{
|
||||
float accumulatedDistances = 0f;
|
||||
float[] accDistances = new float[targetColor.Count];
|
||||
for (int vectorId = 0; vectorId < targetColor.Count; vectorId++)
|
||||
{
|
||||
var minDistanceItem = clusters[0];
|
||||
var minDistance = Vector3.Distance(minDistanceItem, targetColor[vectorId]);
|
||||
for (int clusterIdx = 1; clusterIdx < i; clusterIdx++)
|
||||
{
|
||||
float currentDistance = Vector3.Distance(clusters[clusterIdx], targetColor[vectorId]);
|
||||
if (currentDistance < minDistance)
|
||||
{
|
||||
minDistance = currentDistance;
|
||||
}
|
||||
accumulatedDistances += minDistance * minDistance;
|
||||
accDistances[vectorId] = accumulatedDistances;
|
||||
}
|
||||
}
|
||||
float targetPoint = (float)random.NextDouble() * accumulatedDistances;
|
||||
for (int vectorId = 0; vectorId < targetColor.Count; vectorId++)
|
||||
{
|
||||
if (accDistances[vectorId] >= targetPoint)
|
||||
{
|
||||
clusters.Add(targetColor[vectorId]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
using Impressionist.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Impressionist.Implementations
|
||||
{
|
||||
public class OctTreePaletteGenerator :
|
||||
IThemeColorGenrator,
|
||||
IPaletteGenrator
|
||||
{
|
||||
public Task<ThemeColorResult> CreateThemeColor(Dictionary<Vector3, int> sourceColor, bool ignoreWhite = false)
|
||||
{
|
||||
var quantizer = new PaletteQuantizer();
|
||||
var builder = sourceColor.AsEnumerable();
|
||||
if (ignoreWhite && sourceColor.Count > 1)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.X <= 250 || t.Key.Y <= 250 || t.Key.Z <= 250);
|
||||
}
|
||||
var targetColor = builder.ToDictionary(t => t.Key, t => t.Value);
|
||||
foreach (var color in targetColor)
|
||||
{
|
||||
quantizer.AddColorRange(color.Key, color.Value);
|
||||
}
|
||||
quantizer.Quantize(1);
|
||||
var index = new List<Vector3>() { targetColor.Keys.FirstOrDefault() };
|
||||
var result = quantizer.GetThemeResult();
|
||||
var colorIsDark = result.RGBVectorLStarIsDark();
|
||||
return Task.FromResult(new ThemeColorResult(result, colorIsDark));
|
||||
}
|
||||
public async Task<PaletteResult> CreatePalette(Dictionary<Vector3, int> sourceColor, int clusterCount, bool ignoreWhite = false, bool? isDark = null)
|
||||
{
|
||||
var quantizer = new PaletteQuantizer();
|
||||
if (sourceColor.Count == 1)
|
||||
{
|
||||
ignoreWhite = false;
|
||||
}
|
||||
var builder = sourceColor.AsEnumerable();
|
||||
var colorResult = await CreateThemeColor(sourceColor, ignoreWhite);
|
||||
var colorIsDark = false;
|
||||
if (isDark == null)
|
||||
{
|
||||
if (ignoreWhite)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.X <= 250 || t.Key.Y <= 250 || t.Key.Z <= 250);
|
||||
}
|
||||
colorIsDark = colorResult.ColorIsDark;
|
||||
if (colorIsDark)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
else
|
||||
{
|
||||
builder = builder.Where(t => !t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
colorIsDark = isDark.Value;
|
||||
if (colorIsDark)
|
||||
{
|
||||
builder = builder.Where(t => t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
else
|
||||
{
|
||||
builder = builder.Where(t => !t.Key.RGBVectorLStarIsDark());
|
||||
}
|
||||
}
|
||||
var targetColor = builder.ToDictionary(t => t.Key, t => t.Value);
|
||||
foreach (var color in targetColor)
|
||||
{
|
||||
quantizer.AddColorRange(color.Key, color.Value);
|
||||
}
|
||||
quantizer.Quantize(clusterCount);
|
||||
var index = targetColor.Keys.ToList();
|
||||
List<Vector3> quantizeResult;
|
||||
if (colorIsDark)
|
||||
{
|
||||
quantizeResult = quantizer.GetPaletteResult(clusterCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
quantizeResult = quantizer.GetPaletteResult(clusterCount);
|
||||
}
|
||||
List<Vector3> result;
|
||||
if (quantizeResult.Count < clusterCount)
|
||||
{
|
||||
var count = quantizeResult.Count;
|
||||
result = new List<Vector3>();
|
||||
for (int i = 0; i < clusterCount; i++)
|
||||
{
|
||||
// You know, it is always hard to fullfill a palette when you have no enough colors. So please forgive me when placing the same color over and over again.
|
||||
result.Add(quantizeResult[i % count]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = quantizeResult;
|
||||
}
|
||||
return new PaletteResult(result, colorIsDark, colorResult);
|
||||
}
|
||||
|
||||
private class PaletteQuantizer
|
||||
{
|
||||
private readonly Node Root;
|
||||
private IDictionary<int, List<Node>> levelNodes;
|
||||
|
||||
public PaletteQuantizer()
|
||||
{
|
||||
Root = new Node(this);
|
||||
levelNodes = new Dictionary<int, List<Node>>();
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
levelNodes[i] = new List<Node>();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddColor(Vector3 color)
|
||||
{
|
||||
Root.AddColor(color, 0);
|
||||
}
|
||||
|
||||
public void AddColorRange(Vector3 color, int count)
|
||||
{
|
||||
Root.AddColorRange(color, 0, count);
|
||||
}
|
||||
|
||||
public void AddLevelNode(Node node, int level)
|
||||
{
|
||||
levelNodes[level].Add(node);
|
||||
}
|
||||
|
||||
public List<Vector3> GetPaletteResult()
|
||||
{
|
||||
return Root.GetPaletteResult().Keys.ToList();
|
||||
}
|
||||
public List<Vector3> GetPaletteResult(int count)
|
||||
{
|
||||
return Root.GetPaletteResult().OrderByDescending(t=>t.Value).Take(count).Select(t=>t.Key).ToList();
|
||||
}
|
||||
public Vector3 GetThemeResult()
|
||||
{
|
||||
return Root.GetThemeResult();
|
||||
}
|
||||
public void Quantize(int colorCount)
|
||||
{
|
||||
var nodesToRemove = levelNodes[7].Count - colorCount;
|
||||
int level = 6;
|
||||
var toBreak = false;
|
||||
while (level >= 0 && nodesToRemove > 0)
|
||||
{
|
||||
var leaves = levelNodes[level]
|
||||
.Where(n => n.ChildrenCount - 1 <= nodesToRemove)
|
||||
.OrderBy(n => n.ChildrenCount);
|
||||
foreach (var leaf in leaves)
|
||||
{
|
||||
if (leaf.ChildrenCount > nodesToRemove)
|
||||
{
|
||||
toBreak = true;
|
||||
continue;
|
||||
}
|
||||
nodesToRemove -= (leaf.ChildrenCount - 1);
|
||||
leaf.Merge();
|
||||
if (nodesToRemove <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
levelNodes.Remove(level + 1);
|
||||
level--;
|
||||
if (toBreak)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Node
|
||||
{
|
||||
private readonly PaletteQuantizer parent;
|
||||
private Node[] Children = new Node[8];
|
||||
private Vector3 Color { get; set; }
|
||||
private int Count { get; set; } = 0;
|
||||
|
||||
public int ChildrenCount => Children.Count(c => c != null);
|
||||
|
||||
public Node(PaletteQuantizer parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void AddColor(Vector3 color, int level)
|
||||
{
|
||||
if (level < 8)
|
||||
{
|
||||
var index = GetIndex(color, level);
|
||||
if (Children[index] == null)
|
||||
{
|
||||
var newNode = new Node(parent);
|
||||
Children[index] = newNode;
|
||||
parent.AddLevelNode(newNode, level);
|
||||
}
|
||||
Children[index].AddColor(color, level + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Color = color;
|
||||
Count++;
|
||||
}
|
||||
}
|
||||
public void AddColorRange(Vector3 color, int level, int count)
|
||||
{
|
||||
if (level < 8)
|
||||
{
|
||||
var index = GetIndex(color, level);
|
||||
if (Children[index] == null)
|
||||
{
|
||||
var newNode = new Node(parent);
|
||||
Children[index] = newNode;
|
||||
parent.AddLevelNode(newNode, level);
|
||||
}
|
||||
Children[index].AddColorRange(color, level + 1, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
Color = color;
|
||||
Count += count;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetColor(Vector3 color, int level)
|
||||
{
|
||||
if (ChildrenCount == 0)
|
||||
{
|
||||
return Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = GetIndex(color, level);
|
||||
return Children[index].GetColor(color, level + 1);
|
||||
}
|
||||
}
|
||||
public Vector3 GetThemeResult()
|
||||
{
|
||||
var paletteResult = GetPaletteResult();
|
||||
var sum = new Vector3(0, 0, 0);
|
||||
var count = 0;
|
||||
foreach (var item in paletteResult)
|
||||
{
|
||||
sum += item.Key * item.Value;
|
||||
count += item.Value;
|
||||
}
|
||||
return sum / count;
|
||||
}
|
||||
public Dictionary<Vector3, int> GetPaletteResult()
|
||||
{
|
||||
var result = new Dictionary<Vector3, int>();
|
||||
if (!Children.Any(t => t != null)) result[Color] = Count;
|
||||
else
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.NodeGetResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
private void NodeGetResult(Dictionary<Vector3, int> result)
|
||||
{
|
||||
if (!Children.Any(t => t != null)) result[Color] = Count;
|
||||
else
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child != null)
|
||||
{
|
||||
child.NodeGetResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private byte GetIndex(Vector3 color, int level)
|
||||
{
|
||||
byte ret = 0;
|
||||
var mask = Convert.ToByte(0b10000000 >> level);
|
||||
if (((byte)color.X & mask) != 0)
|
||||
{
|
||||
ret |= 0b100;
|
||||
}
|
||||
if (((byte)color.Y & mask) != 0)
|
||||
{
|
||||
ret |= 0b010;
|
||||
}
|
||||
if (((byte)color.Z & mask) != 0)
|
||||
{
|
||||
ret |= 0b001;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Merge()
|
||||
{
|
||||
Color = Average(Children.Where(c => c != null).Select(c => new Tuple<Vector3, int>(c.Color, c.Count)));
|
||||
Count = Children.Sum(c => c?.Count ?? 0);
|
||||
Children = new Node[8];
|
||||
}
|
||||
|
||||
private static Vector3 Average(IEnumerable<Tuple<Vector3, int>> colors)
|
||||
{
|
||||
var totals = colors.Sum(c => c.Item2);
|
||||
return new Vector3(
|
||||
x: (int)colors.Sum(c => c.Item1.X * c.Item2) / totals,
|
||||
y: (int)colors.Sum(c => c.Item1.Y * c.Item2) / totals,
|
||||
z: (int)colors.Sum(c => c.Item1.Z * c.Item2) / totals);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Impressionist.Implementations
|
||||
{
|
||||
public static class PaletteGenerators
|
||||
{
|
||||
public static readonly KMeansPaletteGenerator KMeansPaletteGenerator = new KMeansPaletteGenerator();
|
||||
public static readonly OctTreePaletteGenerator OctTreePaletteGenerator = new OctTreePaletteGenerator();
|
||||
}
|
||||
}
|
||||
13
Impressionist/Impressionist/Impressionist.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
|
||||
<IsPublishable>False</IsPublishable>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.6.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
21
Impressionist/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Storyteller Studios and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
11
Impressionist/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Impressionist
|
||||
|
||||
Impressionist is a color clustering project using K-Means or other algorithms.
|
||||
|
||||
## Third-Party Notices
|
||||
|
||||
[wieslawsoltes/PaletteGenerator](https://github.com/wieslawsoltes/PaletteGenerator/)
|
||||
|
||||
[tompazourek/Colourful](https://github.com/tompazourek/Colourful)
|
||||
|
||||
[bwaacon/cSharpColourQuantization](https://github.com/bacowan/cSharpColourQuantization)
|
||||
|
Before Width: | Height: | Size: 453 KiB After Width: | Height: | Size: 1.1 MiB |
19
README.md
@@ -1,4 +1,4 @@
|
||||
[_Click here to view frequently asked questions (FAQ)_](https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/FAQ.md)
|
||||
[_Click here to view frequently asked questions (FAQ)_](https://github.com/jayfunc/BetterLyrics/blob/dev/FAQ/index.md)
|
||||
|
||||

|
||||
|
||||
@@ -144,6 +144,10 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
|
||||
### Fullscreen mode
|
||||
|
||||
> ⚠️ Due to GIF format and frame rate limitations, the displayed effect is for preview only. Please refer to the actual device for the actual effect.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Docked mode
|
||||
@@ -154,6 +158,12 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
|
||||

|
||||
|
||||
### Song switching animation
|
||||
|
||||
> ⚠️ Due to GIF format and frame rate limitations, the displayed effect is for preview only. Please refer to the actual device for the actual effect.
|
||||
|
||||

|
||||
|
||||
## Demonstration
|
||||
|
||||
Watch our demo video (uploaded on 21 Oct 2025) on Bilibili [here](https://www.bilibili.com/video/BV1QRstz1EGt/).
|
||||
@@ -182,7 +192,7 @@ Watch our demo video (uploaded on 21 Oct 2025) on Bilibili [here](https://www.bi
|
||||
|
||||
If you are using a third-party modified Windows, you probably can not launch the app.
|
||||
|
||||
To solve this issue, please try to download from [Google Drive (v1.0.82.0)](https://drive.google.com/file/d/1UtGmmDqD5SWGJpp4LmWycHUT9-Bb8fur/view?usp=sharing) (may not be the latest version) and follow the instructions [here](https://github.com/jayfunc/BetterLyrics/blob/dev/How2Install/How2Install.md).
|
||||
To solve this issue, please try to download from Google Drive (see [release](https://github.com/jayfunc/BetterLyrics/releases/latest) for the download link) and follow the instructions [here](https://github.com/jayfunc/BetterLyrics/blob/dev/Sideloadly/index.md).
|
||||
|
||||
## Build
|
||||
|
||||
@@ -225,10 +235,9 @@ Before you build, make sure that you have already replaced `BetterLyrics\BetterL
|
||||
|
||||
## ✍️ Help us translate into your language
|
||||
|
||||
Cannot find your language?
|
||||
Don't worry! Start translating and become one of the contributors! 😆
|
||||
Cannot find your language? Or have better translations? Don't worry! Start translating and becoming one of the contributors! 😆
|
||||
|
||||
Fork this project and navigate to `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Strings\` then open any `.resw` file to start contributing!
|
||||
Visit https://crowdin.com/project/betterlyrics/invite?h=c9bfb28fce061484883c0891e7a26f9b2592556 to accept invitation and become a valuable translator now!
|
||||
|
||||
## Star history
|
||||
|
||||
|
||||
BIN
Screenshots/PixPin_2025-10-24_18-13-44.gif
Normal file
|
After Width: | Height: | Size: 37 MiB |
BIN
Screenshots/PixPin_2025-10-24_18-17-17.gif
Normal file
|
After Width: | Height: | Size: 15 MiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
@@ -5,7 +5,7 @@
|
||||
Be sure that you have already enable developer mode. To do that, you can follow the steps below:
|
||||
|
||||
1. Go to "Settings", select "System", go to "Developer Options".
|
||||

|
||||

|
||||
|
||||
2. Turn on "Developer Mode" and enable local PowerShell script allowance.
|
||||

|
||||