blob: ec285bdbb5c874af5e388a72fc2ade4706f69b27 [file] [log] [blame]
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 double clamp(double number, double min, double 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.
* <p>
* 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));
}
}
}