diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 44d729ca..87a06ea8 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -397,7 +397,7 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx) void LottieBuilder::appendRect(Shape* shape, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx) { - auto temp = (ctx->offset) ? Shape::gen() : shape; + auto temp = (ctx->offset || ctx->puckerBloat) ? Shape::gen() : shape; auto cnt = SHAPE(temp)->rs.path.pts.count; temp->appendRect(pos.x, pos.y, size.x, size.y, r, r, clockwise); @@ -410,6 +410,12 @@ void LottieBuilder::appendRect(Shape* shape, Point& pos, Point& size, float r, b if (ctx->offset) { ctx->offset->modifyRect(SHAPE(temp)->rs.path, SHAPE(shape)->rs.path); + if (ctx->puckerBloat) { + //TODO: properly applied chaining + } + delete(temp); + } else if (ctx->puckerBloat) { + ctx->puckerBloat->modifyRect(SHAPE(temp)->rs.path, SHAPE(shape)->rs.path); delete(temp); } } @@ -453,6 +459,8 @@ static void _appendCircle(Shape* shape, Point& center, Point& radius, bool clock SHAPE(shape)->rs.path.pts[i] *= *ctx->transform; } } + + if (ctx->puckerBloat) ctx->puckerBloat->modifyEllipse(SHAPE(shape)->rs.path); } @@ -514,7 +522,7 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran bool roundedCorner = ctx->roundness && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness)); Shape* shape; - if (roundedCorner || ctx->offset) { + if (roundedCorner || ctx->offset || ctx->puckerBloat) { shape = star->pooling(); shape->reset(); } else { @@ -624,7 +632,7 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo angle += anglePerPoint * direction; Shape* shape; - if (roundedCorner || ctx->offset) { + if (roundedCorner || ctx->offset || ctx->puckerBloat) { shape = star->pooling(); shape->reset(); } else { @@ -724,6 +732,15 @@ void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObjec } +void LottieBuilder::updatePuckerBloat(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +{ + auto puckerBloat = static_cast(*child); + if (!ctx->puckerBloat) ctx->puckerBloat = new LottiePuckerBloatModifier(puckerBloat->amount(frameNo, tween, exps)); + + ctx->update(ctx->puckerBloat); +} + + void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { auto repeater = static_cast(*child); @@ -833,6 +850,10 @@ void LottieBuilder::updateChildren(LottieGroup* parent, float frameNo, Inlistoffset, rhs.offset->miterLimit, rhs.offset->join); update(offset); } + if (rhs.puckerBloat) { + puckerBloat = new LottiePuckerBloatModifier(rhs.puckerBloat->amount); + update(puckerBloat); + } if (rhs.transform) { transform = new Matrix; *transform = *rhs.transform; @@ -174,6 +180,7 @@ private: void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); + void updatePuckerBloat(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); RenderPath buffer; //resusable path LottieExpressions* exps; diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index 9b8e819a..b52581aa 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -269,7 +269,8 @@ struct LottieObject Text, Repeater, RoundedCorner, - OffsetPath + OffsetPath, + PuckerBloat }; virtual ~LottieObject() @@ -554,6 +555,24 @@ struct LottieRoundedCorner : LottieObject }; + +struct LottiePuckerBloat : LottieObject +{ + LottiePuckerBloat() + { + LottieObject::type = LottieObject::PuckerBloat; + } + + LottieProperty* property(uint16_t ix) override + { + if (amount.ix == ix) return &amount; + return nullptr; + } + + LottieFloat amount = 0.0f; +}; + + struct LottiePath : LottieShape { LottiePath() : LottieShape(LottieObject::Path) {} diff --git a/src/loaders/lottie/tvgLottieModifier.cpp b/src/loaders/lottie/tvgLottieModifier.cpp index 9100ba2b..95d33250 100644 --- a/src/loaders/lottie/tvgLottieModifier.cpp +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -168,6 +168,40 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i ++curPt; } + +static Point _center(const PathCommand* cmds, uint32_t cmdsCount, const Point* pts, TVG_UNUSED uint32_t ptsCount) +{ + Point center{}; + auto count = 0; + auto p = (Point*)pts; + + for (uint32_t i = 0; i < cmdsCount; ++i) { + switch (cmds[i]) { + case PathCommand::MoveTo: { + ++p; + break; + } + case PathCommand::CubicTo: { + center = center + *(p - 1) + *p + *(p + 1) + *(p + 2); + p += 3; + count += 4; + break; + } + case PathCommand::LineTo: { + center = center + *(p - 1) + *p; + ++p; + count += 2; + break; + } + case PathCommand::Close: { + break; + } + } + } + + return count > 0 ? center / (float)count : Point{0, 0}; +} + /************************************************************************/ /* External Class Implementation */ /************************************************************************/ @@ -409,4 +443,117 @@ bool LottieOffsetModifier::modifyEllipse(Point& radius) radius.x += offset; radius.y += offset; return true; +} + + +bool LottiePuckerBloatModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, TVG_UNUSED uint32_t inPtsCnt, TVG_UNUSED Matrix* transform, RenderPath& out) +{ + if (next) TVGERR("LOTTIE", "Pucker/Bloat has a next modifier?"); + + out.cmds.reserve(inCmdsCnt); + out.pts.reserve(inPtsCnt); + + auto center = _center(inCmds, inCmdsCnt, inPts, inPtsCnt); + auto a = amount * 0.01f; + auto pts = inPts; + + for (uint32_t i = 0; i < inCmdsCnt; ++i) { + switch (inCmds[i]) { + case PathCommand::MoveTo: { + out.pts.push(*pts + (center - *pts) * a); + out.cmds.push(PathCommand::MoveTo); + ++pts; + break; + } + case PathCommand::CubicTo: { + out.pts.push(*pts - (center - *pts) * a); + out.pts.push(*(pts + 1) - (center - *(pts + 1)) * a); + out.pts.push(*(pts + 2) + (center - *(pts + 2)) * a); + pts += 3; + out.cmds.push(PathCommand::CubicTo); + break; + } + case PathCommand::LineTo: { + out.pts.push(*(pts - 1) - (center - *(pts - 1)) * a); + out.pts.push(*pts - (center - *pts) * a); + out.pts.push(*pts + (center - *pts) * a); + out.cmds.push(PathCommand::CubicTo); + ++pts; + break; + } + case PathCommand::Close: { + out.cmds.push(PathCommand::Close); + break; + } + } + } + + return true; +} + + +bool LottiePuckerBloatModifier::modifyPolystar(RenderPath& in, RenderPath& out, TVG_UNUSED float, TVG_UNUSED bool) +{ + return modifyPath(in.cmds.data, in.cmds.count, in.pts.data, in.pts.count, nullptr, out); +} + + +bool LottiePuckerBloatModifier::modifyEllipse(RenderPath& path) +{ + auto center = _center(path.cmds.data, path.cmds.count, path.pts.data, path.pts.count); + auto a = amount * 0.01f; + auto pts = path.pts.data; + + for (uint32_t i = 0; i < path.cmds.count; ++i) { + switch (path.cmds[i]) { + case PathCommand::MoveTo: { + *pts = *pts + (center - *pts) * a; + ++pts; + break; + } + case PathCommand::CubicTo: { + *pts = *pts - (center - *pts) * a; + *(pts + 1) = *(pts + 1) - (center - *(pts + 1)) * a; + *(pts + 2) = *(pts + 2) + (center - *(pts + 2)) * a; + pts += 3; + break; + } + default: break; + } + } + + return true; +} + + +bool LottiePuckerBloatModifier::modifyRect(const RenderPath& in, RenderPath& out) +{ + //sharp rectangle (5 cmds and 4 pts) – the only case where the close command actually closes the shape + if (in.cmds.count == 5) { + auto center = (in.pts[0] + in.pts[1] + in.pts[2] + in.pts[3]) * 0.25f; + auto a = amount * 0.01f; + + out.cmds.grow(6); + out.pts.grow(13); + auto cmds = out.cmds.end(); + auto pts = out.pts.end(); + + cmds[0] = PathCommand::MoveTo; + cmds[1] = cmds[2] = cmds[3] = cmds[4] = PathCommand::CubicTo; + cmds[5] = PathCommand::Close; + + for (int i = 0, j = 0; i < 4; ++i) { + pts[j++] = in.pts[i] + (center - in.pts[i]) * a; + pts[j++] = in.pts[i] - (center - in.pts[i]) * a; + pts[j++] = in.pts[(i + 1) % 4] - (center - in.pts[(i + 1) % 4]) * a; + } + pts[12] = in.pts[0] + (center - in.pts[0]) * a; + + out.cmds.count += 6; + out.pts.count += 13; + + return true; + } + + return modifyPath(in.cmds.data, in.cmds.count, in.pts.data, in.pts.count, nullptr, out); } \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieModifier.h b/src/loaders/lottie/tvgLottieModifier.h index b9367431..08a4aae9 100644 --- a/src/loaders/lottie/tvgLottieModifier.h +++ b/src/loaders/lottie/tvgLottieModifier.h @@ -31,7 +31,7 @@ struct LottieModifier { - enum Type : uint8_t {Roundness = 0, Offset}; + enum Type : uint8_t {Roundness = 0, Offset, PuckerBloat}; LottieModifier* next = nullptr; Type type; @@ -106,4 +106,20 @@ private: void corner(RenderPath& out, Line& line, Line& nextLine, uint32_t movetoIndex, bool nextClose); }; + +struct LottiePuckerBloatModifier : LottieModifier +{ + float amount; + + LottiePuckerBloatModifier(float a) : amount(a) + { + type = PuckerBloat; + } + + bool modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out) override; + bool modifyPolystar(RenderPath& in, RenderPath& out, float outerRoundness, bool hasRoundness) override; + bool modifyEllipse(RenderPath& path); + bool modifyRect(const RenderPath& in, RenderPath& out); +}; + #endif \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index 9ef4a3bf..2ca25ea4 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -723,6 +723,21 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner() } +LottiePuckerBloat* LottieParser::parsePuckerBloat() +{ + auto puckerBloat = new LottiePuckerBloat; + + context.parent = puckerBloat; + + while (auto key = nextObjectKey()) { + if (parseCommon(puckerBloat, key)) continue; + if (KEY_AS("a")) parseProperty(puckerBloat->amount); + else skip(); + } + return puckerBloat; +} + + void LottieParser::parseColorStop(LottieGradient* gradient) { enterObject(); @@ -871,7 +886,7 @@ LottieObject* LottieParser::parseObject() else if (!strcmp(type, "tm")) return parseTrimpath(); else if (!strcmp(type, "rp")) return parseRepeater(); else if (!strcmp(type, "mm")) TVGLOG("LOTTIE", "MergePath(mm) is not supported yet"); - else if (!strcmp(type, "pb")) TVGLOG("LOTTIE", "Puker/Bloat(pb) is not supported yet"); + else if (!strcmp(type, "pb")) return parsePuckerBloat(); else if (!strcmp(type, "tw")) TVGLOG("LOTTIE", "Twist(tw) is not supported yet"); else if (!strcmp(type, "op")) return parseOffsetPath(); else if (!strcmp(type, "zz")) TVGLOG("LOTTIE", "ZigZag(zz) is not supported yet"); diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h index 66a96290..5cf452dc 100644 --- a/src/loaders/lottie/tvgLottieParser.h +++ b/src/loaders/lottie/tvgLottieParser.h @@ -89,6 +89,7 @@ private: LottiePath* parsePath(); LottiePolyStar* parsePolyStar(); LottieRoundedCorner* parseRoundedCorner(); + LottiePuckerBloat* parsePuckerBloat(); LottieGradientFill* parseGradientFill(); LottieLayer* parseLayers(LottieLayer* root); LottieMask* parseMask();