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);
}
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");
}
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;
}
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;
}
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;
}
Aggregations