Skip to content

Commit 7463321

Browse files
authored
Enabled blur effect for PreComp layers (#454)
1 parent 64bdd18 commit 7463321

File tree

13 files changed

+267
-42
lines changed

13 files changed

+267
-42
lines changed

source/Lottie/Instantiator.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ Wc.CompositionObject GetCompositionObject(Wd.CompositionObject obj) =>
436436
Wd.CompositionObjectType.CompositionEllipseGeometry => GetCompositionEllipseGeometry((Wd.CompositionEllipseGeometry)obj),
437437
Wd.CompositionObjectType.CompositionGeometricClip => GetCompositionGeometricClip((Wd.CompositionGeometricClip)obj),
438438
Wd.CompositionObjectType.CompositionLinearGradientBrush => GetCompositionLinearGradientBrush((Wd.CompositionLinearGradientBrush)obj),
439+
Wd.CompositionObjectType.CompositionMaskBrush => GetCompositionMaskBrush((Wd.CompositionMaskBrush)obj),
439440
Wd.CompositionObjectType.CompositionPathGeometry => GetCompositionPathGeometry((Wd.CompositionPathGeometry)obj),
440441
Wd.CompositionObjectType.CompositionPropertySet => GetCompositionPropertySet((Wd.CompositionPropertySet)obj),
441442
Wd.CompositionObjectType.CompositionRadialGradientBrush => GetCompositionRadialGradientBrush((Wd.CompositionRadialGradientBrush)obj),
@@ -1229,6 +1230,29 @@ Wc.CompositionSurfaceBrush GetCompositionSurfaceBrush(Wd.CompositionSurfaceBrush
12291230
return result;
12301231
}
12311232

1233+
Wc.CompositionMaskBrush GetCompositionMaskBrush(Wd.CompositionMaskBrush obj)
1234+
{
1235+
if (GetExisting<Wc.CompositionMaskBrush>(obj, out var result))
1236+
{
1237+
return result;
1238+
}
1239+
1240+
result = CacheAndInitializeCompositionObject(obj, _c.CreateMaskBrush());
1241+
1242+
if (obj.Mask is not null)
1243+
{
1244+
result.Mask = GetCompositionBrush(obj.Mask);
1245+
}
1246+
1247+
if (obj.Source is not null)
1248+
{
1249+
result.Source = GetCompositionBrush(obj.Source);
1250+
}
1251+
1252+
StartAnimations(obj, result);
1253+
return result;
1254+
}
1255+
12321256
[return: NotNullIfNotNull("obj")]
12331257
Wc.CompositionGeometry? GetCompositionGeometry(Wd.CompositionGeometry? obj)
12341258
{
@@ -1429,6 +1453,8 @@ Wc.CompositionBrush GetCompositionBrush(Wd.CompositionBrush obj)
14291453
return GetCompositionEffectBrush((Wd.CompositionEffectBrush)obj);
14301454
case Wd.CompositionObjectType.CompositionSurfaceBrush:
14311455
return GetCompositionSurfaceBrush((Wd.CompositionSurfaceBrush)obj);
1456+
case Wd.CompositionObjectType.CompositionMaskBrush:
1457+
return GetCompositionMaskBrush((Wd.CompositionMaskBrush)obj);
14321458
case Wd.CompositionObjectType.CompositionLinearGradientBrush:
14331459
case Wd.CompositionObjectType.CompositionRadialGradientBrush:
14341460
return GetCompositionGradientBrush((Wd.CompositionGradientBrush)obj);

source/LottieToWinComp/Animate.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,30 @@ public static void ScaledScalar(
255255
shortDescription);
256256
}
257257

258+
/// <summary>
259+
/// Animates a boolean value.
260+
/// </summary>
261+
public static void Boolean(
262+
LayerContext context,
263+
in TrimmedAnimatable<bool> value,
264+
CompositionObject targetObject,
265+
string targetPropertyName,
266+
string? longDescription = null,
267+
string? shortDescription = null)
268+
{
269+
Debug.Assert(value.IsAnimated, "Precondition");
270+
GenericCreateCompositionKeyFrameAnimation(
271+
context,
272+
value,
273+
context.ObjectFactory.CreateBooleanKeyFrameAnimation,
274+
(ca, progress, val, easing) => ca.InsertKeyFrame(progress, val),
275+
null,
276+
targetObject,
277+
targetPropertyName,
278+
longDescription,
279+
shortDescription);
280+
}
281+
258282
/// <summary>
259283
/// Animates a trim start or trim end value.
260284
/// </summary>

source/LottieToWinComp/CompositionObjectFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ internal CompositionColorBrush CreateNonAnimatedColorBrush(Color color)
167167
return result;
168168
}
169169

