예제 #1
0
        public static LayerTranslator CreateShapeLayerTranslator(ShapeLayerContext context)
        {
            // Emit issues for unupported layer effects.
            context.Effects.EmitIssueIfDropShadow();
            context.Effects.EmitIssueIfGaussianBlur();

            return(new ShapeLayerTranslator(context));
        }
예제 #2
0
 public static LayerTranslator CreateShapeLayerTranslator(ShapeLayerContext context) =>
 new ShapeLayerTranslator(context);
        internal static Animatable <PathGeometryGroup> GroupPaths(
            ShapeLayerContext context,
            IReadOnlyList <Path> paths,
            out bool groupingIsPerfect)
        {
            // Ideally each of the paths would have identical key frames with identical frame numbers.
            // For example:
            //  paths[0] has key frames for frame 2, 5, 7, 15.
            //  paths[1] has key frames for frame 2, 5, 7, 15.
            //  paths[2] has key frames for frame 2, 5, 7, 15.
            //
            // However in practice the key frames for each path could be different, e.g.:
            //  paths[0] has key frames for frame 2, 5, 7, 15.
            //  paths[1] has key frames for frame 3, 5, 7, 17.
            //  paths[2] has key frames for frame 1, 2, 3, 4, 5, 6.
            //
            // We can handle the ideal case perfectly as long as the easings are the same for
            // each path. We'll do a compromise in other cases that may not look quite right but
            // is preferable to just failing.
            //
            //
            // Algorithm:
            // ==========
            //
            // For each key frame number, create a (frame, easing, listOfPathGeometry) triple.
            // For the ideal example:
            // (2,  easing, paths[0].Data.KeyFrames[0], paths[1].Data.KeyFrames[0], paths[2].Data.KeyFrames[0]),
            // (5,  easing, paths[0].Data.KeyFrames[1], paths[1].Data.KeyFrames[1], paths[2].Data.KeyFrames[1]),
            // (7,  easing, paths[0].Data.KeyFrames[2], paths[1].Data.KeyFrames[2], paths[2].Data.KeyFrames[2]),
            // (15, easing, paths[0].Data.KeyFrames[3], paths[1].Data.KeyFrames[3], paths[2].Data.KeyFrames[3])
            //
            // For the non-ideal example:
            // (1,  easing,                       null,                       null, paths[2].Data.KeyFrames[0]),
            // (2,  easing, paths[0].Data.KeyFrames[0],                       null, paths[2].Data.KeyFrames[1]),
            // (3,  easing,                       null, paths[1].Data.KeyFrames[0], paths[2].Data.KeyFrames[2]),
            // (4,  easing,                       null,                       null, paths[2].Data.KeyFrames[3]),
            // (5,  easing, paths[0].Data.KeyFrames[1], paths[1].Data.KeyFrames[1], paths[2].Data.KeyFrames[4]),
            // (6,  easing,                       null,                       null, paths[2].Data.KeyFrames[5]),
            // (7,  easing, paths[0].Data.KeyFrames[2], paths[1].Data.KeyFrames[2],                       null),
            // (15, easing, paths[0].Data.KeyFrames[3],                       null,                       null),
            // (17, easing,                       null, paths[1].Data.KeyFrames[3],                       null)
            //
            // Then fill in the null entries with values that are interpolated from the surrounding data.
            // NOTE: we currently don't try very hard to interpolate the values, so the results in the
            // non-ideal cases still have room for improvement. Luckily the ideal case is quite common.
            //

            // Test for the simplest case - all the paths are non animated.
            if (paths.All(p => !p.Data.IsAnimated))
            {
                var group = new PathGeometryGroup(paths.Select(p => p.Data.InitialValue).ToArray());
                groupingIsPerfect = true;
                return(new Animatable <PathGeometryGroup>(group));
            }

            // At least some of the paths are animated. Create the data structure.
            var records = CreateGroupRecords(paths.Select(p => p.Data).ToArray()).ToArray();

            // We are succeeding if the easing is correct in each record. If not we'll
            // indicate that the result is less than perfect.
            groupingIsPerfect = records.Select(g => g.EasingIsCorrect).Min();

            // Fill in the nulls in the data structure. Ideally we'd fill these in with interpolated
            // values, but interpolation is difficult, so for now we just copy the nearest value.
            for (var pathIndex = 0; pathIndex < paths.Count; pathIndex++)
            {
                for (var frameIndex = 0; frameIndex < records.Length; frameIndex++)
                {
                    // Get the key frame for the current frame number.
                    var keyFrame = records[frameIndex].Geometries[pathIndex];
                    if (keyFrame is null)
                    {
                        // There is no key frame for the current frame number.
                        // Interpolate a key frame to replace the null.
                        records[frameIndex].Geometries[pathIndex] =
                            InterpolateKeyFrame(records, pathIndex, frameIndex);

                        // Indicate that the grouping is not perfect due to having
                        // to interpolate the path value for this keyFrame.
                        groupingIsPerfect = false;
                    }
                }
            }

            // Create the result by creating a key frame containing a PathGeometryGroup for each record,
            // and use the "preferred easing" to ease between the key frames.
            return
                (new Animatable <PathGeometryGroup>(
                     keyFrames:
                     (from g in records
                      let geometryGroup = new PathGeometryGroup(g.Geometries.Select(h => h !.Value).ToArray())
                                          select new KeyFrame <PathGeometryGroup>(g.Frame, geometryGroup, g.PreferredEasing)).ToArray()));
        }
