diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
index 7a08346..3a487b9 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/Package.appxmanifest
@@ -12,7 +12,7 @@
+ Version="1.2.246.0" />
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
index 9f1217e..444f68d 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsAnimator.cs
@@ -1,6 +1,9 @@
-using BetterLyrics.WinUI3.Helper;
+using ATL;
+using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
+using DevWinUI;
+using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -31,7 +34,7 @@ namespace BetterLyrics.WinUI3.Logic
bool isLayoutChanged,
bool isPrimaryPlayingLineChanged,
bool isMouseScrollingChanged,
- double currentProgressMs
+ double currentPositionMs
)
{
if (lines == null) return;
@@ -48,7 +51,7 @@ namespace BetterLyrics.WinUI3.Logic
var line = lines.ElementAtOrDefault(i);
if (line == null) continue;
- bool isSecondaryLinePlaying = currentProgressMs >= line.StartMs && currentProgressMs <= line.EndMs;
+ bool isSecondaryLinePlaying = line.StartMs <= currentPositionMs && currentPositionMs <= line.EndMs;
if (i == primaryPlayingLineIndex) isSecondaryLinePlaying = true;
bool isSecondaryLinePlayingChanged = line.IsPlayingLastFrame != isSecondaryLinePlaying;
line.IsPlayingLastFrame = isSecondaryLinePlaying;
@@ -150,6 +153,93 @@ namespace BetterLyrics.WinUI3.Logic
line.YOffsetTransition.StartTransition(targetYScrollOffset);
}
+ if (line.RenderLyricsOriginalChars != null)
+ {
+ foreach (var renderChar in line.RenderLyricsOriginalChars)
+ {
+ var syllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= renderChar.Index && renderChar.Index <= x.EndIndex);
+ if (syllable == null) continue;
+
+ var avgCharDuration = syllable.DurationMs / syllable.Length;
+ if (avgCharDuration == null || avgCharDuration == 0) continue;
+
+ var charStartMs = syllable.StartMs + (renderChar.Index - syllable.StartIndex) * avgCharDuration.Value;
+ var charEndMs = charStartMs + avgCharDuration;
+ var progressPlayed = (currentPositionMs - charStartMs) / avgCharDuration.Value;
+ progressPlayed = Math.Clamp(progressPlayed, 0, 1);
+ renderChar.ProgressPlayed = progressPlayed;
+
+ bool isCharPlaying = charStartMs <= currentPositionMs && currentPositionMs <= charEndMs;
+ bool isCharPlayingChanged = renderChar.IsPlayingLastFrame != isCharPlaying;
+
+ if (isSecondaryLinePlayingChanged || isCharPlayingChanged)
+ {
+ if (lyricsEffect.IsLyricsScaleEffectEnabled)
+ {
+ double targetScale =
+ lyricsEffect.IsLyricsScaleEffectAmountAutoAdjust ? 1.15 : lyricsEffect.LyricsScaleEffectAmount / 100.0;
+
+ if (isCharPlayingChanged)
+ {
+ if (syllable.DurationMs >= lyricsEffect.LyricsScaleEffectLongSyllableDuration)
+ {
+ renderChar.ScaleTransition.SetDuration((syllable.DurationMs ?? 0) / 1000.0 / 2);
+ renderChar.ScaleTransition.StartTransition(isCharPlaying ? targetScale : 1);
+ }
+ }
+ }
+
+ if (lyricsEffect.IsLyricsGlowEffectEnabled)
+ {
+ double targetGlow = lyricsEffect.IsLyricsGlowEffectAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.2 : lyricsEffect.LyricsGlowEffectAmount;
+ switch (lyricsEffect.LyricsGlowEffectScope)
+ {
+ case Enums.LyricsEffectScope.LongDurationSyllable:
+ if (isCharPlayingChanged)
+ {
+ if (syllable.DurationMs >= lyricsEffect.LyricsGlowEffectLongSyllableDuration)
+ {
+ renderChar.GlowTransition.SetDuration((syllable.DurationMs ?? 0) / 1000.0 / 2);
+ renderChar.GlowTransition.StartTransition(isCharPlaying ? targetGlow : 0);
+ }
+ }
+ break;
+ case Enums.LyricsEffectScope.LineStartToCurrentChar:
+ if (isSecondaryLinePlayingChanged)
+ {
+ renderChar.GlowTransition.SetDuration(renderChar.AnimationDuration);
+ renderChar.GlowTransition.StartTransition(isSecondaryLinePlaying ? targetGlow : 0);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (lyricsEffect.IsLyricsFloatAnimationEnabled)
+ {
+ double targetFloat =
+ lyricsEffect.IsLyricsFloatAnimationAmountAutoAdjust ? renderChar.LayoutRect.Height * 0.1 : lyricsEffect.LyricsFloatAnimationAmount;
+
+ if (isSecondaryLinePlayingChanged)
+ {
+ renderChar.FloatTransition.StartTransition(isSecondaryLinePlaying ? targetFloat : 0);
+ }
+ if (isCharPlayingChanged)
+ {
+ renderChar.FloatTransition.StartTransition(0);
+ }
+ }
+
+ renderChar.IsPlayingLastFrame = isCharPlaying;
+ }
+
+ renderChar.ScaleTransition.Update(elapsedTime);
+ renderChar.GlowTransition.Update(elapsedTime);
+ renderChar.FloatTransition.Update(elapsedTime);
+ }
+ }
+
line.AngleTransition.Update(elapsedTime);
line.ScaleTransition.Update(elapsedTime);
line.BlurAmountTransition.Update(elapsedTime);
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs
index f43d8b5..706d617 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Logic/LyricsLayoutManager.cs
@@ -123,6 +123,8 @@ namespace BetterLyrics.WinUI3.Logic
// 更新中心点
line.UpdateCenterPosition(lyricsWidth, style.LyricsAlignmentType);
+
+ line.RecalculateCharacterGeometries();
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsChar.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsChar.cs
new file mode 100644
index 0000000..3de965f
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsChar.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public class LyricsChar
+ {
+ public string Text { get; set; } = "";
+ public int Index { get; set; }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSyllable.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSyllable.cs
index 4e4c30d..8ffe291 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSyllable.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsSyllable.cs
@@ -4,10 +4,15 @@ namespace BetterLyrics.WinUI3.Models
{
public class LyricsSyllable
{
- public int? EndMs { get; set; }
- public int StartIndex { get; set; }
- public int StartMs { get; set; }
public string Text { get; set; } = string.Empty;
+ public int Length => Text.Length;
+
+ public int StartIndex { get; set; }
+ public int EndIndex => StartIndex + Length - 1;
+
+ public int StartMs { get; set; }
+ public int? EndMs { get; set; }
+
public int? DurationMs => EndMs - StartMs;
public bool IsLongDuration => DurationMs >= 700;
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsChar.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsChar.cs
new file mode 100644
index 0000000..72318b9
--- /dev/null
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsChar.cs
@@ -0,0 +1,43 @@
+using BetterLyrics.WinUI3.Enums;
+using BetterLyrics.WinUI3.Helper;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Windows.Foundation;
+
+namespace BetterLyrics.WinUI3.Models
+{
+ public class RenderLyricsChar : LyricsChar
+ {
+ public Rect LayoutRect { get; set; }
+
+ public double AnimationDuration { get; set; } = 0.3;
+
+ public ValueTransition ScaleTransition { get; set; }
+ public ValueTransition GlowTransition { get; set; }
+ public ValueTransition FloatTransition { get; set; }
+
+ public double ProgressPlayed { get; set; } = 0; // 0~1
+
+ public bool IsPlayingLastFrame { get; set; } = false;
+
+ public RenderLyricsChar()
+ {
+ ScaleTransition = new(
+ initialValue: 1.0,
+ durationSeconds: AnimationDuration,
+ easingType: EasingType.EaseInOutSine
+ );
+ GlowTransition = new(
+ initialValue: 0,
+ durationSeconds: AnimationDuration,
+ easingType: EasingType.EaseInOutSine
+ );
+ FloatTransition = new(
+ initialValue: 0,
+ durationSeconds: AnimationDuration,
+ easingType: EasingType.EaseInOutSine
+ );
+ }
+ }
+}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs
index 430179a..cf79a65 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsLine.cs
@@ -5,14 +5,22 @@ using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Numerics;
+using System.Windows.Documents;
+using Windows.Foundation;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class RenderLyricsLine : LyricsLine
{
+ public List RenderLyricsOriginalChars { get; set; } = [];
+
public double AnimationDuration { get; set; } = 0.3;
+
public ValueTransition AngleTransition { get; set; }
public ValueTransition BlurAmountTransition { get; set; }
public ValueTransition PhoneticOpacityTransition { get; set; }
@@ -231,5 +239,26 @@ namespace BetterLyrics.WinUI3.Models
}
}
+ public void RecalculateCharacterGeometries()
+ {
+ RenderLyricsOriginalChars.Clear();
+ if (OriginalCanvasTextLayout == null) return;
+
+ var textLength = OriginalText.Length;
+
+ for (int i = 0; i < textLength; i++)
+ {
+ var region = OriginalCanvasTextLayout.GetCharacterRegions(i, 1).FirstOrDefault();
+ var bounds = region.LayoutBounds;
+
+ RenderLyricsOriginalChars.Add(new RenderLyricsChar()
+ {
+ Index = i,
+ LayoutRect = bounds,
+ Text = OriginalText[i].ToString()
+ });
+ }
+ }
+
}
}
diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/PlayingLineRenderer.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/PlayingLineRenderer.cs
index 799f1f1..7ea4d87 100644
--- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/PlayingLineRenderer.cs
+++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Renderer/PlayingLineRenderer.cs
@@ -118,7 +118,7 @@ namespace BetterLyrics.WinUI3.Renderer
foreach (var subLineRegion in lineRegions)
{
- DrawSubLineRegion(resourceCreator, ds, source, line, subLineRegion, curCharIndex, fadeWidth, bgColor, fgColor, state, settings);
+ DrawSubLineRegion(resourceCreator, ds, source, line, subLineRegion, curCharIndex, fadeWidth, bgColor, fgColor, settings);
}
}
@@ -132,7 +132,6 @@ namespace BetterLyrics.WinUI3.Renderer
float fadeWidth,
Color bgColor,
Color fgColor,
- LinePlaybackState state,
LyricsEffectSettings settings)
{
var playedOpacity = line.PlayedOriginalOpacityTransition.Value;
@@ -187,7 +186,7 @@ namespace BetterLyrics.WinUI3.Renderer
int endCharIndex = subLineRegion.CharacterIndex + subLineRegion.CharacterCount;
for (int i = subLineRegion.CharacterIndex; i < endCharIndex; i++)
{
- DrawSingleCharacter(ds, line, i, curCharIndex, textWithOpacityLayer, state, settings);
+ DrawSingleCharacter(ds, line, i, textWithOpacityLayer);
}
}
}
@@ -199,161 +198,43 @@ namespace BetterLyrics.WinUI3.Renderer
///
///
///
- /// 遍历的字符索引
- /// 当前播放字符相对于整行的索引
+ /// 遍历的字符索引(相对于整行)
+ /// 当前播放字符的索引(相对于整行)
///
///
- ///
private void DrawSingleCharacter(
CanvasDrawingSession ds,
RenderLyricsLine line,
int charIndex,
- double exactProgressIndex,
- ICanvasImage source,
- LinePlaybackState state,
- LyricsEffectSettings settings)
+ ICanvasImage source)
{
- var curCharIndexInt = (int)Math.Floor(exactProgressIndex);
- if (line.OriginalCanvasTextLayout == null) return;
+ if (charIndex >= line.RenderLyricsOriginalChars.Count) return;
- var charRegions = line.OriginalCanvasTextLayout.GetCharacterRegions(charIndex, 1);
- if (charRegions.Length == 0) return;
- var charRegion = charRegions[0];
- var charLayoutBounds = charRegion.LayoutBounds;
+ RenderLyricsChar renderChar = line.RenderLyricsOriginalChars[charIndex];
+ var rect = renderChar.LayoutRect;
var sourceCharRect = new Rect(
- charLayoutBounds.X + line.OriginalPosition.X,
- charLayoutBounds.Y + line.OriginalPosition.Y,
- charLayoutBounds.Width,
- charLayoutBounds.Height
+ rect.X + line.OriginalPosition.X,
+ rect.Y + line.OriginalPosition.Y,
+ rect.Width,
+ rect.Height
);
- double floatOffset = 0;
- double scale = 1;
- double glow = 0;
+ double scale = renderChar.ScaleTransition.Value;
+ double glow = renderChar.GlowTransition.Value;
+ double floatOffset = renderChar.FloatTransition.Value;
- bool drawGlow = false;
+ var destCharRect = sourceCharRect.Scale(scale).AddY(floatOffset);
- if (settings.IsLyricsFloatAnimationEnabled)
- {
- double targetFloatOffset;
- if (settings.IsLyricsFloatAnimationAmountAutoAdjust)
- {
- targetFloatOffset = sourceCharRect.Height * 0.1;
- }
- else
- {
- targetFloatOffset = settings.LyricsFloatAnimationAmount;
- }
-
- // 已经浮完了的
- if (charIndex < curCharIndexInt)
- {
- floatOffset = 0;
- }
- // 正在浮的
- else if (charIndex == curCharIndexInt)
- {
- var p = exactProgressIndex - curCharIndexInt;
- floatOffset = -targetFloatOffset + p * targetFloatOffset;
- }
- // 还没浮的
- else
- {
- floatOffset = -targetFloatOffset;
- }
-
- // 制造句间上浮过度动画,这里用任何一个 Transition 都行,主要是获取当前行的进入视野的 Progress
- floatOffset *= line.YOffsetTransition.Progress;
- }
-
- var parentSyllable = line.LyricsSyllables.FirstOrDefault(x => x.StartIndex <= charIndex && charIndex < x.StartIndex + x.Text.Length);
-
- if (settings.IsLyricsScaleEffectEnabled)
- {
- if (parentSyllable != null && parentSyllable.StartIndex == state.SyllableStartIndex)
- {
- if (parentSyllable.DurationMs >= settings.LyricsScaleEffectLongSyllableDuration)
- {
- if (settings.IsLyricsScaleEffectAmountAutoAdjust)
- {
- scale += Math.Sin(state.SyllableProgress * Math.PI) * 0.15;
- }
- else
- {
- scale += Math.Sin(state.SyllableProgress * Math.PI) * (settings.LyricsScaleEffectAmount / 100.0 - 1);
- }
- }
- }
- }
-
- if (settings.IsLyricsGlowEffectEnabled)
- {
- double maxGlow;
- if (settings.IsLyricsGlowEffectAmountAutoAdjust)
- {
- maxGlow = sourceCharRect.Height * 0.2;
- }
- else
- {
- maxGlow = settings.LyricsGlowEffectAmount;
- }
- switch (settings.LyricsGlowEffectScope)
- {
- case Enums.LyricsEffectScope.LongDurationSyllable:
- if (parentSyllable != null && parentSyllable.StartIndex == state.SyllableStartIndex)
- {
- if (parentSyllable.DurationMs >= settings.LyricsGlowEffectLongSyllableDuration)
- {
- glow = maxGlow * Math.Sin(state.SyllableProgress * Math.PI);
- drawGlow = true;
- }
- }
- break;
- case Enums.LyricsEffectScope.LineStartToCurrentChar:
- // 已经唱了的
- if (charIndex < curCharIndexInt)
- {
- glow = maxGlow;
- drawGlow = true;
- }
- // 正在唱的
- else if (charIndex == curCharIndexInt)
- {
- var p = exactProgressIndex - curCharIndexInt;
- glow = p * maxGlow;
- drawGlow = true;
- }
- // 还没唱的
- else { }
- glow *= Math.Clamp(line.OriginalText.Length - exactProgressIndex, 0, 1);
- break;
- default:
- break;
- }
- }
-
- var destCharRect = sourceCharRect.Scale(scale).AddY(-floatOffset);
-
- if (drawGlow)
+ // Draw glow
+ if (glow > 0)
{
var sourcePlayedCharRect = new Rect(
sourceCharRect.X,
sourceCharRect.Y,
- sourceCharRect.Width,
+ sourceCharRect.Width * renderChar.ProgressPlayed,
sourceCharRect.Height
);
-
- if (charIndex == curCharIndexInt)
- {
- var p = exactProgressIndex - curCharIndexInt;
- sourcePlayedCharRect.Width *= p;
- }
- else if (charIndex > curCharIndexInt)
- {
- sourcePlayedCharRect.Width = 0;
- }
-
using (var glowEffect = new GaussianBlurEffect
{
Source = new CropEffect
@@ -366,10 +247,11 @@ namespace BetterLyrics.WinUI3.Renderer
BorderMode = EffectBorderMode.Soft
})
{
- ds.DrawImage(glowEffect, destCharRect.Extend(sourceCharRect.Height), sourceCharRect.Extend(sourceCharRect.Height));
+ ds.DrawImage(glowEffect, destCharRect.Extend(destCharRect.Height), sourceCharRect.Extend(sourceCharRect.Height));
}
}
+ // Draw the top layer
ds.DrawImage(source, destCharRect, sourceCharRect);
}
}