Skip to content

Commit e27c69f

Browse files
committed
Fixed gradient offsets for rectangle shape
1 parent ee3344e commit e27c69f

File tree

5 files changed

+123
-15
lines changed

5 files changed

+123
-15
lines changed

source/LottieToWinComp/Brushes.cs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static void TranslateAndApplyLinearGradientStroke(
168168
ApplyCommonStrokeProperties(
169169
context,
170170
shapeStroke,
171-
TranslateLinearGradient(context, shapeStroke, contextOpacity),
171+
TranslateLinearGradient(context, shapeStroke, contextOpacity, null),
172172
sprite);
173173
}
174174

@@ -181,7 +181,7 @@ static void TranslateAndApplyRadialGradientStroke(
181181
ApplyCommonStrokeProperties(
182182
context,
183183
shapeStroke,
184-
TranslateRadialGradient(context, shapeStroke, contextOpacity),
184+
TranslateRadialGradient(context, shapeStroke, contextOpacity, null),
185185
sprite);
186186
}
187187

@@ -254,7 +254,11 @@ static void ApplyCommonStrokeProperties(
254254
sprite.StrokeBrush = brush;
255255
}
256256

257-
public static CompositionBrush? TranslateShapeFill(LayerContext context, ShapeFill? shapeFill, CompositeOpacity opacity)
257+
public static CompositionBrush? TranslateShapeFill(
258+
LayerContext context,
259+
ShapeFill? shapeFill,
260+
CompositeOpacity opacity,
261+
Rectangles.InternalOffset? internalOffset)
258262
{
259263
if (shapeFill is null)
260264
{
@@ -264,8 +268,8 @@ static void ApplyCommonStrokeProperties(
264268
return shapeFill.FillKind switch
265269
{
266270
ShapeFill.ShapeFillKind.SolidColor => TranslateSolidColorFill(context, (SolidColorFill)shapeFill, opacity),
267-
ShapeFill.ShapeFillKind.LinearGradient => TranslateLinearGradient(context, (LinearGradientFill)shapeFill, opacity),
268-
ShapeFill.ShapeFillKind.RadialGradient => TranslateRadialGradient(context, (RadialGradientFill)shapeFill, opacity),
271+
ShapeFill.ShapeFillKind.LinearGradient => TranslateLinearGradient(context, (LinearGradientFill)shapeFill, opacity, internalOffset),
272+
ShapeFill.ShapeFillKind.RadialGradient => TranslateRadialGradient(context, (RadialGradientFill)shapeFill, opacity, internalOffset),
269273
_ => throw new InvalidOperationException(),
270274
};
271275
}
@@ -406,10 +410,40 @@ static CompositionColorBrush TranslateBoundSolidColor(
406410
return result;
407411
}
408412

413+
static void TranslateVector2AnimatableWithInternalOffset(
414+
LayerContext context,
415+
CompositionObject obj,
416+
string propertyName,
417+
TrimmedAnimatable<Vector2> value,
418+
Rectangles.InternalOffset offset)
419+
{
420+
// anomate source property first
421+
string sourcePropertyName = propertyName + "Source";
422+
obj.Properties.InsertVector2(sourcePropertyName, ConvertTo.Vector2(value.InitialValue));
423+
Animate.Vector2(context, value, obj, sourcePropertyName);
424+
425+
// create expression that offsets source property by internal offset
426+
WinCompData.Expressions.Vector2 expression = offset.IsAnimated ?
427+
ExpressionFactory.InternalOffsetExressionAdded(sourcePropertyName, offset.OffsetExpression!) :
428+
ExpressionFactory.InternalOffsetValueAdded(sourcePropertyName, (Sn.Vector2)offset.OffsetValue!);
429+
430+
var expressionAnimation = context.ObjectFactory.CreateExpressionAnimation(expression);
431+
expressionAnimation.SetReferenceParameter("my", obj);
432+
if (offset.IsAnimated)
433+
{
434+
// expression can use geometry
435+
expressionAnimation.SetReferenceParameter("geometry", offset.Geometry);
436+
}
437+
438+
// animate original property with expression that applies internal offset to it
439+
Animate.WithExpression(obj, expressionAnimation, propertyName);
440+
}
441+
409442
static CompositionLinearGradientBrush? TranslateLinearGradient(
410443
LayerContext context,
411444
IGradient linearGradient,
412-
CompositeOpacity opacity)
445+
CompositeOpacity opacity,
446+
Rectangles.InternalOffset? internalOffset)
413447
{
414448
var result = context.ObjectFactory.CreateLinearGradientBrush();
415449

@@ -419,7 +453,11 @@ static CompositionColorBrush TranslateBoundSolidColor(
419453
var startPoint = Optimizer.TrimAnimatable(context, linearGradient.StartPoint);
420454
var endPoint = Optimizer.TrimAnimatable(context, linearGradient.EndPoint);
421455

422-
if (startPoint.IsAnimated)
456+
if (internalOffset is not null)
457+
{
458+
TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.StartPoint), startPoint, internalOffset);
459+
}
460+
else if (startPoint.IsAnimated)
423461
{
424462
Animate.Vector2(context, startPoint, result, nameof(result.StartPoint));
425463
}
@@ -428,7 +466,11 @@ static CompositionColorBrush TranslateBoundSolidColor(
428466
result.StartPoint = ConvertTo.Vector2(startPoint.InitialValue);
429467
}
430468

431-
if (endPoint.IsAnimated)
469+
if (internalOffset is not null)
470+
{
471+
TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.EndPoint), endPoint, internalOffset);
472+
}
473+
else if (endPoint.IsAnimated)
432474
{
433475
Animate.Vector2(context, endPoint, result, nameof(result.EndPoint));
434476
}
@@ -453,13 +495,14 @@ static CompositionColorBrush TranslateBoundSolidColor(
453495
static CompositionGradientBrush? TranslateRadialGradient(
454496
LayerContext context,
455497
IRadialGradient gradient,
456-
CompositeOpacity opacity)
498+
CompositeOpacity opacity,
499+
Rectangles.InternalOffset? internalOffset)
457500
{
458501
if (!context.ObjectFactory.IsUapApiAvailable(nameof(CompositionRadialGradientBrush), versionDependentFeatureDescription: "Radial gradient fill"))
459502
{
460503
// CompositionRadialGradientBrush didn't exist until UAP v8. If the target OS doesn't support
461504
// UAP v8 then fall back to linear gradients as a compromise.
462-
return TranslateLinearGradient(context, gradient, opacity);
505+
return TranslateLinearGradient(context, gradient, opacity, internalOffset);
463506
}
464507

465508
var result = context.ObjectFactory.CreateRadialGradientBrush();
@@ -470,7 +513,11 @@ static CompositionColorBrush TranslateBoundSolidColor(
470513
var startPoint = Optimizer.TrimAnimatable(context, gradient.StartPoint);
471514
var endPoint = Optimizer.TrimAnimatable(context, gradient.EndPoint);
472515

473-
if (startPoint.IsAnimated)
516+
if (internalOffset is not null)
517+
{
518+
TranslateVector2AnimatableWithInternalOffset(context, result, nameof(result.EllipseCenter), startPoint, internalOffset);
519+
}
520+
else if (startPoint.IsAnimated)
474521
{
475522
Animate.Vector2(context, startPoint, result, nameof(result.EllipseCenter));
476523
}

source/LottieToWinComp/ExpressionFactory.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ static class ExpressionFactory
3333
internal static readonly Scalar RootProgress = RootScalar(TranslationContext.ProgressPropertyName);
3434
internal static readonly Scalar MaxTStartTEnd = Max(MyTStart, MyTEnd);
3535
internal static readonly Scalar MinTStartTEnd = Min(MyTStart, MyTEnd);
36-
static readonly Vector2 HalfMySize = MySize / Vector2(2, 2);
36+
internal static readonly Vector2 HalfMySize = MySize / Vector2(2, 2);
37+
internal static readonly Vector2 GeometryHalfSize = NamedVector2("geometry", "Size") / Vector2(2, 2);
3738
internal static readonly Color AnimatedColorWithAnimatedOpacity =
3839
ColorAsVector4MultipliedByOpacities(MyColor, new[] { MyOpacity });
3940

@@ -46,6 +47,10 @@ static class ExpressionFactory
4647
MyPosition.Y - MyAnchor.Y,
4748
0);
4849

50+
internal static Vector2 InternalOffsetExressionAdded(string property, Vector2 offsetExpression) => MyVector2(property) + offsetExpression;
51+
52+
internal static Vector2 InternalOffsetValueAdded(string property, Sn.Vector2 offsetValue) => MyVector2(property) + Vector2(offsetValue);
53+
4954
internal static Color ThemedColorMultipliedByOpacity(string bindingName, Animatables.Opacity opacity)
5055
=> ColorAsVector4MultipliedByOpacity(ThemedColor4Property(bindingName), opacity.Value);
5156

@@ -248,10 +253,14 @@ static Scalar CreateProgressExpression(ArraySegment<Segment> segments, Scalar pr
248253

249254
static Vector2 MyVector2(string propertyName) => Vector2(My(propertyName));
250255

256+
static Vector2 NamedVector2(string name, string propertyName) => Vector2(Named(name, propertyName));
257+
251258
static Vector4 MyVector4(string propertyName) => Vector4(My(propertyName));
252259

253260
static string My(string propertyName) => $"my.{propertyName}";
254261

262+
static string Named(string name, string propertyName) => $"{name}.{propertyName}";
263+
255264
// A property on the root property set. Used to bind to the property set that contains the Progress property.
256265
static string RootProperty(string propertyName) => $"{RootName}.{propertyName}";
257266

source/LottieToWinComp/Rectangles.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,41 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
1818
/// </summary>
1919
static class Rectangles
2020
{
21+
// Rectangles are implemented differently in WinComp API
22+
// and Lottie. In WinComp API coordinates inside rectangle start in
23+
// top left corner and in Lottie they start in the middle
24+
// To account for this we need to offset all the internal points
25+
// for (Rectangle.Size / 2)
26+
// This class represents this offset (static or animated)
27+
public class InternalOffset
28+
{
29+
public RectangleOrRoundedRectangleGeometry Geometry { get; }
30+
31+
// Use expression if size is animated
32+
#nullable enable
33+
public Expressions.Vector2? OffsetExpression { get; }
34+
#nullable disable
35+
36+
// Use constant value if size if static
37+
public Sn.Vector2? OffsetValue { get; }
38+
39+
public bool IsAnimated => OffsetExpression is not null;
40+
41+
public InternalOffset(RectangleOrRoundedRectangleGeometry geometry, Expressions.Vector2 expression)
42+
{
43+
Geometry = geometry;
44+
OffsetExpression = expression;
45+
OffsetValue = null;
46+
}
47+
48+
public InternalOffset(RectangleOrRoundedRectangleGeometry geometry, Sn.Vector2 value)
49+
{
50+
Geometry = geometry;
51+
OffsetExpression = null;
52+
OffsetValue = value;
53+
}
54+
}
55+
2156
// NOTES ABOUT RECTANGLE DRAWING AND CORNERS:
2257
// ==========================================
2358
// A rectangle can be thought of as having 8 components -
@@ -318,10 +353,15 @@ static void ApplyRectangleContentCommon(
318353
var height = size.InitialValue.Y;
319354
var trimOffsetDegrees = (width / (2 * (width + height))) * 360;
320355

356+
InternalOffset internalOffset = size.IsAnimated ?
357+
new InternalOffset(geometry, ExpressionFactory.GeometryHalfSize) :
358+
new InternalOffset(geometry, ConvertTo.Vector2(size.InitialValue / 2));
359+
321360
Shapes.TranslateAndApplyShapeContextWithTrimOffset(
322361
context,
323362
compositionRectangle,
324363
rectangle.DrawingDirection == DrawingDirection.Reverse,
364+
internalOffset,
325365
trimOffsetDegrees: trimOffsetDegrees);
326366

327367
compositionRectangle.SetDescription(context, () => rectangle.Name);
@@ -408,10 +448,15 @@ static void ApplyRectangleContentCommonXY(
408448
var initialHeight = height.InitialValue;
409449
var trimOffsetDegrees = (initialWidth / (2 * (initialWidth + initialHeight))) * 360;
410450

451+
InternalOffset internalOffset = width.IsAnimated || height.IsAnimated ?
452+
new InternalOffset(geometry, ExpressionFactory.GeometryHalfSize) :
453+
new InternalOffset(geometry, ConvertTo.Vector2(width.InitialValue / 2, height.InitialValue / 2));
454+
411455
Shapes.TranslateAndApplyShapeContextWithTrimOffset(
412456
context,
413457
compositionRectangle,
414458
rectangle.DrawingDirection == DrawingDirection.Reverse,
459+
internalOffset,
415460
trimOffsetDegrees: trimOffsetDegrees);
416461

417462
compositionRectangle.SetDescription(context, () => rectangle.Name);

source/LottieToWinComp/Shapes.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@ public static void TranslateAndApplyShapeContext(
3434
ShapeContext context,
3535
CompositionSpriteShape shape,
3636
bool reverseDirection) =>
37-
TranslateAndApplyShapeContextWithTrimOffset(context, shape, reverseDirection, 0);
37+
TranslateAndApplyShapeContextWithTrimOffset(context, shape, reverseDirection, null, 0);
3838

3939
public static void TranslateAndApplyShapeContextWithTrimOffset(
4040
ShapeContext context,
4141
CompositionSpriteShape shape,
4242
bool reverseDirection,
43+
Rectangles.InternalOffset? internalOffset,
4344
double trimOffsetDegrees)
4445
{
4546
Debug.Assert(shape.Geometry is not null, "Precondition");
4647

47-
shape.FillBrush = Brushes.TranslateShapeFill(context, context.Fill, context.Opacity);
48+
shape.FillBrush = Brushes.TranslateShapeFill(context, context.Fill, context.Opacity, internalOffset);
4849
Brushes.TranslateAndApplyStroke(context, context.Stroke, shape, context.Opacity);
4950

5051
TranslateAndApplyTrimPath(

source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2130,7 +2130,13 @@ void WriteCallHelperCreateSpriteShapeWithFillBrush(
21302130

21312131
// Call the helper and initialize the remaining CompositionShape properties.
21322132
WriteMatrixComment(builder, obj.TransformMatrix);
2133-
builder.WriteLine($"{ConstVar} result = CreateSpriteShape({CallFactoryFromFor(node, obj.Geometry)}, {Matrix3x2(transformMatrix)}, {CallFactoryFromFor(node, obj.FillBrush)});");
2133+
2134+
// We need to instantiate geometry first because sometimes it initializes fields
2135+
// that are used in FillBrush, but CreateSpriteShape(GetGeometry(), ..., GetFillBrush()) code
2136+
// will result in evaluating GetFillBrush() first which may cause null dereferencing
2137+
builder.WriteLine($"{ConstVar} geometry = {CallFactoryFromFor(node, obj.Geometry)};");
2138+
2139+
builder.WriteLine($"{ConstVar} result = CreateSpriteShape(geometry, {Matrix3x2(transformMatrix)}, {CallFactoryFromFor(node, obj.FillBrush)});");
21342140
InitializeCompositionObject(builder, obj, node);
21352141
WriteSetPropertyStatement(builder, nameof(obj.CenterPoint), obj.CenterPoint);
21362142
WriteSetPropertyStatement(builder, nameof(obj.Offset), obj.Offset);

0 commit comments

Comments
 (0)