renderer: revise the Shape rect/circle features

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
This commit is contained in:
Hermet Park 2025-02-12 00:52:56 +09:00 committed by Hermet Park
parent 5c950b783c
commit cc4c18d6c6
10 changed files with 196 additions and 163 deletions

View file

@ -94,9 +94,9 @@ void contents()
//////3. Radial gradient shape with a radial dashed stroke //////3. Radial gradient shape with a radial dashed stroke
//Set a shape //Set a shape
Tvg_Paint* shape3 = tvg_shape_new(); 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_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); 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); 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 //Prepare a radial gradient for the fill
Tvg_Gradient* grad2 = tvg_radial_gradient_new(); Tvg_Gradient* grad2 = tvg_radial_gradient_new();
@ -142,8 +142,8 @@ void contents()
//Set circles //Set circles
Tvg_Paint* scene_shape1 = tvg_shape_new(); 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, 80.0f, 650.f, 40.0f, 140.0f, true);
tvg_shape_append_circle(scene_shape1, 180.0f, 600.f, 40.0f, 60.0f); 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_fill_color(scene_shape1, 0, 0, 255, 150);
tvg_shape_set_stroke_color(scene_shape1, 75, 25, 155, 255); tvg_shape_set_stroke_color(scene_shape1, 75, 25, 155, 255);
tvg_shape_set_stroke_width(scene_shape1, 10.0f); tvg_shape_set_stroke_width(scene_shape1, 10.0f);
@ -189,7 +189,7 @@ void contents()
// Set a composite shape // Set a composite shape
Tvg_Paint* comp = tvg_shape_new(); 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_shape_set_fill_color(comp, 0, 0, 0, 200);
tvg_paint_set_mask_method(pict, comp, TVG_MASK_METHOD_INVERSE_ALPHA); tvg_paint_set_mask_method(pict, comp, TVG_MASK_METHOD_INVERSE_ALPHA);

View file

@ -43,13 +43,13 @@ struct UserExample : tvgexam::Example
shape1->strokeJoin(tvg::StrokeJoin::Round); shape1->strokeJoin(tvg::StrokeJoin::Round);
shape1->strokeCap(tvg::StrokeCap::Round); shape1->strokeCap(tvg::StrokeCap::Round);
shape1->strokeWidth(12); shape1->strokeWidth(12);
shape1->strokeTrim(0.0f, 0.5f, false); shape1->strokeTrim(0.25f, 0.75f, false);
auto shape2 = static_cast<tvg::Shape*>(shape1->duplicate()); auto shape2 = static_cast<tvg::Shape*>(shape1->duplicate());
shape2->translate(300, 300); shape2->translate(300, 300);
shape2->fill(0, 155, 50, 100); shape2->fill(0, 155, 50, 100);
shape2->strokeFill(0, 255, 0); shape2->strokeFill(0, 255, 0);
shape2->strokeTrim(0.0f, 0.5f, true); shape2->strokeTrim(0.25f, 0.75f, true);
canvas->push(shape1); canvas->push(shape1);
canvas->push(shape2); canvas->push(shape2);

View file

@ -983,10 +983,11 @@ public:
* @param[in] h The height of the rectangle. * @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] 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] 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. * @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. * @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] cy The vertical coordinate of the center of the ellipse.
* @param[in] rx The x-axis radius 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] 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. * @brief Appends a given sub-path to the path.

View file

