De-dupe gradient stops (#2081)
diff --git a/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
index 0dbfb5b..50a52e3 100644
--- a/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
+++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java
@@ -142,14 +142,12 @@
}
}
- int newColorPoints = colorPoints + opacityStops;
- float[] newPositions = new float[newColorPoints];
+ // Pre-SKIA (Oreo) devices render artifacts when there is two stops in the same position.
+ // As a result, we have to de-dupe the merge color and opacity stop positions.
+ float[] newPositions = mergeUniqueElements(gradientColor.getPositions(), opacityStopPositions);
+ int newColorPoints = newPositions.length;
int[] newColors = new int[newColorPoints];
- System.arraycopy(gradientColor.getPositions(), 0, newPositions, 0, colorPoints);
- System.arraycopy(opacityStopPositions, 0, newPositions, colorPoints, opacityStops);
- Arrays.sort(newPositions);
-
for (int i = 0; i < newColorPoints; i++) {
float position = newPositions[i];
int colorStopIndex = Arrays.binarySearch(colorStopPositions, position);
@@ -223,4 +221,46 @@
}
throw new IllegalArgumentException("Unreachable code.");
}
+
+ /**
+ * Takes two sorted float arrays and merges their elements while removing duplicates.
+ */
+ protected static float[] mergeUniqueElements(float[] arrayA, float[] arrayB) {
+ if (arrayA.length == 0) {
+ return arrayB;
+ } else if (arrayB.length == 0) {
+ return arrayA;
+ }
+
+ int aIndex = 0;
+ int bIndex = 0;
+ int numDuplicates = 0;
+ // This will be the merged list but may be longer than what is needed if there are duplicates.
+ // If there are, the 0 elements at the end need to be truncated.
+ float[] mergedNotTruncated = new float[arrayA.length + arrayB.length];
+ for (int i = 0; i < mergedNotTruncated.length; i++) {
+ final float a = aIndex < arrayA.length ? arrayA[aIndex] : Float.NaN;
+ final float b = bIndex < arrayB.length ? arrayB[bIndex] : Float.NaN;
+
+ if (Float.isNaN(b) || a < b) {
+ mergedNotTruncated[i] = a;
+ aIndex++;
+ } else if (Float.isNaN(a) || b < a) {
+ mergedNotTruncated[i] = b;
+ bIndex++;
+ } else {
+ mergedNotTruncated[i] = a;
+ aIndex++;
+ bIndex++;
+ numDuplicates++;
+ }
+ }
+
+ if (numDuplicates == 0) {
+ return mergedNotTruncated;
+ }
+
+
+ return Arrays.copyOf(mergedNotTruncated, mergedNotTruncated.length - numDuplicates);
+ }
}
\ No newline at end of file
diff --git a/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java b/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java
new file mode 100644
index 0000000..79682e1
--- /dev/null
+++ b/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java
@@ -0,0 +1,32 @@
+package com.airbnb.lottie.parser;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import org.junit.Test;
+
+public class GradientColorParserTest {
+
+ @Test public void testNoDistinctShort() {
+ assertMerged(new float[]{1}, new float[]{2}, new float[]{1, 2});
+ }
+
+ @Test public void testNoDistinct() {
+ assertMerged(new float[]{1, 2, 3}, new float[]{4, 5, 6}, new float[]{1, 2, 3, 4, 5, 6});
+ }
+
+ @Test public void testWithDistinct() {
+ assertMerged(new float[]{1, 2, 3, 5}, new float[]{4, 5, 6}, new float[]{1, 2, 3, 4, 5, 6});
+ }
+
+ @Test public void testWithDistinctInterleavingValues() {
+ assertMerged(new float[]{2, 4, 5, 6, 8, 10}, new float[]{1, 3, 4, 5, 7, 9}, new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+ }
+
+ @Test public void testIdentical() {
+ assertMerged(new float[]{2, 3}, new float[]{2, 3}, new float[]{2, 3});
+ }
+
+ private void assertMerged(float[] arrayA, float[] arrayB, float[] merged) {
+ assertArrayEquals(merged, GradientColorParser.mergeUniqueElements(arrayA, arrayB), 0f);
+ }
+}
\ No newline at end of file