From 6e9bcf03e88ed8428ffce6e37276a28bfafc7851 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 26 May 2022 11:24:55 -0700 Subject: [PATCH 1/2] De-dupe gradient positions --- .../lottie/parser/GradientColorParser.java | 66 +++++++++++++++++-- .../parser/GradientColorParserTest.java | 4 ++ 2 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java 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 0dbfb5b76..ad4c58230 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 @@ private GradientColor addOpacityStopsToGradientIfNeeded(GradientColor gradientCo } } - 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,60 @@ private int getColorInBetweenOpacityStops(float position, int color, float[] opa } 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 mergedIndex = 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; + if (aIndex < arrayA.length) { + a = arrayA[aIndex]; + } else { + a = Float.NaN; + } + + final float b; + if (bIndex < arrayB.length) { + b = arrayB[bIndex]; + } else { + b = Float.NaN; + } + + if (Float.isNaN(b) || a < b) { + mergedNotTruncated[mergedIndex++] = a; + aIndex++; + } else if (Float.isNaN(a) || b < a) { + mergedNotTruncated[mergedIndex++] = b; + bIndex++; + } else { + mergedNotTruncated[mergedIndex++] = a; + aIndex++; + bIndex++; + numDuplicates++; + } + } + + if (numDuplicates == 0) { + return mergedNotTruncated; + } + + + float[] merged = new float[mergedNotTruncated.length - numDuplicates]; + System.arraycopy(mergedNotTruncated, 0, merged, 0, merged.length); + return merged; + } } \ 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 000000000..5d88bbebf --- /dev/null +++ b/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java @@ -0,0 +1,4 @@ +import static org.junit.Assert.*; +public class GradientColorParserTest { + +} \ No newline at end of file From f7a7b5f2633cdf39fb551a6b5903d0048e034911 Mon Sep 17 00:00:00 2001 From: Gabriel Peal Date: Thu, 26 May 2022 11:42:29 -0700 Subject: [PATCH 2/2] Cleanup --- .../lottie/parser/GradientColorParser.java | 26 ++++----------- .../parser/GradientColorParserTest.java | 32 +++++++++++++++++-- 2 files changed, 36 insertions(+), 22 deletions(-) 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 ad4c58230..50a52e3f3 100644 --- a/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java +++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java @@ -234,34 +234,22 @@ protected static float[] mergeUniqueElements(float[] arrayA, float[] arrayB) { int aIndex = 0; int bIndex = 0; - int mergedIndex = 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; - if (aIndex < arrayA.length) { - a = arrayA[aIndex]; - } else { - a = Float.NaN; - } - - final float b; - if (bIndex < arrayB.length) { - b = arrayB[bIndex]; - } else { - b = Float.NaN; - } + 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[mergedIndex++] = a; + mergedNotTruncated[i] = a; aIndex++; } else if (Float.isNaN(a) || b < a) { - mergedNotTruncated[mergedIndex++] = b; + mergedNotTruncated[i] = b; bIndex++; } else { - mergedNotTruncated[mergedIndex++] = a; + mergedNotTruncated[i] = a; aIndex++; bIndex++; numDuplicates++; @@ -273,8 +261,6 @@ protected static float[] mergeUniqueElements(float[] arrayA, float[] arrayB) { } - float[] merged = new float[mergedNotTruncated.length - numDuplicates]; - System.arraycopy(mergedNotTruncated, 0, merged, 0, merged.length); - return merged; + 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 index 5d88bbebf..79682e167 100644 --- a/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java +++ b/lottie/src/test/java/com/airbnb/lottie/parser/GradientColorParserTest.java @@ -1,4 +1,32 @@ -import static org.junit.Assert.*; +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