Strong type of Lottie Opacity. (#219)

Opacity is special because it's expressed in Lottie as a percent, which is inconvenient for multiplication with alpha.
Giving an Opacity a strong type allows us to abstract the percent details, and also make the translator clearer because it's obvious where a value is being used for opacity.
This commit is contained in:
Simeon 2020-01-17 15:04:50 -08:00 коммит произвёл GitHub
Родитель f19e3156d0
Коммит 55399f90f6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
21 изменённых файлов: 346 добавлений и 221 удалений

Просмотреть файл

@ -50,8 +50,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
/// Return the color with the given opacity multiplied into the alpha channel.
/// </summary>
/// <returns>The color with the given opacity multiplied into the alpha channel.</returns>
public Color MultipliedByOpacity(double opacity)
=> opacity == 1 ? this : new Color(opacity * A, R, G, B);
public Color MultipliedByOpacity(Opacity opacity)
=> opacity.IsOpaque ? this : new Color(opacity.Value * A, R, G, B);
/// <inheritdoc/>
public override bool Equals(object obj) => obj is Color && Equals((Color)obj);

Просмотреть файл

@ -100,5 +100,21 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
return result;
}
public static Opacity Max<TSource>(this ReadOnlySpan<TSource> source, Func<TSource, Opacity> selector)
{
var result = Opacity.Transparent;
foreach (var item in source)
{
var candidate = selector(item);
if (candidate > result)
{
result = candidate;
}
}
return result;
}
}
}

Просмотреть файл

@ -12,11 +12,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public LinearGradientFill(
in ShapeLayerContentArgs args,
PathFillType fillType,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
IAnimatableVector3 startPoint,
IAnimatableVector3 endPoint,
Animatable<Sequence<GradientStop>> gradientStops)
: base(in args, fillType, opacityPercent)
: base(in args, fillType, opacity)
{
StartPoint = startPoint;
EndPoint = endPoint;

Просмотреть файл

@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
{
public LinearGradientStroke(
in ShapeLayerContentArgs args,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
Animatable<double> strokeWidth,
LineCapType capType,
LineJoinType joinType,
@ -19,7 +19,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
IAnimatableVector3 startPoint,
IAnimatableVector3 endPoint,
Animatable<Sequence<GradientStop>> gradientStops)
: base(in args, opacityPercent, strokeWidth, capType, joinType, miterLimit)
: base(in args, opacity, strokeWidth, capType, joinType, miterLimit)
{
StartPoint = startPoint;
EndPoint = endPoint;

Просмотреть файл

@ -46,6 +46,7 @@
<Compile Include="$(MSBuildThisFileDirectory)\Mask.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\MergePaths.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\NullLayer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\Opacity.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\OpacityGradientStop.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\Optimization\GradientStopOptimizer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)\Optimization\Optimizer.cs" />

Просмотреть файл

@ -16,14 +16,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
bool inverted,
string name,
Animatable<Sequence<BezierSegment>> points,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
MaskMode mode
)
{
Inverted = inverted;
Name = name;
Points = points;
OpacityPercent = opacityPercent;
Opacity = opacity;
Mode = mode;
}
@ -33,7 +33,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public Animatable<Sequence<BezierSegment>> Points { get; }
public Animatable<double> OpacityPercent { get; }
public Animatable<Opacity> Opacity { get; }
public MaskMode Mode { get; }

Просмотреть файл

@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
{
/// <summary>
/// A percentage value.
/// </summary>
#if PUBLIC_LottieData
public
#endif
struct Opacity : IEquatable<Opacity>
{
Opacity(double value)
{
Value = value;
}
public bool IsOpaque => Value == 1;
public bool IsTransparent => Value == 0;
public static Opacity Opaque { get; } = new Opacity(1);
public double Percent => Value * 100;
public static Opacity Transparent { get; } = new Opacity(0);
public double Value { get; }
public static Opacity FromFloat(double value) => new Opacity(value);
public static Opacity operator *(Opacity left, Opacity right) => new Opacity(left.Value * right.Value);
public static Opacity operator *(Opacity opacity, double scale) => new Opacity(opacity.Value * scale);
public static bool operator >(Opacity left, Opacity right) => left.Value > right.Value;
public static bool operator <(Opacity left, Opacity right) => left.Value < right.Value;
public static bool operator ==(Opacity left, Opacity right) => left.Value == right.Value;
public static bool operator !=(Opacity left, Opacity right) => left.Value != right.Value;
public bool Equals(Opacity other) => other.Value == Value;
public override bool Equals(object obj) => obj is Opacity other ? other.Equals(this) : false;
public override int GetHashCode() => Value.GetHashCode();
public override string ToString() => $"{Percent}%";
}
}

Просмотреть файл

@ -9,18 +9,18 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
#endif
sealed class OpacityGradientStop : GradientStop
{
public OpacityGradientStop(double offset, double opacityPercent)
public OpacityGradientStop(double offset, Opacity opacity)
: base(offset)
{
OpacityPercent = opacityPercent;
Opacity = opacity;
}
public double OpacityPercent { get; }
public Opacity Opacity { get; }
/// <inheritdoc/>
public override GradientStopKind Kind => GradientStopKind.Opacity;
/// <inheritdoc/>
public override string ToString() => $"{OpacityPercent}%@{Offset}";
public override string ToString() => $"{Opacity.Percent}%@{Offset}";
}
}

Просмотреть файл

@ -63,7 +63,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
// The stop is either an OpacityGradientStop or a ColorGradientStop. Convert to a ColorGradientStop
// by interpolating the color or opacity as necessary.
Color color;
double opacityPercent;
Opacity opacity;
if (currentStop.Kind == GradientStop.GradientStopKind.Color)
{
@ -80,7 +80,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
orderedStops[i + 1].Offset == currentStop.Offset &&
orderedStops[i + 1].Kind == GradientStop.GradientStopKind.Opacity)
{
opacityPercent = ((OpacityGradientStop)orderedStops[i + 1]).OpacityPercent;
opacity = ((OpacityGradientStop)orderedStops[i + 1]).Opacity;
}
else
{
@ -102,16 +102,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
if (previousOpacityStop == null)
{
// There is no previous opacity stop. Use the next opacity
// stop if there is one, or 100% if there are no opacity stops.
opacityPercent = nextOpacityStop?.OpacityPercent ?? 100;
// stop if there is one, or Opaque if there are no opacity stops.
opacity = nextOpacityStop?.Opacity ?? Opacity.Opaque;
}
else
{
// If there's a following opacity stop, interpolate between previous
// and next, otherwise continue using the previous opacity.
opacityPercent = nextOpacityStop == null
? previousOpacityStop.OpacityPercent
: InterpolateOpacityPercent(previousOpacityStop, nextOpacityStop, currentStop.Offset);
opacity = nextOpacityStop == null
? previousOpacityStop.Opacity
: InterpolateOpacity(previousOpacityStop, nextOpacityStop, currentStop.Offset);
}
}
}
@ -120,7 +120,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
// The stop is an OpacityGradientStop. Get the opacity value directly from the stop,
// and interpolate the color value from the surrounding ColorStops.
var currentOpacityStop = previousOpacityStop = (OpacityGradientStop)currentStop;
opacityPercent = previousOpacityStop.OpacityPercent;
opacity = previousOpacityStop.Opacity;
// Invalidate nextOpacityStop to force a search for the next opacity stop.
nextOpacityStop = null;
@ -166,7 +166,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
}
}
yield return new ColorGradientStop(currentStop.Offset, color.MultipliedByOpacity(opacityPercent / 100.0));
yield return new ColorGradientStop(currentStop.Offset, color.MultipliedByOpacity(opacity));
}
}
@ -190,8 +190,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Optimization
=> a.y + ((x - a.x) * ((b.y - a.y) / (b.x - a.x)));
// Returns the opacity percent at the given offset between a and b.
static double InterpolateOpacityPercent(OpacityGradientStop a, OpacityGradientStop b, double atOffset)
=> Lerp((a.Offset, a.OpacityPercent / 100.0), (b.Offset, b.OpacityPercent / 100.0), atOffset) * 100;
static Opacity InterpolateOpacity(OpacityGradientStop a, OpacityGradientStop b, double atOffset)
=> Opacity.FromFloat(Lerp((a.Offset, a.Opacity.Value), (b.Offset, b.Opacity.Value), atOffset));
// Returns the color at the given offset between a and b.
static Color InterpolateColor(ColorGradientStop a, ColorGradientStop b, double atOffset)

