From db452ead07707d25df64358c348d157704dc82f2 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 15 Dec 2023 18:59:58 +0100 Subject: [PATCH] GradientColorKeyframeAnimation does not handle progress outside [0,1] (#2427) - Closes #2426 - Improve performance by running GammaEvaluator once Co-authored-by: Sven Obser --- .../lottie/model/content/GradientColor.java | 39 +++++++++++++++ .../lottie/parser/GradientColorParser.java | 11 +++-- .../airbnb/lottie/utils/GammaEvaluator.java | 9 +++- .../model/content/GradientColorTest.java | 46 ++++++++++++++++++ .../Tests/GradientColorKeyframeAnimation.zip | Bin 0 -> 1514 bytes 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 lottie/src/test/java/com/airbnb/lottie/model/content/GradientColorTest.java create mode 100644 snapshot-tests/src/main/assets/Tests/GradientColorKeyframeAnimation.zip diff --git a/lottie/src/main/java/com/airbnb/lottie/model/content/GradientColor.java b/lottie/src/main/java/com/airbnb/lottie/model/content/GradientColor.java index 3485d5832e..41e1e29edf 100644 --- a/lottie/src/main/java/com/airbnb/lottie/model/content/GradientColor.java +++ b/lottie/src/main/java/com/airbnb/lottie/model/content/GradientColor.java @@ -28,6 +28,19 @@ public int getSize() { } public void lerp(GradientColor gc1, GradientColor gc2, float progress) { + // Fast return in case start and end is the same + // or if progress is at start/end or out of [0,1] bounds + if (gc1.equals(gc2)) { + copyFrom(gc1); + return; + } else if (progress <= 0f) { + copyFrom(gc1); + return; + } else if (progress >= 1f) { + copyFrom(gc2); + return; + } + if (gc1.colors.length != gc2.colors.length) { throw new IllegalArgumentException("Cannot interpolate between gradients. Lengths vary (" + gc1.colors.length + " vs " + gc2.colors.length + ")"); @@ -56,6 +69,25 @@ public GradientColor copyWithPositions(float[] positions) { return new GradientColor(positions, colors); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GradientColor that = (GradientColor) o; + return Arrays.equals(positions, that.positions) && Arrays.equals(colors, that.colors); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(positions); + result = 31 * result + Arrays.hashCode(colors); + return result; + } + private int getColorForPosition(float position) { int existingIndex = Arrays.binarySearch(positions, position); if (existingIndex >= 0) { @@ -76,4 +108,11 @@ private int getColorForPosition(float position) { float fraction = (position - startPosition) / (endPosition - startPosition); return GammaEvaluator.evaluate(fraction, startColor, endColor); } + + private void copyFrom(GradientColor other) { + for (int i = 0; i < other.colors.length; i++) { + positions[i] = other.positions[i]; + colors[i] = other.colors[i]; + } + } } 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 84f20273b8..5d779790d2 100644 --- a/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java +++ b/lottie/src/main/java/com/airbnb/lottie/parser/GradientColorParser.java @@ -189,12 +189,16 @@ int getColorInBetweenColorStops(float position, float opacity, float[] colorStop float distanceBetweenColors = colorStopPositions[i] - colorStopPositions[i - 1]; float distanceToLowerColor = position - colorStopPositions[i - 1]; float percentage = distanceToLowerColor / distanceBetweenColors; + int upperColor = colorStopColors[i]; int lowerColor = colorStopColors[i - 1]; + int intermediateColor = GammaEvaluator.evaluate(percentage, lowerColor, upperColor); + int a = (int) (opacity * 255); - int r = GammaEvaluator.evaluate(percentage, Color.red(lowerColor), Color.red(upperColor)); - int g = GammaEvaluator.evaluate(percentage, Color.green(lowerColor), Color.green(upperColor)); - int b = GammaEvaluator.evaluate(percentage, Color.blue(lowerColor), Color.blue(upperColor)); + int r = Color.red(intermediateColor); + int g = Color.green(intermediateColor); + int b = Color.blue(intermediateColor); + return Color.argb(a, r, g, b); } throw new IllegalArgumentException("Unreachable code."); @@ -269,7 +273,6 @@ protected static float[] mergeUniqueElements(float[] arrayA, float[] arrayB) { return mergedNotTruncated; } - return Arrays.copyOf(mergedNotTruncated, mergedNotTruncated.length - numDuplicates); } } diff --git a/lottie/src/main/java/com/airbnb/lottie/utils/GammaEvaluator.java b/lottie/src/main/java/com/airbnb/lottie/utils/GammaEvaluator.java index d509fcf08a..3580d13d4c 100644 --- a/lottie/src/main/java/com/airbnb/lottie/utils/GammaEvaluator.java +++ b/lottie/src/main/java/com/airbnb/lottie/utils/GammaEvaluator.java @@ -25,9 +25,16 @@ private static float EOCF_sRGB(float srgb) { } public static int evaluate(float fraction, int startInt, int endInt) { + // Fast return in case start and end is the same + // or if fraction is at start/end or out of [0,1] bounds if (startInt == endInt) { return startInt; + } else if (fraction <= 0f) { + return startInt; + } else if (fraction >= 1f) { + return endInt; } + float startA = ((startInt >> 24) & 0xff) / 255.0f; float startR = ((startInt >> 16) & 0xff) / 255.0f; float startG = ((startInt >> 8) & 0xff) / 255.0f; @@ -61,4 +68,4 @@ public static int evaluate(float fraction, int startInt, int endInt) { return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b); } -} \ No newline at end of file +} diff --git a/lottie/src/test/java/com/airbnb/lottie/model/content/GradientColorTest.java b/lottie/src/test/java/com/airbnb/lottie/model/content/GradientColorTest.java new file mode 100644 index 0000000000..719a5fca1a --- /dev/null +++ b/lottie/src/test/java/com/airbnb/lottie/model/content/GradientColorTest.java @@ -0,0 +1,46 @@ +package com.airbnb.lottie.model.content; + +import junit.framework.TestCase; +import org.junit.Test; + +import java.util.Arrays; + +public class GradientColorTest extends TestCase { + + private final GradientColor start = new GradientColor(new float[]{0f, 1f}, new int[]{0xFF000000, 0xFF020202}); + + private final GradientColor end = new GradientColor(new float[]{0f, 1f}, new int[]{0xFF020202, 0xFF040404}); + + private final GradientColor gradient = new GradientColor(new float[2], new int[2]); + + @Test + public void testLerpWithOutOfBoundsNegativeProgress() { + gradient.lerp(start, end, -42f); + assertEquals(start, gradient); + } + + @Test + public void testLerpWithZeroProgress() { + gradient.lerp(start, end, 0f); + assertEquals(start, gradient); + } + + @Test + public void testLerpWithHalfProgress() { + gradient.lerp(start, end, 0.5f); + GradientColor half = new GradientColor(new float[]{0f, 1f}, new int[]{0xFF010101, 0xFF030303}); + assertEquals(half, gradient); + } + + @Test + public void testLerpWithOneProgress() { + gradient.lerp(start, end, 1f); + assertEquals(end, gradient); + } + + @Test + public void testLerpWithOutOfBoundsPositiveProgress() { + gradient.lerp(start, end, 42f); + assertEquals(end, gradient); + } +} diff --git a/snapshot-tests/src/main/assets/Tests/GradientColorKeyframeAnimation.zip b/snapshot-tests/src/main/assets/Tests/GradientColorKeyframeAnimation.zip new file mode 100644 index 0000000000000000000000000000000000000000..d8188d104e6fc8ae4af9a3729c280e17f8e0d6d2 GIT binary patch literal 1514 zcmb`H`#Tc~7{@omTw2O;*@!47$&QXQYJ2}cux2%#8!%#~2DV7zIFf3R1$KOsabAdC=Ve+dErZ|0Ej(YYl2 zUPeSq0)WDwfdIg6F#xcr=$1v%Uoq+yA~q?qprF|a7v1r~iI3q%;~089hqs`K3$x=W{$ z^<~VZyKOo8Z4+>~e9Z0q(ikv%6b0mCJR`(qFu?5_tSez}F&Ycn(|%?w!JmWW#Es|! zS>zAd(wj_R8+pPNxg2#vK(KRvI_B$ZQ^|<c5 zNYU$_VAs`h6GO<{%y~L(-pOpA_TWmiQlG877%LPRsjlj$m~fH`wucGLNkiBolQ2`7 za&yj_>b2-xAJ}X|&`7`W{HeU~hm~j+am_kBeXT&^mspH(>uB+K!G+^i#%kf!1|6>QXc+`NX9uK?C*Pie;9mv7TRIAz~({G@+Zt9>SGwv*X9Vb>C zH(Cr;7xFY^;P)@IT{m92zxC{E!uHDC%odNdv96Hw#I!MtGFTTAdze3Q^+?|=@vqgC z%g#BmDY87t>>Jgk&aYZfvfBq`6AF@IvQqCuT;w}Ob?8WzdE^`BC}TcWPD$@s?wKInIi_+4`4Viu zleQ!t+2c9`%iSQGfXru3f*6maVFg#6vMgs1;r^9|j~s2a8gMgkGMlY0L>KICLUm z{78o=W!2w6w2O{g(9Hg|7YNhmgvB0>f#M({hL(P%1PKegj~%&=C|2L?oKRZhcZn~P zVM3{3D;&;fWPtV{xZ%E}qDMh0pYFZM~L#cM$M!4k~CTWOwEZz^xk>D6gKxd@S?!!@3d znBdBJ{XtS!p~d7khe2eRNkr#Y*g(mo1lk)lQ}XcXk5`m3P!Z^}ZymR&hUU|j99y}J zYTHeqshY~`fx8V~%GtFD8W%myehz`8ab}8@%B|`QD$M#SY|6xSGJJ)c+H94Hj52c-1(oTo`TnLHBRR=v^8O!71hU!-?!SM$DLJ@ z1C5icn>s8ty740)C*0)>9L|j26y87?Y%2t~L&1(=#ihcI z-+6D$$69yz%)SNuXJae|EEXfx>b)uq0PKMjivbGBfc}N-e{Xw`{YO;^d(;1bTr5UJ Qbgv4yYvOKR7TMeW2I2a(6951J literal 0 HcmV?d00001