Search in sources :

Example 1 with ShapeData

use of com.airbnb.lottie.model.content.ShapeData 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 ShapeData

use of com.airbnb.lottie.model.content.ShapeData in project lottie-android by airbnb.

the class BaseLayer method applyMasks.

private void applyMasks(Canvas canvas, Matrix matrix) {
    L.beginSection("Layer#saveLayer");
    Utils.saveLayerCompat(canvas, rect, dstInPaint, SAVE_FLAGS);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
        // Pre-Pie, offscreen buffers were opaque which meant that outer border of a mask
        // might get drawn depending on the result of float rounding.
        clearCanvas(canvas);
    }
    L.endSection("Layer#saveLayer");
    for (int i = 0; i < mask.getMasks().size(); i++) {
        Mask mask = this.mask.getMasks().get(i);
        BaseKeyframeAnimation<ShapeData, Path> maskAnimation = this.mask.getMaskAnimations().get(i);
        BaseKeyframeAnimation<Integer, Integer> opacityAnimation = this.mask.getOpacityAnimations().get(i);
        switch(mask.getMaskMode()) {
            case MASK_MODE_NONE:
                // this should noop.
                if (areAllMasksNone()) {
                    contentPaint.setAlpha(255);
                    canvas.drawRect(rect, contentPaint);
                }
                break;
            case MASK_MODE_ADD:
                if (mask.isInverted()) {
                    applyInvertedAddMask(canvas, matrix, maskAnimation, opacityAnimation);
                } else {
                    applyAddMask(canvas, matrix, maskAnimation, opacityAnimation);
                }
                break;
            case MASK_MODE_SUBTRACT:
                if (i == 0) {
                    contentPaint.setColor(Color.BLACK);
                    contentPaint.setAlpha(255);
                    canvas.drawRect(rect, contentPaint);
                }
                if (mask.isInverted()) {
                    applyInvertedSubtractMask(canvas, matrix, maskAnimation, opacityAnimation);
                } else {
                    applySubtractMask(canvas, matrix, maskAnimation);
                }
                break;
            case MASK_MODE_INTERSECT:
                if (mask.isInverted()) {
                    applyInvertedIntersectMask(canvas, matrix, maskAnimation, opacityAnimation);
                } else {
                    applyIntersectMask(canvas, matrix, maskAnimation, opacityAnimation);
                }
                break;
        }
    }
    L.beginSection("Layer#restoreLayer");
    canvas.restore();
    L.endSection("Layer#restoreLayer");
}
Also used : Path(android.graphics.Path) KeyPath(com.airbnb.lottie.model.KeyPath) Mask(com.airbnb.lottie.model.content.Mask) LPaint(com.airbnb.lottie.animation.LPaint) Paint(android.graphics.Paint) ShapeData(com.airbnb.lottie.model.content.ShapeData)

Example 3 with ShapeData

use of com.airbnb.lottie.model.content.ShapeData 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 ShapeData

use of com.airbnb.lottie.model.content.ShapeData 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 ShapeData

use of com.airbnb.lottie.model.content.ShapeData in project lottie-android by airbnb.

the class ShapeKeyframeAnimation method getValue.

@Override
public Path getValue(Keyframe<ShapeData> keyframe, float keyframeProgress) {
    ShapeData startShapeData = keyframe.startValue;
    ShapeData endShapeData = keyframe.endValue;
    tempShapeData.interpolateBetween(startShapeData, endShapeData, keyframeProgress);
    ShapeData modifiedShapeData = tempShapeData;
    if (shapeModifiers != null) {
        for (int i = shapeModifiers.size() - 1; i >= 0; i--) {
            modifiedShapeData = shapeModifiers.get(i).modifyShape(modifiedShapeData);
        }
    }
    MiscUtils.getPathFromData(modifiedShapeData, tempPath);
    return tempPath;
}
Also used : ShapeData(com.airbnb.lottie.model.content.ShapeData)

Aggregations

ShapeData (com.airbnb.lottie.model.content.ShapeData)5 PointF (android.graphics.PointF)3 CubicCurveData (com.airbnb.lottie.model.CubicCurveData)3 ArrayList (java.util.ArrayList)2 Paint (android.graphics.Paint)1 Path (android.graphics.Path)1 NonNull (androidx.annotation.NonNull)1 LPaint (com.airbnb.lottie.animation.LPaint)1 KeyPath (com.airbnb.lottie.model.KeyPath)1 Mask (com.airbnb.lottie.model.content.Mask)1