content: 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 final 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));
      }
    }
  }
  