| package com.airbnb.lottie.utils; |
| |
| import android.graphics.Path; |
| import android.graphics.PointF; |
| import androidx.annotation.FloatRange; |
| |
| import com.airbnb.lottie.animation.content.KeyPathElementContent; |
| import com.airbnb.lottie.model.CubicCurveData; |
| import com.airbnb.lottie.model.KeyPath; |
| import com.airbnb.lottie.model.content.ShapeData; |
| |
| import java.util.List; |
| |
| public class MiscUtils { |
| private static PointF pathFromDataCurrentPoint = new PointF(); |
| |
| public static PointF addPoints(PointF p1, PointF p2) { |
| return new PointF(p1.x + p2.x, p1.y + p2.y); |
| } |
| |
| public static void getPathFromData(ShapeData shapeData, Path outPath) { |
| outPath.reset(); |
| PointF initialPoint = shapeData.getInitialPoint(); |
| outPath.moveTo(initialPoint.x, initialPoint.y); |
| pathFromDataCurrentPoint.set(initialPoint.x, initialPoint.y); |
| for (int i = 0; i < shapeData.getCurves().size(); i++) { |
| CubicCurveData curveData = shapeData.getCurves().get(i); |
| PointF cp1 = curveData.getControlPoint1(); |
| PointF cp2 = curveData.getControlPoint2(); |
| PointF vertex = curveData.getVertex(); |
| |
| if (cp1.equals(pathFromDataCurrentPoint) && cp2.equals(vertex)) { |
| // On some phones like Samsung phones, zero valued control points can cause artifacting. |
| // https://github.com/airbnb/lottie-android/issues/275 |
| // |
| // This does its best to add a tiny value to the vertex without affecting the final |
| // animation as much as possible. |
| // outPath.rMoveTo(0.01f, 0.01f); |
| outPath.lineTo(vertex.x, vertex.y); |
| } else { |
| outPath.cubicTo(cp1.x, cp1.y, cp2.x, cp2.y, vertex.x, vertex.y); |
| } |
| pathFromDataCurrentPoint.set(vertex.x, vertex.y); |
| } |
| if (shapeData.isClosed()) { |
| outPath.close(); |
| } |
| } |
| |
| public static float lerp(float a, float b, @FloatRange(from = 0f, to = 1f) float percentage) { |
| return a + percentage * (b - a); |
| } |
| |
| public static double lerp(double a, double b, @FloatRange(from = 0f, to = 1f) double percentage) { |
| return a + percentage * (b - a); |
| } |
| |
| public static int lerp(int a, int b, @FloatRange(from = 0f, to = 1f) float percentage) { |
| return (int) (a + percentage * (b - a)); |
| } |
| |
| static int floorMod(float x, float y) { |
| return floorMod((int) x, (int) y); |
| } |
| |
| private static int floorMod(int x, int y) { |
| return x - y * floorDiv(x, y); |
| } |
| |
| private static int floorDiv(int x, int y) { |
| int r = x / y; |
| boolean sameSign = (x ^ y) >= 0; |
| int mod = x % y; |
| if (!sameSign && mod != 0) { |
| r--; |
| } |
| return r; |
| } |
| |
| public static int clamp(int number, int min, int max) { |
| return Math.max(min, Math.min(max, number)); |
| } |
| |
| public static float clamp(float number, float min, float max) { |
| return Math.max(min, Math.min(max, number)); |
| } |
| |
| public static boolean contains(float number, float rangeMin, float rangeMax) { |
| return number >= rangeMin && number <= rangeMax; |
| } |
| |
| /** |
| * Helper method for any {@link KeyPathElementContent} that will check if the content |
| * fully matches the keypath then will add itself as the final key, resolve it, and add |
| * it to the accumulator list. |
| * |
| * Any {@link KeyPathElementContent} should call through to this as its implementation of |
| * {@link KeyPathElementContent#resolveKeyPath(KeyPath, int, List, KeyPath)}. |
| */ |
| public static void resolveKeyPath(KeyPath keyPath, int depth, List<KeyPath> accumulator, |
| KeyPath currentPartialKeyPath, KeyPathElementContent content) { |
| if (keyPath.fullyResolvesTo(content.getName(), depth)) { |
| currentPartialKeyPath = currentPartialKeyPath.addKey(content.getName()); |
| accumulator.add(currentPartialKeyPath.resolve(content)); |
| } |
| } |
| } |