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); }
internal void UpdateOpacityFromTransform(LayerContext context, Transform transform) { if (transform is null) { return; } Opacity = Opacity.ComposedWith(Optimizer.TrimAnimatable(context, transform.Opacity)); }
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))); }
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; } } }
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(); } }
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)); } }
public static void ScalarPropertySetValue( LayerContext context, in TrimmedAnimatable <double> value,
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); }
static void InsertOpacityVisualIntoTransformChain( LayerContext context, in TrimmedAnimatable <Opacity> opacity,
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); }
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); }
internal TrimmedAnimatable(LayerContext context, T initialValue, IReadOnlyList <KeyFrame <T> > keyFrames) { Context = context; InitialValue = initialValue; _keyFrames = keyFrames; }
internal TrimmedAnimatable(LayerContext context, T initialValue) { Context = context; InitialValue = initialValue; _keyFrames = Array.Empty <KeyFrame <T> >(); }
public static TrimmedAnimatable <Vector3> TrimAnimatable(LayerContext context, IAnimatableVector3 animatable) => TrimAnimatable <Vector3>(context, (AnimatableVector3)animatable);
public static CompositionSpriteShape TranslatePath( LayerContext context, in TrimmedAnimatable <PathGeometry> path,
internal FrameNumberEqualityComparer(LayerContext context) { _context = context; }
public static CompositionColorBrush CreateAnimatedColorBrush(LayerContext context, Color color, in TrimmedAnimatable <Opacity> opacity)
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); }
// 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); }
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); }