Beispiel #1
0
        // NOTES ABOUT RECTANGLE DRAWING AND CORNERS:
        // ==========================================
        // A rectangle can be thought of as having 8 components -
        // 4 sides (1,3,5,7) and 4 corners (2,4,6,8):
        //
        //             1
        //     8╭ ─────────── ╮2
        //      ╷             ╷
        //     7│             │3
        //      ╵             ╵
        //     6╰ ─────────── ╯4
        //             5
        //
        // Windows.Composition draws in order 1,2,3,4,5,6,7,8.
        //
        // Lottie draws in one of two different ways depending on
        // whether the corners are controlled by RoundCorners or by
        // Rectangle.CornerRadius.
        // If RoundCorners the order is 2,3,4,5,6,7,8,1.
        // If Rectangle.CornerRadius the order is 3,4,5,6,7,8,1,2.
        //
        // If the corners have 0 radius, the corners are irrelevant
        // resulting in:
        // Windows.Composition: 1,3,5,7.
        // Lottie:              3,5,7,1.
        //
        // The order of drawing matters only if there is a TrimPath, and in
        // that case:
        // a) If there are no RoundCorners, a TrimOffset equivalent to 90 degrees
        //    must be added.
        // b) If there are RoundCorners, swap width and height, rotate the rectangle
        //    by 90 degrees around the center, and transform the trim path so that
        //    it effectively draws in the reverse direction.
        //
        // TODO - the RoundCorners case with TrimPath is currently not handled correctly
        //        and will cause the trim to appear to be rotated by 90 degrees.
        //
        //
        // Translates a Lottie rectangle to a CompositionShape.
        public static CompositionShape TranslateRectangleContent(ShapeContext context, Rectangle rectangle)
        {
            var result   = context.ObjectFactory.CreateSpriteShape();
            var position = Optimizer.TrimAnimatable(context, rectangle.Position);

            if (IsNonRounded(context, rectangle))
            {
                // Non-rounded rectangles are slightly more efficient, but they can only be used
                // if there is no roundness or Round Corners.
                TranslateAndApplyNonRoundedRectangleContent(
                    context,
                    rectangle,
                    position,
                    result);
            }
            else
            {
                TranslateAndApplyRoundedRectangleContent(
                    context,
                    rectangle,
                    position,
                    result);
            }

            return(result);
        }
Beispiel #2
0
        public static CanvasGeometry CreateWin2dEllipseGeometry(ShapeContext context, Ellipse ellipse)
        {
            var ellipsePosition = Optimizer.TrimAnimatable(context, ellipse.Position);
            var ellipseDiameter = Optimizer.TrimAnimatable(context, ellipse.Diameter);

            if (ellipsePosition.IsAnimated || ellipseDiameter.IsAnimated)
            {
                context.Translation.Issues.CombiningAnimatedShapesIsNotSupported();
            }

            var xRadius = ellipseDiameter.InitialValue.X / 2;
            var yRadius = ellipseDiameter.InitialValue.Y / 2;

            var result = CanvasGeometry.CreateEllipse(
                null,
                (float)(ellipsePosition.InitialValue.X - (xRadius / 2)),
                (float)(ellipsePosition.InitialValue.Y - (yRadius / 2)),
                (float)xRadius,
                (float)yRadius);

            var transformMatrix = Transforms.CreateMatrixFromTransform(context, context.Transform);

            if (!transformMatrix.IsIdentity)
            {
                result = result.Transform(transformMatrix);
            }

            result.SetDescription(context, () => ellipse.Name);

            return(result);
        }
Beispiel #3
0
        internal void UpdateOpacityFromTransform(LayerContext context, Transform transform)
        {
            if (transform is null)
            {
                return;
            }

            Opacity = Opacity.ComposedWith(Optimizer.TrimAnimatable(context, transform.Opacity));
        }
Beispiel #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;
                }
            }
        }