170+
internal CompositionMaskBrush CreateMaskBrush() => _compositor.CreateMaskBrush();
171+
170172
internal CompositionColorGradientStop CreateColorGradientStop() => _compositor.CreateColorGradientStop();
171173

172174
internal CompositionColorGradientStop CreateColorGradientStop(float offset, Color color) => _compositor.CreateColorGradientStop(offset, Color(color));

source/LottieToWinComp/PreComps.cs

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
// DropShadows are currently disabled because of LayerVisual bugs.
6-
//#define EnableDropShadow
75
#nullable enable
86

97
using System;
8+
using System.Collections.Generic;
109
using System.Diagnostics;
1110
using System.Linq;
1211
using Microsoft.Toolkit.Uwp.UI.Lottie.Animatables;
@@ -73,16 +72,12 @@ static class PreComps
7372
result.Children.Add(rootNode);
7473
}
7574

76-
#if EnableDropShadow
7775
var dropShadowEffect = context.Effects.DropShadowEffect;
7876

7977
if (dropShadowEffect is not null)
8078
{
8179
result = ApplyDropShadow(context, result, dropShadowEffect);
8280
}
83-
#else
84-
context.Effects.EmitIssueIfDropShadow();
85-
#endif
8681

8782
var gaussianBlurEffect = context.Effects.GaussianBlurEffect;
8883

