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)