From cc4c18d6c681432e3378f6664f3e1dcb59dc4a40 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 12 Feb 2025 00:52:56 +0900 Subject: [PATCH] renderer: revise the Shape rect/circle features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The path direction of shapes is now functional for path trimming. Replace the main logic with Lottie's by default to align the spec, migrate the original logic to svg loader side. This revision helps to reduce the binary size by 2–3 KB for lottie loader. API Modifications: - Result Shape::appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0) -> Result Shape::appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0, bool cw = true) - Result Shape::appendCircle(float cx, float cy, float rx, float ry) -> Result Shape::appendCircle(float cx, float cy, float rx, float ry, bool cw = true) - TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry) -> TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry, bool cw) - Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry) -> Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry, bool cw) issue: https://github.com/thorvg/thorvg/issues/3179 --- examples/Capi.cpp | 12 +-- examples/StrokeTrim.cpp | 4 +- inc/thorvg.h | 6 +- src/bindings/capi/thorvg_capi.h | 6 +- src/bindings/capi/tvgCapi.cpp | 8 +- src/loaders/lottie/tvgLottieBuilder.cpp | 115 +++------------------ src/loaders/svg/tvgSvgSceneBuilder.cpp | 58 ++++++++++- src/renderer/tvgShape.cpp | 8 +- src/renderer/tvgShape.h | 131 +++++++++++++++++------- test/testShape.cpp | 11 +- 10 files changed, 196 insertions(+), 163 deletions(-) diff --git a/examples/Capi.cpp b/examples/Capi.cpp index 76b0d06f..db1f032a 100644 --- a/examples/Capi.cpp +++ b/examples/Capi.cpp @@ -94,9 +94,9 @@ void contents() //////3. Radial gradient shape with a radial dashed stroke //Set a shape Tvg_Paint* shape3 = tvg_shape_new(); - tvg_shape_append_rect(shape3, 550.0f, 20.0f, 100.0f, 50.0f, 0.0f, 0.0f); - tvg_shape_append_circle(shape3, 600.0f, 150.0f, 100.0f, 50.0f); - tvg_shape_append_rect(shape3, 550.0f, 230.0f, 100.0f, 100.0f, 20.0f, 40.0f); + tvg_shape_append_rect(shape3, 550.0f, 20.0f, 100.0f, 50.0f, 0.0f, 0.0f, true); + tvg_shape_append_circle(shape3, 600.0f, 150.0f, 100.0f, 50.0f, true); + tvg_shape_append_rect(shape3, 550.0f, 230.0f, 100.0f, 100.0f, 20.0f, 40.0f, true); //Prepare a radial gradient for the fill Tvg_Gradient* grad2 = tvg_radial_gradient_new(); @@ -142,8 +142,8 @@ void contents() //Set circles Tvg_Paint* scene_shape1 = tvg_shape_new(); - tvg_shape_append_circle(scene_shape1, 80.0f, 650.f, 40.0f, 140.0f); - tvg_shape_append_circle(scene_shape1, 180.0f, 600.f, 40.0f, 60.0f); + tvg_shape_append_circle(scene_shape1, 80.0f, 650.f, 40.0f, 140.0f, true); + tvg_shape_append_circle(scene_shape1, 180.0f, 600.f, 40.0f, 60.0f, true); tvg_shape_set_fill_color(scene_shape1, 0, 0, 255, 150); tvg_shape_set_stroke_color(scene_shape1, 75, 25, 155, 255); tvg_shape_set_stroke_width(scene_shape1, 10.0f); @@ -189,7 +189,7 @@ void contents() // Set a composite shape Tvg_Paint* comp = tvg_shape_new(); - tvg_shape_append_circle(comp, 600.0f, 600.0f, 100.0f, 100.0f); + tvg_shape_append_circle(comp, 600.0f, 600.0f, 100.0f, 100.0f, true); tvg_shape_set_fill_color(comp, 0, 0, 0, 200); tvg_paint_set_mask_method(pict, comp, TVG_MASK_METHOD_INVERSE_ALPHA); diff --git a/examples/StrokeTrim.cpp b/examples/StrokeTrim.cpp index 5ff49a5d..d5977ad3 100644 --- a/examples/StrokeTrim.cpp +++ b/examples/StrokeTrim.cpp @@ -43,13 +43,13 @@ struct UserExample : tvgexam::Example shape1->strokeJoin(tvg::StrokeJoin::Round); shape1->strokeCap(tvg::StrokeCap::Round); shape1->strokeWidth(12); - shape1->strokeTrim(0.0f, 0.5f, false); + shape1->strokeTrim(0.25f, 0.75f, false); auto shape2 = static_cast(shape1->duplicate()); shape2->translate(300, 300); shape2->fill(0, 155, 50, 100); shape2->strokeFill(0, 255, 0); - shape2->strokeTrim(0.0f, 0.5f, true); + shape2->strokeTrim(0.25f, 0.75f, true); canvas->push(shape1); canvas->push(shape2); diff --git a/inc/thorvg.h b/inc/thorvg.h index cf441c6b..ad265549 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -983,10 +983,11 @@ public: * @param[in] h The height of the rectangle. * @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle. * @param[in] ry The y-axis radius of the ellipse defining the rounded corners of the rectangle. + * @param[in] cw Specifies the path direction: @c true for clockwise, @c false for counterclockwise. * * @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse. */ - Result appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0) noexcept; + Result appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0, bool cw = true) noexcept; /** * @brief Appends an ellipse to the path. @@ -1001,9 +1002,10 @@ public: * @param[in] cy The vertical coordinate of the center of the ellipse. * @param[in] rx The x-axis radius of the ellipse. * @param[in] ry The y-axis radius of the ellipse. + * @param[in] cw Specifies the path direction: @c true for clockwise, @c false for counterclockwise. * */ - Result appendCircle(float cx, float cy, float rx, float ry) noexcept; + Result appendCircle(float cx, float cy, float rx, float ry, bool cw = true) noexcept; /** * @brief Appends a given sub-path to the path. diff --git a/src/bindings/capi/thorvg_capi.h b/src/bindings/capi/thorvg_capi.h index fc7dbac3..792d2b0d 100644 --- a/src/bindings/capi/thorvg_capi.h +++ b/src/bindings/capi/thorvg_capi.h @@ -1175,13 +1175,14 @@ TVG_API Tvg_Result tvg_shape_close(Tvg_Paint* paint); * @param[in] h The height of the rectangle. * @param[in] rx The x-axis radius of the ellipse defining the rounded corners of the rectangle. * @param[in] ry The y-axis radius of the ellipse defining the rounded corners of the rectangle. +* @param[in] cw Specifies the path direction: @c true for clockwise, @c false for counterclockwise. * * @return Tvg_Result enumeration. * @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer. * & @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse. */ -TVG_API Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry); +TVG_API Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry, bool cw); /*! @@ -1198,11 +1199,12 @@ TVG_API Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, flo * @param[in] cy The vertical coordinate of the center of the ellipse. * @param[in] rx The x-axis radius of the ellipse. * @param[in] ry The y-axis radius of the ellipse. +* @param[in] cw Specifies the path direction: @c true for clockwise, @c false for counterclockwise. * * @return Tvg_Result enumeration. * @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer. */ -TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry); +TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry, bool cw); /*! diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp index 8d6af594..426e5353 100644 --- a/src/bindings/capi/tvgCapi.cpp +++ b/src/bindings/capi/tvgCapi.cpp @@ -352,17 +352,17 @@ TVG_API Tvg_Result tvg_shape_close(Tvg_Paint* paint) } -TVG_API Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry) +TVG_API Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry, bool cw) { if (!paint) return TVG_RESULT_INVALID_ARGUMENT; - return (Tvg_Result) reinterpret_cast(paint)->appendRect(x, y, w, h, rx, ry); + return (Tvg_Result) reinterpret_cast(paint)->appendRect(x, y, w, h, rx, ry, cw); } -TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry) +TVG_API Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry, bool cw) { if (!paint) return TVG_RESULT_INVALID_ARGUMENT; - return (Tvg_Result) reinterpret_cast(paint)->appendCircle(cx, cy, rx, ry); + return (Tvg_Result) reinterpret_cast(paint)->appendCircle(cx, cy, rx, ry, cw); } diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index da3e23a1..eeaac22e 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -371,93 +371,26 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx) } -static void _sharpRect(RenderPath& out, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx) -{ - auto& pts = out.pts; - auto& cmds = out.cmds; - - pts.reserve(4); - pts.count = 4; - cmds.reserve(5); - cmds.count = 5; - - cmds[0] = PathCommand::MoveTo; - cmds[1] = cmds[2] = cmds[3] = PathCommand::LineTo; - cmds[4] = PathCommand::Close; - - pts[0] = {pos.x + size.x, pos.y}; - pts[2] = {pos.x, pos.y + size.y}; - - if (clockwise) { - pts[1] = pos + size; - pts[3] = pos; - } else { - pts[1] = pos; - pts[3] = pos + size; - } -} - -static void _roundRect(RenderPath& out, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx) -{ - auto& pts = out.pts; - auto& cmds = out.cmds; - - pts.reserve(17); - pts.count = 17; - cmds.reserve(10); - cmds.count = 10; - - auto hsize = size * 0.5f; - auto rsize = Point{r > hsize.x ? hsize.x : r, r > hsize.y ? hsize.y : r}; - auto hr = rsize * PATH_KAPPA; - - cmds[0] = PathCommand::MoveTo; - cmds[9] = PathCommand::Close; - pts[0] = {pos.x + size.x, pos.y + rsize.y}; //move - - if (clockwise) { - cmds[1] = cmds[3] = cmds[5] = cmds[7] = PathCommand::LineTo; - cmds[2] = cmds[4] = cmds[6] = cmds[8] = PathCommand::CubicTo; - - pts[1] = {pos.x + size.x, pos.y + size.y - rsize.y}; //line - pts[2] = {pos.x + size.x, pos.y + size.y - rsize.y + hr.y}; pts[3] = {pos.x + size.x - rsize.x + hr.x, pos.y + size.y}; pts[4] = {pos.x + size.x - rsize.x, pos.y + size.y}; //cubic - pts[5] = {pos.x + rsize.x, pos.y + size.y}, //line - pts[6] = {pos.x + rsize.x - hr.x, pos.y + size.y}; pts[7] = {pos.x, pos.y + size.y - rsize.y + hr.y}; pts[8] = {pos.x, pos.y + size.y - rsize.y}; //cubic - pts[9] = {pos.x, pos.y + rsize.y}, //line - pts[10] = {pos.x, pos.y + rsize.y - hr.y}; pts[11] = {pos.x + rsize.x - hr.x, pos.y}; pts[12] = {pos.x + rsize.x, pos.y}; //cubic - pts[13] = {pos.x + size.x - rsize.x, pos.y}; //line - pts[14] = {pos.x + size.x - rsize.x + hr.x, pos.y}; pts[15] = {pos.x + size.x, pos.y + rsize.y - hr.y}; pts[16] = {pos.x + size.x, pos.y + rsize.y}; //cubic - } else { - cmds[1] = cmds[3] = cmds[5] = cmds[7] = PathCommand::CubicTo; - cmds[2] = cmds[4] = cmds[6] = cmds[8] = PathCommand::LineTo; - - pts[1] = {pos.x + size.x, pos.y + rsize.y - hr.y}; pts[2] = {pos.x + size.x - rsize.x + hr.x, pos.y}; pts[3] = {pos.x + size.x - rsize.x, pos.y}; //cubic - pts[4] = {pos.x + rsize.x, pos.y}; //line - pts[5] = {pos.x + rsize.x - hr.x, pos.y}; pts[6] = {pos.x, pos.y + rsize.y - hr.y}; pts[7] = {pos.x, pos.y + rsize.y}; //cubic - pts[8] = {pos.x, pos.y + size.y - rsize.y}; //line - pts[9] = {pos.x, pos.y + size.y - rsize.y + hr.y}; pts[10] = {pos.x + rsize.x - hr.x, pos.y + size.y}; pts[11] = {pos.x + rsize.x, pos.y + size.y}; //cubic - pts[12] = {pos.x + size.x - rsize.x, pos.y + size.y}; //line - pts[13] = {pos.x + size.x - rsize.x + hr.x, pos.y + size.y}; pts[14] = {pos.x + size.x, pos.y + size.y - rsize.y + hr.y}; pts[15] = {pos.x + size.x, pos.y + size.y - rsize.y}; //cubic - pts[16] = {pos.x + size.x, pos.y + rsize.y}; //line - } -} - - void LottieBuilder::appendRect(Shape* shape, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx) { - buffer.clear(); + auto temp = (ctx->offset) ? Shape::gen() : shape; - if (tvg::zero(r)) _sharpRect(buffer, pos, size, r, clockwise, ctx); - else _roundRect(buffer, pos, size, r, clockwise, ctx); + auto before = SHAPE(temp)->rs.path.pts.count; + + temp->appendRect(pos.x, pos.y, size.x, size.y, r, r, clockwise); + + auto after = SHAPE(temp)->rs.path.pts.count; if (ctx->transform) { - ARRAY_FOREACH(pt, buffer.pts) { - *pt *= *ctx->transform; + for (uint32_t i = before; i < after; ++i) { + SHAPE(temp)->rs.path.pts[i] *= *ctx->transform; } } - if (ctx->offset) ctx->offset->modifyRect(buffer, SHAPE(shape)->rs.path); - else shape->appendPath(buffer.cmds.data, buffer.cmds.count, buffer.pts.data, buffer.pts.count); + if (ctx->offset) { + ctx->offset->modifyRect(SHAPE(temp)->rs.path, SHAPE(shape)->rs.path); + delete(temp); + } } @@ -490,32 +423,18 @@ static void _appendCircle(Shape* shape, Point& center, Point& radius, bool clock { if (ctx->offset) ctx->offset->modifyEllipse(radius); - if (tvg::zero(radius)) return; + auto before = SHAPE(shape)->rs.path.pts.count; - auto rKappa = radius * PATH_KAPPA; + shape->appendCircle(center.x, center.y, radius.x, radius.y, clockwise); - constexpr int cmdsCnt = 6; - PathCommand cmds[cmdsCnt] = {PathCommand::MoveTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::Close}; + auto after = SHAPE(shape)->rs.path.pts.count; - constexpr int ptsCnt = 13; - Point pts[ptsCnt]; - - int table[2][ptsCnt] = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, {0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 12}}; - int* idx = clockwise ? table[0] : table[1]; - - pts[idx[0]] = {center.x, center.y - radius.y}; //moveTo - pts[idx[1]] = {center.x + rKappa.x, center.y - radius.y}; pts[idx[2]] = {center.x + radius.x, center.y - rKappa.y}; pts[idx[3]] = {center.x + radius.x, center.y}; //cubicTo - pts[idx[4]] = {center.x + radius.x, center.y + rKappa.y}; pts[idx[5]] = {center.x + rKappa.x, center.y + radius.y}; pts[idx[6]] = {center.x, center.y + radius.y}; //cubicTo - pts[idx[7]] = {center.x - rKappa.x, center.y + radius.y}; pts[idx[8]] = {center.x - radius.x, center.y + rKappa.y}; pts[idx[9]] = {center.x - radius.x, center.y}; //cubicTo - pts[idx[10]] = {center.x - radius.x, center.y - rKappa.y}; pts[idx[11]] = {center.x - rKappa.x, center.y - radius.y}; pts[idx[12]] = {center.x, center.y - radius.y}; //cubicTo if (ctx->transform) { - for (int i = 0; i < ptsCnt; ++i) { - pts[i] *= *ctx->transform; + for (uint32_t i = before; i < after; ++i) { + SHAPE(shape)->rs.path.pts[i] *= *ctx->transform; } } - - shape->appendPath(cmds, cmdsCnt, pts, ptsCnt); } diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp index 971d7407..4c7418b9 100644 --- a/src/loaders/svg/tvgSvgSceneBuilder.cpp +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -180,6 +180,56 @@ static RadialGradient* _applyRadialGradientProperty(SvgStyleGradient* g, const B } +static void _appendRect(Shape* shape, float x, float y, float w, float h, float rx, float ry) +{ + auto halfW = w * 0.5f; + auto halfH = h * 0.5f; + + //clamping cornerRadius by minimum size + if (rx > halfW) rx = halfW; + if (ry > halfH) ry = halfH; + + if (rx == 0 && ry == 0) { + SHAPE(shape)->grow(5, 4); + shape->moveTo(x, y); + shape->lineTo(x + w, y); + shape->lineTo(x + w, y + h); + shape->lineTo(x, y + h); + shape->close(); + } else { + auto hrx = rx * PATH_KAPPA; + auto hry = ry * PATH_KAPPA; + + SHAPE(shape)->grow(10, 17); + shape->moveTo(x + rx, y); + shape->lineTo(x + w - rx, y); + shape->cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); + shape->lineTo(x + w, y + h - ry); + shape->cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); + shape->lineTo(x + rx, y + h); + shape->cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); + shape->lineTo(x, y + ry); + shape->cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); + shape->close(); + } +} + + +static void _appendCircle(Shape* shape, float cx, float cy, float rx, float ry) +{ + auto rxKappa = rx * PATH_KAPPA; + auto ryKappa = ry * PATH_KAPPA; + + SHAPE(shape)->grow(6, 13); + shape->moveTo(cx + rx, cy); + shape->cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); + shape->cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); + shape->cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); + shape->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); + shape->close(); +} + + static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* shape, const Box& vBox, const string& svgPath) { //The SVG standard allows only for 'use' nodes that point directly to a basic shape. @@ -430,7 +480,7 @@ static bool _recognizeShape(SvgNode* node, Shape* shape) break; } case SvgNodeType::Ellipse: { - shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry); + _appendCircle(shape, node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry); break; } case SvgNodeType::Polygon: { @@ -453,11 +503,11 @@ static bool _recognizeShape(SvgNode* node, Shape* shape) break; } case SvgNodeType::Circle: { - shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r); + _appendCircle(shape, node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r); break; } case SvgNodeType::Rect: { - shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry); + _appendRect(shape, node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry); break; } case SvgNodeType::Line: { @@ -774,7 +824,7 @@ static Scene* _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, co if (!node->node.use.symbol->node.symbol.overflowVisible) { auto viewBoxClip = Shape::gen(); - viewBoxClip->appendRect(0, 0, width, height, 0, 0); + viewBoxClip->appendRect(0, 0, width, height); // mClipTransform = mUseTransform * mSymbolTransform Matrix mClipTransform = mUseTransform; diff --git a/src/renderer/tvgShape.cpp b/src/renderer/tvgShape.cpp index 1593df59..de0a3dc9 100644 --- a/src/renderer/tvgShape.cpp +++ b/src/renderer/tvgShape.cpp @@ -95,16 +95,16 @@ Result Shape::close() noexcept } -Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept +Result Shape::appendCircle(float cx, float cy, float rx, float ry, bool cw) noexcept { - SHAPE(this)->appendCircle(cx, cy, rx, ry); + SHAPE(this)->appendCircle(cx, cy, rx, ry, cw); return Result::Success; } -Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept +Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry, bool cw) noexcept { - SHAPE(this)->appendRect(x, y, w, h, rx, ry); + SHAPE(this)->appendRect(x, y, w, h, rx, ry, cw); return Result::Success; } diff --git a/src/renderer/tvgShape.h b/src/renderer/tvgShape.h index 0a504abb..6740cb33 100644 --- a/src/renderer/tvgShape.h +++ b/src/renderer/tvgShape.h @@ -386,52 +386,111 @@ struct Shape::Impl : Paint::Impl return Result::Success; } - void appendCircle(float cx, float cy, float rx, float ry) + void appendCircle(float cx, float cy, float rx, float ry, bool cw) { auto rxKappa = rx * PATH_KAPPA; auto ryKappa = ry * PATH_KAPPA; - grow(6, 13); - moveTo(cx + rx, cy); - cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); - cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); - cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); - cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); - close(); + rs.path.cmds.grow(6); + auto cmds = rs.path.cmds.end(); + + cmds[0] = PathCommand::MoveTo; + cmds[1] = PathCommand::CubicTo; + cmds[2] = PathCommand::CubicTo; + cmds[3] = PathCommand::CubicTo; + cmds[4] = PathCommand::CubicTo; + cmds[5] = PathCommand::Close; + + rs.path.cmds.count += 6; + + int table[2][13] = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, {0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 12}}; + int* idx = cw ? table[0] : table[1]; + + rs.path.pts.grow(13); + auto pts = rs.path.pts.end(); + + pts[idx[0]] = {cx, cy - ry}; //moveTo + pts[idx[1]] = {cx + rxKappa, cy - ry}; pts[idx[2]] = {cx + rx, cy - ryKappa}; pts[idx[3]] = {cx + rx, cy}; //cubicTo + pts[idx[4]] = {cx + rx, cy + ryKappa}; pts[idx[5]] = {cx + rxKappa, cy + ry}; pts[idx[6]] = {cx, cy + ry}; //cubicTo + pts[idx[7]] = {cx - rxKappa, cy + ry}; pts[idx[8]] = {cx - rx, cy + ryKappa}; pts[idx[9]] = {cx - rx, cy}; //cubicTo + pts[idx[10]] = {cx - rx, cy - ryKappa}; pts[idx[11]] = {cx - rxKappa, cy - ry}; pts[idx[12]] = {cx, cy - ry}; //cubicTo + + rs.path.pts.count += 13; + + renderFlag |= RenderUpdateFlag::Path; } - void appendRect(float x, float y, float w, float h, float rx, float ry) + void appendRect(float x, float y, float w, float h, float rx, float ry, bool cw) { - auto halfW = w * 0.5f; - auto halfH = h * 0.5f; + //sharp rect + if (tvg::zero(rx) && tvg::zero(ry)) { + rs.path.cmds.grow(5); + rs.path.pts.grow(4); - //clamping cornerRadius by minimum size - if (rx > halfW) rx = halfW; - if (ry > halfH) ry = halfH; + auto cmds = rs.path.cmds.end(); + auto pts = rs.path.pts.end(); - //rectangle - if (rx == 0 && ry == 0) { - grow(5, 4); - moveTo(x, y); - lineTo(x + w, y); - lineTo(x + w, y + h); - lineTo(x, y + h); - close(); - //rounded rectangle or circle + cmds[0] = PathCommand::MoveTo; + cmds[1] = cmds[2] = cmds[3] = PathCommand::LineTo; + cmds[4] = PathCommand::Close; + + pts[0] = {x + w, y}; + pts[2] = {x, y + h}; + if (cw) { + pts[1] = {x + w, y + h}; + pts[3] = {x, y}; + } else { + pts[1] = {x, y}; + pts[3] = {x + w, y + h}; + } + + rs.path.cmds.count += 5; + rs.path.pts.count += 4; + //round rect } else { - auto hrx = rx * PATH_KAPPA; - auto hry = ry * PATH_KAPPA; - grow(10, 17); - moveTo(x + rx, y); - lineTo(x + w - rx, y); - cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); - lineTo(x + w, y + h - ry); - cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); - lineTo(x + rx, y + h); - cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); - lineTo(x, y + ry); - cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); - close(); + auto hsize = Point{w * 0.5f, h * 0.5f}; + rx = (rx > hsize.x) ? hsize.x : rx; + ry = (ry > hsize.y) ? hsize.y : ry; + auto hr = Point{rx * PATH_KAPPA, ry * PATH_KAPPA}; + + rs.path.cmds.grow(10); + rs.path.pts.grow(17); + + auto cmds = rs.path.cmds.end(); + auto pts = rs.path.pts.end(); + + cmds[0] = PathCommand::MoveTo; + cmds[9] = PathCommand::Close; + pts[0] = {x + w, y + ry}; //move + + if (cw) { + cmds[1] = cmds[3] = cmds[5] = cmds[7] = PathCommand::LineTo; + cmds[2] = cmds[4] = cmds[6] = cmds[8] = PathCommand::CubicTo; + + pts[1] = {x + w, y + h - ry}; //line + pts[2] = {x + w, y + h - ry + hr.y}; pts[3] = {x + w - rx + hr.x, y + h}; pts[4] = {x + w - rx, y + h}; //cubic + pts[5] = {x + rx, y + h}, //line + pts[6] = {x + rx - hr.x, y + h}; pts[7] = {x, y + h - ry + hr.y}; pts[8] = {x, y + h - ry}; //cubic + pts[9] = {x, y + ry}, //line + pts[10] = {x, y + ry - hr.y}; pts[11] = {x + rx - hr.x, y}; pts[12] = {x + rx, y}; //cubic + pts[13] = {x + w - rx, y}; //line + pts[14] = {x + w - rx + hr.x, y}; pts[15] = {x + w, y + ry - hr.y}; pts[16] = {x + w, y + ry}; //cubic + } else { + cmds[1] = cmds[3] = cmds[5] = cmds[7] = PathCommand::CubicTo; + cmds[2] = cmds[4] = cmds[6] = cmds[8] = PathCommand::LineTo; + + pts[1] = {x + w, y + ry - hr.y}; pts[2] = {x + w - rx + hr.x, y}; pts[3] = {x + w - rx, y}; //cubic + pts[4] = {x + rx, y}; //line + pts[5] = {x + rx - hr.x, y}; pts[6] = {x, y + ry - hr.y}; pts[7] = {x, y + ry}; //cubic + pts[8] = {x, y + h - ry}; //line + pts[9] = {x, y + h - ry + hr.y}; pts[10] = {x + rx - hr.x, y + h}; pts[11] = {x + rx, y + h}; //cubic + pts[12] = {x + w - rx, y + h}; //line + pts[13] = {x + w - rx + hr.x, y + h}; pts[14] = {x + w, y + h - ry + hr.y}; pts[15] = {x + w, y + h - ry}; //cubic + pts[16] = {x + w, y + ry}; //line + } + + rs.path.cmds.count += 10; + rs.path.pts.count += 17; } } diff --git a/test/testShape.cpp b/test/testShape.cpp index af0e35d9..7d67f642 100644 --- a/test/testShape.cpp +++ b/test/testShape.cpp @@ -70,13 +70,14 @@ TEST_CASE("Appending Shapes", "[tvgShape]") REQUIRE(shape->lineTo(120, 140) == Result::Success); REQUIRE(shape->appendRect(0, 0, 0, 0, 0, 0) == Result::Success); - REQUIRE(shape->appendRect(0, 0,99999999.0f, -99999999.0f, 0, 0) == Result::Success); - REQUIRE(shape->appendRect(0, 0, 0, 0, -99999999.0f, 99999999.0f) == Result::Success); - REQUIRE(shape->appendRect(99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f) == Result::Success); + REQUIRE(shape->appendRect(0, 0,99999999.0f, -99999999.0f, 0, 0, true) == Result::Success); + REQUIRE(shape->appendRect(0, 0, 0, 0, -99999999.0f, 99999999.0f, false) == Result::Success); + REQUIRE(shape->appendRect(99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f, true) == Result::Success); + REQUIRE(shape->appendRect(99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f, -99999999.0f, false) == Result::Success); REQUIRE(shape->appendCircle(0, 0, 0, 0) == Result::Success); - REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, 0, 0) == Result::Success); - REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f) == Result::Success); + REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, 0, 0, true) == Result::Success); + REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f, false) == Result::Success); } TEST_CASE("Appending Paths", "[tvgShape]")