diff --git a/source/LottieToWinComp/Brushes.cs b/source/LottieToWinComp/Brushes.cs index bf47b98..ccbd7e9 100644 --- a/source/LottieToWinComp/Brushes.cs +++ b/source/LottieToWinComp/Brushes.cs @@ -168,7 +168,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp ApplyCommonStrokeProperties( context, shapeStroke, - TranslateLinearGradient(context, shapeStroke, contextOpacity, null), + TranslateLinearGradient(context, shapeStroke, contextOpacity), sprite); } @@ -181,7 +181,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp ApplyCommonStrokeProperties( context, shapeStroke, - TranslateRadialGradient(context, shapeStroke, contextOpacity, null), + TranslateRadialGradient(context, shapeStroke, contextOpacity), sprite); } @@ -258,7 +258,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp LayerContext context, ShapeFill? shapeFill, CompositeOpacity opacity, - Rectangles.InternalOffset? internalOffset) + Rectangles.OriginOffset? originOffset) { if (shapeFill is null) { @@ -267,9 +267,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp return shapeFill.FillKind switch { - ShapeFill.ShapeFillKind.SolidColor => TranslateSolidColorFill(context, (SolidColorFill)shapeFill, opacity), - ShapeFill.ShapeFillKind.LinearGradient => TranslateLinearGradient(context, (LinearGradientFill)shapeFill, opacity, internalOffset), - ShapeFill.ShapeFillKind.RadialGradient => TranslateRadialGradient(context, (RadialGradientFill)shapeFill, opacity, internalOffset), + ShapeFill.ShapeFillKind.SolidColor => + TranslateSolidColorFill(context, (SolidColorFill)shapeFill, opacity), + ShapeFill.ShapeFillKind.LinearGradient => + TranslateLinearGradient(context, (LinearGradientFill)shapeFill, opacity, originOffset), + ShapeFill.ShapeFillKind.RadialGradient => + TranslateRadialGradient(context, (RadialGradientFill)shapeFill, opacity, originOffset), _ => throw new InvalidOperationException(), }; } @@ -410,40 +413,64 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp return result; } - static void TranslateVector2AnimatableWithInternalOffset( + // Animate Vector2 property of object with TrimmedAnimatable while applying OriginOffset to it. + // Returns non-null Sn.Vector2 value if no animation is needed (and no animation was applied). + static Sn.Vector2? AnimateVector2WithOriginOffsetOrGetValue( LayerContext context, CompositionObject obj, string propertyName, TrimmedAnimatable value, - Rectangles.InternalOffset offset) + Rectangles.OriginOffset? offset) { - // anomate source property first + if (offset is null) + { + if (value.IsAnimated) + { + Animate.Vector2(context, value, obj, propertyName); + return null; + } + else + { + return ConvertTo.Vector2(value.InitialValue); + } + } + + if (!offset.IsAnimated && !value.IsAnimated) + { + return ConvertTo.Vector2(value.InitialValue) + offset.OffsetValue!; + } + + // Animate source property first. + // We are using this auxiliary property to store original animation, + // so that its value can be used in expression animation of property itself. string sourcePropertyName = propertyName + "Source"; obj.Properties.InsertVector2(sourcePropertyName, ConvertTo.Vector2(value.InitialValue)); Animate.Vector2(context, value, obj, sourcePropertyName); - // create expression that offsets source property by internal offset + // Create expression that offsets source property by origin offset. WinCompData.Expressions.Vector2 expression = offset.IsAnimated ? - ExpressionFactory.InternalOffsetExressionAdded(sourcePropertyName, offset.OffsetExpression!) : - ExpressionFactory.InternalOffsetValueAdded(sourcePropertyName, (Sn.Vector2)offset.OffsetValue!); + ExpressionFactory.OriginOffsetExressionAdded(sourcePropertyName, offset.OffsetExpression!) : + ExpressionFactory.OriginOffsetValueAdded(sourcePropertyName, (Sn.Vector2)offset.OffsetValue!); var expressionAnimation = context.ObjectFactory.CreateExpressionAnimation(expression); expressionAnimation.SetReferenceParameter("my", obj); if (offset.IsAnimated) { - // expression can use geometry + // Expression can use geometry. expressionAnimation.SetReferenceParameter("geometry", offset.Geometry); } - // animate original property with expression that applies internal offset to it + // Animate original property with expression that applies origin offset to it. Animate.WithExpression(obj, expressionAnimation, propertyName); + + return null; } static CompositionLinearGradientBrush? TranslateLinearGradient( LayerContext context, IGradient linearGradient, CompositeOpacity opacity, - Rectangles.InternalOffset? internalOffset) + Rectangles.OriginOffset? originOffset = null) { var result = context.ObjectFactory.CreateLinearGradientBrush(); @@ -453,30 +480,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp var startPoint = Optimizer.TrimAnimatable(context, linearGradient.StartPoint); var endPoint = Optimizer.TrimAnimatable(context, linearGradient.EndPoint); - if (internalOffset is not null) + var startPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.StartPoint), startPoint, originOffset); + if (startPointValue is not null) { - TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.StartPoint), startPoint, internalOffset); - } - else if (startPoint.IsAnimated) - { - Animate.Vector2(context, startPoint, result, nameof(result.StartPoint)); - } - else - { - result.StartPoint = ConvertTo.Vector2(startPoint.InitialValue); + result.StartPoint = startPointValue!; } - if (internalOffset is not null) + var endPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.EndPoint), endPoint, originOffset); + if (endPointValue is not null) { - TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.EndPoint), endPoint, internalOffset); - } - else if (endPoint.IsAnimated) - { - Animate.Vector2(context, endPoint, result, nameof(result.EndPoint)); - } - else - { - result.EndPoint = ConvertTo.Vector2(endPoint.InitialValue); + result.EndPoint = endPointValue!; } var gradientStops = Optimizer.TrimAnimatable(context, linearGradient.GradientStops); @@ -496,13 +509,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp LayerContext context, IRadialGradient gradient, CompositeOpacity opacity, - Rectangles.InternalOffset? internalOffset) + Rectangles.OriginOffset? originOffset = null) { if (!context.ObjectFactory.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, opacity, internalOffset); + return TranslateLinearGradient(context, gradient, opacity, originOffset); } var result = context.ObjectFactory.CreateRadialGradientBrush(); @@ -513,17 +526,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp var startPoint = Optimizer.TrimAnimatable(context, gradient.StartPoint); var endPoint = Optimizer.TrimAnimatable(context, gradient.EndPoint); - if (internalOffset is not null) + var startPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.EllipseCenter), startPoint, originOffset); + if (startPointValue is not null) { - TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.EllipseCenter), startPoint, internalOffset); - } - else if (startPoint.IsAnimated) - { - Animate.Vector2(context, startPoint, result, nameof(result.EllipseCenter)); - } - else - { - result.EllipseCenter = ConvertTo.Vector2(startPoint.InitialValue); + result.EllipseCenter = startPointValue!; } if (endPoint.IsAnimated) diff --git a/source/LottieToWinComp/ExpressionFactory.cs b/source/LottieToWinComp/ExpressionFactory.cs index d5c060b..68d1ec5 100644 --- a/source/LottieToWinComp/ExpressionFactory.cs +++ b/source/LottieToWinComp/ExpressionFactory.cs @@ -47,9 +47,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp MyPosition.Y - MyAnchor.Y, 0); - internal static Vector2 InternalOffsetExressionAdded(string property, Vector2 offsetExpression) => MyVector2(property) + offsetExpression; + internal static Vector2 OriginOffsetExressionAdded(string property, Vector2 offsetExpression) => MyVector2(property) + offsetExpression; - internal static Vector2 InternalOffsetValueAdded(string property, Sn.Vector2 offsetValue) => MyVector2(property) + Vector2(offsetValue); + internal static Vector2 OriginOffsetValueAdded(string property, Sn.Vector2 offsetValue) => MyVector2(property) + Vector2(offsetValue); internal static Color ThemedColorMultipliedByOpacity(string bindingName, Animatables.Opacity opacity) => ColorAsVector4MultipliedByOpacity(ThemedColor4Property(bindingName), opacity.Value); diff --git a/source/LottieToWinComp/Rectangles.cs b/source/LottieToWinComp/Rectangles.cs index 5e43d99..12f91fe 100644 --- a/source/LottieToWinComp/Rectangles.cs +++ b/source/LottieToWinComp/Rectangles.cs @@ -21,31 +21,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp // Rectangles are implemented differently in WinComp API // and Lottie. In WinComp API coordinates inside rectangle start in // top left corner and in Lottie they start in the middle - // To account for this we need to offset all the internal points - // for (Rectangle.Size / 2) + // To account for this we need to offset all the points inside + // the rectangle for (Rectangle.Size / 2). // This class represents this offset (static or animated) - public class InternalOffset + public class OriginOffset { public RectangleOrRoundedRectangleGeometry Geometry { get; } // Use expression if size is animated -#nullable enable public Expressions.Vector2? OffsetExpression { get; } -#nullable disable // Use constant value if size if static public Sn.Vector2? OffsetValue { get; } - public bool IsAnimated => OffsetExpression is not null; + public bool IsAnimated => OffsetValue is null; - public InternalOffset(RectangleOrRoundedRectangleGeometry geometry, Expressions.Vector2 expression) + public OriginOffset(RectangleOrRoundedRectangleGeometry geometry, Expressions.Vector2 expression) { Geometry = geometry; OffsetExpression = expression; OffsetValue = null; } - public InternalOffset(RectangleOrRoundedRectangleGeometry geometry, Sn.Vector2 value) + public OriginOffset(RectangleOrRoundedRectangleGeometry geometry, Sn.Vector2 value) { Geometry = geometry; OffsetExpression = null; @@ -353,15 +351,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp var height = size.InitialValue.Y; var trimOffsetDegrees = (width / (2 * (width + height))) * 360; - InternalOffset internalOffset = size.IsAnimated ? - new InternalOffset(geometry, ExpressionFactory.GeometryHalfSize) : - new InternalOffset(geometry, ConvertTo.Vector2(size.InitialValue / 2)); + // If offset is not animated then other computations for fill brush can be optimized. + OriginOffset originOffset = size.IsAnimated ? + new OriginOffset(geometry, ExpressionFactory.GeometryHalfSize) : + new OriginOffset(geometry, ConvertTo.Vector2(size.InitialValue / 2)); Shapes.TranslateAndApplyShapeContextWithTrimOffset( context, compositionRectangle, rectangle.DrawingDirection == DrawingDirection.Reverse, - internalOffset, + originOffset, trimOffsetDegrees: trimOffsetDegrees); compositionRectangle.SetDescription(context, () => rectangle.Name); @@ -448,15 +447,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp var initialHeight = height.InitialValue; var trimOffsetDegrees = (initialWidth / (2 * (initialWidth + initialHeight))) * 360; - InternalOffset internalOffset = width.IsAnimated || height.IsAnimated ? - new InternalOffset(geometry, ExpressionFactory.GeometryHalfSize) : - new InternalOffset(geometry, ConvertTo.Vector2(width.InitialValue / 2, height.InitialValue / 2)); + // If offset is not animated then other computations for fill brush can be optimized. + OriginOffset originOffset = width.IsAnimated || height.IsAnimated ? + new OriginOffset(geometry, ExpressionFactory.GeometryHalfSize) : + new OriginOffset(geometry, ConvertTo.Vector2(width.InitialValue / 2, height.InitialValue / 2)); Shapes.TranslateAndApplyShapeContextWithTrimOffset( context, compositionRectangle, rectangle.DrawingDirection == DrawingDirection.Reverse, - internalOffset, + originOffset, trimOffsetDegrees: trimOffsetDegrees); compositionRectangle.SetDescription(context, () => rectangle.Name); diff --git a/source/LottieToWinComp/Shapes.cs b/source/LottieToWinComp/Shapes.cs index 890df97..bdb52d8 100644 --- a/source/LottieToWinComp/Shapes.cs +++ b/source/LottieToWinComp/Shapes.cs @@ -34,18 +34,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp ShapeContext context, CompositionSpriteShape shape, bool reverseDirection) => - TranslateAndApplyShapeContextWithTrimOffset(context, shape, reverseDirection, null, 0); + TranslateAndApplyShapeContextWithTrimOffset( + context, + shape, + reverseDirection, + originOffset: null, + trimOffsetDegrees: 0); public static void TranslateAndApplyShapeContextWithTrimOffset( ShapeContext context, CompositionSpriteShape shape, bool reverseDirection, - Rectangles.InternalOffset? internalOffset, + Rectangles.OriginOffset? originOffset, double trimOffsetDegrees) { Debug.Assert(shape.Geometry is not null, "Precondition"); - shape.FillBrush = Brushes.TranslateShapeFill(context, context.Fill, context.Opacity, internalOffset); + shape.FillBrush = Brushes.TranslateShapeFill(context, context.Fill, context.Opacity, originOffset); Brushes.TranslateAndApplyStroke(context, context.Stroke, shape, context.Opacity); TranslateAndApplyTrimPath(