Просмотреть файл

@ -12,13 +12,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public RadialGradientFill(
in ShapeLayerContentArgs args,
PathFillType fillType,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
IAnimatableVector3 startPoint,
IAnimatableVector3 endPoint,
Animatable<Sequence<GradientStop>> gradientStops,
Animatable<double> highlightLength,
Animatable<double> highlightDegrees)
: base(in args, fillType, opacityPercent)
: base(in args, fillType, opacity)
{
StartPoint = startPoint;
EndPoint = endPoint;

Просмотреть файл

@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
{
public RadialGradientStroke(
in ShapeLayerContentArgs args,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
Animatable<double> strokeWidth,
LineCapType capType,
LineJoinType joinType,
@ -21,7 +21,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
Animatable<Sequence<GradientStop>> gradientStops,
Animatable<double> highlightLength,
Animatable<double> highlightDegrees)
: base(in args, opacityPercent, strokeWidth, capType, joinType, miterLimit)
: base(in args, opacity, strokeWidth, capType, joinType, miterLimit)
{
StartPoint = startPoint;
EndPoint = endPoint;

Просмотреть файл

@ -15,23 +15,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
IAnimatableVector3 position,
IAnimatableVector3 scalePercent,
Animatable<double> rotationDegrees,
Animatable<double> opacityPercent,
Animatable<double> startOpacityPercent,
Animatable<double> endOpacityPercent)
: base(in args, anchor, position, scalePercent, rotationDegrees, opacityPercent)
Animatable<Opacity> opacity,
Animatable<Opacity> startOpacity,
Animatable<Opacity> endOpacity)
: base(in args, anchor, position, scalePercent, rotationDegrees, opacity)
{
StartOpacityPercent = startOpacityPercent;
EndOpacityPercent = endOpacityPercent;
StartOpacity = startOpacity;
EndOpacity = endOpacity;
}
/// <summary>
/// Gets the opacity of the original shaped. Only used by <see cref="Repeater"/>.
/// </summary>
public Animatable<double> StartOpacityPercent { get; }
public Animatable<Opacity> StartOpacity { get; }
/// <summary>
/// Gets the opacity of the last copy of the original shape. Only used by <see cref="Repeater"/>.
/// </summary>
public Animatable<double> EndOpacityPercent { get; }
public Animatable<Opacity> EndOpacity { get; }
}
}

Просмотреть файл

@ -390,7 +390,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
yield return new XAttribute(nameof(mask.Inverted), mask.Inverted);
yield return new XAttribute(nameof(mask.Name), mask.Name);
yield return FromAnimatable(nameof(mask.Points), mask.Points);
yield return FromAnimatable(nameof(mask.OpacityPercent), mask.OpacityPercent);
yield return FromAnimatable(nameof(mask.Opacity), mask.Opacity);
yield return new XAttribute(nameof(mask.Mode), mask.Mode);
}
}
@ -423,7 +423,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
}
yield return FromAnimatable(nameof(content.Color), content.Color);
yield return FromAnimatable(nameof(content.OpacityPercent), content.OpacityPercent);
yield return FromAnimatable(nameof(content.Opacity), content.Opacity);
yield return FromAnimatable(nameof(content.StrokeWidth), content.StrokeWidth);
}
}
@ -463,7 +463,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
}
yield return FromAnimatable("Color", content.Color);
yield return FromAnimatable("OpacityPercent", content.OpacityPercent);
yield return FromAnimatable("Opacity", content.Opacity);
}
}
@ -506,7 +506,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
yield return FromAnimatable(nameof(content.ScalePercent), content.ScalePercent);
yield return FromAnimatable(nameof(content.Position), content.Position);
yield return FromAnimatable(nameof(content.Anchor), content.Anchor);
yield return FromAnimatable(nameof(content.OpacityPercent), content.OpacityPercent);
yield return FromAnimatable(nameof(content.Opacity), content.Opacity);
yield return FromAnimatable(nameof(content.RotationDegrees), content.RotationDegrees);
}
}

Просмотреть файл

