renderer: revise the Bounding Box Behavior

- modify the concept of AABB to apply only to transformed shapes.
- transform points before computing the bounding box min/max
  to obtain a more compact shape region.
- trimmming memory by removing the cached matrix, about 36kb
  of memory has been reduced per paint instance.
This commit is contained in:
Hermet Park 2025-03-19 00:33:23 +09:00 committed by Hermet Park
parent a7efd0f199
commit 333e65ef7a
18 changed files with 113 additions and 105 deletions

View file

@ -439,8 +439,6 @@ public:
* @retval Result::InvalidArguments @p pt4 is @c nullptr. * @retval Result::InvalidArguments @p pt4 is @c nullptr.
* @retval Result::InsufficientCondition If it failed to compute the bounding box (mostly due to invalid path information). * @retval Result::InsufficientCondition If it failed to compute the bounding box (mostly due to invalid path information).
* *
* @note The paint must be pushed into a canvas and updated before calling this function.
*
* @see Paint::bounds(float* x, float* y, folat* w, float* h) * @see Paint::bounds(float* x, float* y, folat* w, float* h)
* @see Canvas::update() * @see Canvas::update()
* *
@ -449,9 +447,9 @@ public:
Result bounds(Point* pt4) const noexcept; Result bounds(Point* pt4) const noexcept;
/** /**
* @brief Retrieves the axis-aligned bounding box (AABB) of the paint object in local space. * @brief Retrieves the axis-aligned bounding box (AABB) of the paint object in canvas space.
* *
* This function returns the bounding box of the paint object relative to its local coordinate system, without applying any transformations. * This function returns the bounding box of the paint, as an axis-aligned bounding box (AABB) after transformations are applied.
* *
* @param[out] x The x-coordinate of the upper-left corner of the bounding box. * @param[out] x The x-coordinate of the upper-left corner of the bounding box.
* @param[out] y The y-coordinate of the upper-left corner of the bounding box. * @param[out] y The y-coordinate of the upper-left corner of the bounding box.
@ -460,8 +458,6 @@ public:
* *
* @retval Result::InsufficientCondition If it failed to compute the bounding box (mostly due to invalid path information). * @retval Result::InsufficientCondition If it failed to compute the bounding box (mostly due to invalid path information).
* *
* @note The bounding box is calculated in the object's local space, meaning transformations such as scaling, rotation, or translation are not applied.
*
* @see Paint::bounds(Point* pt4) * @see Paint::bounds(Point* pt4)
* @see Canvas::update() * @see Canvas::update()
*/ */

View file