Beispiel #5
0
        static CompositionShape TranslateShapeLayerContents(
            ShapeContext context,
            IReadOnlyList <ShapeLayerContent> contents)
        {
            // The Contents of a ShapeLayer is a list of instructions for a stack machine.

            // When evaluated, the stack of ShapeLayerContent produces a list of CompositionShape.
            // Some ShapeLayerContent modify the evaluation context (e.g. stroke, fill, trim)
            // Some ShapeLayerContent evaluate to geometries (e.g. any geometry, merge path)

            // Create a container to hold the contents.
            var container = context.ObjectFactory.CreateContainerShape();

            // This is the object that will be returned. Containers may be added above this
            // as necessary to hold transforms.
            var result = container;

            // If the contents contains a repeater, generate repeated contents
            if (contents.Any(slc => slc.ContentType == ShapeContentType.Repeater))
            {
                // The contents contains a repeater. Treat it as if there are n sets of items (where n
                // equals the Count of the repeater). In each set, replace the repeater with
                // the transform of the repeater, multiplied.

                // Find the index of the repeater
                var repeaterIndex = 0;
                while (contents[repeaterIndex].ContentType != ShapeContentType.Repeater)
                {
                    // Keep going until the first repeater is found.
                    repeaterIndex++;
                }

                // Get the repeater.
                var repeater = (Repeater)contents[repeaterIndex];

                var repeaterCount  = Optimizer.TrimAnimatable(context, repeater.Count);
                var repeaterOffset = Optimizer.TrimAnimatable(context, repeater.Offset);

                // Make sure we can handle it.
                if (repeaterCount.IsAnimated || repeaterOffset.IsAnimated || repeaterOffset.InitialValue != 0)
                {
                    // TODO - handle all cases.
                    context.Issues.RepeaterIsNotSupported();
                }
                else
                {
                    // Get the items before the repeater, and the items after the repeater.
                    var itemsBeforeRepeater = contents.Slice(0, repeaterIndex).ToArray();
                    var itemsAfterRepeater  = contents.Slice(repeaterIndex + 1).ToArray();

                    var nonAnimatedRepeaterCount = (int)Math.Round(repeaterCount.InitialValue);
                    for (var i = 0; i < nonAnimatedRepeaterCount; i++)
                    {
                        // Treat each repeated value as a list of items where the repeater is replaced
                        // by n transforms.
                        // TODO - currently ignoring the StartOpacity and EndOpacity - should generate a new transform
                        //        that interpolates that.
                        var generatedItems = itemsBeforeRepeater.Concat(Enumerable.Repeat(repeater.Transform, i + 1)).Concat(itemsAfterRepeater).ToArray();

                        // Recurse to translate the synthesized items.
                        container.Shapes.Add(TranslateShapeLayerContents(context, generatedItems));
                    }

                    return(result);
                }
            }

            CheckForUnsupportedShapeGroup(context, contents);

            var stack = new Stack <ShapeLayerContent>(contents.ToArray());

            while (true)
            {
                context.UpdateFromStack(stack);
                if (stack.Count == 0)
                {
                    break;
                }

                var shapeContent = stack.Pop();

                // Complain if the BlendMode is not supported.
                if (shapeContent.BlendMode != BlendMode.Normal)
                {
                    context.Issues.BlendModeNotNormal(context.LayerContext.Layer.Name, shapeContent.BlendMode.ToString());
                }

                switch (shapeContent.ContentType)
                {
                case ShapeContentType.Ellipse:
                    container.Shapes.Add(Ellipses.TranslateEllipseContent(context, (Ellipse)shapeContent));
                    break;

                case ShapeContentType.Group:
                    container.Shapes.Add(TranslateGroupShapeContent(context.Clone(), (ShapeGroup)shapeContent));
                    break;

                case ShapeContentType.MergePaths:
                    var mergedPaths = TranslateMergePathsContent(context, stack, ((MergePaths)shapeContent).Mode);
                    if (mergedPaths != null)
                    {
                        container.Shapes.Add(mergedPaths);
                    }

                    break;

                case ShapeContentType.Path:
                {
                    var paths = new List <Path>();
                    paths.Add(Optimizer.OptimizePath(context, (Path)shapeContent));

                    // Get all the paths that are part of the same group.
                    while (stack.TryPeek(out var item) && item.ContentType == ShapeContentType.Path)
                    {
                        // Optimize the paths as they are added. Optimized paths have redundant keyframes
                        // removed. Optimizing here increases the chances that an animated path will be
                        // turned into a non-animated path which will allow us to group the paths.
                        paths.Add(Optimizer.OptimizePath(context, (Path)stack.Pop()));
                    }

                    CheckForRoundCornersOnPath(context);

                    if (paths.Count == 1)
                    {
                        // There's a single path.
                        container.Shapes.Add(Paths.TranslatePathContent(context, paths[0]));
                    }
                    else
                    {
                        // There are multiple paths. They need to be grouped.
                        container.Shapes.Add(Paths.TranslatePathGroupContent(context, paths));
                    }
                }

                break;

                case ShapeContentType.Polystar:
                    context.Issues.PolystarIsNotSupported();
                    break;

                case ShapeContentType.Rectangle:
                    container.Shapes.Add(Rectangles.TranslateRectangleContent(context, (Rectangle)shapeContent));
                    break;

                case ShapeContentType.Transform:
                {
                    var transform = (Transform)shapeContent;

                    // Multiply the opacity in the transform.
                    context.UpdateOpacityFromTransform(context, transform);

                    // Insert a new container at the top. The transform will be applied to it.
                    var newContainer = context.ObjectFactory.CreateContainerShape();
                    newContainer.Shapes.Add(result);
                    result = newContainer;

                    // Apply the transform to the new container at the top.
                    Transforms.TranslateAndApplyTransform(context, transform, result);
                }

                break;

                case ShapeContentType.Repeater:
                    // TODO - handle all cases. Not clear whether this is valid. Seen on 0605.traffic_light.
                    context.Issues.RepeaterIsNotSupported();
                    break;

                default:
                case ShapeContentType.SolidColorStroke:
                case ShapeContentType.LinearGradientStroke:
                case ShapeContentType.RadialGradientStroke:
                case ShapeContentType.SolidColorFill:
                case ShapeContentType.LinearGradientFill:
                case ShapeContentType.RadialGradientFill:
                case ShapeContentType.TrimPath:
                case ShapeContentType.RoundCorners:
                    throw new InvalidOperationException();
                }
            }

            return(result);
        }
