public static LayerTranslator CreateShapeLayerTranslator(ShapeLayerContext context) { // Emit issues for unupported layer effects. context.Effects.EmitIssueIfDropShadow(); context.Effects.EmitIssueIfGaussianBlur(); return(new ShapeLayerTranslator(context)); }
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())); }
internal ShapeContext(ShapeLayerContext layer) { LayerContext = layer; ObjectFactory = layer.ObjectFactory; }
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); }
public static LayerTranslator CreateShapeLayerTranslator(ShapeLayerContext context) { return(new ShapeLayerTranslator(context)); }