@ -912,9 +912,9 @@ TVG_API Tvg_Paint* tvg_paint_duplicate(Tvg_Paint* paint);
/** /**
* @brief Retrieves the axis-aligned bounding box (AABB) of the paint object in local space. * @brief Retrieves the axis-aligned bounding box (AABB) of the paint object in canvas space.
* *
* This function returns the bounding box of the paint object relative to its local coordinate system, without applying any transformations. * This function returns the bounding box of the paint, as an axis-aligned bounding box (AABB) after transformations are applied.
* *
* @param[in] paint The Tvg_Paint object of which to get the bounds. * @param[in] paint The Tvg_Paint object of which to get the bounds.
* @param[out] x The x-coordinate of the upper-left corner of the bounding box. * @param[out] x The x-coordinate of the upper-left corner of the bounding box.
@ -926,8 +926,6 @@ TVG_API Tvg_Paint* tvg_paint_duplicate(Tvg_Paint* paint);
* @retval TVG_RESULT_INVALID_ARGUMENT An invalid @p paint. * @retval TVG_RESULT_INVALID_ARGUMENT An invalid @p paint.
* @retval TVG_RESULT_INSUFFICIENT_CONDITION If it failed to compute the bounding box (mostly due to invalid path information). * @retval TVG_RESULT_INSUFFICIENT_CONDITION If it failed to compute the bounding box (mostly due to invalid path information).
* *
* @note The bounding box is calculated in the object's local space, meaning transformations such as scaling, rotation, or translation are not applied.
*
* @see tvg_paint_get_obb() * @see tvg_paint_get_obb()
* @see tvg_canvas_update_paint() * @see tvg_canvas_update_paint()
*/ */
@ -946,8 +944,6 @@ TVG_API Tvg_Result tvg_paint_get_aabb(const Tvg_Paint* paint, float* x, float* y
* @retval TVG_RESULT_INVALID_ARGUMENT @p paint or @p pt4 is invalid. * @retval TVG_RESULT_INVALID_ARGUMENT @p paint or @p pt4 is invalid.
* @retval TVG_RESULT_INSUFFICIENT_CONDITION If it failed to compute the bounding box (mostly due to invalid path information). * @retval TVG_RESULT_INSUFFICIENT_CONDITION If it failed to compute the bounding box (mostly due to invalid path information).
* *
* @note The paint must be pushed into a canvas and updated before calling this function.
*
* @see tvg_paint_get_aabb() * @see tvg_paint_get_aabb()
* @see tvg_canvas_update_paint() * @see tvg_canvas_update_paint()
* *

View file

@ -115,6 +115,12 @@ static inline void identity(Matrix* m)
} }
static inline constexpr const Matrix identity()
{
return {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f};
}
static inline void scale(Matrix* m, const Point& p) static inline void scale(Matrix* m, const Point& p)
{ {
m->e11 *= p.x; m->e11 *= p.x;
@ -178,6 +184,20 @@ Point normal(const Point& p1, const Point& p2);
void normalize(Point& pt); void normalize(Point& pt);
static inline constexpr const Point operator*=(Point& pt, const Matrix* m)
{
if (m) pt *= *m;
return pt;
}
static inline Point operator*(const Point& pt, const Matrix* m)
{
if (!m) return pt;
return pt * *m;
}
static inline Point min(const Point& lhs, const Point& rhs) static inline Point min(const Point& lhs, const Point& rhs)
{ {
return {std::min(lhs.x, rhs.x), std::min(lhs.y, rhs.y)}; return {std::min(lhs.x, rhs.x), std::min(lhs.y, rhs.y)};

View file

@ -553,8 +553,7 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran
hasRoundness = true; hasRoundness = true;
} }
Point in = {x, y}; auto in = Point{x, y} * transform;
if (transform) in *= *transform;
shape->moveTo(in.x, in.y); shape->moveTo(in.x, in.y);
for (size_t i = 0; i < numPoints; i++) { for (size_t i = 0; i < numPoints; i++) {
@ -595,18 +594,12 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran
cp2x *= partialPointAmount; cp2x *= partialPointAmount;
cp2y *= partialPointAmount; cp2y *= partialPointAmount;
} }
Point in2 = {previousX - cp1x, previousY - cp1y}; auto in2 = Point{previousX - cp1x, previousY - cp1y} * transform;
Point in3 = {x + cp2x, y + cp2y}; auto in3 = Point{x + cp2x, y + cp2y} * transform;
Point in4 = {x, y}; auto in4 = Point{x, y} * transform;
if (transform) {
in2 *= *transform;
in3 *= *transform;
in4 *= *transform;
}
shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y); shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y);
} else { } else {
Point in = {x, y}; auto in = Point{x, y} * transform;
if (transform) in *= *transform;
shape->lineTo(in.x, in.y); shape->lineTo(in.x, in.y);
} }
angle += dTheta * direction; angle += dTheta * direction;
@ -651,8 +644,7 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
} }
} }
Point in = {x, y}; auto in = Point{x, y} * transform;
if (transform) in *= *transform;
shape->moveTo(in.x, in.y); shape->moveTo(in.x, in.y);
for (size_t i = 0; i < ptsCnt; i++) { for (size_t i = 0; i < ptsCnt; i++) {
@ -674,14 +666,9 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo
auto cp2x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dx; auto cp2x = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dx;
auto cp2y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dy; auto cp2y = radius * outerRoundness * POLYGON_MAGIC_NUMBER * cp2Dy;
Point in2 = {previousX - cp1x, previousY - cp1y}; auto in2 = Point{previousX - cp1x, previousY - cp1y} * transform;
Point in3 = {x + cp2x, y + cp2y}; auto in3 = Point{x + cp2x, y + cp2y} * transform;
Point in4 = {x, y}; auto in4 = Point{x, y} * transform;
if (transform) {
in2 *= *transform;
in3 *= *transform;
in4 *= *transform;
}
shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y); shape->cubicTo(in2.x, in2.y, in3.x, in3.y, in4.x, in4.y);
} else { } else {
Point in = {x, y}; Point in = {x, y};

View file

@ -2996,7 +2996,7 @@ static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgSty
if (!to->transform && from->transform) { if (!to->transform && from->transform) {
to->transform = tvg::malloc<Matrix*>(sizeof(Matrix)); to->transform = tvg::malloc<Matrix*>(sizeof(Matrix));
if (to->transform) memcpy(to->transform, from->transform, sizeof(Matrix)); if (to->transform) *to->transform = *from->transform;
} }
if (to->type == SvgGradientType::Linear) { if (to->type == SvgGradientType::Linear) {
@ -3061,7 +3061,7 @@ static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from)
if (from->transform) { if (from->transform) {
grad->transform = tvg::calloc<Matrix*>(1, sizeof(Matrix)); grad->transform = tvg::calloc<Matrix*>(1, sizeof(Matrix));
if (grad->transform) memcpy(grad->transform, from->transform, sizeof(Matrix)); if (grad->transform) *grad->transform = *from->transform;
} }
if (grad->type == SvgGradientType::Linear) { if (grad->type == SvgGradientType::Linear) {

View file

@ -50,7 +50,7 @@ static inline bool _isGroupType(SvgNodeType type)
static Box _boundingBox(Paint* shape) static Box _boundingBox(Paint* shape)
{ {
float x, y, w, h; float x, y, w, h;
PAINT(shape)->bounds(&x, &y, &w, &h, false); PAINT(shape)->bounds(&x, &y, &w, &h, nullptr, false);
return {x, y, w, h}; return {x, y, w, h};
} }
@ -219,7 +219,7 @@ static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* sh
if (node->type == SvgNodeType::Use) { if (node->type == SvgNodeType::Use) {
if (node->child.count != 1) return false; if (node->child.count != 1) return false;
auto child = *(node->child.data); auto child = *(node->child.data);
Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; auto finalTransform = tvg::identity();
if (node->transform) finalTransform = *node->transform; if (node->transform) finalTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
finalTransform *= {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; finalTransform *= {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
@ -234,7 +234,7 @@ static bool _appendClipChild(SvgLoaderData& loaderData, SvgNode* node, Shape* sh
static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type) static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const SvgNode* compNode, SvgNodeType type)
{ {
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; auto m = tvg::identity();
//The initial mask transformation ignored according to the SVG standard. //The initial mask transformation ignored according to the SVG standard.
if (node->transform && type != SvgNodeType::Mask) { if (node->transform && type != SvgNodeType::Mask) {
m = *node->transform; m = *node->transform;
@ -244,7 +244,7 @@ static Matrix _compositionTransform(Paint* paint, const SvgNode* node, const Svg
} }
if (!compNode->node.clip.userSpace) { if (!compNode->node.clip.userSpace) {
float x, y, w, h; float x, y, w, h;
PAINT(paint)->bounds(&x, &y, &w, &h, false); PAINT(paint)->bounds(&x, &y, &w, &h, nullptr, false);
m *= {w, 0, x, 0, h, y, 0, 0, 1}; m *= {w, 0, x, 0, h, y, 0, 0, 1};
} }
return m; return m;
@ -685,7 +685,7 @@ static Paint* _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const
auto sy = node->node.image.h / h; auto sy = node->node.image.h / h;
m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1}; m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
} else { } else {
m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; m = tvg::identity();
} }
if (node->transform) m = *node->transform * m; if (node->transform) m = *node->transform * m;
picture->transform(m); picture->transform(m);
@ -773,7 +773,7 @@ static Scene* _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, co
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1); auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1);
// mUseTransform = mUseTransform * mTranslate // mUseTransform = mUseTransform * mTranslate
Matrix mUseTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1}; auto mUseTransform = tvg::identity();
if (node->transform) mUseTransform = *node->transform; if (node->transform) mUseTransform = *node->transform;
if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) { if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1}; Matrix mTranslate = {1, 0, node->node.use.x, 0, 1, node->node.use.y, 0, 0, 1};
@ -789,7 +789,7 @@ static Scene* _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, co
auto vw = (symbol.hasViewBox ? symbol.vw : width); auto vw = (symbol.hasViewBox ? symbol.vw : width);
auto vh = (symbol.hasViewBox ? symbol.vh : height); auto vh = (symbol.hasViewBox ? symbol.vh : height);
Matrix mViewBox = {1, 0, 0, 0, 1, 0, 0, 0, 1}; auto mViewBox = tvg::identity();
if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) { if ((!tvg::equal(width, vw) || !tvg::equal(height, vh)) && vw > 0 && vh > 0) {
Box box = {symbol.vx, symbol.vy, vw, vh}; Box box = {symbol.vx, symbol.vy, vw, vh};
mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box); mViewBox = _calculateAspectRatioMatrix(symbol.align, symbol.meetOrSlice, width, height, box);
@ -864,7 +864,7 @@ static Paint* _textBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, c
Matrix textTransform; Matrix textTransform;
if (node->transform) textTransform = *node->transform; if (node->transform) textTransform = *node->transform;
else textTransform = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; else textTransform = tvg::identity();
translateR(&textTransform, {node->node.text.x, node->node.text.y - textNode->fontSize}); translateR(&textTransform, {node->node.text.x, node->node.text.y - textNode->fontSize});
text->transform(textTransform); text->transform(textTransform);

View file

@ -630,7 +630,7 @@ bool SwRenderer::endComposite(RenderCompositor* cmp)
//Default is alpha blending //Default is alpha blending
if (p->method == MaskMethod::None) { if (p->method == MaskMethod::None) {
Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; auto m = tvg::identity();
return rasterImage(surface, &p->image, m, p->bbox, p->opacity); return rasterImage(surface, &p->image, m, p->bbox, p->opacity);
} }

View file

@ -71,7 +71,7 @@ struct Canvas::Impl
auto flag = RenderUpdateFlag::None; auto flag = RenderUpdateFlag::None;
if (status == Status::Damaged || force) flag = RenderUpdateFlag::All; if (status == Status::Damaged || force) flag = RenderUpdateFlag::All;
auto m = Matrix{1, 0, 0, 0, 1, 0, 0, 0, 1}; auto m = tvg::identity();
if (!renderer->preUpdate()) return Result::InsufficientCondition; if (!renderer->preUpdate()) return Result::InsufficientCondition;

View file

@ -24,6 +24,7 @@
#define _TVG_FILL_H_ #define _TVG_FILL_H_
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgMath.h"
#define LINEAR(A) PIMPL(A, LinearGradient) #define LINEAR(A) PIMPL(A, LinearGradient)
#define RADIAL(A) PIMPL(A, RadialGradient) #define RADIAL(A) PIMPL(A, RadialGradient)
@ -31,7 +32,7 @@
struct Fill::Impl struct Fill::Impl
{ {
ColorStop* colorStops = nullptr; ColorStop* colorStops = nullptr;
Matrix transform = {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}; Matrix transform = tvg::identity();
uint16_t cnt = 0; uint16_t cnt = 0;
FillSpread spread = FillSpread::Pad; FillSpread spread = FillSpread::Pad;

View file

@ -267,8 +267,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<R
RenderData rd = nullptr; RenderData rd = nullptr;
tr.cm = pm * tr.m; PAINT_METHOD(rd, update(renderer, pm * tr.m, clips, opacity, newFlag, clipper));
PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
/* 4. Composition Post Processing */ /* 4. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport); if (compFastTrack == Result::Success) renderer->viewport(viewport);
@ -278,10 +277,10 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<R
} }
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool stroking) Result Paint::Impl::bounds(float* x, float* y, float* w, float* h, Matrix* pm, bool stroking)
{ {
Point pts[4]; Point pts[4];
if (!bounds(pts, false, stroking, false)) return false; if (bounds(pts, pm, false, stroking) != Result::Success) return Result::InsufficientCondition;
Point min = {FLT_MAX, FLT_MAX}; Point min = {FLT_MAX, FLT_MAX};
Point max = {-FLT_MAX, -FLT_MAX}; Point max = {-FLT_MAX, -FLT_MAX};
@ -297,23 +296,17 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool stroking)
if (y) *y = min.y; if (y) *y = min.y;
if (w) *w = max.x - min.x; if (w) *w = max.x - min.x;
if (h) *h = max.y - min.y; if (h) *h = max.y - min.y;
return true; return Result::Success;
} }
bool Paint::Impl::bounds(Point* pt4, bool transformed, bool stroking, bool origin) Result Paint::Impl::bounds(Point* pt4, Matrix* pm, bool obb, bool stroking)
{ {
bool ret; auto m = this->transform();
PAINT_METHOD(ret, bounds(pt4, stroking)); if (pm) m = *pm * m;
if (!ret || !transformed) return ret;
const auto& m = this->transform(origin);
pt4[0] *= m;
pt4[1] *= m;
pt4[2] *= m;
pt4[3] *= m;
Result ret;
PAINT_METHOD(ret, bounds(pt4, m, obb, stroking));
return ret; return ret;
} }
@ -369,16 +362,16 @@ Matrix& Paint::transform() noexcept
Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
{ {
if (pImpl->bounds(x, y, w, h, true)) return Result::Success; auto pm = pImpl->ptransform();
return Result::InsufficientCondition; return pImpl->bounds(x, y, w, h, &pm, true);
} }
Result Paint::bounds(Point* pt4) const noexcept Result Paint::bounds(Point* pt4) const noexcept
{ {
if (!pt4) return Result::InvalidArguments; if (!pt4) return Result::InvalidArguments;
if (pImpl->bounds(pt4, true, true, true)) return Result::Success; auto pm = pImpl->ptransform();
return Result::InsufficientCondition; return pImpl->bounds(pt4, &pm, true, true);
} }

View file

@ -59,7 +59,6 @@ namespace tvg
struct { struct {
Matrix m; //input matrix Matrix m; //input matrix
Matrix cm; //multipled parents matrix
float degree; //rotation degree float degree; //rotation degree
float scale; //scale factor float scale; //scale factor
bool overriding; //user transform? bool overriding; //user transform?
@ -144,14 +143,24 @@ namespace tvg
return true; return true;
} }
Matrix& transform(bool origin = false) Matrix& transform()
{ {
//update transform //update transform
if (renderFlag & RenderUpdateFlag::Transform) tr.update(); if (renderFlag & RenderUpdateFlag::Transform) tr.update();
if (origin) return tr.cm;
return tr.m; return tr.m;
} }
Matrix ptransform()
{
auto p = this;
auto tm = identity();
while (p->parent) {
p = PAINT(p->parent);
tm = p->transform() * tm;
}
return tm;
}
Result clip(Paint* clp) Result clip(Paint* clp)
{ {
if (PAINT(clp)->parent) return Result::InsufficientCondition; if (PAINT(clp)->parent) return Result::InsufficientCondition;
@ -263,8 +272,8 @@ namespace tvg
RenderRegion bounds(RenderMethod* renderer) const; RenderRegion bounds(RenderMethod* renderer) const;
Iterator* iterator(); Iterator* iterator();
bool bounds(float* x, float* y, float* w, float* h, bool stroking); Result bounds(float* x, float* y, float* w, float* h, Matrix* pm, bool stroking);
bool bounds(Point* pt4, bool transformed, bool stroking, bool origin = false); Result bounds(Point* pt4, Matrix* pm, bool obb, bool stroking);
RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false); RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer); bool render(RenderMethod* renderer);
Paint* duplicate(Paint* ret = nullptr); Paint* duplicate(Paint* ret = nullptr);

View file

@ -114,13 +114,13 @@ struct Picture::Impl : Paint::Impl
return Result::Success; return Result::Success;
} }
bool bounds(Point* pt4, bool stroking) Result bounds(Point* pt4, Matrix& m, TVG_UNUSED bool obb, TVG_UNUSED bool stroking)
{ {
pt4[0] = {0.0f, 0.0f}; pt4[0] = Point{0.0f, 0.0f} * m;
pt4[1] = {w, 0.0f}; pt4[1] = Point{w, 0.0f} * m;
pt4[2] = {w, h}; pt4[2] = Point{w, h} * m;
pt4[3] = {0.0f, h}; pt4[3] = Point{0.0f, h} * m;
return true; return Result::Success;
} }
Result load(const char* filename) Result load(const char* filename)

View file

@ -44,7 +44,7 @@ uint32_t RenderMethod::unref()
/* RenderPath Class Implementation */ /* RenderPath Class Implementation */
/************************************************************************/ /************************************************************************/
bool RenderPath::bounds(float* x, float* y, float* w, float* h) bool RenderPath::bounds(Matrix* m, float* x, float* y, float* w, float* h)
{ {
//unexpected //unexpected
if (cmds.empty() || cmds.first() == PathCommand::CubicTo) return false; if (cmds.empty() || cmds.first() == PathCommand::CubicTo) return false;
@ -55,11 +55,11 @@ bool RenderPath::bounds(float* x, float* y, float* w, float* h)
auto pt = pts.begin(); auto pt = pts.begin();
auto cmd = cmds.begin(); auto cmd = cmds.begin();
auto assign = [&](Point* pt, Point& min, Point& max) -> void { auto assign = [&](const Point& pt, Point& min, Point& max) -> void {
if (pt->x < min.x) min.x = pt->x; if (pt.x < min.x) min.x = pt.x;
if (pt->y < min.y) min.y = pt->y; if (pt.y < min.y) min.y = pt.y;
if (pt->x > max.x) max.x = pt->x; if (pt.x > max.x) max.x = pt.x;
if (pt->y > max.y) max.y = pt->y; if (pt.y > max.y) max.y = pt.y;
}; };
while (cmd < cmds.end()) { while (cmd < cmds.end()) {
@ -69,19 +69,19 @@ bool RenderPath::bounds(float* x, float* y, float* w, float* h)
if (cmd + 1 < cmds.end()) { if (cmd + 1 < cmds.end()) {
auto next = *(cmd + 1); auto next = *(cmd + 1);
if (next == PathCommand::LineTo || next == PathCommand::CubicTo) { if (next == PathCommand::LineTo || next == PathCommand::CubicTo) {
assign(pt, min, max); assign(*pt * m, min, max);
} }
} }
++pt; ++pt;
break; break;
} }
case PathCommand::LineTo: { case PathCommand::LineTo: {
assign(pt, min, max); assign(*pt * m, min, max);
++pt; ++pt;
break; break;
} }
case PathCommand::CubicTo: { case PathCommand::CubicTo: {
Bezier bz = {pt[-1], pt[0], pt[1], pt[2]}; Bezier bz = {pt[-1] * m, pt[0] * m, pt[1] * m, pt[2] * m};
bz.bounds(min, max); bz.bounds(min, max);
pt += 3; pt += 3;
break; break;

View file

@ -104,7 +104,7 @@ struct RenderPath
cmds.clear(); cmds.clear();
} }
bool bounds(float* x, float* y, float* w, float* h); bool bounds(Matrix* m, float* x, float* y, float* w, float* h);
}; };
struct RenderTrimPath struct RenderTrimPath

View file

@ -194,16 +194,16 @@ struct Scene::Impl : Paint::Impl
return ret; return ret;
} }
bool bounds(Point* pt4, bool stroking) Result bounds(Point* pt4, Matrix& m, TVG_UNUSED bool obb, bool stroking)
{ {
if (paints.empty()) return false; if (paints.empty()) return Result::InsufficientCondition;
Point min = {FLT_MAX, FLT_MAX}; Point min = {FLT_MAX, FLT_MAX};
Point max = {-FLT_MAX, -FLT_MAX}; Point max = {-FLT_MAX, -FLT_MAX};
for (auto paint : paints) { for (auto paint : paints) {
Point tmp[4]; Point tmp[4];
if (!PAINT(paint)->bounds(tmp, true, stroking)) continue; if (PAINT(paint)->bounds(tmp, nullptr, false, stroking) != Result::Success) continue;
//Merge regions //Merge regions
for (int i = 0; i < 4; ++i) { for (int i = 0; i < 4; ++i) {
if (tmp[i].x < min.x) min.x = tmp[i].x; if (tmp[i].x < min.x) min.x = tmp[i].x;
@ -212,12 +212,12 @@ struct Scene::Impl : Paint::Impl
if (tmp[i].y > max.y) max.y = tmp[i].y; if (tmp[i].y > max.y) max.y = tmp[i].y;
} }
} }
pt4[0] = min; pt4[0] = min * m;
pt4[1] = {max.x, min.y}; pt4[1] = Point{max.x, min.y} * m;
pt4[2] = max; pt4[2] = max * m;
pt4[3] = {min.x, max.y}; pt4[3] = Point{min.x, max.y} * m;
return true; return Result::Success;
} }
Paint* duplicate(Paint* ret) Paint* duplicate(Paint* ret)

View file

@ -116,10 +116,10 @@ struct Shape::Impl : Paint::Impl
return renderer->region(rd); return renderer->region(rd);
} }
bool bounds(Point* pt4, bool stroking) Result bounds(Point* pt4, Matrix& m, bool obb, bool stroking)
{ {
float x, y, w, h; float x, y, w, h;
if (!rs.path.bounds(&x, &y, &w, &h)) return false; if (!rs.path.bounds(obb ? nullptr : &m, &x, &y, &w, &h)) return Result::InsufficientCondition;
//Stroke feathering //Stroke feathering
if (stroking && rs.stroke) { if (stroking && rs.stroke) {
@ -134,7 +134,14 @@ struct Shape::Impl : Paint::Impl
pt4[2] = {x + w, y + h}; pt4[2] = {x + w, y + h};
pt4[3] = {x, y + h}; pt4[3] = {x, y + h};
return true; if (obb) {
pt4[0] *= m;
pt4[1] *= m;
pt4[2] *= m;
pt4[3] *= m;
}
return Result::Success;
} }
void reserveCmd(uint32_t cmdCnt) void reserveCmd(uint32_t cmdCnt)

View file

@ -135,11 +135,10 @@ struct Text::Impl : Paint::Impl
return PAINT(shape)->update(renderer, transform, clips, opacity, pFlag, false); return PAINT(shape)->update(renderer, transform, clips, opacity, pFlag, false);
} }
bool bounds(Point* pt4, TVG_UNUSED bool stroking) Result bounds(Point* pt4, Matrix& m, bool obb, TVG_UNUSED bool stroking)
{ {
if (!load()) return false; if (!load()) return Result::InsufficientCondition;
PAINT(shape)->bounds(pt4, true, true, false); return PAINT(shape)->bounds(pt4, &m, obb, true);
return true;
} }
Paint* duplicate(Paint* ret) Paint* duplicate(Paint* ret)

View file

@ -133,8 +133,8 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
REQUIRE(shape->appendRect(0.0f, 10.0f, 20.0f, 100.0f, 50.0f, 50.0f) == Result::Success); REQUIRE(shape->appendRect(0.0f, 10.0f, 20.0f, 100.0f, 50.0f, 50.0f) == Result::Success);
REQUIRE(shape->translate(100.0f, 111.0f) == Result::Success); REQUIRE(shape->translate(100.0f, 111.0f) == Result::Success);
REQUIRE(shape->bounds(&x, &y, &w, &h) == Result::Success); REQUIRE(shape->bounds(&x, &y, &w, &h) == Result::Success);
REQUIRE(x == 0.0f); REQUIRE(x == 100.0f);
REQUIRE(y == 10.0f); REQUIRE(y == 121.0f);
REQUIRE(w == 20.0f); REQUIRE(w == 20.0f);
REQUIRE(h == 100.0f); REQUIRE(h == 100.0f);