From 448d84ffb727840686f7ba57862718169621e5a4 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Wed, 21 Aug 2024 21:04:07 +0200 Subject: [PATCH] lottie: roundness refactored based on #2295 --- src/loaders/lottie/meson.build | 2 + src/loaders/lottie/tvgLottieBuilder.cpp | 104 +++---------- src/loaders/lottie/tvgLottieBuilder.h | 6 +- src/loaders/lottie/tvgLottieExpressions.h | 5 +- src/loaders/lottie/tvgLottieModifier.cpp | 180 ++++++++++++++++++++++ src/loaders/lottie/tvgLottieModifier.h | 43 ++++++ src/loaders/lottie/tvgLottieProperty.h | 102 ++---------- 7 files changed, 268 insertions(+), 174 deletions(-) create mode 100644 src/loaders/lottie/tvgLottieModifier.cpp create mode 100644 src/loaders/lottie/tvgLottieModifier.h diff --git a/src/loaders/lottie/meson.build b/src/loaders/lottie/meson.build index 4504c8a6..d367a922 100644 --- a/src/loaders/lottie/meson.build +++ b/src/loaders/lottie/meson.build @@ -9,6 +9,7 @@ source_file = [ 'tvgLottieInterpolator.h', 'tvgLottieLoader.h', 'tvgLottieModel.h', + 'tvgLottieModifier.h', 'tvgLottieParser.h', 'tvgLottieParserHandler.h', 'tvgLottieProperty.h', @@ -19,6 +20,7 @@ source_file = [ 'tvgLottieInterpolator.cpp', 'tvgLottieLoader.cpp', 'tvgLottieModel.cpp', + 'tvgLottieModifier.cpp', 'tvgLottieParserHandler.cpp', 'tvgLottieParser.cpp' ] diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 42e81d3c..07ab72f1 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -469,21 +469,21 @@ void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float auto position = rect->position(frameNo, exps); auto size = rect->size(frameNo, exps); - auto roundness = rect->radius(frameNo, exps); - if (roundness == 0.0f) { - if (ctx->roundness > ROUNDNESS_EPSILON) roundness = std::min(ctx->roundness, std::max(size.x, size.y) * 0.5f); + auto r = rect->radius(frameNo, exps); + if (r == 0.0f) { + if (ctx->roundness) ctx->roundness->modifyRect(size, r); } else { - roundness = std::min({roundness, size.x * 0.5f, size.y * 0.5f}); + r = std::min({r, size.x * 0.5f, size.y * 0.5f}); } if (!ctx->repeaters.empty()) { auto shape = rect->pooling(); shape->reset(); - _appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform, rect->clockwise); + _appendRect(shape, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->transform, rect->clockwise); _repeat(parent, shape, ctx); } else { _draw(parent, rect, ctx); - _appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform, rect->clockwise); + _appendRect(ctx->merging, position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, r, ctx->transform, rect->clockwise); } } @@ -563,70 +563,7 @@ void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float } -static void _applyRoundedCorner(Shape* star, Shape* merging, float outerRoundness, float roundness, bool hasRoundness) -{ - static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; - - auto cmdCnt = star->pathCommands(nullptr); - const Point *pts = nullptr; - auto ptsCnt = star->pathCoords(&pts); - - auto len = length(pts[1] - pts[2]); - auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, roundness) / len : 0.0f; - - if (hasRoundness) { - P(merging)->rs.path.cmds.grow((uint32_t)(1.5 * cmdCnt)); - P(merging)->rs.path.pts.grow((uint32_t)(4.5 * cmdCnt)); - - int start = 3 * tvg::zero(outerRoundness); - merging->moveTo(pts[start].x, pts[start].y); - - for (uint32_t i = 1 + start; i < ptsCnt; i += 6) { - auto& prev = pts[i]; - auto& curr = pts[i + 2]; - auto& next = (i < ptsCnt - start) ? pts[i + 4] : pts[2]; - auto& nextCtrl = (i < ptsCnt - start) ? pts[i + 5] : pts[3]; - auto dNext = r * (curr - next); - auto dPrev = r * (curr - prev); - - auto p0 = curr - 2.0f * dPrev; - auto p1 = curr - dPrev; - auto p2 = curr - dNext; - auto p3 = curr - 2.0f * dNext; - - merging->cubicTo(prev.x, prev.y, p0.x, p0.y, p0.x, p0.y); - merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - merging->cubicTo(p3.x, p3.y, next.x, next.y, nextCtrl.x, nextCtrl.y); - } - } else { - P(merging)->rs.path.cmds.grow(2 * cmdCnt); - P(merging)->rs.path.pts.grow(4 * cmdCnt); - - auto dPrev = r * (pts[1] - pts[0]); - auto p = pts[0] + 2.0f * dPrev; - merging->moveTo(p.x, p.y); - - for (uint32_t i = 1; i < ptsCnt; ++i) { - auto& curr = pts[i]; - auto& next = (i == ptsCnt - 1) ? pts[1] : pts[i + 1]; - auto dNext = r * (curr - next); - - auto p0 = curr - 2.0f * dPrev; - auto p1 = curr - dPrev; - auto p2 = curr - dNext; - auto p3 = curr - 2.0f * dNext; - - merging->lineTo(p0.x, p0.y); - merging->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - - dPrev = -1.0f * dNext; - } - } - merging->close(); -} - - -static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, float roundness, float frameNo, Shape* merging, LottieExpressions* exps) +static void _updateStar(TVG_UNUSED LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, float frameNo, Shape* merging, LottieExpressions* exps) { static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; @@ -645,7 +582,7 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans auto numPoints = size_t(ceilf(ptsCnt) * 2); auto direction = star->clockwise ? 1.0f : -1.0f; auto hasRoundness = false; - bool roundedCorner = (roundness > ROUNDNESS_EPSILON) && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness)); + bool roundedCorner = roundness && (tvg::zero(innerRoundness) || tvg::zero(outerRoundness)); Shape* shape; if (roundedCorner) { @@ -742,11 +679,11 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans } shape->close(); - if (roundedCorner) _applyRoundedCorner(shape, merging, outerRoundness, roundness, hasRoundness); + if (roundedCorner) roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, outerRoundness, hasRoundness); } -static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, float roundness, float frameNo, Shape* merging, LottieExpressions* exps) +static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, const LottieRoundnessModifier* roundness, float frameNo, Shape* merging, LottieExpressions* exps) { static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f; @@ -758,7 +695,7 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt); auto direction = star->clockwise ? 1.0f : -1.0f; auto hasRoundness = !tvg::zero(outerRoundness); - bool roundedCorner = roundness > ROUNDNESS_EPSILON && !hasRoundness; + bool roundedCorner = roundness && !hasRoundness; auto x = radius * cosf(angle); auto y = radius * sinf(angle); @@ -820,7 +757,7 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr } shape->close(); - if (roundedCorner) _applyRoundedCorner(shape, merging, 0.0f, roundness, false); + if (roundedCorner) roundness->modifyPolystar(P(shape)->rs.path.cmds, P(shape)->rs.path.pts, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, 0.0f, false); } @@ -856,9 +793,12 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { - auto roundedCorner= static_cast(*child); - auto roundness = roundedCorner->radius(frameNo, exps); - if (ctx->roundness < roundness) ctx->roundness = roundness; + auto roundedCorner = static_cast(*child); + auto r = roundedCorner->radius(frameNo, exps); + if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return; + + if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(r); + else if (ctx->roundness->r < r) ctx->roundness->r = r; } @@ -1068,7 +1008,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) { auto group = static_cast(*g); for (auto p = group->children.begin(); p < group->children.end(); ++p) { - if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f)) { + if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr)) { P(shape)->update(RenderUpdateFlag::Path); } } @@ -1157,7 +1097,7 @@ void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo) pShape->reset(); pShape->fill(255, 255, 255, pMask->opacity(frameNo)); pShape->transform(layer->cache.matrix); - pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, 0.0f, exps); + pMask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, exps); if (pMethod == CompositeMethod::SubtractMask || pMethod == CompositeMethod::InvAlphaMask) { layer->scene->composite(tvg::cast(pShape), CompositeMethod::InvAlphaMask); @@ -1173,14 +1113,14 @@ void LottieBuilder::updateMaskings(LottieLayer* layer, float frameNo) //Append the mask shape if (pMethod == method && (method == CompositeMethod::SubtractMask || method == CompositeMethod::DifferenceMask)) { - mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, 0.0f, exps); + mask->pathset(frameNo, P(pShape)->rs.path.cmds, P(pShape)->rs.path.pts, nullptr, nullptr, exps); //Chain composition } else { auto shape = layer->pooling(); shape->reset(); shape->fill(255, 255, 255, mask->opacity(frameNo)); shape->transform(layer->cache.matrix); - mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, 0.0f, exps); + mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, nullptr, exps); pShape->composite(tvg::cast(shape), method); pShape = shape; pMethod = method; diff --git a/src/loaders/lottie/tvgLottieBuilder.h b/src/loaders/lottie/tvgLottieBuilder.h index 918e4ab2..a60f0161 100644 --- a/src/loaders/lottie/tvgLottieBuilder.h +++ b/src/loaders/lottie/tvgLottieBuilder.h @@ -28,6 +28,7 @@ #include "tvgPaint.h" #include "tvgShape.h" #include "tvgLottieExpressions.h" +#include "tvgLottieModifier.h" struct LottieComposition; @@ -55,7 +56,7 @@ struct RenderContext LottieObject** begin = nullptr; //iteration entry point Array repeaters; Matrix* transform = nullptr; - float roundness = 0.0f; + LottieRoundnessModifier* roundness = nullptr; bool fragmenting = false; //render context has been fragmented by filling bool reqFragment = false; //requirement to fragment the render context @@ -70,6 +71,7 @@ struct RenderContext { PP(propagator)->unref(); free(transform); + delete(roundness); } RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false) @@ -78,7 +80,7 @@ struct RenderContext PP(propagator)->ref(); this->propagator = propagator; this->repeaters = rhs.repeaters; - this->roundness = rhs.roundness; + if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r); } }; diff --git a/src/loaders/lottie/tvgLottieExpressions.h b/src/loaders/lottie/tvgLottieExpressions.h index 63e65f34..408c1dfe 100644 --- a/src/loaders/lottie/tvgLottieExpressions.h +++ b/src/loaders/lottie/tvgLottieExpressions.h @@ -29,6 +29,7 @@ struct LottieExpression; struct LottieComposition; struct LottieLayer; +struct LottieRoundnessModifier; #ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT @@ -110,7 +111,7 @@ public: } template - bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness, LottieExpression* exp) + bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness, LottieExpression* exp) { auto bm_rt = evaluate(frameNo, exp); if (jerry_value_is_undefined(bm_rt)) return false; @@ -155,7 +156,7 @@ struct LottieExpressions template bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; } template bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; } template bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; } - template bool result(TVG_UNUSED float, TVG_UNUSED Array&, TVG_UNUSED Array&, TVG_UNUSED Matrix* transform, TVG_UNUSED float, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Array&, TVG_UNUSED Array&, TVG_UNUSED Matrix* transform, TVG_UNUSED const LottieRoundnessModifier*, TVG_UNUSED LottieExpression*) { return false; } void update(TVG_UNUSED float) {} static LottieExpressions* instance() { return nullptr; } static void retrieve(TVG_UNUSED LottieExpressions* instance) {} diff --git a/src/loaders/lottie/tvgLottieModifier.cpp b/src/loaders/lottie/tvgLottieModifier.cpp new file mode 100644 index 00000000..b5476829 --- /dev/null +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -0,0 +1,180 @@ +/* +* Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgLottieModifier.h" +#include "tvgMath.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static void _roundCorner(Array& cmds, Array& pts, const Point& prev, const Point& curr, const Point& next, float r) +{ + auto lenPrev = length(prev - curr); + auto rPrev = lenPrev > 0.0f ? 0.5f * std::min(lenPrev * 0.5f, r) / lenPrev : 0.0f; + auto lenNext = length(next - curr); + auto rNext = lenNext > 0.0f ? 0.5f * std::min(lenNext * 0.5f, r) / lenNext : 0.0f; + + auto dPrev = rPrev * (curr - prev); + auto dNext = rNext * (curr - next); + + pts.push(curr - 2.0f * dPrev); + pts.push(curr - dPrev); + pts.push(curr - dNext); + pts.push(curr - 2.0f * dNext); + cmds.push(PathCommand::LineTo); + cmds.push(PathCommand::CubicTo); +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool LottieRoundnessModifier::modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts, Matrix* transform) const +{ + outCmds.reserve(inCmdsCnt * 2); + outPts.reserve((uint32_t)(inPtsCnt * 1.5)); + auto ptsCnt = outPts.count; + + uint32_t startIndex = 0; + for (uint32_t iCmds = 0, iPts = 0; iCmds < inCmdsCnt; ++iCmds) { + switch (inCmds[iCmds]) { + case PathCommand::MoveTo: { + startIndex = outPts.count; + outCmds.push(PathCommand::MoveTo); + outPts.push(inPts[iPts++]); + break; + } + case PathCommand::CubicTo: { + auto& prev = inPts[iPts - 1]; + auto& curr = inPts[iPts + 2]; + if (iCmds < inCmdsCnt - 1 && + tvg::zero(inPts[iPts - 1] - inPts[iPts]) && + tvg::zero(inPts[iPts + 1] - inPts[iPts + 2])) { + if (inCmds[iCmds + 1] == PathCommand::CubicTo && + tvg::zero(inPts[iPts + 2] - inPts[iPts + 3]) && + tvg::zero(inPts[iPts + 4] - inPts[iPts + 5])) { + _roundCorner(outCmds, outPts, prev, curr, inPts[iPts + 5], r); + iPts += 3; + break; + } else if (inCmds[iCmds + 1] == PathCommand::Close) { + _roundCorner(outCmds, outPts, prev, curr, inPts[2], r); + outPts[startIndex] = outPts.last(); + iPts += 3; + break; + } + } + outCmds.push(PathCommand::CubicTo); + outPts.push(inPts[iPts++]); + outPts.push(inPts[iPts++]); + outPts.push(inPts[iPts++]); + break; + } + case PathCommand::Close: { + outCmds.push(PathCommand::Close); + break; + } + default: break; + } + } + if (transform) { + for (auto i = ptsCnt; i < outPts.count; ++i) { + outPts[i] *= *transform; + } + } + return true; +} + + +bool LottieRoundnessModifier::modifyPolystar(TVG_UNUSED const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts, float outerRoundness, bool hasRoundness) const +{ + static constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; + + auto len = length(inPts[1] - inPts[2]); + auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f; + + if (hasRoundness) { + outCmds.grow((uint32_t)(1.5 * inCmds.count)); + outPts.grow((uint32_t)(4.5 * inCmds.count)); + + int start = 3 * tvg::zero(outerRoundness); + outCmds.push(PathCommand::MoveTo); + outPts.push(inPts[start]); + + for (uint32_t i = 1 + start; i < inPts.count; i += 6) { + auto& prev = inPts[i]; + auto& curr = inPts[i + 2]; + auto& next = (i < inPts.count - start) ? inPts[i + 4] : inPts[2]; + auto& nextCtrl = (i < inPts.count - start) ? inPts[i + 5] : inPts[3]; + auto dNext = r * (curr - next); + auto dPrev = r * (curr - prev); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + outCmds.push(PathCommand::CubicTo); + outPts.push(prev); outPts.push(p0); outPts.push(p0); + outCmds.push(PathCommand::CubicTo); + outPts.push(p1); outPts.push(p2); outPts.push(p3); + outCmds.push(PathCommand::CubicTo); + outPts.push(p3); outPts.push(next); outPts.push(nextCtrl); + } + } else { + outCmds.grow(2 * inCmds.count); + outPts.grow(4 * inCmds.count); + + auto dPrev = r * (inPts[1] - inPts[0]); + auto p = inPts[0] + 2.0f * dPrev; + outCmds.push(PathCommand::MoveTo); + outPts.push(p); + + for (uint32_t i = 1; i < inPts.count; ++i) { + auto& curr = inPts[i]; + auto& next = (i == inPts.count - 1) ? inPts[1] : inPts[i + 1]; + auto dNext = r * (curr - next); + + auto p0 = curr - 2.0f * dPrev; + auto p1 = curr - dPrev; + auto p2 = curr - dNext; + auto p3 = curr - 2.0f * dNext; + + outCmds.push(PathCommand::LineTo); + outPts.push(p0); + outCmds.push(PathCommand::CubicTo); + outPts.push(p1); outPts.push(p2); outPts.push(p3); + + dPrev = -1.0f * dNext; + } + } + outCmds.push(PathCommand::Close); + return true; +} + + +bool LottieRoundnessModifier::modifyRect(const Point& size, float& r) const +{ + r = std::min(this->r, std::max(size.x, size.y) * 0.5f); + return true; +} \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieModifier.h b/src/loaders/lottie/tvgLottieModifier.h new file mode 100644 index 00000000..ba95d727 --- /dev/null +++ b/src/loaders/lottie/tvgLottieModifier.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_MODIFIER_H_ +#define _TVG_LOTTIE_MODIFIER_H_ + +#include "tvgCommon.h" +#include "tvgArray.h" + + +struct LottieRoundnessModifier +{ + static constexpr float ROUNDNESS_EPSILON = 1.0f; + float r; + + LottieRoundnessModifier(float r) : r(r) {}; + + bool modifyPath(const PathCommand* inCmds, uint32_t inCmdsCnt, const Point* inPts, uint32_t inPtsCnt, Array& outCmds, Array& outPts, Matrix* transform) const; + bool modifyPolystar(const Array& inCmds, const Array& inPts, Array& outCmds, Array& outPts, float outerRoundness, bool hasRoundness) const; + bool modifyRect(const Point& size, float& r) const; +}; + + +#endif \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index ef82a971..9e746c6f 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -28,8 +28,8 @@ #include "tvgLottieCommon.h" #include "tvgLottieInterpolator.h" #include "tvgLottieExpressions.h" +#include "tvgLottieModifier.h" -#define ROUNDNESS_EPSILON 1.0f struct LottieFont; struct LottieLayer; @@ -178,81 +178,6 @@ static void _copy(PathSet* pathset, Array& outCmds) } -static void _roundCorner(Array& cmds, Array& pts, const Point& prev, const Point& curr, const Point& next, float roundness) -{ - auto lenPrev = length(prev - curr); - auto rPrev = lenPrev > 0.0f ? 0.5f * std::min(lenPrev * 0.5f, roundness) / lenPrev : 0.0f; - auto lenNext = length(next - curr); - auto rNext = lenNext > 0.0f ? 0.5f * std::min(lenNext * 0.5f, roundness) / lenNext : 0.0f; - - auto dPrev = rPrev * (curr - prev); - auto dNext = rNext * (curr - next); - - pts.push(curr - 2.0f * dPrev); - pts.push(curr - dPrev); - pts.push(curr - dNext); - pts.push(curr - 2.0f * dNext); - cmds.push(PathCommand::LineTo); - cmds.push(PathCommand::CubicTo); -} - - -static bool _modifier(Point* inPts, uint32_t inPtsCnt, PathCommand* inCmds, uint32_t inCmdsCnt, Array& cmds, Array& pts, Matrix* transform, float roundness) -{ - cmds.reserve(inCmdsCnt * 2); - pts.reserve((uint16_t)(inPtsCnt * 1.5)); - auto ptsCnt = pts.count; - - auto startIndex = 0; - for (uint32_t iCmds = 0, iPts = 0; iCmds < inCmdsCnt; ++iCmds) { - switch (inCmds[iCmds]) { - case PathCommand::MoveTo: { - startIndex = pts.count; - cmds.push(PathCommand::MoveTo); - pts.push(inPts[iPts++]); - break; - } - case PathCommand::CubicTo: { - auto& prev = inPts[iPts - 1]; - auto& curr = inPts[iPts + 2]; - if (iCmds < inCmdsCnt - 1 && - tvg::zero(inPts[iPts - 1] - inPts[iPts]) && - tvg::zero(inPts[iPts + 1] - inPts[iPts + 2])) { - if (inCmds[iCmds + 1] == PathCommand::CubicTo && - tvg::zero(inPts[iPts + 2] - inPts[iPts + 3]) && - tvg::zero(inPts[iPts + 4] - inPts[iPts + 5])) { - _roundCorner(cmds, pts, prev, curr, inPts[iPts + 5], roundness); - iPts += 3; - break; - } else if (inCmds[iCmds + 1] == PathCommand::Close) { - _roundCorner(cmds, pts, prev, curr, inPts[2], roundness); - pts[startIndex] = pts.last(); - iPts += 3; - break; - } - } - cmds.push(PathCommand::CubicTo); - pts.push(inPts[iPts++]); - pts.push(inPts[iPts++]); - pts.push(inPts[iPts++]); - break; - } - case PathCommand::Close: { - cmds.push(PathCommand::Close); - break; - } - default: break; - } - } - if (transform) { - for (auto i = ptsCnt; i < pts.count; ++i) { - pts[i] *= *transform; - } - } - return true; -} - - template uint32_t _bsearch(T* frames, float frameNo) { @@ -482,7 +407,7 @@ struct LottiePathSet : LottieProperty return (*frames)[frames->count]; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness) + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness) { PathSet* path = nullptr; LottieScalarFrame* frame = nullptr; @@ -507,7 +432,8 @@ struct LottiePathSet : LottieProperty } if (!interpolate) { - if (roundness > ROUNDNESS_EPSILON) return _modifier(path->pts, path->ptsCnt, path->cmds, path->cmdsCnt, cmds, pts, transform, roundness); + if (roundness) return roundness->modifyPath(path->cmds, path->cmdsCnt, path->pts, path->ptsCnt, cmds, pts, transform); + _copy(path, cmds); _copy(path, pts, transform); return true; @@ -516,29 +442,29 @@ struct LottiePathSet : LottieProperty auto s = frame->value.pts; auto e = (frame + 1)->value.pts; - if (roundness > ROUNDNESS_EPSILON) { + if (roundness) { auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point)); auto p = interpPts; for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) { *p = lerp(*s, *e, t); if (transform) *p *= *transform; } - _modifier(interpPts, frame->value.ptsCnt, frame->value.cmds, frame->value.cmdsCnt, cmds, pts, nullptr, roundness); + roundness->modifyPath(frame->value.cmds, frame->value.cmdsCnt, interpPts, frame->value.ptsCnt, cmds, pts, nullptr); free(interpPts); return true; - } else { - for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { - auto pt = lerp(*s, *e, t); - if (transform) pt *= *transform; - pts.push(pt); - } - _copy(&frame->value, cmds); } + + for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) { + auto pt = lerp(*s, *e, t); + if (transform) pt *= *transform; + pts.push(pt); + } + _copy(&frame->value, cmds); return true; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, float roundness, LottieExpressions* exps) + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, const LottieRoundnessModifier* roundness, LottieExpressions* exps) { if (exps && exp) { if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);