Пример #1
0
        public static Sn.Matrix3x2 CreateMatrixFromTransform(LayerContext context, Transform transform)
        {
            if (transform is null)
            {
                return(Sn.Matrix3x2.Identity);
            }

            if (transform.IsAnimated)
            {
                // TODO - report an issue. We can't handle an animated transform.
                // TODO - we could handle it if the only thing that is animated is the Opacity.
            }

            var anchor   = ConvertTo.Vector2(transform.Anchor.InitialValue);
            var position = ConvertTo.Vector2(transform.Position.InitialValue);
            var scale    = ConvertTo.Vector2(transform.ScalePercent.InitialValue / 100.0);
            var rotation = (float)transform.Rotation.InitialValue.Radians;

            // Calculate the matrix that is equivalent to the properties.
            var combinedMatrix =
                Sn.Matrix3x2.CreateScale(scale, anchor) *
                Sn.Matrix3x2.CreateRotation(rotation, anchor) *
                Sn.Matrix3x2.CreateTranslation(position + anchor);

            return(combinedMatrix);
        }
Пример #2
0
        internal void UpdateOpacityFromTransform(LayerContext context, Transform transform)
        {
            if (transform is null)
            {
                return;
            }

            Opacity = Opacity.ComposedWith(Optimizer.TrimAnimatable(context, transform.Opacity));
        }
Пример #3
0
        public static Path OptimizePath(LayerContext context, Path path)
        {
            // Optimize the path data. This may result in a previously animated path
            // becoming non-animated.
            var optimizedPathData = TrimAnimatable(context, path.Data);

            return(path.CloneWithNewGeometry(
                       optimizedPathData.IsAnimated
                    ? new Animatable <PathGeometry>(optimizedPathData.KeyFrames)
                    : new Animatable <PathGeometry>(optimizedPathData.InitialValue)));
        }
Пример #4
0
        internal Effects(LayerContext context)
        {
            _context = context;

            // Validate the effects, and save the valid ones for use by the layer translator.
            foreach (var effect in context.Layer.Effects.Where(e => e.IsEnabled))
            {
                switch (effect.Type)
                {
                case Effect.EffectType.DropShadow:
                    if (DropShadowEffect is not null)
                    {
                        // Emit an issue about there being more than one.
                        context.Issues.RepeatedLayerEffect("Drop shadow");
                    }

                    DropShadowEffect = (DropShadowEffect)effect;
                    break;

                case Effect.EffectType.GaussianBlur:
                    var gaussianBlurEffect = (GaussianBlurEffect)effect;

                    // Ignore if the effect has no blurriness. It is effectively disabled.
                    var trimmedBlurriness = Optimizer.TrimAnimatable(context, gaussianBlurEffect.Blurriness);
                    if (!trimmedBlurriness.IsAlways(0))
                    {
                        if (GaussianBlurEffect is not null)
                        {
                            // Emit an issue about there being more than one.
                            context.Issues.RepeatedLayerEffect("Gaussian blur");
                        }

                        GaussianBlurEffect = gaussianBlurEffect;
                    }

                    break;

                default:
                    EmitIssueAboutUnsupportedEffect(effect.Type.ToString());
                    break;
                }
            }
        }
Пример #5
0
        public static void EnsureColorThemePropertyExists(LayerContext context, string bindingName, string displayName, Color defaultValue)
        {
            var defaultValueAsWinUIColor = ConvertTo.Color(defaultValue);
            var defaultValueAsVector4    = ConvertTo.Vector4(defaultValueAsWinUIColor);
            var themePropertySet         = GetThemePropertySet(context);

            // Insert a property set value for the scalar if one hasn't yet been added.
            switch (themePropertySet.TryGetVector4(bindingName, out var existingColorAsVector4))
            {
            case CompositionGetValueStatus.NotFound:
                // The property hasn't been added yet. Add it.
                themePropertySet.InsertVector4(bindingName, ConvertTo.Vector4(defaultValueAsWinUIColor));
                context.Translation.PropertyBindings.AddPropertyBinding(new CompMetadata.PropertyBinding(
                                                                            bindingName: bindingName,
                                                                            displayName: displayName,
                                                                            actualType: PropertySetValueType.Vector4,
                                                                            exposedType: PropertySetValueType.Color,
                                                                            defaultValue: defaultValueAsWinUIColor));
                break;

            case CompositionGetValueStatus.Succeeded:
                // The property has already been added.
                var existingValue = ConvertTo.Color(ConvertTo.Color(existingColorAsVector4 !.Value));

                if (defaultValueAsVector4 != existingColorAsVector4)
                {
                    context.Issues.ThemePropertyValuesAreInconsistent(
                        bindingName,
                        context.CompositionContext.Path,
                        existingValue.ToString(),
                        ConvertTo.Color(ConvertTo.Color(defaultValueAsVector4)).ToString());
                }

                break;

            case CompositionGetValueStatus.TypeMismatch:
            default:
                throw new InvalidOperationException();
            }
        }
