use of com.airbnb.lottie.model.CubicCurveData in project lottie-android by airbnb.
the class ShapeDataParser method parse.
@Override
public ShapeData parse(JsonReader reader, float scale) throws IOException {
// level.
if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) {
reader.beginArray();
}
boolean closed = false;
List<PointF> pointsArray = null;
List<PointF> inTangents = null;
List<PointF> outTangents = null;
reader.beginObject();
while (reader.hasNext()) {
switch(reader.selectName(NAMES)) {
case 0:
closed = reader.nextBoolean();
break;
case 1:
pointsArray = JsonUtils.jsonToPoints(reader, scale);
break;
case 2:
inTangents = JsonUtils.jsonToPoints(reader, scale);
break;
case 3:
outTangents = JsonUtils.jsonToPoints(reader, scale);
break;
default:
reader.skipName();
reader.skipValue();
}
}
reader.endObject();
if (reader.peek() == JsonReader.Token.END_ARRAY) {
reader.endArray();
}
if (pointsArray == null || inTangents == null || outTangents == null) {
throw new IllegalArgumentException("Shape data was missing information.");
}
if (pointsArray.isEmpty()) {
return new ShapeData(new PointF(), false, Collections.<CubicCurveData>emptyList());
}
int length = pointsArray.size();
PointF vertex = pointsArray.get(0);
PointF initialPoint = vertex;
List<CubicCurveData> curves = new ArrayList<>(length);
for (int i = 1; i < length; i++) {
vertex = pointsArray.get(i);
PointF previousVertex = pointsArray.get(i - 1);
PointF cp1 = outTangents.get(i - 1);
PointF cp2 = inTangents.get(i);
PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
}
if (closed) {
vertex = pointsArray.get(0);
PointF previousVertex = pointsArray.get(length - 1);
PointF cp1 = outTangents.get(length - 1);
PointF cp2 = inTangents.get(0);
PointF shapeCp1 = MiscUtils.addPoints(previousVertex, cp1);
PointF shapeCp2 = MiscUtils.addPoints(vertex, cp2);
curves.add(new CubicCurveData(shapeCp1, shapeCp2, vertex));
}
return new ShapeData(initialPoint, closed, curves);
}
use of com.airbnb.lottie.model.CubicCurveData in project lottie-android by airbnb.
the class MiscUtils method getPathFromData.
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();
}
}
use of com.airbnb.lottie.model.CubicCurveData in project lottie-android by airbnb.
the class RoundedCornersContent method modifyShape.
/**
* Rounded corner algorithm:
* Iterate through each vertex.
* If a vertex is a sharp corner, it rounds it.
* If a vertex has control points, it is already rounded, so it does nothing.
* <p>
* To round a vertex:
* Split the vertex into two.
* Move vertex 1 directly towards the previous vertex.
* Set vertex 1's in control point to itself so it is not rounded on that side.
* Extend vertex 1's out control point towards the original vertex.
* <p>
* Repeat for vertex 2:
* Move vertex 2 directly towards the next vertex.
* Set vertex 2's out point to itself so it is not rounded on that side.
* Extend vertex 2's in control point towards the original vertex.
* <p>
* The distance that the vertices and control points are moved are relative to the
* shape's vertex distances and the roundedness set in the animation.
*/
@Override
public ShapeData modifyShape(ShapeData startingShapeData) {
List<CubicCurveData> startingCurves = startingShapeData.getCurves();
if (startingCurves.size() <= 2) {
return startingShapeData;
}
float roundedness = roundedCorners.getValue();
if (roundedness == 0f) {
return startingShapeData;
}
ShapeData modifiedShapeData = getShapeData(startingShapeData);
modifiedShapeData.setInitialPoint(startingShapeData.getInitialPoint().x, startingShapeData.getInitialPoint().y);
List<CubicCurveData> modifiedCurves = modifiedShapeData.getCurves();
int modifiedCurvesIndex = 0;
boolean isClosed = startingShapeData.isClosed();
// outCp=if closed vertex else curves[0].cp1
for (int i = 0; i < startingCurves.size(); i++) {
CubicCurveData startingCurve = startingCurves.get(i);
CubicCurveData previousCurve = startingCurves.get(floorMod(i - 1, startingCurves.size()));
CubicCurveData previousPreviousCurve = startingCurves.get(floorMod(i - 2, startingCurves.size()));
PointF vertex = (i == 0 && !isClosed) ? startingShapeData.getInitialPoint() : previousCurve.getVertex();
PointF inPoint = (i == 0 && !isClosed) ? vertex : previousCurve.getControlPoint2();
PointF outPoint = startingCurve.getControlPoint1();
PointF previousVertex = previousPreviousCurve.getVertex();
PointF nextVertex = startingCurve.getVertex();
// We can't round the corner of the end of a non-closed curve.
boolean isEndOfCurve = !startingShapeData.isClosed() && (i == 0 && i == startingCurves.size() - 1);
if (inPoint.equals(vertex) && outPoint.equals(vertex) && !isEndOfCurve) {
// This vertex is a point. Round its corners
float dxToPreviousVertex = vertex.x - previousVertex.x;
float dyToPreviousVertex = vertex.y - previousVertex.y;
float dxToNextVertex = nextVertex.x - vertex.x;
float dyToNextVertex = nextVertex.y - vertex.y;
float dToPreviousVertex = (float) Math.hypot(dxToPreviousVertex, dyToPreviousVertex);
float dToNextVertex = (float) Math.hypot(dxToNextVertex, dyToNextVertex);
float previousVertexPercent = Math.min(roundedness / dToPreviousVertex, 0.5f);
float nextVertexPercent = Math.min(roundedness / dToNextVertex, 0.5f);
// Split the vertex into two and move each vertex towards the previous/next vertex.
float newVertex1X = vertex.x + (previousVertex.x - vertex.x) * previousVertexPercent;
float newVertex1Y = vertex.y + (previousVertex.y - vertex.y) * previousVertexPercent;
float newVertex2X = vertex.x + (nextVertex.x - vertex.x) * nextVertexPercent;
float newVertex2Y = vertex.y + (nextVertex.y - vertex.y) * nextVertexPercent;
// Extend the new vertex control point towards the original vertex.
float newVertex1OutPointX = newVertex1X - (newVertex1X - vertex.x) * ROUNDED_CORNER_MAGIC_NUMBER;
float newVertex1OutPointY = newVertex1Y - (newVertex1Y - vertex.y) * ROUNDED_CORNER_MAGIC_NUMBER;
float newVertex2InPointX = newVertex2X - (newVertex2X - vertex.x) * ROUNDED_CORNER_MAGIC_NUMBER;
float newVertex2InPointY = newVertex2Y - (newVertex2Y - vertex.y) * ROUNDED_CORNER_MAGIC_NUMBER;
// Remap vertex/in/out point to CubicCurveData.
// Refer to the docs for CubicCurveData for more info on the difference.
CubicCurveData previousCurveData = modifiedCurves.get(floorMod(modifiedCurvesIndex - 1, modifiedCurves.size()));
CubicCurveData currentCurveData = modifiedCurves.get(modifiedCurvesIndex);
previousCurveData.setControlPoint2(newVertex1X, newVertex1Y);
previousCurveData.setVertex(newVertex1X, newVertex1Y);
if (i == 0) {
modifiedShapeData.setInitialPoint(newVertex1X, newVertex1Y);
}
currentCurveData.setControlPoint1(newVertex1OutPointX, newVertex1OutPointY);
modifiedCurvesIndex++;
previousCurveData = currentCurveData;
currentCurveData = modifiedCurves.get(modifiedCurvesIndex);
previousCurveData.setControlPoint2(newVertex2InPointX, newVertex2InPointY);
previousCurveData.setVertex(newVertex2X, newVertex2Y);
currentCurveData.setControlPoint1(newVertex2X, newVertex2Y);
modifiedCurvesIndex++;
} else {
// This vertex is not a point. Don't modify it. Refer to the documentation above and for CubicCurveData for mapping a vertex
// oriented point to CubicCurveData (path segments).
CubicCurveData previousCurveData = modifiedCurves.get(floorMod(modifiedCurvesIndex - 1, modifiedCurves.size()));
CubicCurveData currentCurveData = modifiedCurves.get(modifiedCurvesIndex);
previousCurveData.setControlPoint2(previousCurve.getVertex().x, previousCurve.getVertex().y);
previousCurveData.setVertex(previousCurve.getVertex().x, previousCurve.getVertex().y);
currentCurveData.setControlPoint1(startingCurve.getVertex().x, startingCurve.getVertex().y);
modifiedCurvesIndex++;
}
}
return modifiedShapeData;
}
use of com.airbnb.lottie.model.CubicCurveData in project lottie-android by airbnb.
the class RoundedCornersContent method getShapeData.
/**
* Returns a shape data with the correct number of vertices for the rounded corners shape.
* This just returns the object. It does not update any values within the shape.
*/
@NonNull
private ShapeData getShapeData(ShapeData startingShapeData) {
List<CubicCurveData> startingCurves = startingShapeData.getCurves();
boolean isClosed = startingShapeData.isClosed();
int vertices = 0;
for (int i = startingCurves.size() - 1; i >= 0; i--) {
CubicCurveData startingCurve = startingCurves.get(i);
CubicCurveData previousCurve = startingCurves.get(floorMod(i - 1, startingCurves.size()));
PointF vertex = (i == 0 && !isClosed) ? startingShapeData.getInitialPoint() : previousCurve.getVertex();
PointF inPoint = (i == 0 && !isClosed) ? vertex : previousCurve.getControlPoint2();
PointF outPoint = startingCurve.getControlPoint1();
boolean isEndOfCurve = !startingShapeData.isClosed() && (i == 0 && i == startingCurves.size() - 1);
if (inPoint.equals(vertex) && outPoint.equals(vertex) && !isEndOfCurve) {
vertices += 2;
} else {
vertices += 1;
}
}
if (shapeData == null || shapeData.getCurves().size() != vertices) {
List<CubicCurveData> newCurves = new ArrayList<>(vertices);
for (int i = 0; i < vertices; i++) {
newCurves.add(new CubicCurveData());
}
shapeData = new ShapeData(new PointF(0f, 0f), false, newCurves);
}
shapeData.setClosed(isClosed);
return shapeData;
}
use of com.airbnb.lottie.model.CubicCurveData in project lottie-android by airbnb.
the class ShapeData method interpolateBetween.
public void interpolateBetween(ShapeData shapeData1, ShapeData shapeData2, @FloatRange(from = 0f, to = 1f) float percentage) {
if (initialPoint == null) {
initialPoint = new PointF();
}
closed = shapeData1.isClosed() || shapeData2.isClosed();
if (shapeData1.getCurves().size() != shapeData2.getCurves().size()) {
Logger.warning("Curves must have the same number of control points. Shape 1: " + shapeData1.getCurves().size() + "\tShape 2: " + shapeData2.getCurves().size());
}
int points = Math.min(shapeData1.getCurves().size(), shapeData2.getCurves().size());
if (curves.size() < points) {
for (int i = curves.size(); i < points; i++) {
curves.add(new CubicCurveData());
}
} else if (curves.size() > points) {
for (int i = curves.size() - 1; i >= points; i--) {
curves.remove(curves.size() - 1);
}
}
PointF initialPoint1 = shapeData1.getInitialPoint();
PointF initialPoint2 = shapeData2.getInitialPoint();
setInitialPoint(MiscUtils.lerp(initialPoint1.x, initialPoint2.x, percentage), MiscUtils.lerp(initialPoint1.y, initialPoint2.y, percentage));
for (int i = curves.size() - 1; i >= 0; i--) {
CubicCurveData curve1 = shapeData1.getCurves().get(i);
CubicCurveData curve2 = shapeData2.getCurves().get(i);
PointF cp11 = curve1.getControlPoint1();
PointF cp21 = curve1.getControlPoint2();
PointF vertex1 = curve1.getVertex();
PointF cp12 = curve2.getControlPoint1();
PointF cp22 = curve2.getControlPoint2();
PointF vertex2 = curve2.getVertex();
curves.get(i).setControlPoint1(MiscUtils.lerp(cp11.x, cp12.x, percentage), MiscUtils.lerp(cp11.y, cp12.y, percentage));
curves.get(i).setControlPoint2(MiscUtils.lerp(cp21.x, cp22.x, percentage), MiscUtils.lerp(cp21.y, cp22.y, percentage));
curves.get(i).setVertex(MiscUtils.lerp(vertex1.x, vertex2.x, percentage), MiscUtils.lerp(vertex1.y, vertex2.y, percentage));
}
}
Aggregations