@@ -132,17 +132,57 @@ public static CompositionShape TranslatePathContent(ShapeContext context, Path p
132132
133133 private static TrimmedAnimatable < PathGeometry > TranslateRoundCorners ( ShapeContext context , TrimmedAnimatable < PathGeometry > pathGeometry )
134134 {
135- if ( context . RoundCorners . Radius . IsAlways ( 0 ) || context . RoundCorners . Radius . IsAnimated )
135+ var radius = context . RoundCorners . Radius ;
136+
137+ if ( radius . IsAlways ( 0 ) )
136138 {
137139 return pathGeometry ;
138140 }
139141
140- if ( ! pathGeometry . IsAnimated )
142+ bool animated = radius . IsAnimated || pathGeometry . IsAnimated ;
143+
144+ // Unfortunately, it is not 100% perfect when animated and does not produce animation identical to After Effects.
145+ // After Effects applies radius to the shape after the interpolation, but we are applying radius to the keyframes and
146+ // then Composition layer interpolates paths that are already rounded.
147+ if ( animated )
141148 {
142- return new TrimmedAnimatable < PathGeometry > ( pathGeometry . Context , MakeRoundCorners ( pathGeometry . InitialValue , context . RoundCorners . Radius . InitialValue ) ) ;
149+ context . Issues . PathWithRoundCornersIsNotFullySupported ( ) ;
143150 }
144151
145- return pathGeometry ;
152+ if ( radius . IsAnimated && pathGeometry . IsAnimated )
153+ {
154+ // We do not support animating both yet.
155+ return pathGeometry ;
156+ }
157+
158+ // If there is an animation we do not want to optimize number of points on resulting path because
159+ // we want the number of points to be the same for different keyframes.
160+ var initialValue = MakeRoundCorners ( pathGeometry . InitialValue , radius . InitialValue , optimizeNumberOfPoints : ! animated ) ;
161+
162+ if ( pathGeometry . IsAnimated )
163+ {
164+ List < KeyFrame < PathGeometry > > keyframes = new List < KeyFrame < PathGeometry > > ( ) ;
165+ foreach ( var keyframe in pathGeometry . KeyFrames )
166+ {
167+ var path = MakeRoundCorners ( keyframe . Value , context . RoundCorners . Radius . InitialValue , optimizeNumberOfPoints : false ) ;
168+ keyframes . Add ( new KeyFrame < PathGeometry > ( keyframe . Frame , path , keyframe . SpatialBezier , keyframe . Easing ) ) ;
169+ }
170+
171+ return new TrimmedAnimatable < PathGeometry > ( pathGeometry . Context , initialValue , keyframes ) ;
172+ }
173+ else if ( radius . IsAnimated )
174+ {
175+ List < KeyFrame < PathGeometry > > keyframes = new List < KeyFrame < PathGeometry > > ( ) ;
176+ foreach ( var keyframe in radius . KeyFrames )
177+ {
178+ var path = MakeRoundCorners ( pathGeometry . InitialValue , keyframe . Value , optimizeNumberOfPoints : false ) ;
179+ keyframes . Add ( new KeyFrame < PathGeometry > ( keyframe . Frame , path , keyframe . SpatialBezier , keyframe . Easing ) ) ;
180+ }
181+
182+ return new TrimmedAnimatable < PathGeometry > ( pathGeometry . Context , initialValue , keyframes ) ;
183+ }
184+
185+ return new TrimmedAnimatable < PathGeometry > ( pathGeometry . Context , initialValue ) ;
146186 }
147187
148188 /// <summary>
@@ -435,21 +475,27 @@ static BezierSegment MakeCubicBezier(Vector2 cp0, Vector2 cp1, Vector2 cp2)
435475 return new BezierSegment ( cp0 , cp0 + ( ( cp1 - cp0 ) * 0.55 ) , cp2 + ( ( cp1 - cp2 ) * 0.55 ) , cp2 ) ;
436476 }
437477
438- // The way this function works is it detects if two segments form a corner (if they do not have smooth connection)
439- // Then it duplicates this point (shared by two segments) and moves newly generated points in different directions
440- // for "radius" pixels along the segment.
441- // After that we are joining both new points with a bezier curve to make the corner look rounded.
442- //
443- // There are 3 possible cases:
444- // 1. When the segment is curved from both sides, then we can just keep it as it is
445- // 2. When the segment is not curved from both sides, then we can make two rounded corners, from both ends.
446- // 3. When the segment curved from one side (begin or end) we are making only one rounded corner.
447- //
448- // In order to make a rounded corner we also need two points on segments next to the current segment.
449- // In this algortihm we are processing segments one by one, and passing one point from one segment to another,
450- // so that currently processed segment can create rounded corner using this point, and pass new point
451- // to the next segment, so that it can create next rounded corner and so on.
452- static PathGeometry MakeRoundCorners ( PathGeometry pathGeometry , double radius )
478+ /// <summary>
479+ /// The way this function works is it detects if two segments form a corner (if they do not have smooth connection)
480+ /// Then it duplicates this point (shared by two segments) and moves newly generated points in different directions
481+ /// for "radius" pixels along the segment.
482+ /// After that we are joining both new points with a bezier curve to make the corner look rounded.
483+ ///
484+ /// There are 3 possible cases:
485+ /// 1. When the segment is curved from both sides, then we can just keep it as it is
486+ /// 2. When the segment is not curved from both sides, then we can make two rounded corners, from both ends.
487+ /// 3. When the segment curved from one side (begin or end) we are making only one rounded corner.
488+ ///
489+ /// In order to make a rounded corner we also need two points on segments next to the current segment.
490+ /// In this algortihm we are processing segments one by one, and passing one point from one segment to another,
491+ /// so that currently processed segment can create rounded corner using this point, and pass new point
492+ /// to the next segment, so that it can create next rounded corner and so on.
493+ /// </summary>
494+ /// <param name="pathGeometry">Path.</param>
495+ /// <param name="radius">Radius of corners.</param>
496+ /// <param name="optimizeNumberOfPoints">Use optimizeNumberOfPoints = false if you need to keep the number of points constant,
497+ /// it can be needed for animated path.</param>
498+ static PathGeometry MakeRoundCorners ( PathGeometry pathGeometry , double radius , bool optimizeNumberOfPoints = true )
453499 {
454500 // There is no corners if we have less than two segments.
455501 if ( pathGeometry . BezierSegments . Count < 2 )
@@ -517,7 +563,23 @@ static PathGeometry MakeRoundCorners(PathGeometry pathGeometry, double radius)
517563 // Case #2:
518564 if ( length <= 2 * radius )
519565 {
520- point0 = point1 = ( segment . ControlPoint0 + segment . ControlPoint3 ) * 0.5 ;
566+ if ( optimizeNumberOfPoints )
567+ {
568+ // Middle of the segment (ControlPoint0; ControlPoint3)
569+ point0 = point1 = ( segment . ControlPoint0 + segment . ControlPoint3 ) * 0.5 ;
570+ }
571+ else
572+ {
573+ float halfPlusEpsilon = Float32 . NextLargerThan ( 0.5f ) ;
574+ float halfMinusEpsilon = 1 - halfPlusEpsilon ;
575+
576+ // Generate two points instead of one, but place them close to each other.
577+ // These two points are placed on the segment (ControlPoint0; ControlPoint3)
578+ // Almost in the middle but point0 is a bit closer to the ControlPoint0 and
579+ // point1 is a bit closer po ControlPoint3.
580+ point0 = ( segment . ControlPoint0 * halfPlusEpsilon ) + ( segment . ControlPoint3 * halfMinusEpsilon ) ;
581+ point1 = ( segment . ControlPoint0 * halfMinusEpsilon ) + ( segment . ControlPoint3 * halfPlusEpsilon ) ;
582+ }
521583 }
522584
523585 // Rounded corner.
0 commit comments