Skip to content

Commit 12769c4

Browse files
author
Simeon
authored
Fixes for AnimatedIcon (#424)
* Nudge the markers forward by 1/100 of a frame to stop flashing. Due to floating point imprecision, a marker value may refer to the previous frame, which causes flashing for AnimatedIcon. This change is a test to see if it helps the problem. If it does, it needs a little more tweaking so that the frame numbers in the comments don't look whacky. * Remove the decimal place from the frame number. Frame numbers from Lottie are always integer values. * Remove accidentally added "using". * Fix bug in visibility optimization. This was causing some things to be visible when they were not supposed to be. While I was there I refactored the the types that are used to calculate visibility to make the code a bit easier to understand.
1 parent 636de5c commit 12769c4

File tree

8 files changed

+208
-97
lines changed

8 files changed

+208
-97
lines changed

source/LottieMetadata/Duration.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieMetadata
1616
#endif
1717
readonly struct Duration
1818
{
19-
internal Duration(double frames, double fps)
19+
public Duration(double frames, double fps)
2020
{
2121
if (frames < 0 || fps < 0)
2222
{
@@ -27,7 +27,7 @@ internal Duration(double frames, double fps)
2727
FPS = fps;
2828
}
2929

30-
internal Duration(double frames, Duration other)
30+
public Duration(double frames, Duration other)
3131
{
3232
Frames = frames;
3333
FPS = other.FPS;

source/LottieMetadata/Marker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieMetadata
1212
#endif
1313
readonly struct Marker
1414
{
15-
internal Marker(string name, Frame frame, Duration duration)
15+
public Marker(string name, Frame frame, Duration duration)
1616
{
1717
Name = name;
1818
Frame = frame;

source/UIData/Tools/GraphCompactor.cs

Lines changed: 10 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -709,79 +709,6 @@ from n in graph.CompositionObjectNodes
709709
}
710710
}
711711

712-
static VisibilityDescription ComposeVisibilities(in VisibilityDescription a, in VisibilityDescription b)
713-
{
714-
if (a.Sequence.Length == 0)
715-
{
716-
return b;
717-
}
718-
719-
if (b.Sequence.Length == 0)
720-
{
721-
return a;
722-
}
723-
724-
if (a.Duration != b.Duration)
725-
{
726-
throw new InvalidOperationException();
727-
}
728-
729-
if (a.Sequence.SequenceEqual(b.Sequence))
730-
{
731-
// They're identical.
732-
return a;
733-
}
734-
735-
// Combine and optimize the 2 sequences.
736-
var composedSequence = ComposeVisibilitySequences(a.Sequence, b.Sequence).ToArray();
737-
738-
return new VisibilityDescription(a.Duration, composedSequence);
739-
}
740-
741-
// Composes 2 visibility sequence.
742-
static IEnumerable<(bool isVisible, float progress)> ComposeVisibilitySequences(
743-
(bool isVisible, float progress)[] a,
744-
(bool isVisible, float progress)[] b)
745-
{
746-
var currentVisibility = false;
747-
var currentProgress = 0F;
748-
749-
var ai = 0;
750-
var bi = 0;
751-
752-
while (ai < a.Length || bi < b.Length)
753-
{
754-
var cura = ai < a.Length ? a[ai] : (a[a.Length - 1].isVisible, progress: float.MaxValue);
755-
var curb = bi < b.Length ? b[bi] : (b[b.Length - 1].isVisible, progress: float.MaxValue);
756-
757-
// Is the visibility changing?
758-
if ((cura.isVisible & curb.isVisible) != currentVisibility)
759-
{
760-
yield return (currentVisibility, currentProgress);
761-
currentVisibility = !currentVisibility;
762-
}
763-
764-
if (cura.progress == curb.progress)
765-
{
766-
currentProgress = cura.progress;
767-
ai++;
768-
bi++;
769-
}
770-
else if (cura.progress < curb.progress)
771-
{
772-
currentProgress = cura.progress;
773-
ai++;
774-
}
775-
else
776-
{
777-
currentProgress = curb.progress;
778-
bi++;
779-
}
780-
}
781-
782-
yield return (currentVisibility, currentProgress);
783-
}
784-
785712
// Find ContainerVisuals that have a single ShapeVisual child with orthongonal properties and
786713
// push the properties down to the ShapeVisual.
787714
static void PushPropertiesDownToShapeVisual(ObjectGraph<Node> graph)
@@ -875,18 +802,18 @@ void ApplyVisibility(Visual to, VisibilityDescription fromVisibility, Compositio
875802
{
876803
var toVisibility = GetVisiblityAnimationDescription(to);
877804

878-
var compositeVisibility = ComposeVisibilities(fromVisibility, toVisibility);
805+
var compositeVisibility = VisibilityDescription.Compose(fromVisibility, toVisibility);
879806

880807
if (compositeVisibility.Sequence.Length > 0)
881808
{
882809
_madeProgress = true;
883810
var c = new Compositor();
884811
var animation = c.CreateBooleanKeyFrameAnimation();
885812
animation.Duration = compositeVisibility.Duration;
886-
if (compositeVisibility.Sequence[0].progress == 0)
813+
if (compositeVisibility.Sequence[0].Progress == 0)
887814
{
888815
// Set the initial visiblity.
889-
to.IsVisible = compositeVisibility.Sequence[0].isVisible;
816+
to.IsVisible = compositeVisibility.Sequence[0].IsVisible;
890817
}
891818

892819
foreach (var (isVisible, progress) in compositeVisibility.Sequence)
@@ -919,14 +846,14 @@ static VisibilityDescription GetVisiblityAnimationDescription(Visual visual)
919846

920847
if (animator is null)
921848
{
922-
return new VisibilityDescription(TimeSpan.Zero, Array.Empty<(bool, float)>());
849+
return new VisibilityDescription(TimeSpan.Zero, Array.Empty<VisibilityAtProgress>());
923850
}
924851

925852
var visibilityAnimation = (BooleanKeyFrameAnimation)animator.Animation;
926853

927854
return new VisibilityDescription(visibilityAnimation.Duration, GetDescription().ToArray());
928855

929-
IEnumerable<(bool isVisible, float progress)> GetDescription()
856+
IEnumerable<VisibilityAtProgress> GetDescription()
930857
{
931858
if (animator is null)
932859
{
@@ -947,11 +874,11 @@ static VisibilityDescription GetVisiblityAnimationDescription(Visual visual)
947874
if (kf.Progress != 0 && visual.IsVisible == false)
948875
{
949876
// Output an initial keyframe.
950-
yield return (false, 0);
877+
yield return new VisibilityAtProgress(false, 0);
951878
}
952879
}
953880

954-
yield return (kf.Value, kf.Progress);
881+
yield return new VisibilityAtProgress(kf.Value, kf.Progress);
955882
}
956883
}
957884
}
@@ -980,7 +907,7 @@ static VisibilityDescription GetVisiblityAnimationDescription(CompositionShape s
980907

981908
return new VisibilityDescription(scaleAnimation.Duration, GetDescription().ToArray());
982909

983-
IEnumerable<(bool isVisible, float progress)> GetDescription()
910+
IEnumerable<VisibilityAtProgress> GetDescription()
984911
{
985912
foreach (KeyFrameAnimation<Vector2, Expr.Vector2>.ValueKeyFrame kf in scaleAnimation.KeyFrames)
986913
{
@@ -1004,11 +931,11 @@ static VisibilityDescription GetVisiblityAnimationDescription(CompositionShape s
1004931
// add a non-visible state at 0.
1005932
if (kf.Progress != 0 && shape.Scale == Vector2.Zero)
1006933
{
1007-
yield return (false, 0);
934+
yield return new VisibilityAtProgress(false, 0);
1008935
}
1009936
}
1010937

1011-
yield return (kf.Value == Vector2.One, kf.Progress);
938+
yield return new VisibilityAtProgress(kf.Value == Vector2.One, kf.Progress);
1012939
}
1013940
}
1014941
}
@@ -1513,16 +1440,6 @@ void CopyDescriptions(IDescribable from, IDescribable to)
15131440
static CompositionObject.Animator? TryGetAnimatorByPropertyName(CompositionObject obj, string name) =>
15141441
obj.Animators.Where(anim => anim.AnimatedProperty == name).FirstOrDefault();
15151442

1516-
readonly struct VisibilityDescription
1517-
{
1518-
internal VisibilityDescription(TimeSpan duration, (bool isVisible, float progress)[] sequence)
1519-
=> (Duration, Sequence) = (duration, sequence);
1520-
1521-
internal TimeSpan Duration { get; }
1522-
1523-
internal (bool isVisible, float progress)[] Sequence { get; }
1524-
}
1525-
15261443
sealed class Node : Graph.Node<Node>
15271444
{
15281445
internal CompositionObject? Parent { get; set; }
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
11+
namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
12+
{
13+
readonly struct VisibilityAtProgress
14+
{
15+
internal VisibilityAtProgress(bool isVisible, float progress)
16+
=> (IsVisible, Progress) = (isVisible, progress);
17+
18+
internal bool IsVisible { get; }
19+
20+
internal float Progress { get; }
21+
22+
internal void Deconstruct(out bool isVisible, out float progress)
23+
{
24+
isVisible = IsVisible;
25+
progress = Progress;
26+
}
27+
28+
// Composes 2 lists of VisibilityAtProgress by ANDing them together.
29+
// The resulting sequence always has a value at progress 0. There will
30+
// be no consective items with the same visibility (i.e. each item describes
31+
// a change of visibility state). The visibility will be visibile when
32+
// both a AND b are visible, and invisible if either a OR b is invisible.
33+
internal static IEnumerable<VisibilityAtProgress> Compose(
34+
VisibilityAtProgress[] a,
35+
VisibilityAtProgress[] b)
36+
{
37+
// Get the visibilities in order, with any redundant visibilities removed.
38+
var items = SanitizeSequence(a).Concat(SanitizeSequence(b)).OrderBy(v => v.Progress).ThenBy(v => !v.IsVisible).ToArray();
39+
40+
// The output is visible any time both a and b are visible at the same time.
41+
var visibilityCounter = 0;
42+
43+
var has0ProgressBeenOutput = false;
44+
45+
foreach (var item in items)
46+
{
47+
visibilityCounter += item.IsVisible ? 1 : -1;
48+
if (visibilityCounter == 2)
49+
{
50+
// Both a and b are now visible.
51+
if (!has0ProgressBeenOutput)
52+
{
53+
// If we haven't output a value for progress 0, and the current
54+
// item isn't for 0, output a progress 0 value now.
55+
if (item.Progress != 0)
56+
{
57+
yield return new VisibilityAtProgress(false, 0);
58+
}
59+
60+
has0ProgressBeenOutput = true;
61+
}
62+
63+
yield return new VisibilityAtProgress(true, item.Progress);
64+
}
65+
else if (visibilityCounter == 1 && !item.IsVisible)
66+
{
67+
// We were visible, but now we're not.
68+
yield return new VisibilityAtProgress(false, item.Progress);
69+
}
70+
}
71+
}
72+
73+
// Orders the items in the sequence by Progress, and removes any redundant items.
74+
static IEnumerable<VisibilityAtProgress> SanitizeSequence(IEnumerable<VisibilityAtProgress> sequence)
75+
{
76+
// Sequences start implicitly invisible.
77+
var previousVisibility = false;
78+
79+
foreach (var item in sequence.GroupBy(v => v.Progress).OrderBy(v => v.Key))
80+
{
81+
// Do not allow multiple visibilities at the same progress. It should
82+
// never happen, and if it did it's not clear what it means.
83+
var group = item.ToArray();
84+
if (group.Length > 1)
85+
{
86+
throw new ArgumentException();
87+
}
88+
89+
var visibility = group[0];
90+
91+
// Ignore any repeats of the same visibility state.
92+
if (previousVisibility != visibility.IsVisible)
93+
{
94+
yield return visibility;
95+
previousVisibility = visibility.IsVisible;
96+
}
97+
}
98+
}
99+
100+
public override string ToString() => $"{(IsVisible ? "Visible" : "Invisible")} @ {Progress}";
101+
}
102+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Linq;
9+
10+
namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
11+
{
12+
readonly struct VisibilityDescription
13+
{
14+
internal VisibilityDescription(TimeSpan duration, VisibilityAtProgress[] sequence)
15+
=> (Duration, Sequence) = (duration, sequence);
16+
17+
/// <summary>
18+
/// The time over which the <see cref="VisibilityDescription"/> is valid.
19+
/// </summary>
20+
internal TimeSpan Duration { get; }
21+
22+
/// <summary>
23+
/// The sequence of visibilites, ordered by progress. Initial visibility
24+
/// is "not visible". Each entry in the sequence represents a change to
25+
/// a different visibility.
26+
/// </summary>
27+
internal VisibilityAtProgress[] Sequence { get; }
28+
29+
/// <summary>
30+
/// Composes the given <see cref="VisibilityDescription"/>s by ANDing them
31+
/// together.
32+
/// </summary>
33+
/// <returns>A composed <see cref="VisibilityDescription"/>.</returns>
34+
internal static VisibilityDescription Compose(
35+
in VisibilityDescription a,
36+
in VisibilityDescription b)
37+
{
38+
if (a.Sequence.Length == 0)
39+
{
40+
return b;
41+
}
42+
43+
if (b.Sequence.Length == 0)
44+
{
45+
return a;
46+
}
47+
48+
if (a.Duration != b.Duration)
49+
{
50+
throw new InvalidOperationException();
51+
}
52+
53+
if (a.Sequence.SequenceEqual(b.Sequence))
54+
{
55+
// They're identical.
56+
return a;
57+
}
58+
59+
// Combine the 2 sequences.
60+
var composedSequence = VisibilityAtProgress.Compose(a.Sequence, b.Sequence).ToArray();
61+
62+
return new VisibilityDescription(a.Duration, composedSequence);
63+
}
64+
}
65+
}

source/UIData/UIData.projitems

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@
1616
<Compile Include="$(MSBuildThisFileDirectory)Tools\PropertyValueOptimizer.cs" />
1717
<Compile Include="$(MSBuildThisFileDirectory)Tools\PropertyId.cs" />
1818
<Compile Include="$(MSBuildThisFileDirectory)Tools\Stats.cs" />
19+
<Compile Include="$(MSBuildThisFileDirectory)Tools\VisibilityAtProgress.cs" />
20+
<Compile Include="$(MSBuildThisFileDirectory)Tools\VisibilityDescription.cs" />
1921
</ItemGroup>
2022
</Project>

0 commit comments

Comments
 (0)