Search in sources :

Example 1 with CubicCurveData

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);
}
Also used : PointF(android.graphics.PointF) CubicCurveData(com.airbnb.lottie.model.CubicCurveData) ArrayList(java.util.ArrayList) ShapeData(com.airbnb.lottie.model.content.ShapeData)

Example 2 with CubicCurveData

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();
    }
}
Also used : PointF(android.graphics.PointF) CubicCurveData(com.airbnb.lottie.model.CubicCurveData)

Example 3 with CubicCurveData

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;
}
Also used : PointF(android.graphics.PointF) CubicCurveData(com.airbnb.lottie.model.CubicCurveData) ShapeData(com.airbnb.lottie.model.content.ShapeData)

Example 4 with CubicCurveData

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;
}
Also used : PointF(android.graphics.PointF) CubicCurveData(com.airbnb.lottie.model.CubicCurveData) ArrayList(java.util.ArrayList) ShapeData(com.airbnb.lottie.model.content.ShapeData) NonNull(androidx.annotation.NonNull)

Example 5 with CubicCurveData

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));
    }
}
Also used : PointF(android.graphics.PointF) CubicCurveData(com.airbnb.lottie.model.CubicCurveData)

Aggregations

PointF (android.graphics.PointF)5 CubicCurveData (com.airbnb.lottie.model.CubicCurveData)5 ShapeData (com.airbnb.lottie.model.content.ShapeData)3 ArrayList (java.util.ArrayList)2 NonNull (androidx.annotation.NonNull)1