renderer/paint: revise the Paint::bounds() behavior

The current Paint::bounds(transform=true) returns the coordinates
of the paint in its local coordinates after transformation.

However, it did not convert the origin to the world coordinate.

This is problematic when the user wants to determine
the paint's position and size with the origin being the canvas.
Specifically, this matters that when the paint is belonged
to a certain scene.

Now, the bounds() method returns the coordinates
of the paint's bounding box with the corrected world space.
User can figure out the actual boundary within the painted result.

Remark that, this may break the functional behavior compatibility.
This commit is contained in:
Hermet Park 2024-07-30 10:54:53 +09:00
parent d1664a162e
commit 730b0d23f0
7 changed files with 41 additions and 17 deletions

View file

@ -395,15 +395,16 @@ public:
/**
* @brief Gets the axis-aligned bounding box of the paint object.
*
* In case @p transform is @c true, all object's transformations are applied first, and then the bounding box is established. Otherwise, the bounding box is determined before any transformations.
*
* @param[out] x The x-coordinate of the upper-left corner of the object.
* @param[out] y The y-coordinate of the upper-left corner of the object.
* @param[out] w The width of the object.
* @param[out] h The height of the object.
* @param[in] transformed If @c true, the paint's transformations are taken into account, otherwise they aren't.
* @param[in] transformed If @c true, the paint's transformations are taken into account in the scene it belongs to. Otherwise they aren't.
*
* @note This is useful when you need to figure out the bounding box of the paint in the canvas space.
* @note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object.
* @note If @p transformed is @c true, the paint needs to be pushed into a canvas and updated before this api is called.
* @see Canvas::update()
*/
Result bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept;

View file

@ -954,12 +954,15 @@ TVG_API Tvg_Paint* tvg_paint_duplicate(Tvg_Paint* paint);
* \param[out] y The y-coordinate of the upper-left corner of the object.
* \param[out] w The width of the object.
* \param[out] h The height of the object.
* \param[in] transformed If @c true, the transformation of the paint is taken into account, otherwise it isn't.
* \param[in] transformed If @c true, the paint's transformations are taken into account in the scene it belongs to. Otherwise they aren't.
*
* \return Tvg_Result enumeration.
* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Paint pointer.
*
* \note This is useful when you need to figure out the bounding box of the paint in the canvas space.
* \note The bounding box doesn't indicate the actual drawing region. It's the smallest rectangle that encloses the object.
* \note If @p transformed is @c true, the paint needs to be pushed into a canvas and updated before this api is called.
* \see tvg_canvas_update_paint()
*/
TVG_API Tvg_Result tvg_paint_get_bounds(const Tvg_Paint* paint, float* x, float* y, float* w, float* h, bool transformed);

View file