Пример #6
0
        public static TrimmedAnimatable <T> TrimAnimatable <T>(LayerContext context, Animatable <T> animatable)
            where T : IEquatable <T>
        {
            if (animatable.IsAnimated)
            {
                var trimmedKeyFrames = LottieOptimizer.RemoveRedundantKeyFrames(
                    LottieOptimizer.TrimKeyFrames(
                        animatable,
                        context.CompositionContext.StartTime,
                        context.CompositionContext.EndTime));

                return(new TrimmedAnimatable <T>(
                           context,
                           trimmedKeyFrames.Count == 0
                        ? animatable.InitialValue
                        : trimmedKeyFrames[0].Value,
                           trimmedKeyFrames));
            }
            else
            {
                return(new TrimmedAnimatable <T>(context, animatable.InitialValue, animatable.KeyFrames));
            }
        }
Пример #7
0
 public static void ScalarPropertySetValue(
     LayerContext context,
     in TrimmedAnimatable <double> value,
Пример #8
0
        public static bool TryCreateShapeVisualTransformChain(
            LayerContext context,
            out ContainerVisual rootNode,
            out CompositionContainerShape contentsNode)
        {
            // Create containers for the contents in the layer.
            // The rootNode is the root for the layer.
            //
            //     +---------------+
            //     |   rootNode    |-- Root node, optionally with opacity animation for the layer.
            //     +---------------+
            //            ^
            //            |
            //     +-----------------+
            //     |  visiblityNode  |-- Optional visiblity node (only used if the visiblity is animated).
            //     +-----------------+
            //            ^
            //            |
            //     +-----------------+
            //     |  opacityNode    |-- Optional opacity node.
            //     +-----------------+
            //            ^
            //            |
            //     +-----------------+
            //     |   ShapeVisual   |-- Start of the shape tree.
            //     +-----------------+
            //            ^
            //            |
            //     +-------------------+
            //     | rootTransformNode |--Transform without opacity (inherited from root ancestor of the transform tree).
            //     +-------------------+
            //            ^
            //            |
            //     + - - - - - - - - - - - - +
            //     | other transforms nodes  |--Transform without opacity (inherited from the transform tree).
            //     + - - - - - - - - - - - - +
            //            ^
            //            |
            //     +-------------------+
            //     | leafTransformNode |--Transform without opacity defined on the layer.
            //     +-------------------+
            //        ^        ^
            //        |        |
            // +---------+ +---------+
            // | content | | content | ...
            // +---------+ +---------+
            //

            // Get the opacity of the layer.
            var layerOpacity = Optimizer.TrimAnimatable(context, context.Layer.Transform.Opacity);

            // Convert the layer's in point and out point into absolute progress (0..1) values.
            var inProgress  = context.InPointAsProgress;
            var outProgress = context.OutPointAsProgress;

            if (inProgress > 1 || outProgress <= 0 || inProgress >= outProgress || layerOpacity.IsAlways(LottieData.Opacity.Transparent))
            {
                // The layer is never visible. Don't create anything.
                rootNode     = null;
                contentsNode = null;
                return(false);
            }

            rootNode = context.ObjectFactory.CreateContainerVisual();
            ContainerVisual contentsVisual = rootNode;

            // Implement opacity for the layer.
            InsertOpacityVisualIntoTransformChain(context, layerOpacity, ref rootNode);

            // Implement visibility for the layer.
            InsertVisibilityVisualIntoTransformChain(context, inProgress, outProgress, ref rootNode);

            // Create the transforms chain.
            TranslateTransformOnContainerShapeForLayer(context, context.Layer, out var transformsRoot, out contentsNode);

            // Create the shape visual.
            var shapeVisual = context.ObjectFactory.CreateShapeVisualWithChild(transformsRoot, context.CompositionContext.Size);

            shapeVisual.SetDescription(context, () => $"Shape tree root for layer: {context.Layer.Name}");

            contentsVisual.Children.Add(shapeVisual);

            return(true);
        }
Пример #9
0
 static void InsertOpacityVisualIntoTransformChain(
     LayerContext context,
     in TrimmedAnimatable <Opacity> opacity,
Пример #10
0
        public static bool TryCreateContainerVisualTransformChain(
            LayerContext context,
            out ContainerVisual rootNode,
            out ContainerVisual contentsNode)
        {
            // Create containers for the contents in the layer.
            // The rootTransformNode is the root for the layer. It may be the same object
            // as the contentsNode if there are no inherited transforms.
            //
            //     +---------------+
            //     |      ...      |
            //     +---------------+
            //            ^
            //            |
            //     +-----------------+
            //     |  visiblityNode  |-- Optional visiblity node (only used if the visiblity is animated)
            //     +-----------------+
            //            ^
            //            |
            //     +-------------------+
            //     | rootTransformNode |--Transform (values are inherited from root ancestor of the transform tree)
            //     +-------------------+
            //            ^
            //            |
            //     + - - - - - - - - - - - - +
            //     | other transforms nodes  |--Transform (values inherited from the transform tree)
            //     + - - - - - - - - - - - - +
            //            ^
            //            |
            //     +---------------+
            //     | contentsNode  |--Transform defined on the layer
            //     +---------------+
            //        ^        ^
            //        |        |
            // +---------+ +---------+
            // | content | | content | ...
            // +---------+ +---------+
            //

            // Get the opacity of the layer.
            var layerOpacity = Optimizer.TrimAnimatable(context, context.Layer.Transform.Opacity);

            // Convert the layer's in point and out point into absolute progress (0..1) values.
            var inProgress  = context.InPointAsProgress;
            var outProgress = context.OutPointAsProgress;

            if (inProgress > 1 || outProgress <= 0 || inProgress >= outProgress || layerOpacity.IsAlways(LottieData.Opacity.Transparent))
            {
                // The layer is never visible. Don't create anything.
                rootNode     = null;
                contentsNode = null;
                return(false);
            }

            // Create the transforms chain.
            TranslateTransformOnContainerVisualForLayer(context, context.Layer, out rootNode, out contentsNode);

            // Implement opacity for the layer.
            InsertOpacityVisualIntoTransformChain(context, layerOpacity, ref rootNode);

            // Implement visibility for the layer.
            InsertVisibilityVisualIntoTransformChain(context, inProgress, outProgress, ref rootNode);

            return(true);
        }
Пример #11
0
        public static bool TryCreateContainerShapeTransformChain(
            LayerContext context,
            out CompositionContainerShape rootNode,
            out CompositionContainerShape contentsNode)
        {
            // Create containers for the contents in the layer.
            // The rootNode is the root for the layer. It may be the same object
            // as the contentsNode if there are no inherited transforms and no visibility animation.
            //
            //     +---------------+
            //     |      ...      |
            //     +---------------+
            //            ^
            //            |
            //     +-----------------+
            //     |  visiblityNode  |-- Optional visiblity node (only used if the visiblity is animated)
            //     +-----------------+
            //            ^
            //            |
            //     +-------------------+
            //     | rootTransformNode |--Transform (values are inherited from root ancestor of the transform tree)
            //     +-------------------+
            //            ^
            //            |
            //     + - - - - - - - - - - - - +
            //     | other transforms nodes  |--Transform (values inherited from the transform tree)
            //     + - - - - - - - - - - - - +
            //            ^
            //            |
            //     +-------------------+
            //     | leafTransformNode |--Transform defined on the layer
            //     +-------------------+
            //        ^        ^
            //        |        |
            // +---------+ +---------+
            // | content | | content | ...
            // +---------+ +---------+
            //

            // Get the opacity of the layer.
            var layerOpacity = Optimizer.TrimAnimatable(context, context.Layer.Transform.Opacity);

            // Convert the layer's in point and out point into absolute progress (0..1) values.
            var inProgress  = context.InPointAsProgress;
            var outProgress = context.OutPointAsProgress;

            if (inProgress > 1 || outProgress <= 0 || inProgress >= outProgress || layerOpacity.IsAlways(LottieData.Opacity.Transparent))
            {
                // The layer is never visible. Don't create anything.
                rootNode     = null;
                contentsNode = null;
                return(false);
            }

            // Create the transforms chain.
            TranslateTransformOnContainerShapeForLayer(context, context.Layer, out var transformsRoot, out contentsNode);

            // Implement the Visibility for the layer. Only needed if the layer becomes visible after
            // the LottieComposition's in point, or it becomes invisible before the LottieComposition's out point.
            if (inProgress > 0 || outProgress < 1)
            {
                // Create a node to control visibility.
                var visibilityNode = context.ObjectFactory.CreateContainerShape();
                visibilityNode.Shapes.Add(transformsRoot);
                rootNode = visibilityNode;

                visibilityNode.SetDescription(context, () => $"Layer: {context.Layer.Name}");

                // Animate between Scale(0,0) and Scale(1,1).
                var visibilityAnimation = context.ObjectFactory.CreateVector2KeyFrameAnimation();

                visibilityAnimation.SetName("ShapeVisibilityAnimation");

                if (inProgress > 0)
                {
                    // Set initial value to be non-visible (default is visible).
                    visibilityNode.Scale = Sn.Vector2.Zero;
                    visibilityAnimation.InsertKeyFrame(inProgress, Sn.Vector2.One, context.ObjectFactory.CreateHoldThenStepEasingFunction());
                }

                if (outProgress < 1)
                {
                    visibilityAnimation.InsertKeyFrame(outProgress, Sn.Vector2.Zero, context.ObjectFactory.CreateHoldThenStepEasingFunction());
                }

                visibilityAnimation.Duration = context.Translation.LottieComposition.Duration;
                Animate.WithKeyFrame(context, visibilityNode, nameof(visibilityNode.Scale), visibilityAnimation);
            }
            else
            {
                rootNode = transformsRoot;
            }

            return(true);
        }
Пример #12
0
 internal TrimmedAnimatable(LayerContext context, T initialValue, IReadOnlyList <KeyFrame <T> > keyFrames)
 {
     Context      = context;
     InitialValue = initialValue;
     _keyFrames   = keyFrames;
 }
Пример #13
0
 internal TrimmedAnimatable(LayerContext context, T initialValue)
 {
     Context      = context;
     InitialValue = initialValue;
     _keyFrames   = Array.Empty <KeyFrame <T> >();
 }
Пример #14
0
 public static TrimmedAnimatable <Vector3> TrimAnimatable(LayerContext context, IAnimatableVector3 animatable)
 => TrimAnimatable <Vector3>(context, (AnimatableVector3)animatable);
Пример #15
0
 public static CompositionSpriteShape TranslatePath(
     LayerContext context,
     in TrimmedAnimatable <PathGeometry> path,
Пример #16
0
 internal FrameNumberEqualityComparer(LayerContext context)
 {
     _context = context;
 }
Пример #17
0
 public static CompositionColorBrush CreateAnimatedColorBrush(LayerContext context, Color color, in TrimmedAnimatable <Opacity> opacity)
Пример #18
0
        public static Visual ApplyGaussianBlur(
            LayerContext context,
            Visual source,
            GaussianBlurEffect gaussianBlurEffect)
        {
            Debug.Assert(gaussianBlurEffect.IsEnabled, "Precondition");
            Debug.Assert(context is PreCompLayerContext || context is ShapeLayerContext, "Precondition");

            var factory = context.ObjectFactory;

            if (!factory.IsUapApiAvailable(nameof(CompositionVisualSurface), versionDependentFeatureDescription: "Gaussian blur"))
            {
                // The effect can't be displayed on the targeted version.
                return(source);
            }

            // Gaussian blur:
            // +--------------+
            // | SpriteVisual | -- Has the final composited result.
            // +--------------+
            //     ^
            //     |
            // +--------------+
            // | EffectBrush  | -- Composition effect brush allows the composite effect result to be used as a brush.
            // +--------------+
            //     ^
            //     |
            // +--------------------+
            // | GaussianBlurEffect |
            // +--------------------+
            //     ^ Source
            //     |
            // +--------------+
            // | SurfaceBrush | -- Surface brush that will paint with the output of the VisualSurface
            // +--------------+    that has the source visual assigned to it.
            //      ^ CompositionEffectSourceParameter("source")
            //      |
            // +---------------+
            // | VisualSurface | -- The visual surface captures the renderable contents of its source visual.
            // +---------------+
            //      ^
            //      |
            //  +--------+
            //  | Visual | -- The layer translated to a Visual.
            //  +--------+
            var size = context.CompositionContext.Size;

            if (context is PreCompLayerContext)
            {
                size = ConvertTo.Vector2(((PreCompLayerContext)context).Layer.Width, ((PreCompLayerContext)context).Layer.Height);
            }

            // Build from the bottom up.
            var visualSurface = factory.CreateVisualSurface();

            visualSurface.SourceVisual = source;
            visualSurface.SourceSize   = size;

            var surfaceBrush = factory.CreateSurfaceBrush(visualSurface);

            var effect = new WinCompData.Mgce.GaussianBlurEffect();

            var blurriness = Optimizer.TrimAnimatable(context, gaussianBlurEffect.Blurriness);

            if (blurriness.IsAnimated)
            {
                context.Issues.AnimatedLayerEffectParameters("Gaussian blur");
            }

            effect.BlurAmount = ConvertTo.Float(blurriness.InitialValue / 10.0);

            // We only support HorizontalAndVertical blur dimension.
            var blurDimensions            = Optimizer.TrimAnimatable(context, gaussianBlurEffect.BlurDimensions);
            var unsupportedBlurDimensions = blurDimensions
                                            .KeyFrames
                                            .Select(kf => kf.Value)
                                            .Distinct()
                                            .Where(v => v.Value != BlurDimension.HorizontalAndVertical).ToArray();

            foreach (var value in unsupportedBlurDimensions)
            {
                context.Issues.UnsupportedLayerEffectParameter("gaussian blur", "blur dimension", value.Value.ToString());
            }

            effect.Source = new CompositionEffectSourceParameter("source");

            var effectBrush = factory.CreateEffectFactory(effect).CreateBrush();

            effectBrush.SetSourceParameter("source", surfaceBrush);

            var result = factory.CreateSpriteVisual();

            result.Brush = effectBrush;
            result.Size  = size;

            return(result);
        }
Пример #19
0
        // Translate a mask into shapes for a shape visual. The mask is applied to the visual to be masked
        // using the VisualSurface. The VisualSurface can take the rendered contents of a visual tree and
        // use it as a brush. The final masked result is achieved by taking the visual to be masked, putting
        // it into a VisualSurface, then taking the mask and putting that in a VisualSurface and then combining
        // the result with a composite effect.
        public static Visual TranslateAndApplyMasksForLayer(
            LayerContext context,
            Visual visualToMask)
        {
            var result = visualToMask;
            var layer  = context.Layer;

            if (layer.Masks.Count > 0)
            {
                if (layer.Masks.Count == 1)
                {
                    // Common case for masks: exactly one mask.
                    var masks = layer.Masks.Slice(0, 1);

                    switch (masks[0].Mode)
                    {
                    // If there's only 1 mask, Difference and Intersect act the same as Add.
                    case Mask.MaskMode.Add:
                    case Mask.MaskMode.Difference:
                    case Mask.MaskMode.Intersect:
                    case Mask.MaskMode.None:
                        // Composite using the mask.
                        result = TranslateAndApplyMasks(context, masks, result, CanvasComposite.DestinationIn);
                        break;

                    case Mask.MaskMode.Subtract:
                        // Composite using the mask.
                        result = TranslateAndApplyMasks(context, masks, result, CanvasComposite.DestinationOut);
                        break;

                    default:
                        context.Issues.MaskWithUnsupportedMode(masks[0].Mode.ToString());
                        break;
                    }
                }
                else
                {
                    // Uncommon case for masks: multiple masks.
                    // Get the contiguous segments of masks that have the same mode, create a shape tree for each
                    // segment, and composite the shape trees.
                    // The goal here is to use the smallest possible number of composites.
                    // 1) Get the masks that have the same mode and are next to each other in the list of masks.
                    // 2) Translate the masks to a ShapeVisual.
                    // 3) Composite each ShapeVisual with the previous ShapeVisual.
                    foreach (var(index, count) in EnumerateMaskListSegments(layer.Masks.ToArray()))
                    {
                        // Every mask in the segment has the same mode or None. The first mask is never None.
                        var masksWithSameMode = layer.Masks.Slice(index, count);
                        switch (masksWithSameMode[0].Mode)
                        {
                        case Mask.MaskMode.Add:
                            // Composite using the mask, and apply to what has been already masked.
                            result = TranslateAndApplyMasks(context, masksWithSameMode, result, CanvasComposite.DestinationIn);
                            break;

                        case Mask.MaskMode.Subtract:
                            // Composite using the mask, and apply to what has been already masked.
                            result = TranslateAndApplyMasks(context, masksWithSameMode, result, CanvasComposite.DestinationOut);
                            break;

                        default:
                            // Only Add, Subtract, and None modes are currently supported.
                            context.Issues.MaskWithUnsupportedMode(masksWithSameMode[0].Mode.ToString());
                            break;
                        }
                    }
                }
            }

            return(result);
        }
Пример #20
0
        public static Visual ApplyDropShadow(
            LayerContext context,
            Visual source,
            DropShadowEffect dropShadowEffect)
        {
            if (!context.ObjectFactory.IsUapApiAvailable(nameof(CompositionVisualSurface), versionDependentFeatureDescription: "Drop Shadow"))
            {
                // The effect can't be displayed on the targeted version.
                return(source);
            }

            Debug.Assert(dropShadowEffect.IsEnabled, "Precondition");
            Debug.Assert(context is PreCompLayerContext || context is ShapeLayerContext, "Precondition");

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

            var factory = context.ObjectFactory;
            var size    = context.CompositionContext.Size;

            if (context is PreCompLayerContext)
            {
                size = ConvertTo.Vector2(((PreCompLayerContext)context).Layer.Width, ((PreCompLayerContext)context).Layer.Height);
            }

            var visualSurface = factory.CreateVisualSurface();

            visualSurface.SourceSize   = size;
            visualSurface.SourceVisual = source;

            var maskBrush = factory.CreateMaskBrush();

            var colorBrush = factory.CreateColorBrush(dropShadowEffect.Color.InitialValue);

            var color = Optimizer.TrimAnimatable(context, dropShadowEffect.Color);

            if (color.IsAnimated)
            {
                Animate.Color(context, color, colorBrush, nameof(colorBrush.Color));
            }
            else
            {
                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, blurResult, nameof(blurResult.Opacity));
            }
            else
            {
                blurResult.Opacity = (float)opacity.InitialValue.Value;
            }

            // Convert direction and distance to a Vector3.
            var direction = Optimizer.TrimAnimatable(context, dropShadowEffect.Direction);
            var distance  = Optimizer.TrimAnimatable(context, dropShadowEffect.Distance);

            if (direction.IsAnimated)
            {
                if (distance.IsAnimated)
                {
                    // Direction and distance are animated.
                    // NOTE: we could support this in some cases. The worst cases are
                    //       where the keyframes don't line up, and/or the easings are different
                    //       between distance and direction.
                    context.Issues.AnimatedLayerEffectParameters("drop shadow");
                }
                else
                {
                    // Only direction is animated.
                    var distanceValue = distance.InitialValue;
                    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, blurResult, nameof(blurResult.Offset));
                }
            }
            else if (distance.IsAnimated)
            {
                // Only distance is animated.
                var directionRadians = direction.InitialValue.Radians;
                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, blurResult, nameof(blurResult.Offset));
            }
            else
            {
                // Direction and distance are both not animated.
                var directionRadians = direction.InitialValue.Radians;
                var distanceValue    = 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 false
            if (!dropShadowEffect.IsShadowOnly.IsAlways(true))
            {
                // Check if ShadowOnly can be true
                if (!dropShadowEffect.IsShadowOnly.IsAlways(false))
                {
                    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);
        }