@ -1175,13 +1175,14 @@ TVG_API Tvg_Result tvg_shape_close(Tvg_Paint* paint);
* @param[in] h The height of the rectangle. * @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] 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] 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. * @return Tvg_Result enumeration.
* @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer. * @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. & @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] cy The vertical coordinate of the center of the ellipse.
* @param[in] rx The x-axis radius 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] 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. * @return Tvg_Result enumeration.
* @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer. * @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);
/*! /*!

View file

@ -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; if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Shape*>(paint)->appendRect(x, y, w, h, rx, ry); return (Tvg_Result) reinterpret_cast<Shape*>(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; if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Shape*>(paint)->appendCircle(cx, cy, rx, ry); return (Tvg_Result) reinterpret_cast<Shape*>(paint)->appendCircle(cx, cy, rx, ry, cw);
} }

View file

@ -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) 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); auto before = SHAPE(temp)->rs.path.pts.count;
else _roundRect(buffer, pos, size, r, clockwise, ctx);
temp->appendRect(pos.x, pos.y, size.x, size.y, r, r, clockwise);
auto after = SHAPE(temp)->rs.path.pts.count;
if (ctx->transform) { if (ctx->transform) {
ARRAY_FOREACH(pt, buffer.pts) { for (uint32_t i = before; i < after; ++i) {
*pt *= *ctx->transform; SHAPE(temp)->rs.path.pts[i] *= *ctx->transform;
} }
} }
if (ctx->offset) ctx->offset->modifyRect(buffer, SHAPE(shape)->rs.path); if (ctx->offset) {
else shape->appendPath(buffer.cmds.data, buffer.cmds.count, buffer.pts.data, buffer.pts.count); 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 (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; auto after = SHAPE(shape)->rs.path.pts.count;
PathCommand cmds[cmdsCnt] = {PathCommand::MoveTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::CubicTo, PathCommand::Close};
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) { if (ctx->transform) {
for (int i = 0; i < ptsCnt; ++i) { for (uint32_t i = before; i < after; ++i) {
pts[i] *= *ctx->transform; SHAPE(shape)->rs.path.pts[i] *= *ctx->transform;
} }
} }
shape->appendPath(cmds, cmdsCnt, pts, ptsCnt);
} }

View file

@ -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) 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. //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; break;
} }
case SvgNodeType::Ellipse: { 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; break;
} }
case SvgNodeType::Polygon: { case SvgNodeType::Polygon: {
@ -453,11 +503,11 @@ static bool _recognizeShape(SvgNode* node, Shape* shape)
break; break;
} }
case SvgNodeType::Circle: { 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; break;
} }
case SvgNodeType::Rect: { 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; break;
} }
case SvgNodeType::Line: { case SvgNodeType::Line: {
@ -774,7 +824,7 @@ static Scene* _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, co
if (!node->node.use.symbol->node.symbol.overflowVisible) { if (!node->node.use.symbol->node.symbol.overflowVisible) {
auto viewBoxClip = Shape::gen(); auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0); viewBoxClip->appendRect(0, 0, width, height);
// mClipTransform = mUseTransform * mSymbolTransform // mClipTransform = mUseTransform * mSymbolTransform
Matrix mClipTransform = mUseTransform; Matrix mClipTransform = mUseTransform;

View file

@ -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; 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; return Result::Success;
} }

View file

@ -386,52 +386,111 @@ struct Shape::Impl : Paint::Impl
return Result::Success; 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 rxKappa = rx * PATH_KAPPA;
auto ryKappa = ry * PATH_KAPPA; auto ryKappa = ry * PATH_KAPPA;
grow(6, 13); rs.path.cmds.grow(6);
moveTo(cx + rx, cy); auto cmds = rs.path.cmds.end();
cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry);
cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); cmds[0] = PathCommand::MoveTo;
cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); cmds[1] = PathCommand::CubicTo;
cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); cmds[2] = PathCommand::CubicTo;
close(); 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; //sharp rect
auto halfH = h * 0.5f; if (tvg::zero(rx) && tvg::zero(ry)) {
rs.path.cmds.grow(5);
rs.path.pts.grow(4);
//clamping cornerRadius by minimum size auto cmds = rs.path.cmds.end();
if (rx > halfW) rx = halfW; auto pts = rs.path.pts.end();
if (ry > halfH) ry = halfH;
//rectangle cmds[0] = PathCommand::MoveTo;
if (rx == 0 && ry == 0) { cmds[1] = cmds[2] = cmds[3] = PathCommand::LineTo;
grow(5, 4); cmds[4] = PathCommand::Close;
moveTo(x, y);
lineTo(x + w, y); pts[0] = {x + w, y};
lineTo(x + w, y + h); pts[2] = {x, y + h};
lineTo(x, y + h); if (cw) {
close(); pts[1] = {x + w, y + h};
//rounded rectangle or circle pts[3] = {x, y};
} else { } else {
auto hrx = rx * PATH_KAPPA; pts[1] = {x, y};
auto hry = ry * PATH_KAPPA; pts[3] = {x + w, y + h};
grow(10, 17); }
moveTo(x + rx, y);
lineTo(x + w - rx, y); rs.path.cmds.count += 5;
cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); rs.path.pts.count += 4;
lineTo(x + w, y + h - ry); //round rect
cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); } else {
lineTo(x + rx, y + h); auto hsize = Point{w * 0.5f, h * 0.5f};
cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); rx = (rx > hsize.x) ? hsize.x : rx;
lineTo(x, y + ry); ry = (ry > hsize.y) ? hsize.y : ry;
cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); auto hr = Point{rx * PATH_KAPPA, ry * PATH_KAPPA};
close();
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;
} }
} }

View file

@ -70,13 +70,14 @@ TEST_CASE("Appending Shapes", "[tvgShape]")
REQUIRE(shape->lineTo(120, 140) == Result::Success); REQUIRE(shape->lineTo(120, 140) == Result::Success);
REQUIRE(shape->appendRect(0, 0, 0, 0, 0, 0) == 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,99999999.0f, -99999999.0f, 0, 0, true) == Result::Success);
REQUIRE(shape->appendRect(0, 0, 0, 0, -99999999.0f, 99999999.0f) == 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) == 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(0, 0, 0, 0) == Result::Success);
REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, 0, 0) == 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) == Result::Success); REQUIRE(shape->appendCircle(-99999999.0f, 99999999.0f, -99999999.0f, 99999999.0f, false) == Result::Success);
} }
TEST_CASE("Appending Paths", "[tvgShape]") TEST_CASE("Appending Paths", "[tvgShape]")