Beispiel #6
0
        static void TranslateAndApplyTrimPath(
            ShapeContext context,
            CompositionGeometry geometry,
            bool reverseDirection,
            double trimOffsetDegrees)
        {
            var trimPath = context.TrimPath;

            if (trimPath is null)
            {
                return;
            }

            if (reverseDirection)
            {
                trimPath = trimPath.CloneWithReversedDirection();
            }

            var startTrim      = Optimizer.TrimAnimatable(context, trimPath.Start);
            var endTrim        = Optimizer.TrimAnimatable(context, trimPath.End);
            var trimPathOffset = Optimizer.TrimAnimatable(context, trimPath.Offset);

            if (!startTrim.IsAnimated && !endTrim.IsAnimated)
            {
                // Handle some well-known static cases.
                if (startTrim.InitialValue.Value == 0 && endTrim.InitialValue.Value == 1)
                {
                    // The trim does nothing.
                    return;
                }
                else if (startTrim.InitialValue == endTrim.InitialValue)
                {
                    // TODO - the trim trims away all of the path.
                }
            }

            var order = GetAnimatableOrder(in startTrim, in endTrim);

            switch (order)
            {
            case AnimatableOrder.Before:
            case AnimatableOrder.Equal:
                break;

            case AnimatableOrder.After:
            {
                // Swap is necessary to match the WinComp semantics.
                var temp = startTrim;
                startTrim = endTrim;
                endTrim   = temp;
            }

            break;

            case AnimatableOrder.BeforeAndAfter:
                break;

            default:
                throw new InvalidOperationException();
            }

            if (order == AnimatableOrder.BeforeAndAfter)
            {
                // Add properties that will be animated. The TrimStart and TrimEnd properties
                // will be set by these values through an expression.
                Animate.TrimStartOrTrimEndPropertySetValue(context, startTrim, geometry, "TStart");
                var trimStartExpression = context.ObjectFactory.CreateExpressionAnimation(ExpressionFactory.MinTStartTEnd);
                trimStartExpression.SetReferenceParameter("my", geometry);
                Animate.WithExpression(geometry, trimStartExpression, nameof(geometry.TrimStart));

                Animate.TrimStartOrTrimEndPropertySetValue(context, endTrim, geometry, "TEnd");
                var trimEndExpression = context.ObjectFactory.CreateExpressionAnimation(ExpressionFactory.MaxTStartTEnd);
                trimEndExpression.SetReferenceParameter("my", geometry);
                Animate.WithExpression(geometry, trimEndExpression, nameof(geometry.TrimEnd));
            }
            else
            {
                // Directly animate the TrimStart and TrimEnd properties.
                if (startTrim.IsAnimated)
                {
                    Animate.TrimStartOrTrimEnd(context, startTrim, geometry, nameof(geometry.TrimStart), "TrimStart", null);
                }
                else
                {
                    geometry.TrimStart = ConvertTo.Float(startTrim.InitialValue);
                }

                if (endTrim.IsAnimated)
                {
                    Animate.TrimStartOrTrimEnd(context, endTrim, geometry, nameof(geometry.TrimEnd), "TrimEnd", null);
                }
                else
                {
                    geometry.TrimEnd = ConvertTo.Float(endTrim.InitialValue);
                }
            }

            if (trimOffsetDegrees != 0 && !trimPathOffset.IsAnimated)
            {
                // Rectangle shapes are treated specially here to account for Lottie rectangle 0,0 being
                // top right and WinComp rectangle 0,0 being top left. As long as the TrimOffset isn't
                // being animated we can simply add an offset to the trim path.
                geometry.TrimOffset = (float)((trimPathOffset.InitialValue.Degrees + trimOffsetDegrees) / 360);
            }
            else
            {
                if (trimOffsetDegrees != 0)
                {
                    // TODO - can be handled with another property.
                    context.Issues.AnimatedTrimOffsetWithStaticTrimOffsetIsNotSupported();
                }

                if (trimPathOffset.IsAnimated)
                {
                    Animate.ScaledRotation(context, trimPathOffset, 1 / 360.0, geometry, nameof(geometry.TrimOffset), "TrimOffset", null);
                }
                else
                {
                    geometry.TrimOffset = ConvertTo.Float(trimPathOffset.InitialValue.Degrees / 360);
                }
            }
        }