@ -328,7 +328,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
{ "Name", mask.Name },
{ "Inverted", mask.Inverted },
{ "Mode", Scalar(mask.Mode) },
{ "OpacityPercent", FromAnimatable(mask.OpacityPercent) },
{ "OpacityPercent", FromAnimatable(mask.Opacity, FromOpacityPercent) },
{ "Points", FromAnimatable(mask.Points, p => FromSequence(p, FromBezierSegment)) },
};
return result;
@ -345,7 +345,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
{
var result = superclassContent;
result.Add("Color", FromAnimatable(content.Color, FromColor));
result.Add("OpacityPercent", FromAnimatable(content.OpacityPercent));
result.Add("OpacityPercent", FromAnimatable(content.Opacity, FromOpacityPercent));
result.Add("Thickness", FromAnimatable(content.StrokeWidth));
return result;
}
@ -366,14 +366,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
{
var result = superclassContent;
result.Add("Color", FromAnimatable(content.Color, FromColor));
result.Add("OpacityPercent", FromAnimatable(content.OpacityPercent));
result.Add("OpacityPercent", FromAnimatable(content.Opacity, FromOpacityPercent));
return result;
}
YamlObject FromLinearGradientFill(LinearGradientFill content, YamlMap superclassContent)
{
var result = superclassContent;
result.Add("OpacityPercent", FromAnimatable(content.OpacityPercent));
result.Add("OpacityPercent", FromAnimatable(content.Opacity, FromOpacityPercent));
result.Add("GradientStops", FromAnimatable(content.GradientStops, p => FromSequence(p, FromGradientStop)));
return result;
}
@ -381,7 +381,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
YamlObject FromRadialGradientFill(RadialGradientFill content, YamlMap superclassContent)
{
var result = superclassContent;
result.Add("OpacityPercent", FromAnimatable(content.OpacityPercent));
result.Add("OpacityPercent", FromAnimatable(content.Opacity, FromOpacityPercent));
result.Add("GradientStops", FromAnimatable(content.GradientStops, p => FromSequence(p, FromGradientStop)));
return result;
}
@ -392,7 +392,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
result.Add("ScalePercent", FromAnimatable(content.ScalePercent));
result.Add("Position", FromAnimatable(content.Position));
result.Add("Anchor", FromAnimatable(content.Anchor));
result.Add("OpacityPercent", FromAnimatable(content.OpacityPercent));
result.Add("OpacityPercent", FromAnimatable(content.Opacity, FromOpacityPercent));
result.Add("RotationDegrees", FromAnimatable(content.RotationDegrees));
return result;
}
@ -440,6 +440,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
static YamlObject FromColor(Color value) => (YamlScalar)value?.ToString();
static YamlObject FromOpacityPercent(Opacity value) => (YamlScalar)value.Percent;
static YamlObject FromVector3(Vector3 value)
{
var result = new YamlMap
@ -500,7 +502,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
{
var result = new YamlMap
{
{ "OpacityPercent", value.OpacityPercent },
{ "OpacityPercent", value.Opacity.Percent },
{ "Offset", value.Offset },
};
return result;

Просмотреть файл

@ -12,14 +12,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public ShapeFill(
in ShapeLayerContentArgs args,
PathFillType fillType,
Animatable<double> opacityPercent)
Animatable<Opacity> opacity)
: base(in args)
{
OpacityPercent = opacityPercent;
Opacity = opacity;
FillType = fillType;
}
public Animatable<double> OpacityPercent { get; }
public Animatable<Opacity> Opacity { get; }
public abstract ShapeFillKind FillKind { get; }

Просмотреть файл

@ -11,21 +11,21 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
{
public ShapeStroke(
in ShapeLayerContentArgs args,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
Animatable<double> strokeWidth,
LineCapType capType,
LineJoinType joinType,
double miterLimit)
: base(in args)
{
OpacityPercent = opacityPercent;
Opacity = opacity;
StrokeWidth = strokeWidth;
CapType = capType;
JoinType = joinType;
MiterLimit = miterLimit;
}
public Animatable<double> OpacityPercent { get; }
public Animatable<Opacity> Opacity { get; }
public Animatable<double> StrokeWidth { get; }

Просмотреть файл

@ -12,9 +12,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public SolidColorFill(
in ShapeLayerContentArgs args,
PathFillType fillType,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
Animatable<Color> color)
: base(in args, fillType, opacityPercent)
: base(in args, fillType, opacity)
{
Color = color;
}

Просмотреть файл

@ -20,12 +20,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
Animatable<double> dashOffset,
IEnumerable<double> dashPattern,
Animatable<Color> color,
Animatable<double> opacityPercent,
Animatable<Opacity> opacity,
Animatable<double> strokeWidth,
LineCapType capType,
LineJoinType joinType,
double miterLimit)
: base(in args, opacityPercent, strokeWidth, capType, joinType, miterLimit)
: base(in args, opacity, strokeWidth, capType, joinType, miterLimit)
{
DashOffset = dashOffset;
_dashPattern = dashPattern.ToArray();

Просмотреть файл

@ -15,14 +15,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
IAnimatableVector3 position,
IAnimatableVector3 scalePercent,
Animatable<double> rotationDegrees,
Animatable<double> opacityPercent)
Animatable<Opacity> opacity)
: base(in args)
{
Anchor = anchor;
Position = position;
ScalePercent = scalePercent;
RotationDegrees = rotationDegrees;
OpacityPercent = opacityPercent;
Opacity = opacity;
}
/// <summary>
@ -39,9 +39,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData
public Animatable<double> RotationDegrees { get; }
public Animatable<double> OpacityPercent { get; }
public Animatable<Opacity> Opacity { get; }
public bool IsAnimated => Anchor.IsAnimated || Position.IsAnimated || ScalePercent.IsAnimated || RotationDegrees.IsAnimated || OpacityPercent.IsAnimated;
public bool IsAnimated => Anchor.IsAnimated || Position.IsAnimated || ScalePercent.IsAnimated || RotationDegrees.IsAnimated || Opacity.IsAnimated;
/// <inheritdoc/>
public override ShapeContentType ContentType => ShapeContentType.Transform;

Просмотреть файл

