Enabled blur effect for PreComp layers (#454)

This commit is contained in:
aborziak-ms 2021-07-29 20:37:29 -07:00 коммит произвёл GitHub
Родитель 64bdd18e6d
Коммит 7463321424
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 267 добавлений и 42 удалений

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

@ -436,6 +436,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
Wd.CompositionObjectType.CompositionEllipseGeometry => GetCompositionEllipseGeometry((Wd.CompositionEllipseGeometry)obj),
Wd.CompositionObjectType.CompositionGeometricClip => GetCompositionGeometricClip((Wd.CompositionGeometricClip)obj),
Wd.CompositionObjectType.CompositionLinearGradientBrush => GetCompositionLinearGradientBrush((Wd.CompositionLinearGradientBrush)obj),
Wd.CompositionObjectType.CompositionMaskBrush => GetCompositionMaskBrush((Wd.CompositionMaskBrush)obj),
Wd.CompositionObjectType.CompositionPathGeometry => GetCompositionPathGeometry((Wd.CompositionPathGeometry)obj),
Wd.CompositionObjectType.CompositionPropertySet => GetCompositionPropertySet((Wd.CompositionPropertySet)obj),
Wd.CompositionObjectType.CompositionRadialGradientBrush => GetCompositionRadialGradientBrush((Wd.CompositionRadialGradientBrush)obj),
@ -1229,6 +1230,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
return result;
}
Wc.CompositionMaskBrush GetCompositionMaskBrush(Wd.CompositionMaskBrush obj)
{
if (GetExisting<Wc.CompositionMaskBrush>(obj, out var result))
{
return result;
}
result = CacheAndInitializeCompositionObject(obj, _c.CreateMaskBrush());
if (obj.Mask is not null)
{
result.Mask = GetCompositionBrush(obj.Mask);
}
if (obj.Source is not null)
{
result.Source = GetCompositionBrush(obj.Source);
}
StartAnimations(obj, result);
return result;
}
[return: NotNullIfNotNull("obj")]
Wc.CompositionGeometry? GetCompositionGeometry(Wd.CompositionGeometry? obj)
{
@ -1429,6 +1453,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie
return GetCompositionEffectBrush((Wd.CompositionEffectBrush)obj);
case Wd.CompositionObjectType.CompositionSurfaceBrush:
return GetCompositionSurfaceBrush((Wd.CompositionSurfaceBrush)obj);
case Wd.CompositionObjectType.CompositionMaskBrush:
return GetCompositionMaskBrush((Wd.CompositionMaskBrush)obj);
case Wd.CompositionObjectType.CompositionLinearGradientBrush:
case Wd.CompositionObjectType.CompositionRadialGradientBrush:
return GetCompositionGradientBrush((Wd.CompositionGradientBrush)obj);

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

@ -255,6 +255,30 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
shortDescription);
}
/// <summary>
/// Animates a boolean value.
/// </summary>
public static void Boolean(
LayerContext context,
in TrimmedAnimatable<bool> value,
CompositionObject targetObject,
string targetPropertyName,
string? longDescription = null,
string? shortDescription = null)
{
Debug.Assert(value.IsAnimated, "Precondition");
GenericCreateCompositionKeyFrameAnimation(
context,
value,
context.ObjectFactory.CreateBooleanKeyFrameAnimation,
(ca, progress, val, easing) => ca.InsertKeyFrame(progress, val),
null,
targetObject,
targetPropertyName,
longDescription,
shortDescription);
}
/// <summary>
/// Animates a trim start or trim end value.
/// </summary>

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

@ -167,6 +167,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
return result;
}
internal CompositionMaskBrush CreateMaskBrush() => _compositor.CreateMaskBrush();
internal CompositionColorGradientStop CreateColorGradientStop() => _compositor.CreateColorGradientStop();
internal CompositionColorGradientStop CreateColorGradientStop(float offset, Color color) => _compositor.CreateColorGradientStop(offset, Color(color));

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