Beispiel #7
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);
        }
Beispiel #8
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);
        }
Beispiel #9
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);
        }
Beispiel #10
0
        public static CompositionShape TranslateEllipseContent(ShapeContext context, Ellipse shapeContent)
        {
            // An ellipse is represented as a SpriteShape with a CompositionEllipseGeometry.
            var compositionSpriteShape = context.ObjectFactory.CreateSpriteShape();

            compositionSpriteShape.SetDescription(context, () => shapeContent.Name);

            var compositionEllipseGeometry = context.ObjectFactory.CreateEllipseGeometry();

            compositionEllipseGeometry.SetDescription(context, () => $"{shapeContent.Name}.EllipseGeometry");

            compositionSpriteShape.Geometry = compositionEllipseGeometry;

            var position = Optimizer.TrimAnimatable(context, shapeContent.Position);

            if (position.IsAnimated)
            {
                Animate.Vector2(context, position, compositionEllipseGeometry, "Center");
            }
            else
            {
                compositionEllipseGeometry.Center = ConvertTo.Vector2(position.InitialValue);
            }

            // Ensure that the diameter is expressed in a form that has only one easing per channel.
            var diameter = AnimatableVector3Rewriter.EnsureOneEasingPerChannel(shapeContent.Diameter);

            if (diameter is AnimatableXYZ diameterXYZ)
            {
                var diameterX = Optimizer.TrimAnimatable(context, diameterXYZ.X);
                var diameterY = Optimizer.TrimAnimatable(context, diameterXYZ.Y);
                if (diameterX.IsAnimated)
                {
                    Animate.ScaledScalar(context, diameterX, 0.5, compositionEllipseGeometry, $"{nameof(CompositionEllipseGeometry.Radius)}.X");
                }

                if (diameterY.IsAnimated)
                {
                    Animate.ScaledScalar(context, diameterY, 0.5, compositionEllipseGeometry, $"{nameof(CompositionEllipseGeometry.Radius)}.Y");
                }

                if (!diameterX.IsAnimated || !diameterY.IsAnimated)
                {
                    compositionEllipseGeometry.Radius = ConvertTo.Vector2(diameter.InitialValue) * 0.5F;
                }
            }
            else
            {
                var diameter3 = Optimizer.TrimAnimatable <Vector3>(context, (AnimatableVector3)diameter);
                if (diameter3.IsAnimated)
                {
                    Animate.ScaledVector2(context, diameter3, 0.5, compositionEllipseGeometry, nameof(CompositionEllipseGeometry.Radius));
                }
                else
                {
                    compositionEllipseGeometry.Radius = ConvertTo.Vector2(diameter.InitialValue) * 0.5F;
                }
            }

            Shapes.TranslateAndApplyShapeContext(
                context,
                compositionSpriteShape,
                reverseDirection: shapeContent.DrawingDirection == DrawingDirection.Reverse);

            return(compositionSpriteShape);
        }
