diff --git a/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java b/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java index cfa952366d..9c5fc83c6b 100644 --- a/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java +++ b/lottie/src/main/java/com/airbnb/lottie/animation/content/PolystarContent.java @@ -1,10 +1,9 @@ package com.airbnb.lottie.animation.content; import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.PointF; - import androidx.annotation.Nullable; - import com.airbnb.lottie.LottieDrawable; import com.airbnb.lottie.LottieProperty; import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation; @@ -28,6 +27,9 @@ public class PolystarContent private static final float POLYSTAR_MAGIC_NUMBER = .47829f; private static final float POLYGON_MAGIC_NUMBER = .25f; private final Path path = new Path(); + private final Path lastSegmentPath = new Path(); + private final PathMeasure lastSegmentPathMeasure = new PathMeasure(); + private final float[] lastSegmentPosition = new float[2]; private final String name; private final LottieDrawable lottieDrawable; @@ -291,8 +293,28 @@ private void createPolygonPath() { float cp1y = radius * roundedness * POLYGON_MAGIC_NUMBER * cp1Dy; float cp2x = radius * roundedness * POLYGON_MAGIC_NUMBER * cp2Dx; float cp2y = radius * roundedness * POLYGON_MAGIC_NUMBER * cp2Dy; - path.cubicTo(previousX - cp1x, previousY - cp1y, x + cp2x, y + cp2y, x, y); + + if (i == numPoints - 1) { + // When there is a huge stroke, it will flash if the path ends where it starts. + // We want the final bezier curve to end *slightly* before the start. + // The close() call at the end will complete the polystar. + // https://github.com/airbnb/lottie-android/issues/2329 + lastSegmentPath.reset(); + lastSegmentPath.moveTo(previousX, previousY); + lastSegmentPath.cubicTo(previousX - cp1x, previousY - cp1y, x + cp2x, y + cp2y, x, y); + lastSegmentPathMeasure.setPath(lastSegmentPath, false); + lastSegmentPathMeasure.getPosTan(lastSegmentPathMeasure.getLength() * 0.9999f, lastSegmentPosition, null); + path.cubicTo(previousX - cp1x, previousY - cp1y, x + cp2x, y + cp2y,lastSegmentPosition[0], lastSegmentPosition[1]); + } else { + path.cubicTo(previousX - cp1x, previousY - cp1y, x + cp2x, y + cp2y, x, y); + } } else { + if (i == numPoints - 1) { + // When there is a huge stroke, it will flash if the path ends where it starts. + // The close() call should make the path effectively equivalent. + // https://github.com/airbnb/lottie-android/issues/2329 + continue; + } path.lineTo(x, y); } diff --git a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/LottieSnapshotTest.kt b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/LottieSnapshotTest.kt index 06a521354a..a4f120bd58 100644 --- a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/LottieSnapshotTest.kt +++ b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/LottieSnapshotTest.kt @@ -32,6 +32,7 @@ import com.airbnb.lottie.snapshots.tests.MarkersTestCase import com.airbnb.lottie.snapshots.tests.NightModeTestCase import com.airbnb.lottie.snapshots.tests.OutlineMasksAndMattesTestCase import com.airbnb.lottie.snapshots.tests.PartialFrameProgressTestCase +import com.airbnb.lottie.snapshots.tests.PolygonStrokeTestCase import com.airbnb.lottie.snapshots.tests.ProdAnimationsTestCase import com.airbnb.lottie.snapshots.tests.ScaleTypesTestCase import com.airbnb.lottie.snapshots.tests.SeekBarTestCase @@ -160,6 +161,7 @@ class LottieSnapshotTest { ClipChildrenTestCase(), SoftwareRenderingDynamicPropertiesInvalidationTestCase(), SeekBarTestCase(), + PolygonStrokeTestCase(), CompositionFrameRate(), ClipTextToBoundingBoxTestCase(), ) diff --git a/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/PolygonStrokeTestCase.kt b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/PolygonStrokeTestCase.kt new file mode 100644 index 0000000000..470e179376 --- /dev/null +++ b/snapshot-tests/src/androidTest/java/com/airbnb/lottie/snapshots/tests/PolygonStrokeTestCase.kt @@ -0,0 +1,13 @@ +package com.airbnb.lottie.snapshots.tests + +import com.airbnb.lottie.snapshots.SnapshotTestCase +import com.airbnb.lottie.snapshots.SnapshotTestCaseContext +import com.airbnb.lottie.snapshots.withDrawable + +class PolygonStrokeTestCase : SnapshotTestCase { + override suspend fun SnapshotTestCaseContext.run() { + withDrawable("Tests/TriangleLargeStroke.json", "Triangle", "Large Stroke") { drawable -> + drawable.progress = 0.40999988f + } + } +} diff --git a/snapshot-tests/src/main/assets/Tests/TriangleLargeStroke.json b/snapshot-tests/src/main/assets/Tests/TriangleLargeStroke.json new file mode 100644 index 0000000000..19c4144c61 --- /dev/null +++ b/snapshot-tests/src/main/assets/Tests/TriangleLargeStroke.json @@ -0,0 +1 @@ +{"v":"5.12.2","fr":29.9700012207031,"ip":0,"op":60.0000024438501,"w":1200,"h":1200,"nm":"Repro 2 - Large","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[600,376,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":301,"ix":7},"os":{"a":0,"k":30,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":100,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-205.188,19.281],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":59.0000024031193,"s":[360]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"sr","sy":2,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"or":{"a":0,"k":301,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 2","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"st","c":{"a":0,"k":[1,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":100,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[225,421],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":59.0000024031193,"s":[360]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60.0000024438501,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}}