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:
aborziak-ms 2021-06-14 16:48:57 -07:00 коммит произвёл GitHub
Родитель ee3344ed48 60a2c6b6f1
Коммит d87f131703
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 137 добавлений и 20 удалений

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

@ -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);