Beispiel #11
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);
        }
Beispiel #12
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);
        }
Beispiel #13
0
            internal override CompositionShape?GetShapeRoot(TranslationContext context)
            {
                if (_context.Layer.IsHidden || _context.Layer.Transform.Opacity.IsAlways(LottieData.Opacity.Transparent))
                {
                    // The layer does not render anything. Nothing to translate. This can happen when someone
                    // creates a solid layer to act like a Null layer.
                    return(null);
                }

                if (!Transforms.TryCreateContainerShapeTransformChain(_context, out var containerRootNode, out var containerContentNode))
                {
                    // The layer is never visible.
                    return(null);
                }

                var rectangle = context.ObjectFactory.CreateSpriteShape();

                var rectangleGeometry = context.ObjectFactory.CreateRectangleGeometry();

                rectangleGeometry.Size = new Sn.Vector2(_context.Layer.Width, _context.Layer.Height);

                rectangle.Geometry = rectangleGeometry;

                containerContentNode.Shapes.Add(rectangle);

                // Opacity is implemented via the alpha channel on the brush.
                rectangle.FillBrush = Brushes.CreateAnimatedColorBrush(_context, _context.Layer.Color, Optimizer.TrimAnimatable(_context, _context.Layer.Transform.Opacity));

                rectangle.SetDescription(context, () => "SolidLayerRectangle");
                rectangle.Geometry.SetDescription(context, () => "SolidLayerRectangle.RectangleGeometry");
                Describe(context, containerRootNode);

                return(containerRootNode);
            }
Beispiel #14
0
        static LayerVisual ApplyDropShadow(PreCompLayerContext context, Visual visual, 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);

            // 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 shadow = context.ObjectFactory.CreateDropShadow();

            result.Shadow       = shadow;
            shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;

            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 color = Optimizer.TrimAnimatable(context, dropShadowEffect.Color);

            if (color.IsAnimated)
            {
                Animate.Color(context, color, shadow, nameof(shadow.Color));
            }
            else
            {
                shadow.Color = ConvertTo.Color(color.InitialValue);
            }

            var opacity = Optimizer.TrimAnimatable(context, dropShadowEffect.Opacity);

            if (opacity.IsAnimated)
            {
                Animate.Opacity(context, opacity, shadow, nameof(shadow.Opacity));
            }
            else
            {
                shadow.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, shadow, nameof(shadow.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, shadow, nameof(shadow.Offset));
            }
            else
            {
                // Direction and distance are both not animated.
                var directionRadians = direction.InitialValue.Radians;
                var distanceValue    = distance.InitialValue;

                shadow.Offset = ConvertTo.Vector3(VectorFromRotationAndDistance(direction.InitialValue, distance.InitialValue));
            }

            return(result);
        }