@ -292,8 +292,9 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<R
opacity = MULTIPLY(opacity, this->opacity);
RenderData rd = nullptr;
auto m = pm * tr.m;
PAINT_METHOD(rd, update(renderer, m, clips, opacity, newFlag, clipper));
tr.cm = pm * tr.m;
PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
/* 3. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport);
@ -303,10 +304,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 transformed, bool stroking)
bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin)
{
bool ret;
const auto& m = this->transform();
const auto& m = this->transform(origin);
//Case: No transformed, quick return!
if (!transformed || identity(&m)) {
@ -405,9 +406,9 @@ TVG_DEPRECATED Result Paint::bounds(float* x, float* y, float* w, float* h) cons
}
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transform) const noexcept
Result Paint::bounds(float* x, float* y, float* w, float* h, bool transformed) const noexcept
{
if (pImpl->bounds(x, y, w, h, transform, true)) return Result::Success;
if (pImpl->bounds(x, y, w, h, transformed, true, true)) return Result::Success;
return Result::InsufficientCondition;
}

View file

@ -52,7 +52,8 @@ namespace tvg
RenderMethod* renderer = nullptr;
BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
struct {
Matrix m;
Matrix m; //input matrix
Matrix cm; //multipled parents matrix
float degree = 0.0f; //rotation degree
float scale = 1.0f; //scale factor
bool overriding = false; //user transform?
@ -111,10 +112,11 @@ namespace tvg
return true;
}
Matrix& transform()
Matrix& transform(bool origin = false)
{
//update transform
if (renderFlag & RenderUpdateFlag::Transform) tr.update();
if (origin) return tr.cm;
return tr.m;
}
@ -150,7 +152,7 @@ namespace tvg
bool rotate(float degree);
bool scale(float factor);
bool translate(float x, float y);
bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking);
bool bounds(float* x, float* y, float* w, float* h, bool transformed, bool stroking, bool origin = false);
RenderData update(RenderMethod* renderer, const Matrix& pm, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod* renderer);
Paint* duplicate(Paint* ret = nullptr);

View file

@ -111,7 +111,6 @@ struct RenderRegion
}
};
struct RenderStroke
{
float width = 0.0f;

View file

@ -137,11 +137,17 @@ TEST_CASE("Paint Opacity", "[capiPaint]")
TEST_CASE("Paint Bounds", "[capiPaint]")
{
tvg_engine_init(TVG_ENGINE_SW, 0);
Tvg_Canvas* canvas = tvg_swcanvas_create();
Tvg_Paint* paint = tvg_shape_new();
REQUIRE(paint);
REQUIRE(tvg_canvas_push(canvas, paint) == TVG_RESULT_SUCCESS);
float x, y, w, h;
REQUIRE(tvg_canvas_update_paint(canvas, paint) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_shape_append_rect(paint, 0, 10, 20, 100, 0, 0) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_paint_get_bounds(paint, &x, &y, &w, &h, true) == TVG_RESULT_SUCCESS);
@ -164,6 +170,7 @@ TEST_CASE("Paint Bounds", "[capiPaint]")
REQUIRE(w == 20);
REQUIRE(h == 100);
REQUIRE(tvg_canvas_update_paint(canvas, paint) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_paint_get_bounds(paint, &x, &y, &w, &h, true) == TVG_RESULT_SUCCESS);
REQUIRE(x == Approx(100.0f).margin(0.000001));
@ -171,7 +178,8 @@ TEST_CASE("Paint Bounds", "[capiPaint]")
REQUIRE(w == Approx(40.0f).margin(0.000001));
REQUIRE(h == Approx(200.0f).margin(0.000001));
REQUIRE(tvg_paint_del(paint) == TVG_RESULT_SUCCESS);
tvg_canvas_destroy(canvas);
tvg_engine_term(TVG_ENGINE_SW);
}
TEST_CASE("Paint Duplication", "[capiPaint]")

View file

@ -118,8 +118,12 @@ TEST_CASE("Opacity", "[tvgPaint]")
TEST_CASE("Bounding Box", "[tvgPaint]")
{
auto shape = Shape::gen();
REQUIRE(shape);
Initializer::init(tvg::CanvasEngine::Sw, 0);
auto canvas = SwCanvas::gen();
auto shape = Shape::gen().release();
canvas->push(tvg::cast(shape));
canvas->sync();
//Negative
float x = 0, y = 0, w = 0, h = 0;
@ -133,6 +137,8 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
REQUIRE(y == 10.0f);
REQUIRE(w == 20.0f);
REQUIRE(h == 100.0f);
REQUIRE(canvas->update(shape) == Result::Success);
REQUIRE(shape->bounds(&x, &y, &w, &h, true) == Result::Success);
REQUIRE(x == 100.0f);
REQUIRE(y == 121.0f);
@ -150,11 +156,15 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
REQUIRE(y == 10.0f);
REQUIRE(w == 20.0f);
REQUIRE(h == 200.0f);
REQUIRE(canvas->update(shape) == Result::Success);
REQUIRE(shape->bounds(&x, &y, &w, &h, true) == Result::Success);
REQUIRE(x == 0.0f);
REQUIRE(y == 10.0f);
REQUIRE(w == 20.0f);
REQUIRE(h == 200.0f);
Initializer::term(tvg::CanvasEngine::Sw);
}
TEST_CASE("Duplication", "[tvgPaint]")