@ -34,6 +34,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
sealed class LottieCompositionReader
{
static readonly AnimatableFloatParser s_animatableFloatParser = new AnimatableFloatParser();
static readonly AnimatableOpacityParser s_animatableOpacityParser = new AnimatableOpacityParser();
static readonly AnimatableVector2Parser s_animatableVector2Parser = new AnimatableVector2Parser();
static readonly AnimatableVector3Parser s_animatableVector3Parser = new AnimatableVector3Parser();
static readonly AnimatableGeometryParser s_animatableGeometryParser = new AnimatableGeometryParser();
@ -812,7 +813,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
var inverted = obj.GetNamedBoolean("inv");
var name = ReadName(obj);
var animatedGeometry = ReadAnimatableGeometry(obj.GetNamedObject("pt"));
var opacityPercent = ReadAnimatableFloat(obj.GetNamedObject("o"));
var opacity = ReadOpacityFromO(obj);
var mode = Mask.MaskMode.None;
var maskMode = obj.GetNamedString("mode");
switch (maskMode)
@ -848,7 +849,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
inverted,
name,
animatedGeometry,
opacityPercent,
opacity,
mode
);
}
@ -983,8 +984,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
IgnoreFieldThatIsNotYetSupported(obj, "fillEnabled");
IgnoreFieldThatIsNotYetSupported(obj, "hd");
var color = ReadColor(obj);
var opacityPercent = ReadOpacityPercent(obj);
var color = ReadColorFromC(obj);
var opacity = ReadOpacityFromO(obj);
var strokeWidth = ReadAnimatableFloat(obj.GetNamedObject("w"));
var capType = LcToLineCapType(obj.GetNamedNumber("lc"));
var joinType = LjToLineJoinType(obj.GetNamedNumber("lj"));
@ -1019,7 +1020,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
offset ?? s_animatable_0,
dashPattern,
color,
opacityPercent,
opacity,
strokeWidth,
capType,
joinType,
@ -1047,7 +1048,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
IgnoreFieldThatIsNotYetSupported(obj, "t");
IgnoreFieldThatIsNotYetSupported(obj, "1");
var opacityPercent = ReadOpacityPercent(obj);
var opacity = ReadOpacityFromO(obj);
var strokeWidth = ReadAnimatableFloat(obj.GetNamedObject("w"));
var capType = LcToLineCapType(obj.GetNamedNumber("lc"));
var joinType = LjToLineJoinType(obj.GetNamedNumber("lj"));
@ -1059,7 +1060,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
AssertAllFieldsRead(obj);
return new LinearGradientStroke(
in shapeLayerContentArgs,
opacityPercent,
opacity,
strokeWidth,
capType,
joinType,
@ -1092,7 +1093,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
highlightDegrees = ReadAnimatableFloat(highlightAngleObject);
}
var opacityPercent = ReadOpacityPercent(obj);
var opacity = ReadOpacityFromO(obj);
var strokeWidth = ReadAnimatableFloat(obj.GetNamedObject("w"));
var capType = LcToLineCapType(obj.GetNamedNumber("lc"));
var joinType = LjToLineJoinType(obj.GetNamedNumber("lj"));
@ -1104,7 +1105,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
AssertAllFieldsRead(obj);
return new RadialGradientStroke(
in shapeLayerContentArgs,
opacityPercent: opacityPercent,
opacity: opacity,
strokeWidth: strokeWidth,
capType: capType,
joinType: joinType,
@ -1125,11 +1126,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
IgnoreFieldThatIsNotYetSupported(obj, "hd");
var fillType = ReadFillType(obj);
var opacityPercent = ReadOpacityPercent(obj);
var color = ReadColor(obj);
var opacity = ReadOpacityFromO(obj);
var color = ReadColorFromC(obj);
AssertAllFieldsRead(obj);
return new SolidColorFill(in shapeLayerContentArgs, fillType, opacityPercent, color);
return new SolidColorFill(in shapeLayerContentArgs, fillType, opacity, color);
}
// gf
@ -1153,7 +1154,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
IgnoreFieldThatIsNotYetSupported(obj, "1");
var fillType = ReadFillType(obj);
var opacityPercent = ReadOpacityPercent(obj);
var opacity = ReadOpacityFromO(obj);
var startPoint = ReadAnimatableVector3(obj.GetNamedObject("s"));
var endPoint = ReadAnimatableVector3(obj.GetNamedObject("e"));
ReadAnimatableGradientStops(obj.GetNamedObject("g"), out var gradientStops);
@ -1176,7 +1177,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
return new RadialGradientFill(
in shapeLayerContentArgs,
fillType: fillType,
opacityPercent: opacityPercent,
opacity: opacity,
startPoint: startPoint,
endPoint: endPoint,
gradientStops: gradientStops,
@ -1190,7 +1191,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
IgnoreFieldThatIsNotYetSupported(obj, "hd");
var fillType = ReadFillType(obj);
var opacityPercent = ReadOpacityPercent(obj);
var opacity = ReadOpacityFromO(obj);
var startPoint = ReadAnimatableVector3(obj.GetNamedObject("s"));
var endPoint = ReadAnimatableVector3(obj.GetNamedObject("e"));
ReadAnimatableGradientStops(obj.GetNamedObject("g"), out var gradientStops);
@ -1199,7 +1200,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
return new LinearGradientFill(
in shapeLayerContentArgs,
fillType: fillType,
opacityPercent: opacityPercent,
opacity: opacity,
startPoint: startPoint,
endPoint: endPoint,
gradientStops: gradientStops);
@ -1386,21 +1387,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
return isWindingFill ? ShapeFill.PathFillType.Winding : ShapeFill.PathFillType.EvenOdd;
}
Animatable<double> ReadOpacityPercent(JObject obj)
{
var jsonOpacity = obj.GetNamedObject("o", null);
return ReadOpacityPercentFromObject(jsonOpacity);
}
Animatable<double> ReadOpacityPercentFromObject(JObject obj)
{
var result = obj != null
? ReadAnimatableFloat(obj)
: new Animatable<double>(100, null);
return result;
}
Animatable<Color> ReadColor(JObject obj) =>
Animatable<Color> ReadColorFromC(JObject obj) =>
ReadAnimatableColor(obj.GetNamedObject("c", null));
Animatable<Color> ReadAnimatableColor(JObject obj)
@ -1423,8 +1410,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
// except they have an extra couple properties.
RepeaterTransform ReadRepeaterTransform(JObject obj, in ShapeLayerContent.ShapeLayerContentArgs shapeLayerContentArgs)
{
var startOpacityPercent = ReadOpacityPercentFromObject(obj.GetNamedObject("so", null));
var endOpacityPercent = ReadOpacityPercentFromObject(obj.GetNamedObject("eo", null));
var startOpacity = ReadOpacityFromObject(obj.GetNamedObject("so", null));
var endOpacity = ReadOpacityFromObject(obj.GetNamedObject("eo", null));
var transform = ReadTransform(obj, in shapeLayerContentArgs);
return new RepeaterTransform(
in shapeLayerContentArgs,
@ -1432,9 +1419,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
transform.Position,
transform.ScalePercent,
transform.RotationDegrees,
transform.OpacityPercent,
startOpacityPercent,
endOpacityPercent);
transform.Opacity,
startOpacity,
endOpacity);
}
Transform ReadTransform(JObject obj, in ShapeLayerContent.ShapeLayerContentArgs shapeLayerContentArgs)
@ -1467,9 +1454,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
? ReadAnimatableFloat(rotationJson)
: new Animatable<double>(0, null);
var opacityPercent = ReadOpacityPercent(obj);
var opacity = ReadOpacityFromO(obj);
return new Transform(in shapeLayerContentArgs, anchor, position, scalePercent, rotation, opacityPercent);
return new Transform(in shapeLayerContentArgs, anchor, position, scalePercent, rotation, opacity);
}
static bool? ReadBool(JObject obj, string name)
@ -1598,8 +1585,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
if (numberOfColorStops.HasValue)
{
// There may be opacity stops. Read them.
var animatableOpacityPercentStopsParser = new AnimatableOpacityPercentStopsParser(numberOfColorStops.Value);
animatableOpacityPercentStopsParser.ParseJson(
var animatableOpacityStopsParser = new AnimatableOpacityStopsParser(numberOfColorStops.Value);
animatableOpacityStopsParser.ParseJson(
this,
obj.GetNamedObject("k"),
out var opacityKeyFrames,
@ -1682,6 +1669,30 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
: new Animatable<double>(initialValue, propertyIndex);
}
Animatable<Opacity> ReadOpacityFromO(JObject obj)
{
var jsonOpacity = obj.GetNamedObject("o", null);
return ReadOpacityFromObject(jsonOpacity);
}
Animatable<Opacity> ReadOpacityFromObject(JObject obj)
{
var result = obj != null
? ReadAnimatableOpacity(obj)
: new Animatable<Opacity>(Opacity.Opaque, null);
return result;
}
Animatable<Opacity> ReadAnimatableOpacity(JObject obj)
{
s_animatableOpacityParser.ParseJson(this, obj, out IEnumerable<KeyFrame<Opacity>> keyFrames, out Opacity initialValue);
var propertyIndex = ReadInt(obj, "ix");
return keyFrames.Any()
? new Animatable<Opacity>(keyFrames, propertyIndex)
: new Animatable<Opacity>(initialValue, propertyIndex);
}
static Vector3 ReadVector3FromJsonArray(JArray array)
{
double x = 0;
@ -1981,12 +1992,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
}
}
sealed class AnimatableOpacityPercentStopsParser : AnimatableParser<Sequence<GradientStop>>
sealed class AnimatableOpacityStopsParser : AnimatableParser<Sequence<GradientStop>>
{
// The number of color stops. The opacity stops follow this number of color stops.
readonly int _colorStopCount;
internal AnimatableOpacityPercentStopsParser(int colorStopCount)
internal AnimatableOpacityStopsParser(int colorStopCount)
{
_colorStopCount = colorStopCount;
}
@ -2014,7 +2025,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
opacity /= 255;
}
gradientStops[i / 2] = new OpacityGradientStop(offset, opacityPercent: opacity * 100);
gradientStops[i / 2] = new OpacityGradientStop(offset, opacity: Opacity.FromFloat(opacity));
break;
}
}
@ -2028,6 +2039,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieData.Serialization
protected override double ReadValue(JToken obj) => ReadFloat(obj);
}
sealed class AnimatableOpacityParser : AnimatableParser<Opacity>
{
protected override Opacity ReadValue(JToken obj) => Opacity.FromFloat(ReadFloat(obj) / 100.0);
}
abstract class AnimatableParser<T>
where T : IEquatable<T>
{

Просмотреть файл

@ -395,8 +395,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
continue;
}
if (mask.OpacityPercent.IsAnimated ||
mask.OpacityPercent.InitialValue != 100)
if (mask.Opacity.IsAnimated ||
!mask.Opacity.InitialValue.IsOpaque)
{
_issues.MaskWithAlphaIsNotSupported();
@ -953,13 +953,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
//
// Get the opacity of the layer.
var layerOpacityPercent = context.TrimAnimatable(context.Layer.Transform.OpacityPercent);
var layerOpacity = context.TrimAnimatable(context.Layer.Transform.Opacity);
// Convert the layer's in point and out point into absolute progress (0..1) values.
var inProgress = GetInPointProgress(context);
var outProgress = GetOutPointProgress(context);
if (inProgress > 1 || outProgress <= 0 || inProgress >= outProgress || layerOpacityPercent.AlwaysEquals(0))
if (inProgress > 1 || outProgress <= 0 || inProgress >= outProgress || layerOpacity.AlwaysEquals(LottieData.Opacity.Transparent))
{
// The layer is never visible. Don't create anything.
rootNode = null;
@ -971,7 +971,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
TranslateTransformOnContainerVisualForLayer(context, context.Layer, out rootNode, out contentsNode);
// Implement opacity for the layer.
if (layerOpacityPercent.IsAnimated || layerOpacityPercent.InitialValue < 100)
if (layerOpacity.IsAnimated || layerOpacity.InitialValue < LottieData.Opacity.Opaque)
{
// Insert a new node to control opacity at the top of the chain.
var opacityNode = _c.CreateContainerVisual();
@ -984,13 +984,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
opacityNode.Children.Add(rootNode);
rootNode = opacityNode;
if (layerOpacityPercent.IsAnimated)
if (layerOpacity.IsAnimated)
{
ApplyPercentKeyFrameAnimation(context, layerOpacityPercent, opacityNode, "Opacity", "Layer opacity animation");
ApplyOpacityKeyFrameAnimation(context, layerOpacity, opacityNode, "Opacity", "Layer opacity animation");
}
else
{
opacityNode.Opacity = PercentF(layerOpacityPercent.InitialValue);
opacityNode.Opacity = Opacity(layerOpacity.InitialValue);
}
}
@ -1175,7 +1175,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
sealed class ShapeContentContext
{
static readonly Animatable<double> s_100Percent = new Animatable<double>(100, null);
static readonly Animatable<Opacity> s_100Percent = new Animatable<Opacity>(LottieData.Opacity.Opaque, null);
readonly LottieToWinCompTranslator _owner;
@ -1192,14 +1192,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
// Opacity is not part of the Lottie context for shapes. But because WinComp
// doesn't support opacity on shapes, the opacity is inherited from
// the Transform and passed through to the brushes here.
internal Animatable<double> OpacityPercent { get; private set; }
internal Animatable<Opacity> Opacity { get; private set; }
internal ShapeContentContext(LottieToWinCompTranslator owner)
{
_owner = owner;
// Assume opacity is 100% unless otherwise set.
OpacityPercent = s_100Percent;
Opacity = s_100Percent;
}
internal void UpdateFromStack(Stack<ShapeLayerContent> stack)
@ -1244,7 +1244,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
Stroke = Stroke,
TrimPath = TrimPath,
RoundedCorner = RoundedCorner,
OpacityPercent = OpacityPercent,
Opacity = Opacity,
Transform = Transform,
};
}
@ -1256,7 +1256,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return;
}
OpacityPercent = ComposeOpacityPercents(OpacityPercent, transform.OpacityPercent);
Opacity = ComposeOpacities(Opacity, transform.Opacity);
}
// Only used when translating geometries. Layers use an extra Shape or Visual to
@ -1267,7 +1267,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
Transform = transform;
}
Animatable<double> ComposeOpacityPercents(Animatable<double> a, Animatable<double> b)
Animatable<Opacity> ComposeOpacities(Animatable<Opacity> a, Animatable<Opacity> b)
{
if (a == null || ReferenceEquals(a, s_100Percent))
{
@ -1282,7 +1282,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
if (!a.IsAnimated && !b.IsAnimated)
{
// Neither is animated. Just use the initial values.
return new Animatable<double>(a.InitialValue * (b.InitialValue / 100.0), null);
return new Animatable<Opacity>(a.InitialValue * b.InitialValue, null);
}
if (a.IsAnimated && b.IsAnimated)
@ -1312,36 +1312,36 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
// Only one is animated.
if (a.IsAnimated)
{
if (b.InitialValue == 100)
if (b.InitialValue.IsOpaque)
{
return a;
}
else
{
var bScale = b.InitialValue;
return new Animatable<double>(
return new Animatable<Opacity>(
initialValue: a.InitialValue * bScale,
keyFrames: a.KeyFrames.SelectToSpan(kf => ScaleKeyFrame(kf, bScale / 100)),
keyFrames: a.KeyFrames.SelectToSpan(kf => ScaleKeyFrame(kf, bScale)),
propertyIndex: null);
}
}
else
{
return ComposeOpacityPercents(b, a);
return ComposeOpacities(b, a);
}
}
// Composes 2 animated percent values where the frames in first come before second.
Animatable<double> ComposeNonOverlappingAnimatedPercents(Animatable<double> first, Animatable<double> second)
Animatable<Opacity> ComposeNonOverlappingAnimatedPercents(Animatable<Opacity> first, Animatable<Opacity> second)
{
Debug.Assert(first.IsAnimated, "Precondition");
Debug.Assert(second.IsAnimated, "Precondition");
Debug.Assert(first.KeyFrames[first.KeyFrames.Length - 1].Frame <= second.KeyFrames[0].Frame, "Precondition");
var resultFrames = new KeyFrame<double>[first.KeyFrames.Length + second.KeyFrames.Length];
var resultFrames = new KeyFrame<Opacity>[first.KeyFrames.Length + second.KeyFrames.Length];
var resultCount = 0;
var secondInitialScale = second.InitialValue / 100;
var firstFinalScale = first.KeyFrames[first.KeyFrames.Length - 1].Value / 100;
var secondInitialScale = second.InitialValue;
var firstFinalScale = first.KeyFrames[first.KeyFrames.Length - 1].Value;
foreach (var kf in first.KeyFrames)
{
@ -1355,15 +1355,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
resultCount++;
}
return new Animatable<double>(
return new Animatable<Opacity>(
first.InitialValue,
new ReadOnlySpan<KeyFrame<double>>(resultFrames, 0, resultCount),
new ReadOnlySpan<KeyFrame<Opacity>>(resultFrames, 0, resultCount),
null);
}
KeyFrame<double> ScaleKeyFrame(KeyFrame<double> keyFrame, double scale)
KeyFrame<Opacity> ScaleKeyFrame(KeyFrame<Opacity> keyFrame, Opacity scale)
{
return new KeyFrame<double>(
return new KeyFrame<Opacity>(
keyFrame.Frame,
keyFrame.Value * scale,
keyFrame.SpatialControlPoint1,
@ -1400,15 +1400,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
SolidColorFill ComposeSolidColorFills(SolidColorFill a, SolidColorFill b)
{
if (!b.Color.IsAnimated && !b.OpacityPercent.IsAnimated)
if (!b.Color.IsAnimated && !b.Opacity.IsAnimated)
{
if (b.OpacityPercent.InitialValue == 100 &&
if (b.Opacity.InitialValue.IsOpaque &&
b.Color.InitialValue.A == 1)
{
// b overrides a.
return b;
}
else if (b.OpacityPercent.InitialValue == 0 || b.Color.InitialValue.A == 0)
else if (b.Opacity.InitialValue.IsTransparent || b.Color.InitialValue.A == 0)
{
// b is transparent, so a wins.
return a;
@ -1454,7 +1454,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
Debug.Assert(a != null && b != null, "Precondition");
if (!a.StrokeWidth.IsAnimated && !b.StrokeWidth.IsAnimated &&
a.OpacityPercent.AlwaysEquals(100) && b.OpacityPercent.AlwaysEquals(100))
a.Opacity.AlwaysEquals(LottieData.Opacity.Opaque) && b.Opacity.AlwaysEquals(LottieData.Opacity.Opaque))
{
if (a.StrokeWidth.InitialValue >= b.StrokeWidth.InitialValue)
{
@ -1472,7 +1472,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
Debug.Assert(a != null && b != null, "Precondition");
if (!a.StrokeWidth.IsAnimated && !b.StrokeWidth.IsAnimated &&
a.OpacityPercent.AlwaysEquals(100) && b.OpacityPercent.AlwaysEquals(100))
a.Opacity.AlwaysEquals(LottieData.Opacity.Opaque) && b.Opacity.AlwaysEquals(LottieData.Opacity.Opaque))
{
if (a.StrokeWidth.InitialValue >= b.StrokeWidth.InitialValue)
{
@ -1491,7 +1491,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
if (!a.StrokeWidth.IsAnimated && !b.StrokeWidth.IsAnimated &&
!a.DashPattern.Any() && !b.DashPattern.Any() &&
a.OpacityPercent.AlwaysEquals(100) && b.OpacityPercent.AlwaysEquals(100))
a.Opacity.AlwaysEquals(LottieData.Opacity.Opaque) && b.Opacity.AlwaysEquals(LottieData.Opacity.Opaque))
{
if (a.StrokeWidth.InitialValue >= b.StrokeWidth.InitialValue)
{
@ -1703,7 +1703,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
{
// Treat each repeated value as a list of items where the repeater is replaced
// by n transforms.
// TODO - currently ignoring the StartOpacityPercent and EndOpacityPercent - should generate a new transform
// TODO - currently ignoring the StartOpacity and EndOpacity - should generate a new transform
// that interpolates that.
var generatedItems = itemsBeforeRepeater.Concat(Enumerable.Repeat(repeater.Transform, i + 1)).Concat(itemsAfterRepeater).ToArray();
@ -2482,8 +2482,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
void TranslateAndApplyShapeContentContext(TranslationContext context, ShapeContentContext shapeContext, CompositionSpriteShape shape, double trimOffsetDegrees = 0)
{
shape.FillBrush = TranslateShapeFill(context, shapeContext.Fill, context.TrimAnimatable(shapeContext.OpacityPercent));
TranslateAndApplyStroke(context, shapeContext.Stroke, shape, context.TrimAnimatable(shapeContext.OpacityPercent));
shape.FillBrush = TranslateShapeFill(context, shapeContext.Fill, context.TrimAnimatable(shapeContext.Opacity));
TranslateAndApplyStroke(context, shapeContext.Stroke, shape, context.TrimAnimatable(shapeContext.Opacity));
TranslateAndApplyTrimPath(context, shapeContext.TrimPath, shape.Geometry, trimOffsetDegrees);
}
@ -2692,7 +2692,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
TranslationContext context,
ShapeStroke shapeStroke,
CompositionSpriteShape sprite,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
if (shapeStroke == null)
{
@ -2707,13 +2707,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
switch (shapeStroke.StrokeKind)
{
case ShapeStroke.ShapeStrokeKind.SolidColor:
TranslateAndApplySolidColorStroke(context, (SolidColorStroke)shapeStroke, sprite, contextOpacityPercent);
TranslateAndApplySolidColorStroke(context, (SolidColorStroke)shapeStroke, sprite, contextOpacity);
break;
case ShapeStroke.ShapeStrokeKind.LinearGradient:
TranslateAndApplyLinearGradientStroke(context, (LinearGradientStroke)shapeStroke, sprite, contextOpacityPercent);
TranslateAndApplyLinearGradientStroke(context, (LinearGradientStroke)shapeStroke, sprite, contextOpacity);
break;
case ShapeStroke.ShapeStrokeKind.RadialGradient:
TranslateAndApplyRadialGradientStroke(context, (RadialGradientStroke)shapeStroke, sprite, contextOpacityPercent);
TranslateAndApplyRadialGradientStroke(context, (RadialGradientStroke)shapeStroke, sprite, contextOpacity);
break;
default:
throw new InvalidOperationException();
@ -2724,12 +2724,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
TranslationContext context,
LinearGradientStroke shapeStroke,
CompositionSpriteShape sprite,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
ApplyCommonStrokeProperties(
context,
shapeStroke,
TranslateLinearGradientStroke(context, shapeStroke, contextOpacityPercent),
TranslateLinearGradientStroke(context, shapeStroke, contextOpacity),
sprite);
}
@ -2737,12 +2737,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
TranslationContext context,
RadialGradientStroke shapeStroke,
CompositionSpriteShape sprite,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
ApplyCommonStrokeProperties(
context,
shapeStroke,
TranslateRadialGradientStroke(context, shapeStroke, contextOpacityPercent),
TranslateRadialGradientStroke(context, shapeStroke, contextOpacity),
sprite);
}
@ -2750,12 +2750,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
TranslationContext context,
SolidColorStroke shapeStroke,
CompositionSpriteShape sprite,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
ApplyCommonStrokeProperties(
context,
shapeStroke,
TranslateSolidColorStrokeColor(context, shapeStroke, contextOpacityPercent),
TranslateSolidColorStrokeColor(context, shapeStroke, contextOpacity),
sprite);
// NOTE: DashPattern animation (animating dash sizes) are not supported on CompositionSpriteShape.
@ -2802,7 +2802,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
sprite.StrokeBrush = brush;
}
CompositionBrush TranslateShapeFill(TranslationContext context, ShapeFill shapeFill, TrimmedAnimatable<double> opacityPercent)
CompositionBrush TranslateShapeFill(TranslationContext context, ShapeFill shapeFill, TrimmedAnimatable<Opacity> opacity)
{
if (shapeFill == null)
{
@ -2812,11 +2812,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
switch (shapeFill.FillKind)
{
case ShapeFill.ShapeFillKind.SolidColor:
return TranslateSolidColorFill(context, (SolidColorFill)shapeFill, opacityPercent);
return TranslateSolidColorFill(context, (SolidColorFill)shapeFill, opacity);
case ShapeFill.ShapeFillKind.LinearGradient:
return TranslateLinearGradientFill(context, (LinearGradientFill)shapeFill, opacityPercent);
return TranslateLinearGradientFill(context, (LinearGradientFill)shapeFill, opacity);
case ShapeFill.ShapeFillKind.RadialGradient:
return TranslateRadialGradientFill(context, (RadialGradientFill)shapeFill, opacityPercent);
return TranslateRadialGradientFill(context, (RadialGradientFill)shapeFill, opacity);
default:
throw new InvalidOperationException();
}
@ -2825,15 +2825,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
CompositionColorBrush TranslateSolidColor(
TranslationContext context,
Animatable<Color> color,
Animatable<double> opacityPercentA,
TrimmedAnimatable<double> opacityPercentB)
Animatable<Opacity> opacityA,
TrimmedAnimatable<Opacity> opacityB)
{
return CreateAnimatedColorBrush(
context,
MultiplyAnimatableColorByAnimatableOpacityPercent(
MultiplyAnimatableColorByAnimatableOpacity(
context.TrimAnimatable(color),
context.TrimAnimatable(opacityPercentA)),
opacityPercentB);
context.TrimAnimatable(opacityA)),
opacityB);
}
// Parses the given binding string and returns the binding name for the given property, or
@ -2849,30 +2849,31 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
CompositionColorBrush TranslateSolidColorStrokeColor(
TranslationContext context,
SolidColorStroke shapeStroke,
TrimmedAnimatable<double> inheritedOpacityPercent)
TrimmedAnimatable<Opacity> inheritedOpacity)
=> TranslateSolidColorWithBindings(
context,
shapeStroke.Color,
shapeStroke.OpacityPercent,
inheritedOpacityPercent,
shapeStroke.Opacity,
inheritedOpacity,
bindingSpec: shapeStroke.Name);
CompositionColorBrush TranslateSolidColorFill(
TranslationContext context,
SolidColorFill shapeFill,
TrimmedAnimatable<double> inheritedOpacityPercent)
TrimmedAnimatable<Opacity> inheritedOpacity)
=> TranslateSolidColorWithBindings(
context,
shapeFill.Color,
shapeFill.OpacityPercent,
inheritedOpacityPercent,
shapeFill.Opacity,
inheritedOpacity,
bindingSpec: shapeFill.Name);
CompositionColorBrush TranslateSolidColorWithBindings(
TranslationContext context,
Animatable<Color> color,
Animatable<double> colorOpacityPercent,
TrimmedAnimatable<double> inheritedOpacityPercent,
Animatable<Opacity
> colorOpacity,
TrimmedAnimatable<Opacity> inheritedOpacity,
string bindingSpec)
{
// Read property bindings embedded into the name of the fill.
@ -2882,17 +2883,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
if (bindingName is string)
{
// The fill is bound to a property name.
return TranslateBoundSolidColor(context, inheritedOpacityPercent, bindingName);
return TranslateBoundSolidColor(context, inheritedOpacity, bindingName);
}
}
return TranslateSolidColor(context, color, colorOpacityPercent, inheritedOpacityPercent);
return TranslateSolidColor(context, color, colorOpacity, inheritedOpacity);
}
// Translates a SolidColorFill that gets its color value from a property set value with the given name.
CompositionColorBrush TranslateBoundSolidColor(
TranslationContext context,
TrimmedAnimatable<double> opacityPercent,
TrimmedAnimatable<Opacity> opacity,
string bindingName)
{
// Insert a property set value for the color if one hasn't yet been added.
@ -2932,12 +2933,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
ExpressionAnimation anim;
if (opacityPercent.IsAnimated)
if (opacity.IsAnimated)
{
// The opacity is animated. Add an animated property for the opacity and use it to multiply
// the alpha channel of the color.
brush.Properties.InsertScalar("Opacity", PercentF(opacityPercent.InitialValue));
ApplyPercentKeyFrameAnimation(context, opacityPercent, brush.Properties, "Opacity", "Opacity", null);
brush.Properties.InsertScalar("Opacity", Opacity(opacity.InitialValue));
ApplyOpacityKeyFrameAnimation(context, opacity, brush.Properties, "Opacity", "Opacity", null);
anim = _c.CreateExpressionAnimation(BoundColorWithAnimatedOpacity(bindingName));
anim.SetReferenceParameter("my", brush);
@ -2945,7 +2946,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
else
{
// Opacity isn't animated. Multiply the alpha channel of the color by the non-animated opacity value.
anim = _c.CreateExpressionAnimation(BoundColor(bindingName, opacityPercent.InitialValue / 100));
anim = _c.CreateExpressionAnimation(BoundColor(bindingName, opacity.InitialValue.Value));
}
anim.SetReferenceParameter(RootName, _rootVisual);
@ -2956,9 +2957,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
CompositionLinearGradientBrush TranslateLinearGradientFill(
TranslationContext context,
LinearGradientFill shapeFill,
TrimmedAnimatable<double> opacityPercent)
TrimmedAnimatable<Opacity> opacity)
{
if (opacityPercent.IsAnimated)
if (opacity.IsAnimated)
{
// We don't yet support animated opacity with LinearGradientFill.
_issues.GradientFillIsNotSupported("Linear", "animated opacity");
@ -2967,15 +2968,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return TranslateLinearGradient(
context,
shapeFill,
MaxOpacityPercent(in opacityPercent));
MaxOpacity(in opacity));
}
CompositionGradientBrush TranslateLinearGradientStroke(
TranslationContext context,
LinearGradientStroke shapeStroke,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
if (contextOpacityPercent.IsAnimated)
if (contextOpacity.IsAnimated)
{
// We don't yet support animated opacity with LinearGradientFill.
_issues.GradientStrokeIsNotSupported("Linear", "animated opacity");
@ -2984,15 +2985,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return TranslateLinearGradient(
context,
shapeStroke,
MaxOpacityPercent(in contextOpacityPercent));
MaxOpacity(in contextOpacity));
}
CompositionBrush TranslateRadialGradientFill(
TranslationContext context,
RadialGradientFill shapeFill,
TrimmedAnimatable<double> opacityPercent)
TrimmedAnimatable<Opacity> opacity)
{
if (opacityPercent.IsAnimated)
if (opacity.IsAnimated)
{
// We don't yet support animated opacity with RadialGradientFill.
_issues.GradientFillIsNotSupported("Radial", "animated opacity");
@ -3001,15 +3002,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return TranslateRadialGradient(
context,
shapeFill,
MaxOpacityPercent(in opacityPercent));
MaxOpacity(in opacity));
}
CompositionGradientBrush TranslateRadialGradientStroke(
TranslationContext context,
RadialGradientStroke shapeStroke,
TrimmedAnimatable<double> contextOpacityPercent)
TrimmedAnimatable<Opacity> contextOpacity)
{
if (contextOpacityPercent.IsAnimated)
if (contextOpacity.IsAnimated)
{
// We don't yet support animated opacity with RadialGradientStroke.
_issues.GradientStrokeIsNotSupported("Radial", "animated opacity");
@ -3018,13 +3019,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return TranslateRadialGradient(
context,
shapeStroke,
MaxOpacityPercent(in contextOpacityPercent));
MaxOpacity(in contextOpacity));
}
CompositionLinearGradientBrush TranslateLinearGradient(
TranslationContext context,
IGradient linearGradient,
double opacityPercentValue)
Opacity opacity)
{
var result = _c.CreateLinearGradientBrush();
@ -3080,7 +3081,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
var colorKeyFrames = ExtractKeyFramesFromColorStopKeyFrames(
colorStopKeyFrames,
i,
gs => MultiplyColorByOpacityPercent(gs.Color, opacityPercentValue)).ToArray();
gs => MultiplyColorByOpacity(gs.Color, opacity)).ToArray();
ApplyColorKeyFrameAnimation(
context,
@ -3110,7 +3111,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
foreach (var stop in GradientStopOptimizer.Optimize(gradientStops.InitialValue.Items.ToArray()).Distinct())
{
var offset = stop.Offset;
var color = MultiplyColorByOpacityPercent(stop.Color, opacityPercentValue);
var color = MultiplyColorByOpacity(stop.Color, opacity);
brushStops.Add(_c.CreateColorGradientStop(Float(offset), color));
}
}
@ -3121,13 +3122,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
CompositionGradientBrush TranslateRadialGradient(
TranslationContext context,
IRadialGradient gradient,
double opacityPercentValue)
Opacity opacity)
{
if (!IsUapApiAvailable(nameof(CompositionRadialGradientBrush), versionDependentFeatureDescription: "Radial gradient fill"))
{
// CompositionRadialGradientBrush didn't exist until UAP v8. If the target OS doesn't support
// UAP v8 then fall back to linear gradients as a compromise.
return TranslateLinearGradient(context, gradient, opacityPercentValue);
return TranslateLinearGradient(context, gradient, opacity);
}
var result = _c.CreateRadialGradientBrush();
@ -3191,7 +3192,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
var colorKeyFrames = ExtractKeyFramesFromColorStopKeyFrames(
colorStopKeyFrames,
i,
gs => MultiplyColorByOpacityPercent(gs.Color, opacityPercentValue)).ToArray();
gs => MultiplyColorByOpacity(gs.Color, opacity)).ToArray();
ApplyColorKeyFrameAnimation(
context,
@ -3217,7 +3218,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
foreach (var stop in GradientStopOptimizer.Optimize(gradientStops.InitialValue.Items.ToArray()).Distinct())
{
var offset = stop.Offset;
var color = MultiplyColorByOpacityPercent(stop.Color, opacityPercentValue);
var color = MultiplyColorByOpacity(stop.Color, opacity);
brushStops.Add(_c.CreateColorGradientStop(Float(offset), color));
}
}
@ -3243,7 +3244,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
ShapeOrVisual TranslateSolidLayer(TranslationContext.For<SolidLayer> context)
{
if (context.Layer.IsHidden || context.Layer.Transform.OpacityPercent.AlwaysEquals(0))
if (context.Layer.IsHidden || context.Layer.Transform.Opacity.AlwaysEquals(LottieData.Opacity.Transparent))
{
// The layer does not render anything. Nothing to translate. This can happen when someone
// creates a solid layer to act like a Null layer.
@ -3291,7 +3292,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
// If that layer has no masks, opacity is implemented via the alpha channel on the brush.
rectangle.FillBrush = layerHasMasks
? _c.CreateNonAnimatedColorBrush(context.Layer.Color)
: CreateAnimatedColorBrush(context, context.Layer.Color, context.TrimAnimatable(context.Layer.Transform.OpacityPercent));
: CreateAnimatedColorBrush(context, context.Layer.Color, context.TrimAnimatable(context.Layer.Transform.Opacity));
if (_addDescriptions)
{
@ -3673,6 +3674,37 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
string shortDescription = null)
=> ApplyScaledScalarKeyFrameAnimation(context, value, 0.01, targetObject, targetPropertyName, longDescription, shortDescription);
void ApplyOpacityKeyFrameAnimation(
TranslationContext context,
in TrimmedAnimatable<Opacity> value,
CompositionObject targetObject,
string targetPropertyName,
string longDescription = null,
string shortDescription = null)
=> ApplyScaledOpacityKeyFrameAnimation(context, value, 1, targetObject, targetPropertyName, longDescription, shortDescription);
void ApplyScaledOpacityKeyFrameAnimation(
TranslationContext context,
in TrimmedAnimatable<Opacity> value,
double scale,
CompositionObject targetObject,
string targetPropertyName,
string longDescription,
string shortDescription)
{
Debug.Assert(value.IsAnimated, "Precondition");
GenericCreateCompositionKeyFrameAnimation(
context,
value,
_c.CreateScalarKeyFrameAnimation,
(ca, progress, val, easing) => ca.InsertKeyFrame(progress, (float)(val.Value * scale), easing),
null,
targetObject,
targetPropertyName,
longDescription,
shortDescription);
}
void ApplyScaledScalarKeyFrameAnimation(
TranslationContext context,
in TrimmedAnimatable<double> value,
@ -4113,85 +4145,85 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return result;
}
TrimmedAnimatable<Color> MultiplyAnimatableColorByAnimatableOpacityPercent(
TrimmedAnimatable<Color> MultiplyAnimatableColorByAnimatableOpacity(
in TrimmedAnimatable<Color> color,
in TrimmedAnimatable<double> opacityPercent)
in TrimmedAnimatable<Opacity> opacity)
{
if (color.IsAnimated)
{
var opacityPercentInitialValue = opacityPercent.InitialValue;
var opacityInitialValue = opacity.InitialValue;
if (opacityPercent.IsAnimated)
if (opacity.IsAnimated)
{
// TOOD: multiply animations to produce a new set of key frames for the opacity-multiplied color.
_issues.OpacityAndColorAnimatedTogetherIsNotSupported();
// Multiply the color values by the maximum opacity. This is a heuristic
// that can give a better result than just returning the color.
opacityPercentInitialValue = MaxOpacityPercent(in opacityPercent);
opacityInitialValue = MaxOpacity(in opacity);
}
// Multiply the color animation by the single opacity value.
return new TrimmedAnimatable<Color>(
color.Context,
initialValue: MultiplyColorByOpacityPercent(color.InitialValue, opacityPercent.InitialValue),
initialValue: MultiplyColorByOpacity(color.InitialValue, opacity.InitialValue),
keyFrames: color.KeyFrames.SelectToSpan(kf =>
new KeyFrame<Color>(
kf.Frame,
MultiplyColorByOpacityPercent(kf.Value, opacityPercentInitialValue),
MultiplyColorByOpacity(kf.Value, opacityInitialValue),
kf.SpatialControlPoint1,
kf.SpatialControlPoint2,
kf.Easing)));
}
else if (opacityPercent.IsAnimated)
else if (opacity.IsAnimated)
{
// Color is not animated.
return MultiplyColorByAnimatableOpacityPercent(color.InitialValue, opacityPercent);
return MultiplyColorByAnimatableOpacity(color.InitialValue, opacity);
}
else
{
// Multiply color by opacity
var nonAnimatedMultipliedColor = MultiplyColorByOpacityPercent(color.InitialValue, opacityPercent.InitialValue);
var nonAnimatedMultipliedColor = MultiplyColorByOpacity(color.InitialValue, opacity.InitialValue);
return new TrimmedAnimatable<Color>(color.Context, nonAnimatedMultipliedColor);
}
}
TrimmedAnimatable<Color> MultiplyColorByAnimatableOpacityPercent(
TrimmedAnimatable<Color> MultiplyColorByAnimatableOpacity(
Color color,
in TrimmedAnimatable<double> opacityPercent)
in TrimmedAnimatable<Opacity> opacity)
{
if (!opacityPercent.IsAnimated)
if (!opacity.IsAnimated)
{
return new TrimmedAnimatable<Color>(opacityPercent.Context, MultiplyColorByOpacityPercent(color, opacityPercent.InitialValue));
return new TrimmedAnimatable<Color>(opacity.Context, MultiplyColorByOpacity(color, opacity.InitialValue));
}
else
{
// Multiply the single color value by the opacity animation.
return new TrimmedAnimatable<Color>(
opacityPercent.Context,
initialValue: MultiplyColorByOpacityPercent(color, opacityPercent.InitialValue),
keyFrames: opacityPercent.KeyFrames.SelectToSpan(kf =>
opacity.Context,
initialValue: MultiplyColorByOpacity(color, opacity.InitialValue),
keyFrames: opacity.KeyFrames.SelectToSpan(kf =>
new KeyFrame<Color>(
kf.Frame,
MultiplyColorByOpacityPercent(color, kf.Value),
MultiplyColorByOpacity(color, kf.Value),
kf.SpatialControlPoint1,
kf.SpatialControlPoint2,
kf.Easing)));
}
}
static Color MultiplyColorByOpacityPercent(Color color, double opacityPercent)
=> color?.MultipliedByOpacity(opacityPercent / 100);
static Color MultiplyColorByOpacity(Color color, Opacity opacity)
=> color?.MultipliedByOpacity(opacity);
CompositionColorBrush CreateAnimatedColorBrush(TranslationContext context, Color color, in TrimmedAnimatable<double> opacityPercent)
CompositionColorBrush CreateAnimatedColorBrush(TranslationContext context, Color color, in TrimmedAnimatable<Opacity> opacity)
{
var multipliedColor = MultiplyColorByAnimatableOpacityPercent(color, opacityPercent);
var multipliedColor = MultiplyColorByAnimatableOpacity(color, opacity);
return CreateAnimatedColorBrush(context, multipliedColor);
}
CompositionColorBrush CreateAnimatedColorBrush(TranslationContext context, in TrimmedAnimatable<Color> color, in TrimmedAnimatable<double> opacityPercent)
CompositionColorBrush CreateAnimatedColorBrush(TranslationContext context, in TrimmedAnimatable<Color> color, in TrimmedAnimatable<Opacity> opacity)
{
var multipliedColor = MultiplyAnimatableColorByAnimatableOpacityPercent(color, opacityPercent);
var multipliedColor = MultiplyAnimatableColorByAnimatableOpacity(color, opacity);
return CreateAnimatedColorBrush(context, multipliedColor);
}
@ -4223,9 +4255,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
}
// Returns the maximum opacity value from the given TrimmedAnimatable.
// This works for any double value, but we call it MaxOpacityPercent because that
// This works for any double value, but we call it MaxOpacity because that
// is where it is useful, and the name makes the intention clearer.
static double MaxOpacityPercent(in TrimmedAnimatable<double> animatable)
static Opacity MaxOpacity(in TrimmedAnimatable<Opacity> animatable)
=> animatable.IsAnimated
? animatable.KeyFrames.Max(kf => kf.Value)
: animatable.InitialValue;
@ -4307,6 +4339,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
static float? FloatDefaultIsOne(double value) => value == 1 ? null : (float?)value;
static float Opacity(Opacity value) => (float)value.Value;
static float PercentF(double value) => (float)value / 100F;
static Sn.Vector2 Vector2(LottieData.Vector3 vector3) => Vector2(vector3.X, vector3.Y);