@ -2,11 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// DropShadows are currently disabled because of LayerVisual bugs.
//#define EnableDropShadow
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Toolkit.Uwp.UI.Lottie.Animatables;
@ -73,16 +72,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
result.Children.Add(rootNode);
}
#if EnableDropShadow
var dropShadowEffect = context.Effects.DropShadowEffect;
if (dropShadowEffect is not null)
{
result = ApplyDropShadow(context, result, dropShadowEffect);
}
#else
context.Effects.EmitIssueIfDropShadow();
#endif
var gaussianBlurEffect = context.Effects.GaussianBlurEffect;
@ -97,59 +92,84 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
/// <summary>
/// Applies the given <see cref="DropShadowEffect"/>.
/// </summary>
static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, DropShadowEffect dropShadowEffect)
static ContainerVisual ApplyDropShadow(
PreCompLayerContext context,
ContainerVisual source,
DropShadowEffect dropShadowEffect)
{
Debug.Assert(dropShadowEffect.IsEnabled, "Precondition");
// Create a LayerVisual so we can add a drop shadow.
var result = context.ObjectFactory.CreateLayerVisual();
result.Children.Add(visual);
// Shadow:
// +------------------+
// | Container Visual | -- Has the final composited result.
// +------------------+ <
// ^ Child #1 \ Child #2 (original layer)
// | (shadow layer) \
// | \
// +---------------------+ \
// | ApplyGaussianBlur() | \
// +---------------------+ +-----------------+
// ^ | ContainerVisual | - Original Visual node.
// | +-----------------+
// +----------------+ .
// | SpriteVisual | .
// +----------------+ .
// ^ Source .
// | .
// +--------------+ .
// | MaskBrush | .
// +--------------+ .
// ^ Source ^ Mask . Source
// | \ V
// +----------+ +---------------+
// |ColorBrush| | VisualSurface |
// +----------+ +---------------+
GaussianBlurEffect gaussianBlurEffect = new GaussianBlurEffect(
name: dropShadowEffect.Name + "_blur",
isEnabled: true,
blurriness: dropShadowEffect.Softness,
blurDimensions: new Animatable<Enum<BlurDimension>>(BlurDimension.HorizontalAndVertical),
repeatEdgePixels: new Animatable<bool>(true),
forceGpuRendering: true);
// TODO: Due to a Composition bug, LayerVisual currently must be given a size for the drop
// shadow to show up correctly. And even then it is not reliable.
result.Size = context.CompositionContext.Size;
var factory = context.ObjectFactory;
var size = ConvertTo.Vector2(context.Layer.Width, context.Layer.Height);
var shadow = context.ObjectFactory.CreateDropShadow();
var visualSurface = factory.CreateVisualSurface();
visualSurface.SourceSize = size;
visualSurface.SourceVisual = source;
result.Shadow = shadow;
shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
var maskBrush = factory.CreateMaskBrush();
var isShadowOnly = Optimizer.TrimAnimatable(context, dropShadowEffect.IsShadowOnly);
if (!isShadowOnly.IsAlways(true))
{
context.Issues.ShadowOnlyShadowEffect();
}
// TODO - it's not clear whether BlurRadius and Softness are equivalent. We may
// need to scale Softness to convert it to BlurRadius.
var blurRadius = Optimizer.TrimAnimatable(context, dropShadowEffect.Softness);
if (blurRadius.IsAnimated)
{
Animate.Scalar(context, blurRadius, shadow, nameof(shadow.BlurRadius));
}
else
{
shadow.BlurRadius = (float)blurRadius.InitialValue;
}
var colorBrush = factory.CreateColorBrush(dropShadowEffect.Color.InitialValue);
var color = Optimizer.TrimAnimatable(context, dropShadowEffect.Color);
if (color.IsAnimated)
{
Animate.Color(context, color, shadow, nameof(shadow.Color));
Animate.Color(context, color, colorBrush, nameof(colorBrush.Color));
}
else
{
shadow.Color = ConvertTo.Color(color.InitialValue);
colorBrush.Color = ConvertTo.Color(color.InitialValue);
}
maskBrush.Source = colorBrush;
maskBrush.Mask = factory.CreateSurfaceBrush(visualSurface);
var shadowSpriteVisual = factory.CreateSpriteVisual();
shadowSpriteVisual.Size = size;
shadowSpriteVisual.Brush = maskBrush;
var blurResult = ApplyGaussianBlur(context, shadowSpriteVisual, gaussianBlurEffect);
var opacity = Optimizer.TrimAnimatable(context, dropShadowEffect.Opacity);
if (opacity.IsAnimated)
{
Animate.Opacity(context, opacity, shadow, nameof(shadow.Opacity));
Animate.Opacity(context, opacity, blurResult, nameof(blurResult.Opacity));
}
else
{
shadow.Opacity = (float)opacity.InitialValue.Value;
blurResult.Opacity = (float)opacity.InitialValue.Value;
}
// Convert direction and distance to a Vector3.
@ -173,7 +193,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
var keyFrames = direction.KeyFrames.Select(
kf => new KeyFrame<Vector3>(kf.Frame, VectorFromRotationAndDistance(kf.Value, distanceValue), kf.Easing)).ToArray();
var directionAnimation = new TrimmedAnimatable<Vector3>(context, keyFrames[0].Value, keyFrames);
Animate.Vector3(context, directionAnimation, shadow, nameof(shadow.Offset));
Animate.Vector3(context, directionAnimation, blurResult, nameof(blurResult.Offset));
}
}
else if (distance.IsAnimated)
@ -183,7 +203,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
var keyFrames = distance.KeyFrames.Select(
kf => new KeyFrame<Vector3>(kf.Frame, VectorFromRotationAndDistance(directionRadians, kf.Value), kf.Easing)).ToArray();
var distanceAnimation = new TrimmedAnimatable<Vector3>(context, keyFrames[0].Value, keyFrames);
Animate.Vector3(context, distanceAnimation, shadow, nameof(shadow.Offset));
Animate.Vector3(context, distanceAnimation, blurResult, nameof(blurResult.Offset));
}
else
{
@ -191,7 +211,33 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
var directionRadians = direction.InitialValue.Radians;
var distanceValue = distance.InitialValue;
shadow.Offset = ConvertTo.Vector3(VectorFromRotationAndDistance(direction.InitialValue, distance.InitialValue));
blurResult.Offset = ConvertTo.Vector3(VectorFromRotationAndDistance(direction.InitialValue, distance.InitialValue));
}
var result = factory.CreateContainerVisual();
result.Size = size;
result.Children.Add(blurResult);
// Check if ShadowOnly can be true
if (!dropShadowEffect.IsShadowOnly.IsAlways(false))
{
// Check if ShadowOnly can be false
if (!dropShadowEffect.IsShadowOnly.IsAlways(true))
{
var isVisible = FlipBoolAnimatable(dropShadowEffect.IsShadowOnly); // isVisible = !isShadowOnly
source.IsVisible = isVisible.InitialValue;
if (isVisible.IsAnimated)
{
Animate.Boolean(
context,
Optimizer.TrimAnimatable(context, isVisible),
source,
nameof(blurResult.IsVisible));
}
}
result.Children.Add(source);
}
return result;
@ -200,12 +246,37 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.LottieToWinComp
static Vector3 VectorFromRotationAndDistance(Rotation direction, double distance) =>
VectorFromRotationAndDistance(direction.Radians, distance);
/// <summary>
/// Construct a 2D vector with a given rotation and length.
/// Note: In After Effects 0 degrees angle corresponds to UP direction
/// and 90 degrees angle corresponds to RIGHT direction.
/// </summary>
/// <param name="directionRadians">Rotation in radians.</param>
/// <param name="distance">Vector length.</param>
/// <returns>Vector with given parameters.</returns>
static Vector3 VectorFromRotationAndDistance(double directionRadians, double distance) =>
new Vector3(
x: Math.Sin(directionRadians) * distance,
y: Math.Cos(directionRadians) * distance,
y: -Math.Cos(directionRadians) * distance,
z: 1);
static Animatable<bool> FlipBoolAnimatable(Animatable<bool> animatable)
{
if (!animatable.IsAnimated)
{
return new Animatable<bool>(!animatable.InitialValue);
}
var keyFrames = new List<KeyFrame<bool>>();
foreach (var keyFrame in animatable.KeyFrames)
{
keyFrames.Add(new KeyFrame<bool>(keyFrame.Frame, !keyFrame.Value, keyFrame.Easing));
}
return new Animatable<bool>(!animatable.InitialValue, keyFrames);
}
/// <summary>
/// Applies a Gaussian blur effect to the given <paramref name="source"/> and
/// returns a new root. This is only designed to work on a <see cref="PreCompLayer"/>

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