@@ -97,59 +92,84 @@ static class PreComps
9792
/// <summary>
9893
/// Applies the given <see cref="DropShadowEffect"/>.
9994
/// </summary>
100-
static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, DropShadowEffect dropShadowEffect)
95+
static ContainerVisual ApplyDropShadow(
96+
PreCompLayerContext context,
97+
ContainerVisual source,
98+
DropShadowEffect dropShadowEffect)
10199
{
102100
Debug.Assert(dropShadowEffect.IsEnabled, "Precondition");
103101

104-
// Create a LayerVisual so we can add a drop shadow.
105-
var result = context.ObjectFactory.CreateLayerVisual();
106-
result.Children.Add(visual);
102+
// Shadow:
103+
// +------------------+
104+
// | Container Visual | -- Has the final composited result.
105+
// +------------------+ <
106+
// ^ Child #1 \ Child #2 (original layer)
107+
// | (shadow layer) \
108+
// | \
109+
// +---------------------+ \
110+
// | ApplyGaussianBlur() | \
111+
// +---------------------+ +-----------------+
112+
// ^ | ContainerVisual | - Original Visual node.
113+
// | +-----------------+
114+
// +----------------+ .
115+
// | SpriteVisual | .
116+
// +----------------+ .
117+
// ^ Source .
118+
// | .
119+
// +--------------+ .
120+
// | MaskBrush | .
121+
// +--------------+ .
122+
// ^ Source ^ Mask . Source
123+
// | \ V
124+
// +----------+ +---------------+
125+
// |ColorBrush| | VisualSurface |
126+
// +----------+ +---------------+
127+
GaussianBlurEffect gaussianBlurEffect = new GaussianBlurEffect(
128+
name: dropShadowEffect.Name + "_blur",
129+
isEnabled: true,
130+
blurriness: dropShadowEffect.Softness,
131+
blurDimensions: new Animatable<Enum<BlurDimension>>(BlurDimension.HorizontalAndVertical),
132+
repeatEdgePixels: new Animatable<bool>(true),
133+
forceGpuRendering: true);
107134

108-
// TODO: Due to a Composition bug, LayerVisual currently must be given a size for the drop
109-
// shadow to show up correctly. And even then it is not reliable.
110-
result.Size = context.CompositionContext.Size;
111-
112-
var shadow = context.ObjectFactory.CreateDropShadow();
135+
var factory = context.ObjectFactory;
136+
var size = ConvertTo.Vector2(context.Layer.Width, context.Layer.Height);
113137

114-
result.Shadow = shadow;
115-
shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
138+
var visualSurface = factory.CreateVisualSurface();
139+
visualSurface.SourceSize = size;
140+
visualSurface.SourceVisual = source;
116141

117-
var isShadowOnly = Optimizer.TrimAnimatable(context, dropShadowEffect.IsShadowOnly);
118-
if (!isShadowOnly.IsAlways(true))
119-
{
120-
context.Issues.ShadowOnlyShadowEffect();
121-
}
142+
var maskBrush = factory.CreateMaskBrush();
122143

123-
// TODO - it's not clear whether BlurRadius and Softness are equivalent. We may
124-
// need to scale Softness to convert it to BlurRadius.
125-
var blurRadius = Optimizer.TrimAnimatable(context, dropShadowEffect.Softness);
126-
if (blurRadius.IsAnimated)
127-
{
128-
Animate.Scalar(context, blurRadius, shadow, nameof(shadow.BlurRadius));
129-
}
130-
else
131-
{
132-
shadow.BlurRadius = (float)blurRadius.InitialValue;
133-
}
144+
var colorBrush = factory.CreateColorBrush(dropShadowEffect.Color.InitialValue);
134145

135146
var color = Optimizer.TrimAnimatable(context, dropShadowEffect.Color);
136147
if (color.IsAnimated)
137148
{
138-
Animate.Color(context, color, shadow, nameof(shadow.Color));
149+
Animate.Color(context, color, colorBrush, nameof(colorBrush.Color));
139150
}
140151
else
141152
{
142-
shadow.Color = ConvertTo.Color(color.InitialValue);
153+
colorBrush.Color = ConvertTo.Color(color.InitialValue);
143154
}
144155

156+
maskBrush.Source = colorBrush;
157+
maskBrush.Mask = factory.CreateSurfaceBrush(visualSurface);
158+
159+
var shadowSpriteVisual = factory.CreateSpriteVisual();
160+
shadowSpriteVisual.Size = size;
161+
shadowSpriteVisual.Brush = maskBrush;
162+
163+
var blurResult = ApplyGaussianBlur(context, shadowSpriteVisual, gaussianBlurEffect);
164+
145165
var opacity = Optimizer.TrimAnimatable(context, dropShadowEffect.Opacity);
146166
if (opacity.IsAnimated)
147167
{
148-
Animate.Opacity(context, opacity, shadow, nameof(shadow.Opacity));
168+
Animate.Opacity(context, opacity, blurResult, nameof(blurResult.Opacity));
149169
}
150170
else
151171
{
152-
shadow.Opacity = (float)opacity.InitialValue.Value;
172+
blurResult.Opacity = (float)opacity.InitialValue.Value;
153173
}
154174

155175
// Convert direction and distance to a Vector3.
@@ -173,7 +193,7 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
173193
var keyFrames = direction.KeyFrames.Select(
174194
kf => new KeyFrame<Vector3>(kf.Frame, VectorFromRotationAndDistance(kf.Value, distanceValue), kf.Easing)).ToArray();
175195
var directionAnimation = new TrimmedAnimatable<Vector3>(context, keyFrames[0].Value, keyFrames);
176-
Animate.Vector3(context, directionAnimation, shadow, nameof(shadow.Offset));
196+
Animate.Vector3(context, directionAnimation, blurResult, nameof(blurResult.Offset));
177197
}
178198
}
179199
else if (distance.IsAnimated)
@@ -183,15 +203,41 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
183203
var keyFrames = distance.KeyFrames.Select(
184204
kf => new KeyFrame<Vector3>(kf.Frame, VectorFromRotationAndDistance(directionRadians, kf.Value), kf.Easing)).ToArray();
185205
var distanceAnimation = new TrimmedAnimatable<Vector3>(context, keyFrames[0].Value, keyFrames);
186-
Animate.Vector3(context, distanceAnimation, shadow, nameof(shadow.Offset));
206+
Animate.Vector3(context, distanceAnimation, blurResult, nameof(blurResult.Offset));
187207
}
188208
else
189209
{
190210
// Direction and distance are both not animated.
191211
var directionRadians = direction.InitialValue.Radians;
192212
var distanceValue = distance.InitialValue;
193213

194-
shadow.Offset = ConvertTo.Vector3(VectorFromRotationAndDistance(direction.InitialValue, distance.InitialValue));
214+
blurResult.Offset = ConvertTo.Vector3(VectorFromRotationAndDistance(direction.InitialValue, distance.InitialValue));
215+
}
216+
217+
var result = factory.CreateContainerVisual();
218+
result.Size = size;
219+
result.Children.Add(blurResult);
220+
221+
// Check if ShadowOnly can be true
222+
if (!dropShadowEffect.IsShadowOnly.IsAlways(false))
223+
{
224+
// Check if ShadowOnly can be false
225+
if (!dropShadowEffect.IsShadowOnly.IsAlways(true))
226+
{
227+
var isVisible = FlipBoolAnimatable(dropShadowEffect.IsShadowOnly); // isVisible = !isShadowOnly
228+
229+
source.IsVisible = isVisible.InitialValue;
230+
if (isVisible.IsAnimated)
231+
{
232+
Animate.Boolean(
233+
context,
234+
Optimizer.TrimAnimatable(context, isVisible),
235+
source,
236+
nameof(blurResult.IsVisible));
237+
}
238+
}
239+
240+
result.Children.Add(source);
195241
}
196242

