From 30a5f2891bb269db436a7b409a5e97549bb0e6e2 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Mon, 3 Mar 2025 16:21:29 +0900 Subject: [PATCH] renderer: improved the paint bounding box accuracy previously, the bounding box calculation was simply determined by comparing all the points, which led to incorrect sizing due to Bezier control points. Now, it accurately computes the curve boundary, properly addressing this issue. --- src/common/tvgMath.cpp | 40 ++++++++++++++++++++++++-- src/common/tvgMath.h | 1 + src/renderer/tvgRender.cpp | 58 ++++++++++++++++++++++++++++++++++++++ src/renderer/tvgRender.h | 1 + src/renderer/tvgShape.h | 21 ++------------ 5 files changed, 100 insertions(+), 21 deletions(-) diff --git a/src/common/tvgMath.cpp b/src/common/tvgMath.cpp index 0ad828e7..511aa2dc 100644 --- a/src/common/tvgMath.cpp +++ b/src/common/tvgMath.cpp @@ -100,6 +100,13 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc namespace tvg { + +uint8_t lerp(const uint8_t &start, const uint8_t &end, float t) +{ + return static_cast(tvg::clamp(static_cast(start + (end - start) * t), 0, 255)); +} + + float length(const PathCommand* cmds, uint32_t cmdsCnt, const Point* pts, uint32_t ptsCnt) { if (ptsCnt < 2) return 0.0f; @@ -408,9 +415,38 @@ float Bezier::angle(float t) const } -uint8_t lerp(const uint8_t &start, const uint8_t &end, float t) +void Bezier::bounds(Point& min, Point& max) const { - return static_cast(tvg::clamp(static_cast(start + (end - start) * t), 0, 255)); + if (min.x > start.x) min.x = start.x; + if (min.y > start.y) min.y = start.y; + if (min.x > end.x) min.x = end.x; + if (min.y > end.y) min.y = end.y; + + if (max.x < start.x) max.x = start.x; + if (max.y < start.y) max.y = start.y; + if (max.x < end.x) max.x = end.x; + if (max.y < end.y) max.y = end.y; + + //find x/y-direction extrema (solving derivative of Bezier curve) + auto findMinMax = [&](float start, float ctrl1, float ctrl2, float end, float& min, float& max) -> void { + auto a = -1.0f * start + 3.0f * ctrl1 - 3.0f * ctrl2 + end; + auto b = start - 2.0f * ctrl1 + ctrl2; + auto c = -1.0f * start + ctrl1; + auto h = b * b - a * c; + if (h <= 0.0f) return; + h = sqrtf(h); + float t[2] = {(-b - h) / a, (-b + h) / a}; + for (int i = 0; i < 2; ++i) { + if (t[i] <= 0.0f || t[i] >= 1.0f) continue; + auto s = 1.0f - t[i]; + auto q = s * s * s * start + 3.0f * s * s * t[i] * ctrl1 + 3.0f * s * t[i] * t[i] * ctrl2 + t[i] * t[i] * t[i] * end; + if (q < min) min = q; + if (q > max) max = q; + } + }; + + findMinMax(start.x, ctrl1.x, ctrl2.x, end.x, min.x, max.x); + findMinMax(start.y, ctrl1.y, ctrl2.y, end.y, min.y, max.y); } } diff --git a/src/common/tvgMath.h b/src/common/tvgMath.h index 8d72968a..58a64330 100644 --- a/src/common/tvgMath.h +++ b/src/common/tvgMath.h @@ -344,6 +344,7 @@ struct Bezier float atApprox(float at, float length) const; Point at(float t) const; float angle(float t) const; + void bounds(Point& min, Point& max) const; }; diff --git a/src/renderer/tvgRender.cpp b/src/renderer/tvgRender.cpp index f7486988..72406e7d 100644 --- a/src/renderer/tvgRender.cpp +++ b/src/renderer/tvgRender.cpp @@ -40,6 +40,64 @@ uint32_t RenderMethod::unref() return (--refCnt); } +/************************************************************************/ +/* RenderPath Class Implementation */ +/************************************************************************/ + +bool RenderPath::bounds(float* x, float* y, float* w, float* h) +{ + //unexpected + if (cmds.empty() || cmds.first() == PathCommand::CubicTo) return false; + + auto min = Point{FLT_MAX, FLT_MAX}; + auto max = Point{-FLT_MAX, -FLT_MAX}; + + auto pt = pts.begin(); + auto cmd = cmds.begin(); + + auto assign = [&](Point* pt, Point& min, Point& max) -> void { + if (pt->x < min.x) min.x = pt->x; + if (pt->y < min.y) min.y = pt->y; + if (pt->x > max.x) max.x = pt->x; + if (pt->y > max.y) max.y = pt->y; + }; + + while (cmd < cmds.end()) { + switch (*cmd) { + case PathCommand::MoveTo: { + //skip the invalid assignments + if (cmd + 1 < cmds.end()) { + auto next = *(cmd + 1); + if (next == PathCommand::LineTo || next == PathCommand::CubicTo) { + assign(pt, min, max); + } + } + ++pt; + break; + } + case PathCommand::LineTo: { + assign(pt, min, max); + ++pt; + break; + } + case PathCommand::CubicTo: { + Bezier bz = {pt[-1], pt[0], pt[1], pt[2]}; + bz.bounds(min, max); + pt += 3; + break; + } + default: break; + } + ++cmd; + } + + if (x) *x = min.x; + if (y) *y = min.y; + if (w) *w = max.x - min.x; + if (h) *h = max.y - min.y; + + return true; +} /************************************************************************/ /* RenderRegion Class Implementation */ diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index fb5a9a38..b3b2628d 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -104,6 +104,7 @@ struct RenderPath cmds.clear(); } + bool bounds(float* x, float* y, float* w, float* h); }; struct RenderTrimPath diff --git a/src/renderer/tvgShape.h b/src/renderer/tvgShape.h index 40f4446b..0c675e41 100644 --- a/src/renderer/tvgShape.h +++ b/src/renderer/tvgShape.h @@ -118,24 +118,7 @@ struct Shape::Impl : Paint::Impl bool bounds(float* x, float* y, float* w, float* h, bool stroking) { - //Path bounding size - if (rs.path.pts.count > 0 ) { - auto pts = rs.path.pts.begin(); - Point min = { pts->x, pts->y }; - Point max = { pts->x, pts->y }; - - for (auto pts2 = pts + 1; pts2 < rs.path.pts.end(); ++pts2) { - if (pts2->x < min.x) min.x = pts2->x; - if (pts2->y < min.y) min.y = pts2->y; - if (pts2->x > max.x) max.x = pts2->x; - if (pts2->y > max.y) max.y = pts2->y; - } - - if (x) *x = min.x; - if (y) *y = min.y; - if (w) *w = max.x - min.x; - if (h) *h = max.y - min.y; - } + if (!rs.path.bounds(x, y, w, h)) return false; //Stroke feathering if (stroking && rs.stroke) { @@ -144,7 +127,7 @@ struct Shape::Impl : Paint::Impl if (w) *w += rs.stroke->width; if (h) *h += rs.stroke->width; } - return rs.path.pts.count > 0 ? true : false; + return true; } void reserveCmd(uint32_t cmdCnt)