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]")