Merge pull request #450 from windows-toolkit/user/aborziak-ms/rectangles-gradient-fill-fix
Gradient fill for rectangle(rounded rectangle) shape fixed
This commit is contained in:
Коммит
d87f131703
|
@ -406,6 +406,60 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
return result;
|
||||
}
|
||||
|
||||
// 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<Vector2> value)
|
||||
{
|
||||
if (context is not ShapeLayerContext || ((ShapeLayerContext)context).OriginOffset is null)
|
||||
{
|
||||
if (value.IsAnimated)
|
||||
{
|
||||
Animate.Vector2(context, value, obj, propertyName);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConvertTo.Vector2(value.InitialValue);
|
||||
}
|
||||
}
|
||||
|
||||
var offset = ((ShapeLayerContext)context).OriginOffset!;
|
||||
|
||||
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 origin offset.
|
||||
WinCompData.Expressions.Vector2 expression = offset.IsAnimated ?
|
||||
ExpressionFactory.OriginOffsetExressionAdded(sourcePropertyName, offset.OffsetExpression) :
|
||||
ExpressionFactory.OriginOffsetValueAdded(sourcePropertyName, offset.OffsetValue);
|
||||
|
||||
var expressionAnimation = context.ObjectFactory.CreateExpressionAnimation(expression);
|
||||
expressionAnimation.SetReferenceParameter("my", obj);
|
||||
if (offset.IsAnimated)
|
||||
{
|
||||
// Expression can use geometry.
|
||||
expressionAnimation.SetReferenceParameter("geometry", offset.Geometry);
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -419,22 +473,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var startPoint = Optimizer.TrimAnimatable(context, linearGradient.StartPoint);
|
||||
var endPoint = Optimizer.TrimAnimatable(context, linearGradient.EndPoint);
|
||||
|
||||
if (startPoint.IsAnimated)
|
||||
var startPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.StartPoint), startPoint);
|
||||
if (startPointValue is not null)
|
||||
{
|
||||
Animate.Vector2(context, startPoint, result, nameof(result.StartPoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.StartPoint = ConvertTo.Vector2(startPoint.InitialValue);
|
||||
result.StartPoint = startPointValue!;
|
||||
}
|
||||
|
||||
if (endPoint.IsAnimated)
|
||||
var endPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.EndPoint), endPoint);
|
||||
if (endPointValue is not null)
|
||||
{
|
||||
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);
|
||||
|
@ -470,13 +518,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var startPoint = Optimizer.TrimAnimatable(context, gradient.StartPoint);
|
||||
var endPoint = Optimizer.TrimAnimatable(context, gradient.EndPoint);
|
||||
|
||||
if (startPoint.IsAnimated)
|
||||
var startPointValue = AnimateVector2WithOriginOffsetOrGetValue(context, result, nameof(result.EllipseCenter), startPoint);
|
||||
if (startPointValue is not null)
|
||||
{
|
||||
Animate.Vector2(context, startPoint, result, nameof(result.EllipseCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.EllipseCenter = ConvertTo.Vector2(startPoint.InitialValue);
|
||||
result.EllipseCenter = startPointValue!;
|
||||
}
|
||||
|
||||
if (endPoint.IsAnimated)
|
||||
|
|
|
@ -33,7 +33,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
internal static readonly Scalar RootProgress = RootScalar(TranslationContext.ProgressPropertyName);
|
||||
internal static readonly Scalar MaxTStartTEnd = Max(MyTStart, MyTEnd);
|
||||
internal static readonly Scalar MinTStartTEnd = Min(MyTStart, MyTEnd);
|
||||
static readonly Vector2 HalfMySize = MySize / Vector2(2, 2);
|
||||
internal static readonly Vector2 HalfMySize = MySize / Vector2(2, 2);
|
||||
internal static readonly Vector2 GeometryHalfSize = NamedVector2("geometry", "Size") / Vector2(2, 2);
|
||||
internal static readonly Color AnimatedColorWithAnimatedOpacity =
|
||||
ColorAsVector4MultipliedByOpacities(MyColor, new[] { MyOpacity });
|
||||
|
||||
|
@ -46,6 +47,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
MyPosition.Y - MyAnchor.Y,
|
||||
0);
|
||||
|
||||
internal static Vector2 OriginOffsetExressionAdded(string property, Vector2 offsetExpression) => MyVector2(property) + offsetExpression;
|
||||
|
||||
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);
|
||||
|
||||
|
@ -248,10 +253,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
|
||||
static Vector2 MyVector2(string propertyName) => Vector2(My(propertyName));
|
||||
|
||||
static Vector2 NamedVector2(string name, string propertyName) => Vector2(Named(name, propertyName));
|
||||
|
||||
static Vector4 MyVector4(string propertyName) => Vector4(My(propertyName));
|
||||
|
||||
static string My(string propertyName) => $"my.{propertyName}";
|
||||
|
||||
static string Named(string name, string propertyName) => $"{name}.{propertyName}";
|
||||
|
||||
// A property on the root property set. Used to bind to the property set that contains the Progress property.
|
||||
static string RootProperty(string propertyName) => $"{RootName}.{propertyName}";
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ using Microsoft.Toolkit.Uwp.UI.Lottie.Animatables;
|
|||
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieData;
|
||||
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData;
|
||||
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.Mgcg;
|
||||
using static Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp.ShapeLayerContext;
|
||||
using Expressions = Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.Expressions;
|
||||
using Sn = System.Numerics;
|
||||
|
||||
|
@ -318,6 +319,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var height = size.InitialValue.Y;
|
||||
var trimOffsetDegrees = (width / (2 * (width + height))) * 360;
|
||||
|
||||
// If offset is not animated then other computations for fill brush can be optimized.
|
||||
context.LayerContext.OriginOffset = size.IsAnimated ?
|
||||
new OriginOffsetContainer(geometry, ExpressionFactory.GeometryHalfSize) :
|
||||
new OriginOffsetContainer(geometry, ConvertTo.Vector2(size.InitialValue / 2));
|
||||
|
||||
Shapes.TranslateAndApplyShapeContextWithTrimOffset(
|
||||
context,
|
||||
compositionRectangle,
|
||||
|
@ -408,6 +414,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
var initialHeight = height.InitialValue;
|
||||
var trimOffsetDegrees = (initialWidth / (2 * (initialWidth + initialHeight))) * 360;
|
||||
|
||||
// If offset is not animated then other computations for fill brush can be optimized.
|
||||
context.LayerContext.OriginOffset = width.IsAnimated || height.IsAnimated ?
|
||||
new OriginOffsetContainer(geometry, ExpressionFactory.GeometryHalfSize) :
|
||||
new OriginOffsetContainer(geometry, ConvertTo.Vector2(width.InitialValue / 2, height.InitialValue / 2));
|
||||
|
||||
Shapes.TranslateAndApplyShapeContextWithTrimOffset(
|
||||
context,
|
||||
compositionRectangle,
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Toolkit.Uwp.UI.Lottie.LottieData;
|
||||
using Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData.Expressions;
|
||||
using Sn = System.Numerics;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
||||
{
|
||||
|
@ -14,6 +16,45 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
Layer = layer;
|
||||
}
|
||||
|
||||
// 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 points inside
|
||||
// the rectangle for (Rectangle.Size / 2).
|
||||
// This class represents this offset (static or animated)
|
||||
public class OriginOffsetContainer
|
||||
{
|
||||
public RectangleOrRoundedRectangleGeometry Geometry { get; }
|
||||
|
||||
// Use expression if size is animated
|
||||
public WinCompData.Expressions.Vector2 OffsetExpression { get; }
|
||||
|
||||
// Use constant value if size is static
|
||||
public Sn.Vector2 OffsetValue { get; }
|
||||
|
||||
// IsAnimated = true means that we have to use OffsetExpression.
|
||||
// IsAnimated = false means that we can use OffsetValue instead of OffsetExpression to optimize the code.
|
||||
public bool IsAnimated { get; }
|
||||
|
||||
public OriginOffsetContainer(RectangleOrRoundedRectangleGeometry geometry, WinCompData.Expressions.Vector2 expression)
|
||||
{
|
||||
IsAnimated = true;
|
||||
Geometry = geometry;
|
||||
OffsetExpression = expression;
|
||||
OffsetValue = new Sn.Vector2(0, 0);
|
||||
}
|
||||
|
||||
public OriginOffsetContainer(RectangleOrRoundedRectangleGeometry geometry, Sn.Vector2 value)
|
||||
{
|
||||
IsAnimated = false;
|
||||
Geometry = geometry;
|
||||
OffsetExpression = Expression.Vector2(value.X, value.Y);
|
||||
OffsetValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal OriginOffsetContainer? OriginOffset { get; set; }
|
||||
|
||||
public new ShapeLayer Layer { get; }
|
||||
}
|
||||
}
|
|
@ -45,6 +45,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
|
|||
Debug.Assert(shape.Geometry is not null, "Precondition");
|
||||
|
||||
shape.FillBrush = Brushes.TranslateShapeFill(context, context.Fill, context.Opacity);
|
||||
|
||||
// OriginOffset is used to adjust cordinates of FillBrush for Rectangle shapes.
|
||||
// It is not needed afterwards, so we clean it up to not affect other code.
|
||||
context.LayerContext.OriginOffset = null;
|
||||
|
||||
Brushes.TranslateAndApplyStroke(context, context.Stroke, shape, context.Opacity);
|
||||
|
||||
TranslateAndApplyTrimPath(
|
||||
|
|
|
@ -2130,7 +2130,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
|
|||
|
||||
// Call the helper and initialize the remaining CompositionShape properties.
|
||||
WriteMatrixComment(builder, obj.TransformMatrix);
|
||||
builder.WriteLine($"{ConstVar} result = CreateSpriteShape({CallFactoryFromFor(node, obj.Geometry)}, {Matrix3x2(transformMatrix)}, {CallFactoryFromFor(node, obj.FillBrush)});");
|
||||
|
||||
// We need to instantiate geometry first because sometimes it initializes fields
|
||||
// that are used in FillBrush, but CreateSpriteShape(GetGeometry(), ..., GetFillBrush()) code
|
||||
// will result in evaluating GetFillBrush() first which may cause null dereferencing
|
||||
builder.WriteLine($"{ConstVar} geometry = {CallFactoryFromFor(node, obj.Geometry)};");
|
||||
|
||||
builder.WriteLine($"{ConstVar} result = CreateSpriteShape(geometry, {Matrix3x2(transformMatrix)}, {CallFactoryFromFor(node, obj.FillBrush)});");
|
||||
InitializeCompositionObject(builder, obj, node);
|
||||
WriteSetPropertyStatement(builder, nameof(obj.CenterPoint), obj.CenterPoint);
|
||||
WriteSetPropertyStatement(builder, nameof(obj.Offset), obj.Offset);
|
||||
|
|
Загрузка…
Ссылка в новой задаче