@ -101,6 +101,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
graph[child].Parent = node.Object;
}
break;
case CompositionObjectType.CompositionVisualSurface:
Visual? source = ((CompositionVisualSurface)node.Object).SourceVisual;
if (source is not null)
{
graph[source].AllowCoalesing = false;
}
break;
}
}
@ -1074,7 +1082,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
// The parent may have been removed already.
let parent = n.Node.Parent
where parent is not null
where parent is not null && n.Node.AllowCoalesing
select ((ContainerVisual)parent, containerVisual)).ToArray();
// Pull the children of the container into the parent of the container. Remove the unnecessary containers.
@ -1443,6 +1451,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
sealed class Node : Graph.Node<Node>
{
internal CompositionObject? Parent { get; set; }
internal bool AllowCoalesing { get; set; } = true;
}
}
}

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

@ -142,6 +142,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
case CompositionObjectType.CompositionLinearGradientBrush:
VisitCompositionLinearGradientBrush((CompositionLinearGradientBrush)obj, node);
break;
case CompositionObjectType.CompositionMaskBrush:
VisitCompositionMaskBrush((CompositionMaskBrush)obj, node);
break;
case CompositionObjectType.CompositionPathGeometry:
VisitCompositionPathGeometry((CompositionPathGeometry)obj, node);
break;
@ -603,6 +606,23 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
return true;
}
bool VisitCompositionMaskBrush(CompositionMaskBrush obj, T node)
{
VisitCompositionBrush(obj, node);
if (obj.Mask is not null)
{
Reference(node, obj.Mask);
}
if (obj.Source is not null)
{
Reference(node, obj.Source);
}
return true;
}
bool VisitCompositionRadialGradientBrush(CompositionRadialGradientBrush obj, T node)
{
return VisitCompositionGradientBrush(obj, node);

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

@ -487,6 +487,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
CompositionObjectType.CompositionEllipseGeometry => GetCompositionEllipseGeometry((CompositionEllipseGeometry)obj),
CompositionObjectType.CompositionGeometricClip => GetCompositionGeometricClip((CompositionGeometricClip)obj),
CompositionObjectType.CompositionLinearGradientBrush => GetCompositionLinearGradientBrush((CompositionLinearGradientBrush)obj),
CompositionObjectType.CompositionMaskBrush => GetCompositionMaskBrush((CompositionMaskBrush)obj),
CompositionObjectType.CompositionPathGeometry => GetCompositionPathGeometry((CompositionPathGeometry)obj),
CompositionObjectType.CompositionPropertySet => GetCompositionPropertySet((CompositionPropertySet)obj),
CompositionObjectType.CompositionRadialGradientBrush => GetCompositionRadialGradientBrush((CompositionRadialGradientBrush)obj),
@ -1179,6 +1180,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
return result;
}
CompositionMaskBrush GetCompositionMaskBrush(CompositionMaskBrush obj)
{
if (GetExisting(obj, out var result))
{
return result;
}
result = CacheAndInitializeCompositionObject(obj, _c.CreateMaskBrush());
if (obj.Mask is not null)
{
result.Mask = GetCompositionBrush(obj.Mask);
}
if (obj.Source is not null)
{
result.Source = GetCompositionBrush(obj.Source);
}
StartAnimationsAndFreeze(obj, result);
return result;
}
[return: NotNullIfNotNull("obj")]
CompositionGeometry? GetCompositionGeometry(CompositionGeometry? obj)
{
@ -1386,6 +1410,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
return GetCompositionGradientBrush((CompositionGradientBrush)obj);
case CompositionObjectType.CompositionSurfaceBrush:
return GetCompositionSurfaceBrush((CompositionSurfaceBrush)obj);
case CompositionObjectType.CompositionMaskBrush:
return GetCompositionMaskBrush((CompositionMaskBrush)obj);
default:
throw new InvalidOperationException();
}

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