197243
return result;
@@ -200,12 +246,37 @@ static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, D
200246
static Vector3 VectorFromRotationAndDistance(Rotation direction, double distance) =>
201247
VectorFromRotationAndDistance(direction.Radians, distance);
202248

249+
/// <summary>
250+
/// Construct a 2D vector with a given rotation and length.
251+
/// Note: In After Effects 0 degrees angle corresponds to UP direction
252+
/// and 90 degrees angle corresponds to RIGHT direction.
253+
/// </summary>
254+
/// <param name="directionRadians">Rotation in radians.</param>
255+
/// <param name="distance">Vector length.</param>
256+
/// <returns>Vector with given parameters.</returns>
203257
static Vector3 VectorFromRotationAndDistance(double directionRadians, double distance) =>
204258
new Vector3(
205259
x: Math.Sin(directionRadians) * distance,
206-
y: Math.Cos(directionRadians) * distance,
260+
y: -Math.Cos(directionRadians) * distance,
207261
z: 1);
208262

263+
static Animatable<bool> FlipBoolAnimatable(Animatable<bool> animatable)
264+
{
265+
if (!animatable.IsAnimated)
266+
{
267+
return new Animatable<bool>(!animatable.InitialValue);
268+
}
269+
270+
var keyFrames = new List<KeyFrame<bool>>();
271+
272+
foreach (var keyFrame in animatable.KeyFrames)
273+
{
274+
keyFrames.Add(new KeyFrame<bool>(keyFrame.Frame, !keyFrame.Value, keyFrame.Easing));
275+
}
276+
277+
return new Animatable<bool>(!animatable.InitialValue, keyFrames);
278+
}
279+
209280
/// <summary>
210281
/// Applies a Gaussian blur effect to the given <paramref name="source"/> and
211282
/// returns a new root. This is only designed to work on a <see cref="PreCompLayer"/>

source/UIData/Tools/GraphCompactor.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ void Compact(ObjectGraph<Node> graph)
101101
graph[child].Parent = node.Object;
102102
}
103103

104+
break;
105+
case CompositionObjectType.CompositionVisualSurface:
106+
Visual? source = ((CompositionVisualSurface)node.Object).SourceVisual;
107+
if (source is not null)
108+
{
109+
graph[source].AllowCoalesing = false;
110+
}
111+
104112
break;
105113
}
106114
}
@@ -1074,7 +1082,7 @@ where GetNonDefaultVisualProperties(containerVisual) == PropertyId.None
10741082

10751083
// The parent may have been removed already.
10761084
let parent = n.Node.Parent
1077-
where parent is not null
1085+
where parent is not null && n.Node.AllowCoalesing
10781086
select ((ContainerVisual)parent, containerVisual)).ToArray();
10791087

10801088
// Pull the children of the container into the parent of the container. Remove the unnecessary containers.
@@ -1443,6 +1451,8 @@ void CopyDescriptions(IDescribable from, IDescribable to)
14431451
sealed class Node : Graph.Node<Node>
14441452
{
14451453
internal CompositionObject? Parent { get; set; }
1454+
1455+
internal bool AllowCoalesing { get; set; } = true;
14461456
}
14471457
}
14481458
}

source/UIData/Tools/ObjectGraph.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ sealed class ObjectGraph<T> : Graph
142142
case CompositionObjectType.CompositionLinearGradientBrush:
143143
VisitCompositionLinearGradientBrush((CompositionLinearGradientBrush)obj, node);
144144
break;
145+
case CompositionObjectType.CompositionMaskBrush:
146+
VisitCompositionMaskBrush((CompositionMaskBrush)obj, node);
147+
break;
145148
case CompositionObjectType.CompositionPathGeometry:
146149
VisitCompositionPathGeometry((CompositionPathGeometry)obj, node);
147150
break;
@@ -603,6 +606,23 @@ bool VisitCompositionPathGeometry(CompositionPathGeometry obj, T node)
603606
return true;
604607
}
605608

609+
bool VisitCompositionMaskBrush(CompositionMaskBrush obj, T node)
610+
{
611+
VisitCompositionBrush(obj, node);
612+
613+
if (obj.Mask is not null)
614+
{
615+
Reference(node, obj.Mask);
616+
}
617+
618+
if (obj.Source is not null)
619+
{
620+
Reference(node, obj.Source);
621+
}
622+
623+
return true;
624+
}
625+
606626
bool VisitCompositionRadialGradientBrush(CompositionRadialGradientBrush obj, T node)
607627
{
608628
return VisitCompositionGradientBrush(obj, node);

0 commit comments

Comments
 (0)