Esempio n. 1
0
        static void InsertVisibilityVisualIntoTransformChain(
            TranslationContext context,
            float inProgress,
            float outProgress,
            ref ContainerVisual root)
        {
            // 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)
            {
                // Insert a new node to control visibility at the top of the chain.
                var visibilityNode = context.ObjectFactory.CreateContainerVisual();
                visibilityNode.Children.Add(root);
                root = visibilityNode;

                var visibilityAnimation = context.ObjectFactory.CreateBooleanKeyFrameAnimation();
                if (inProgress > 0)
                {
                    // Set initial value to be non-visible.
                    visibilityNode.IsVisible = false;
                    visibilityAnimation.InsertKeyFrame(inProgress, true);
                }

                if (outProgress < 1)
                {
                    visibilityAnimation.InsertKeyFrame(outProgress, false);
                }

                visibilityAnimation.Duration = context.LottieComposition.Duration;
                Animate.WithKeyFrame(context, visibilityNode, "IsVisible", visibilityAnimation);
            }
        }
Esempio n. 2
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);
                }
            }
        }
Esempio n. 3
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);
        }
Esempio n. 4
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);
        }
Esempio n. 5
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);
        }
Esempio n. 6
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);
        }