@ -68,6 +68,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
case CompositionObjectType.CompositionLinearGradientBrush:
LinearGradientBrushCount++;
break;
case CompositionObjectType.CompositionMaskBrush:
MaskBrushCount++;
break;
case CompositionObjectType.CompositionPathGeometry:
PathGeometryCount++;
break;
@ -184,6 +187,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools
public int LinearGradientBrushCount { get; }
public int MaskBrushCount { get; }
public int PathGeometryCount { get; }
public int PropertySetPropertyCount { get; }

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

@ -1672,6 +1672,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
CompositionObjectType.CompositionEllipseGeometry => GenerateCompositionEllipseGeometryFactory(builder, (CompositionEllipseGeometry)obj, node),
CompositionObjectType.CompositionGeometricClip => GenerateCompositionGeometricClipFactory(builder, (CompositionGeometricClip)obj, node),
CompositionObjectType.CompositionLinearGradientBrush => GenerateCompositionLinearGradientBrushFactory(builder, (CompositionLinearGradientBrush)obj, node),
CompositionObjectType.CompositionMaskBrush => GenerateCompositionMaskBrushFactory(builder, (CompositionMaskBrush)obj, node),
CompositionObjectType.CompositionPathGeometry => GenerateCompositionPathGeometryFactory(builder, (CompositionPathGeometry)obj, node),
// Do not generate code for property sets. It is done inline in the CompositionObject initialization.
@ -1754,6 +1755,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen
return true;
}
bool GenerateCompositionMaskBrushFactory(CodeBuilder builder, CompositionMaskBrush obj, ObjectData node)
{
WriteObjectFactoryStart(builder, node);
WriteCreateAssignment(builder, node, $"_c{Deref}CreateMaskBrush()");
InitializeCompositionBrush(builder, obj, node);
WriteSetPropertyStatement(builder, "Mask", CallFactoryFromFor(node, obj.Mask));
WriteSetPropertyStatement(builder, "Source", CallFactoryFromFor(node, obj.Source));
WriteCompositionObjectFactoryEnd(builder, obj, node);
return true;
}
bool GenerateCompositionRadialGradientBrushFactory(CodeBuilder builder, CompositionRadialGradientBrush obj, ObjectData node)
{
WriteObjectFactoryStart(builder, node);

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

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData
{
[MetaData.UapVersion(3)]
#if PUBLIC_WinCompData
public
#endif
sealed class CompositionMaskBrush : CompositionBrush
{
public CompositionBrush? Source { get; set; }
public CompositionBrush? Mask { get; set; }
internal CompositionMaskBrush()
{
}
public override CompositionObjectType Type => CompositionObjectType.CompositionMaskBrush;
}
}

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

@ -20,6 +20,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData
CompositionEllipseGeometry,
CompositionGeometricClip,
CompositionLinearGradientBrush,
CompositionMaskBrush,
CompositionPathGeometry,
CompositionPropertySet,
CompositionRadialGradientBrush,

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

@ -17,6 +17,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.WinCompData
public CompositionColorBrush CreateColorBrush() => new CompositionColorBrush();
public CompositionMaskBrush CreateMaskBrush() => new CompositionMaskBrush();
public CompositionColorBrush CreateColorBrush(Wui.Color color) => new CompositionColorBrush(color);
public CompositionColorGradientStop CreateColorGradientStop() => new CompositionColorGradientStop();

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

@ -19,6 +19,7 @@
<Compile Include="$(MSBuildThisFileDirectory)CompositionContainerShape.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionDropShadowSourcePolicy.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionEasingFunction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionMaskBrush.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionEffectBrush.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionEffectFactory.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CompositionEffectSourceParameter.cs" />