Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
d0b626c508 | ||
|
|
9debdc76f9 | ||
|
|
67a45e90fa | ||
|
|
b4c7655043 | ||
|
|
2adc2aced2 | ||
|
|
e638739638 | ||
|
|
c24213358e | ||
|
|
6e78f849c4 | ||
|
|
80444b69e0 | ||
|
|
78775e9bb3 |
@@ -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.82.0" />
|
||||
Version="1.0.84.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"
|
||||
|
||||
@@ -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}">
|
||||
|
||||
@@ -137,6 +137,27 @@
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageAdaptEnvColor"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
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"
|
||||
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" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDockMonitor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.MonitorDeviceName, Mode=TwoWay}" />
|
||||
@@ -275,24 +296,6 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.IsBorderless, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageAdaptEnvColor"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
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">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.EnvironmentSampleMode, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleBelow" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleAbove" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleInner" />
|
||||
<ComboBoxItem x:Uid="SettingsPageEnvColorSampleEdge" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageDragArea" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.TitleBarArea, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageTitleBarAreaNone" />
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
}
|
||||
case WindowPixelSampleMode.AboveWindow:
|
||||
{
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - 3, screenWidth, 1);
|
||||
return GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - 2, screenWidth, 1);
|
||||
}
|
||||
case WindowPixelSampleMode.WindowArea:
|
||||
{
|
||||
@@ -149,49 +149,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
if (width <= 0 || height <= 0)
|
||||
return System.Drawing.Color.Transparent;
|
||||
|
||||
var edgeThickness = new Thickness(36, 0, 36, 0);
|
||||
var edgeThickness = new Thickness(36, 36, 36, 36);
|
||||
List<System.Drawing.Color> edgeColors = [];
|
||||
|
||||
// Top edge
|
||||
if (edgeThickness.Top > 0 && edgeThickness.Top < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top,
|
||||
width,
|
||||
(int)edgeThickness.Top
|
||||
)
|
||||
);
|
||||
if (edgeThickness.Top > 0)
|
||||
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left, myRect.Top - (int)edgeThickness.Top, width, (int)edgeThickness.Top));
|
||||
// Bottom edge
|
||||
if (edgeThickness.Bottom > 0 && edgeThickness.Bottom < height)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Bottom - (int)edgeThickness.Bottom,
|
||||
width,
|
||||
(int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
if (edgeThickness.Bottom > 0)
|
||||
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left, myRect.Bottom, width, (int)edgeThickness.Bottom));
|
||||
// Left edge
|
||||
if (edgeThickness.Left > 0 && edgeThickness.Left < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Left,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Left,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
if (edgeThickness.Left > 0)
|
||||
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Left - (int)edgeThickness.Left, myRect.Top, (int)edgeThickness.Left, height));
|
||||
// Right edge
|
||||
if (edgeThickness.Right > 0 && edgeThickness.Right < width)
|
||||
edgeColors.Add(
|
||||
GetAverageColorFromScreenRegion(
|
||||
myRect.Right - (int)edgeThickness.Right,
|
||||
myRect.Top + (int)edgeThickness.Top,
|
||||
(int)edgeThickness.Right,
|
||||
height - (int)edgeThickness.Top - (int)edgeThickness.Bottom
|
||||
)
|
||||
);
|
||||
if (edgeThickness.Right > 0)
|
||||
edgeColors.Add(GetAverageColorFromScreenRegion(myRect.Right, myRect.Top, (int)edgeThickness.Right, height));
|
||||
|
||||
// 合并四边平均色
|
||||
if (edgeColors.Count == 0)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using Lyricify.Lyrics.Models;
|
||||
using Lyricify.Lyrics.Parsers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -13,7 +14,7 @@ using LyricsData = BetterLyrics.WinUI3.Models.LyricsData;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LyricsParser
|
||||
public partial class LyricsParser
|
||||
{
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
@@ -34,10 +35,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
ParseLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.Qrc:
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.QrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(QrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Krc:
|
||||
ParseQQNeteaseKugou(Lyricify.Lyrics.Parsers.KrcParser.Parse(raw).Lines);
|
||||
ParseQQNeteaseKugou(KrcParser.Parse(raw).Lines);
|
||||
break;
|
||||
case LyricsFormat.Ttml:
|
||||
ParseTtml(raw);
|
||||
@@ -121,9 +122,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
new List<(int time, string text, List<(int time, string text)> syllables)>();
|
||||
|
||||
// 支持 [mm:ss.xx]字、<mm:ss.xx>字,毫秒两位或三位
|
||||
var syllableRegex = new Regex(
|
||||
@"(\[|\<)(\d{2}):(\d{2})\.(\d{2,3})(\]|\>)([^\[\]\<\>]*)"
|
||||
);
|
||||
var syllableRegex = SyllableRegex();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
@@ -140,7 +139,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
syllables.Add((totalMs, text));
|
||||
}
|
||||
if (syllables.Count > 0)
|
||||
if (syllables.Count > 1)
|
||||
{
|
||||
lrcLines.Add(
|
||||
(
|
||||
@@ -153,18 +152,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
else
|
||||
{
|
||||
// 普通LRC行
|
||||
var bracketRegex = new Regex(@"\[(\d{2}):(\d{2})\.(\d{2,3})\]");
|
||||
Regex? bracketRegex = LrcRegex();
|
||||
var bracketMatches = bracketRegex.Matches(line);
|
||||
|
||||
string content = line;
|
||||
int? lineStartTime = null;
|
||||
if (bracketMatches.Count > 0)
|
||||
{
|
||||
var m = bracketMatches[0];
|
||||
var m = bracketMatches![0];
|
||||
int min = int.Parse(m.Groups[1].Value);
|
||||
int sec = int.Parse(m.Groups[2].Value);
|
||||
int ms = int.Parse(m.Groups[3].Value.PadRight(3, '0'));
|
||||
int ms = int.Parse(m.Groups[4].Value.PadRight(3, '0'));
|
||||
lineStartTime = min * 60_000 + sec * 1000 + ms;
|
||||
content = bracketRegex.Replace(line, "");
|
||||
content = bracketRegex!.Replace(line, "");
|
||||
lrcLines.Add((lineStartTime.Value, content, new List<(int, string)>()));
|
||||
}
|
||||
}
|
||||
@@ -450,5 +450,10 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
_lyricsDataArr.Add(new LyricsData(lyricsLines));
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\[(\d*):(\d*)(\.|\:)(\d*)\]")]
|
||||
private static partial Regex LrcRegex();
|
||||
[GeneratedRegex(@"(\[|\<)(\d*):(\d*)\.(\d*)(\]|\>)([^\[\]\<\>]*)")]
|
||||
private static partial Regex SyllableRegex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PlayerIdMatcher
|
||||
{
|
||||
private static readonly List<string> _neteaseFamilyRegex =
|
||||
[
|
||||
"cloudmusic.exe", //NetEaseCloudMusic
|
||||
"^17588BrandonWong\\.LyricEase_", //LyricEase
|
||||
"^48848aaaaaaccd\\.HyPlayer_" //HyPlayer
|
||||
];
|
||||
|
||||
public static bool IsNeteaseFamily(string player)
|
||||
{
|
||||
foreach (var regex in _neteaseFamilyRegex)
|
||||
{
|
||||
var isMatch = Regex.IsMatch(player, regex);
|
||||
if (isMatch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>()
|
||||
@@ -286,22 +285,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
if (_workAreas.Contains(hwnd)) return;
|
||||
|
||||
RegisterWorkArea(hwnd);
|
||||
|
||||
double y = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
||||
|
||||
y -= 1;
|
||||
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
||||
(int)y,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Width,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.DockHeight + 1,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
}
|
||||
|
||||
private static void RegisterWorkArea(IntPtr hwnd)
|
||||
@@ -350,55 +333,40 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_workAreas.Remove(hwnd);
|
||||
}
|
||||
|
||||
public static void UpdateWorkAreaHeight<T>()
|
||||
public static void UpdateWorkArea<T>()
|
||||
{
|
||||
var window = GetWindowByWindowType<T>() as Window;
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = WindowNative.GetWindowHandle(window);
|
||||
|
||||
App.DispatcherQueueTimer?.Debounce(() =>
|
||||
if (!_workAreas.Contains(hwnd))
|
||||
return;
|
||||
|
||||
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
|
||||
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
||||
|
||||
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
{
|
||||
if (!_workAreas.Contains(hwnd))
|
||||
return;
|
||||
|
||||
var uEdge = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
|
||||
|
||||
double top = _liveStatesService.LiveStates.LyricsWindowStatus.DockPlacement == DockPlacement.Top ?
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Top :
|
||||
_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
||||
|
||||
double bottom = top + _liveStatesService.LiveStates.LyricsWindowStatus.DockHeight;
|
||||
|
||||
Shell32.APPBARDATA abd = new()
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
cbSize = (uint)Marshal.SizeOf<Shell32.APPBARDATA>(),
|
||||
hWnd = hwnd,
|
||||
uEdge = uEdge,
|
||||
rc = new RECT
|
||||
{
|
||||
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
||||
Top = (int)top,
|
||||
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
|
||||
Bottom = (int)bottom,
|
||||
},
|
||||
};
|
||||
Left = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
||||
Top = (int)top,
|
||||
Right = (int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Right,
|
||||
Bottom = (int)bottom,
|
||||
},
|
||||
};
|
||||
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
|
||||
// 同步窗口实际高度和位置
|
||||
User32.SetWindowPos(
|
||||
hwnd,
|
||||
IntPtr.Zero,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Left,
|
||||
(int)top - 1,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.MonitorBounds.Width,
|
||||
(int)_liveStatesService.LiveStates.LyricsWindowStatus.DockHeight + 1,
|
||||
User32.SetWindowPosFlags.SWP_SHOWWINDOW
|
||||
);
|
||||
|
||||
}, TimeSpan.FromMilliseconds(100));
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
|
||||
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
|
||||
}
|
||||
|
||||
public static void SetLyricsWindowVisibilityByPlayingStatus()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.WinUI.Controls;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
@@ -33,11 +34,21 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(LyricsWindowStatus.DockHeight):
|
||||
case nameof(LyricsWindowStatus.IsWorkArea):
|
||||
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
UpdateWindowBoundsWhenWorkArea();
|
||||
}
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.DockHeight):
|
||||
case nameof(LyricsWindowStatus.DockPlacement):
|
||||
case nameof(LyricsWindowStatus.MonitorDeviceName):
|
||||
WindowHelper.UpdateWorkAreaHeight<LyricsWindow>();
|
||||
WindowHelper.UpdateWorkArea<LyricsWindow>();
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
UpdateWindowBoundsWhenWorkArea();
|
||||
}
|
||||
break;
|
||||
case nameof(LyricsWindowStatus.IsShownInSwitchers):
|
||||
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
@@ -85,17 +96,43 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
|
||||
|
||||
public void RefreshLyricsWindowStatus()
|
||||
{
|
||||
// Order matters!!!
|
||||
WindowHelper.SetIsWorkArea<LyricsWindow>(LiveStates.LyricsWindowStatus.IsWorkArea);
|
||||
|
||||
if (LiveStates.LyricsWindowStatus.IsWorkArea)
|
||||
{
|
||||
UpdateWindowBoundsWhenWorkArea();
|
||||
}
|
||||
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
|
||||
LiveStates.LyricsWindowStatus.UpdateMonitorNameAndBounds();
|
||||
LiveStates.LyricsWindowStatus.UpdateDemoWindowAndMonitorBounds();
|
||||
|
||||
WindowHelper.SetIsShowInSwitchers<LyricsWindow>(LiveStates.LyricsWindowStatus.IsShownInSwitchers);
|
||||
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
|
||||
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
|
||||
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
|
||||
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
|
||||
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
|
||||
WindowHelper.MoveAndResize<LyricsWindow>(LiveStates.LyricsWindowStatus.WindowBounds);
|
||||
LiveStates.LyricsWindowStatus.UpdateMonitorNameAndBounds();
|
||||
LiveStates.LyricsWindowStatus.UpdateDemoWindowAndMonitorBounds();
|
||||
}
|
||||
|
||||
private void UpdateWindowBoundsWhenWorkArea()
|
||||
{
|
||||
LiveStates.LyricsWindowStatus.WindowBounds = new Windows.Foundation.Rect(
|
||||
LiveStates.LyricsWindowStatus.MonitorBounds.X,
|
||||
LiveStates.LyricsWindowStatus.DockPlacement switch
|
||||
{
|
||||
Enums.DockPlacement.Top => LiveStates.LyricsWindowStatus.MonitorBounds.Top,
|
||||
Enums.DockPlacement.Bottom => LiveStates.LyricsWindowStatus.MonitorBounds.Bottom - LiveStates.LyricsWindowStatus.DockHeight - 1,
|
||||
_ => LiveStates.LyricsWindowStatus.MonitorBounds.Top,
|
||||
},
|
||||
LiveStates.LyricsWindowStatus.MonitorBounds.Width,
|
||||
LiveStates.LyricsWindowStatus.DockPlacement switch
|
||||
{
|
||||
Enums.DockPlacement.Top => LiveStates.LyricsWindowStatus.DockHeight,
|
||||
Enums.DockPlacement.Bottom => LiveStates.LyricsWindowStatus.DockHeight + 1,
|
||||
_ => LiveStates.LyricsWindowStatus.DockHeight,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -322,13 +324,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
|
||||
fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
|
||||
}
|
||||
else if (sessionId == Constants.PlayerID.NetEaseCloudMusic)
|
||||
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId))
|
||||
{
|
||||
songId = mediaProperties.Genres.FirstOrDefault()?.Replace("NCM-", "");
|
||||
if (songId != null && songId.Length != 10)
|
||||
{
|
||||
songId = null;
|
||||
}
|
||||
}
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
@@ -356,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,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();
|
||||
}
|
||||
}
|
||||
@@ -688,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>
|
||||
@@ -1249,6 +1261,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI;
|
||||
@@ -84,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);
|
||||
@@ -406,15 +408,19 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
_titleXTransition.Update(_elapsedTime);
|
||||
_titleYTransition.Update(_elapsedTime);
|
||||
|
||||
_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);
|
||||
@@ -433,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
|
||||
{
|
||||
@@ -779,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;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,10 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
presenter.IsAlwaysOnTop = true;
|
||||
}
|
||||
UpdateBackdropAccentColor(hwnd);
|
||||
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAdaptToEnvironment)
|
||||
{
|
||||
UpdateBackdropAccentColor(hwnd);
|
||||
}
|
||||
}, Constants.Time.DebounceTimeout);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -52,5 +52,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindow>();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private static void OpenLyricsWindowSwitch()
|
||||
{
|
||||
WindowHelper.OpenOrShowWindow<LyricsWindowSwitchWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
|
||||
<Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged" RightTapped="RootGrid_RightTapped">
|
||||
<!-- Lyrics area -->
|
||||
<renderer:LyricsRenderer />
|
||||
<!--<Image Source="/Assets/Cover.jpg" />-->
|
||||
|
||||
@@ -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 |
|
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 |
40
FAQ/index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Welcome to BetterLyrics
|
||||
|
||||
### 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 add more modes?
|
||||
|
||||
If this is the first time that you use this app, only 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 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 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.
|
||||
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)
|
||||
16
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,9 +158,15 @@ 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 introduction video (uploaded on 18 Aug 2025) on Bilibili [here](https://www.bilibili.com/video/BV1yLYtzQEME/).
|
||||
Watch our demo video (uploaded on 21 Oct 2025) on Bilibili [here](https://www.bilibili.com/video/BV1QRstz1EGt/).
|
||||
|
||||
## Try it now
|
||||
|
||||
@@ -182,7 +192,7 @@ Watch our introduction video (uploaded on 18 Aug 2025) on Bilibili [here](https:
|
||||
|
||||
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
|
||||
|
||||
|
||||
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.
|
||||

|
||||