예제 #4
0
 internal ShapeContext(ShapeLayerContext layer)
 {
     LayerContext  = layer;
     ObjectFactory = layer.ObjectFactory;
 }
예제 #5
0
        internal static bool TryGroupPaths(
            ShapeLayerContext context,
            IEnumerable <Path> paths,
            out Animatable <PathGeometryGroup> result)
        {
            // Store the keyframes in a dictionary, keyed by frame.
            var ps = paths.ToArray();

            var groupsByFrame = new Dictionary <double, GeometryKeyFrame[]>(context.FrameNumberComparer)
            {
                { 0, new GeometryKeyFrame[ps.Length] },
            };

            for (var i = 0; i < ps.Length; i++)
            {
                var p = ps[i];

                // Add the initial value.
                groupsByFrame[0][i] = new GeometryKeyFrame(p, p.Data.InitialValue, HoldEasing.Instance);

                // Add any keyframes.
                foreach (var kf in p.Data.KeyFrames.ToArray().Skip(1))
                {
                    // See if there's a key frame at the frame number already.
                    if (!groupsByFrame.TryGetValue(kf.Frame, out var array))
                    {
                        array = new GeometryKeyFrame[ps.Length];
                        groupsByFrame.Add(kf.Frame, array);
                    }

                    // NOTE: this could result in a key frame being overwritten and
                    // lost if the frame numbers are very close together. This seems
                    // to be extremely rare, so rather than trying to be too clever
                    // here we'll just let it happen. The assert should help us find
                    // any cases where this happens to determine if we should be trying
                    // harder.
                    Debug.Assert(array[i] is null, "Path key frames very close together");

                    array[i] = new GeometryKeyFrame(p, kf.Value, kf.Easing);
                }
            }

            // Make sure that every frame has a geometry from each path.
            // For any missing path, fill the hole with the path from the
            // previous frame.
            var frames             = groupsByFrame.OrderBy(kvp => kvp.Key).Select(kvp => (frame: kvp.Key, geometries: kvp.Value)).ToArray();
            var previousGeometries = frames[0].geometries;
            var success            = true;

            // Start from the second frame. The initial frame (0) will always have a value (.InitialValue).
            foreach (var(frame, geometries) in frames.Skip(1))
            {
                // Get the easing for this frame.
                var easings = geometries.Where(g => g != null).Select(g => g.Easing).Distinct().ToArray();
                if (easings.Length > 1)
                {
                    // There are conflicting easings. We can't currently handle that.
                    success = false;
                }

                for (var i = 0; i < geometries.Length; i++)
                {
                    if (geometries[i] == null)
                    {
                        // The frame doesn't have a correponding geometry for this path.
                        // Use the geometry from the previous frame, but with the easing
                        // from this frame.
                        geometries[i] = previousGeometries[i].CloneWithDifferentEasing(easings[0]);

                        // It's correct to use the previous frame's path if it isn't animated, but if
                        // it is animated it would need to be interpolated to be correct. We currently
                        // don't handle interpolation of paths in the translator, so indicate that we
                        // weren't able to do things correctly.
                        if (geometries[i].Path.Data.IsAnimated)
                        {
                            // This is a case that we can't handle correctly.
                            success = false;
                        }
                    }
                }

                previousGeometries = geometries;
            }

            // Every entry in frames now has path data. Return the groups.
            result =
                new Animatable <PathGeometryGroup>(
                    keyFrames:
                    (from f in frames
                     let firstGeometry = f.geometries[0]
                                         let easing = firstGeometry.Easing
                                                      let geometryGroup = new PathGeometryGroup(f.geometries.Select(g => g.Geometry).ToArray(), easing)
                                                                          select new KeyFrame <PathGeometryGroup>(f.frame, geometryGroup, easing)).ToArray(),
                    propertyIndex: null);

            return(success);
        }
예제 #6
0
 public static LayerTranslator CreateShapeLayerTranslator(ShapeLayerContext context)
 {
     return(new ShapeLayerTranslator(context));
 }