From a0b6511a539ab6ba3be27c4b4f3ecfed87e04de0 Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Wed, 7 Jan 2026 20:08:28 -0500 Subject: [PATCH] chores: extra data update algo from draw method to update method --- .../Package.appxmanifest | 2 +- .../Logic/LyricsAnimator.cs | 96 ++++++++++- .../Logic/LyricsLayoutManager.cs | 2 + .../BetterLyrics.WinUI3/Models/LyricsChar.cs | 12 ++ .../Models/LyricsSyllable.cs | 11 +- .../Models/RenderLyricsChar.cs | 43 +++++ .../Models/RenderLyricsLine.cs | 29 ++++ .../Renderer/PlayingLineRenderer.cs | 160 +++--------------- 8 files changed, 209 insertions(+), 146 deletions(-) create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/LyricsChar.cs create mode 100644 BetterLyrics.WinUI3/BetterLyrics.WinUI3/Models/RenderLyricsChar.cs 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); } }