renderer: added parent scene query api

added 2 more APIs for user convenience to allow backtracking tree traversal.

API Additions:
- const Paint* Paint::scene() const
- const Tvg_Paint* tvg_paint_get_scene(const Tvg_Paint* paint)
This commit is contained in:
Hermet Park 2025-03-18 16:53:42 +09:00
parent 0f20a4a1a8
commit 563f8b2f78
11 changed files with 115 additions and 57 deletions

View file

@ -309,6 +309,21 @@ class TVG_API Paint
public: public:
virtual ~Paint(); virtual ~Paint();
/**
* @brief Retrieves the parent paint object.
*
* This function returns a pointer to the parent object if the current paint
* belongs to one. Otherwise, it returns @c nullptr.
*
* @return A pointer to the parent object if available, otherwise @c nullptr.
*
* @see Scene::push()
* @see Canvas::push()
*
* @since 1.0
*/
const Paint* parent() const noexcept;
/** /**
* @brief Sets the angle by which the object is rotated. * @brief Sets the angle by which the object is rotated.
* *
@ -381,6 +396,8 @@ public:
* *
* @param[in] target The paint of the target object. * @param[in] target The paint of the target object.
* @param[in] method The method used to mask the source object with the target. * @param[in] method The method used to mask the source object with the target.
*
* @retval Result::InsufficientCondition if the target has already belonged to another paint.
*/ */
Result mask(Paint* target, MaskMethod method) noexcept; Result mask(Paint* target, MaskMethod method) noexcept;
@ -392,6 +409,7 @@ public:
* @param[in] clipper The shape object as the clipper. * @param[in] clipper The shape object as the clipper.
* *
* @retval Result::NonSupport If the @p clipper type is not Shape. * @retval Result::NonSupport If the @p clipper type is not Shape.
* @retval Result::InsufficientCondition if the target has already belonged to another paint.
* *
* @note @p clipper only supports the Shape type. * @note @p clipper only supports the Shape type.
* @since 1.0 * @since 1.0

View file

@ -715,7 +715,6 @@ TVG_API Tvg_Result tvg_canvas_set_viewport(Tvg_Canvas* canvas, int32_t x, int32_
/** \} */ // end defgroup ThorVGCapi_Canvas /** \} */ // end defgroup ThorVGCapi_Canvas
/** /**
* @defgroup ThorVGCapi_Paint Paint * @defgroup ThorVGCapi_Paint Paint
* @brief A module for managing graphical elements. It enables duplication, transformation and composition. * @brief A module for managing graphical elements. It enables duplication, transformation and composition.
@ -964,6 +963,8 @@ TVG_API Tvg_Result tvg_paint_get_obb(const Tvg_Paint* paint, Tvg_Point* pt4);
* @param[in] target The target object of the masking. * @param[in] target The target object of the masking.
* @param[in] method The method used to mask the source object with the target. * @param[in] method The method used to mask the source object with the target.
* *
* @retval TVG_RESULT_INSUFFICIENT_CONDITION if the target has already belonged to another paint.
*
* @return Tvg_Result enumeration. * @return Tvg_Result enumeration.
*/ */
@ -993,6 +994,7 @@ TVG_API Tvg_Result tvg_paint_get_mask_method(const Tvg_Paint* paint, const Tvg_P
* *
* @return Tvg_Result enumeration. * @return Tvg_Result enumeration.
* @retval TVG_RESULT_INVALID_ARGUMENT In case a @c nullptr is passed as the argument. * @retval TVG_RESULT_INVALID_ARGUMENT In case a @c nullptr is passed as the argument.
* @retval TVG_RESULT_INSUFFICIENT_CONDITION if the target has already belonged to another paint.
* @retval TVG_RESULT_NOT_SUPPORTED If the @p clipper type is not Shape. * @retval TVG_RESULT_NOT_SUPPORTED If the @p clipper type is not Shape.
* *
* @since 1.0 * @since 1.0
@ -1000,6 +1002,24 @@ TVG_API Tvg_Result tvg_paint_get_mask_method(const Tvg_Paint* paint, const Tvg_P
TVG_API Tvg_Result tvg_paint_clip(Tvg_Paint* paint, Tvg_Paint* clipper); TVG_API Tvg_Result tvg_paint_clip(Tvg_Paint* paint, Tvg_Paint* clipper);
/**
* @brief Retrieves the parent paint object.
*
* This function returns a pointer to the parent object if the current paint
* belongs to one. Otherwise, it returns @c nullptr.
*
* @param[in] paint The Tvg_Paint object of which to get the scene.
*
* @return A pointer to the parent object if available, otherwise @c nullptr.
*
* @see tvg_scene_push()
* @see tvg_canvas_push()
*
* @since 1.0
*/
TVG_API const Tvg_Paint* tvg_paint_get_parent(const Tvg_Paint* paint);
/** /**
* @brief Gets the unique value of the paint instance indicating the instance type. * @brief Gets the unique value of the paint instance indicating the instance type.
* *

View file

@ -169,6 +169,12 @@ TVG_API Tvg_Result tvg_canvas_set_viewport(Tvg_Canvas* canvas, int32_t x, int32_
/* Paint API */ /* Paint API */
/************************************************************************/ /************************************************************************/
TVG_API const Tvg_Paint* tvg_paint_get_parent(const Tvg_Paint* paint)
{
return (const Tvg_Paint*) reinterpret_cast<const Paint*>(paint)->parent();
}
TVG_API Tvg_Result tvg_paint_del(Tvg_Paint* paint) TVG_API Tvg_Result tvg_paint_del(Tvg_Paint* paint)
{ {
if (!paint) return TVG_RESULT_INVALID_ARGUMENT; if (!paint) return TVG_RESULT_INVALID_ARGUMENT;

View file

@ -51,10 +51,7 @@ struct Canvas::Impl
Result push(Paint* target, Paint* at) Result push(Paint* target, Paint* at)
{ {
//You cannot push paints during rendering. //You cannot push paints during rendering.
if (status == Status::Drawing) { if (status == Status::Drawing) return Result::InsufficientCondition;
TVG_DELETE(target);
return Result::InsufficientCondition;
}
auto ret = scene->push(target, at); auto ret = scene->push(target, at);
if (ret != Result::Success) return ret; if (ret != Result::Success) return ret;

View file

@ -392,19 +392,15 @@ Result Paint::clip(Paint* clipper) noexcept
{ {
if (clipper && clipper->type() != Type::Shape) { if (clipper && clipper->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!"); TVGERR("RENDERER", "Clipping only supports the Shape!");
TVG_DELETE(clipper);
return Result::NonSupport; return Result::NonSupport;
} }
pImpl->clip(clipper); return pImpl->clip(clipper);
return Result::Success;
} }
Result Paint::mask(Paint* target, MaskMethod method) noexcept Result Paint::mask(Paint* target, MaskMethod method) noexcept
{ {
if (pImpl->mask(target, method)) return Result::Success; return pImpl->mask(target, method);
if (target) TVG_DELETE(target);
return Result::InvalidArguments;
} }
@ -448,7 +444,7 @@ uint8_t Paint::ref() noexcept
uint8_t Paint::unref(bool free) noexcept uint8_t Paint::unref(bool free) noexcept
{ {
return pImpl->unref(free); return pImpl->unrefx(free);
} }
@ -456,3 +452,9 @@ uint8_t Paint::refCnt() const noexcept
{ {
return pImpl->refCnt; return pImpl->refCnt;
} }
const Paint* Paint::parent() const noexcept
{
return pImpl->parent;
}

View file

@ -51,6 +51,7 @@ namespace tvg
struct Paint::Impl struct Paint::Impl
{ {
Paint* paint = nullptr; Paint* paint = nullptr;
Paint* parent = nullptr;
Mask* maskData = nullptr; Mask* maskData = nullptr;
Paint* clipper = nullptr; Paint* clipper = nullptr;
RenderMethod* renderer = nullptr; RenderMethod* renderer = nullptr;
@ -91,11 +92,11 @@ namespace tvg
virtual ~Impl() virtual ~Impl()
{ {
if (maskData) { if (maskData) {
maskData->target->unref(); PAINT(maskData->target)->unref();
tvg::free(maskData); tvg::free(maskData);
} }
if (clipper) clipper->unref(); if (clipper) PAINT(clipper)->unref();
if (renderer) { if (renderer) {
if (rd) renderer->dispose(rd); if (rd) renderer->dispose(rd);
@ -110,13 +111,18 @@ namespace tvg
return refCnt; return refCnt;
} }
uint8_t unref(bool free) uint8_t unref(bool free = true)
{
parent = nullptr;
return unrefx(free);
}
uint8_t unrefx(bool free)
{ {
if (refCnt > 0) --refCnt; if (refCnt > 0) --refCnt;
else TVGERR("RENDERER", "Corrupted Reference Count!"); else TVGERR("RENDERER", "Corrupted Reference Count!");
if (free && refCnt == 0) { if (free && refCnt == 0) {
//TODO: use the global dismiss function?
delete(paint); delete(paint);
return 0; return 0;
} }
@ -146,37 +152,37 @@ namespace tvg
return tr.m; return tr.m;
} }
void clip(Paint* clp) Result clip(Paint* clp)
{ {
if (clipper) clipper->unref(clipper != clp); if (PAINT(clp)->parent) return Result::InsufficientCondition;
if (clipper) PAINT(clipper)->unref(clipper != clp);
clipper = clp; clipper = clp;
if (!clp) return; if (clp) {
clp->ref();
clipper->ref(); PAINT(clp)->parent = parent;
}
return Result::Success;
} }
bool mask(Paint* target, MaskMethod method) Result mask(Paint* target, MaskMethod method)
{ {
//Invalid case if (target && PAINT(target)->parent) return Result::InsufficientCondition;
if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false;
if (maskData) { if (maskData) {
maskData->target->unref(maskData->target != target); PAINT(maskData->target)->unref(maskData->target != target);
//Reset scenario tvg::free(maskData);
if (!target && method == MaskMethod::None) { maskData = nullptr;
tvg::free(maskData);
maskData = nullptr;
return true;
}
} else {
if (!target && method == MaskMethod::None) return true;
maskData = tvg::malloc<Mask*>(sizeof(Mask));
} }
if (!target && method == MaskMethod::None) return Result::Success;
maskData = tvg::malloc<Mask*>(sizeof(Mask));
target->ref(); target->ref();
maskData->target = target; maskData->target = target;
PAINT(target)->parent = parent;
maskData->source = paint; maskData->source = paint;
maskData->method = method; maskData->method = method;
return true; return Result::Success;
} }
MaskMethod mask(const Paint** target) const MaskMethod mask(const Paint** target) const
@ -193,12 +199,12 @@ namespace tvg
void reset() void reset()
{ {
if (clipper) { if (clipper) {
clipper->unref(); PAINT(clipper)->unref();
clipper = nullptr; clipper = nullptr;
} }
if (maskData) { if (maskData) {
maskData->target->unref(); PAINT(maskData->target)->unref();
tvg::free(maskData); tvg::free(maskData);
maskData = nullptr; maskData = nullptr;
} }
@ -208,6 +214,7 @@ namespace tvg
tr.scale = 1.0f; tr.scale = 1.0f;
tr.overriding = false; tr.overriding = false;
parent = nullptr;
blendMethod = BlendMethod::Normal; blendMethod = BlendMethod::Normal;
renderFlag = RenderUpdateFlag::None; renderFlag = RenderUpdateFlag::None;
ctxFlag = ContextFlag::Default; ctxFlag = ContextFlag::Default;

View file

@ -165,7 +165,10 @@ struct Picture::Impl : Paint::Impl
auto picture = Picture::gen(); auto picture = Picture::gen();
auto dup = PICTURE(picture); auto dup = PICTURE(picture);
if (vector) dup->vector = vector->duplicate(); if (vector) {
dup->vector = vector->duplicate();
PAINT(dup->vector)->parent = picture;
}
if (loader) { if (loader) {
dup->loader = loader; dup->loader = loader;
@ -211,6 +214,7 @@ struct Picture::Impl : Paint::Impl
} else { } else {
vector = loader->paint(); vector = loader->paint();
if (vector) { if (vector) {
PAINT(vector)->parent = paint;
if (w != loader->w || h != loader->h) { if (w != loader->w || h != loader->h) {
if (!resizing) { if (!resizing) {
w = loader->w; w = loader->w;

View file

@ -229,6 +229,7 @@ struct Scene::Impl : Paint::Impl
for (auto paint : paints) { for (auto paint : paints) {
auto cdup = paint->duplicate(); auto cdup = paint->duplicate();
PAINT(cdup)->parent = scene;
cdup->ref(); cdup->ref();
dup->paints.push_back(cdup); dup->paints.push_back(cdup);
} }
@ -242,7 +243,7 @@ struct Scene::Impl : Paint::Impl
{ {
auto itr = paints.begin(); auto itr = paints.begin();
while (itr != paints.end()) { while (itr != paints.end()) {
(*itr)->unref(); PAINT((*itr))->unref();
paints.erase(itr++); paints.erase(itr++);
} }
return Result::Success; return Result::Success;
@ -250,31 +251,24 @@ struct Scene::Impl : Paint::Impl
Result remove(Paint* paint) Result remove(Paint* paint)
{ {
owned(paint); if (PAINT(paint)->parent != this->paint) return Result::InsufficientCondition;
paint->unref(); PAINT(paint)->unref();
paints.remove(paint); paints.remove(paint);
return Result::Success; return Result::Success;
} }
void owned(Paint* paint)
{
#ifdef THORVG_LOG_ENABLED
for (auto p : paints) {
if (p == paint) return;
}
TVGERR("RENDERER", "The paint(%p) is not existed from the scene(%p)", paint, this->paint);
#endif
}
Result insert(Paint* target, Paint* at) Result insert(Paint* target, Paint* at)
{ {
if (!target) return Result::InvalidArguments; if (!target) return Result::InvalidArguments;
auto timpl = PAINT(target);
if (timpl->parent) return Result::InsufficientCondition;
target->ref(); target->ref();
//Relocated the paint to the current scene space //Relocated the paint to the current scene space
PAINT(target)->renderFlag |= RenderUpdateFlag::Transform; timpl->renderFlag |= RenderUpdateFlag::Transform;
if (at == nullptr) { if (!at) {
paints.push_back(target); paints.push_back(target);
} else { } else {
//OPTIMIZE: Remove searching? //OPTIMIZE: Remove searching?
@ -282,6 +276,9 @@ struct Scene::Impl : Paint::Impl
if (itr == paints.end()) return Result::InvalidArguments; if (itr == paints.end()) return Result::InvalidArguments;
paints.insert(itr, target); paints.insert(itr, target);
} }
timpl->parent = paint;
if (timpl->clipper) PAINT(timpl->clipper)->parent = paint;
if (timpl->maskData) PAINT(timpl->maskData->target)->parent = paint;
return Result::Success; return Result::Success;
} }

View file

@ -41,6 +41,7 @@ struct Text::Impl : Paint::Impl
Impl(Text* p) : Paint::Impl(p), shape(Shape::gen()) Impl(Text* p) : Paint::Impl(p), shape(Shape::gen())
{ {
PAINT(shape)->parent = p;
} }
~Impl() ~Impl()
@ -149,6 +150,8 @@ struct Text::Impl : Paint::Impl
auto text = Text::gen(); auto text = Text::gen();
auto dup = TEXT(text); auto dup = TEXT(text);
dup->parent = text;
SHAPE(shape)->duplicate(dup->shape); SHAPE(shape)->duplicate(dup->shape);
if (loader) { if (loader) {

View file

@ -218,11 +218,8 @@ TEST_CASE("Composition", "[tvgPaint]")
//Negative //Negative
REQUIRE(shape->mask(nullptr) == MaskMethod::None); REQUIRE(shape->mask(nullptr) == MaskMethod::None);
auto comp = Shape::gen();
REQUIRE(shape->mask(comp, MaskMethod::None) == Result::InvalidArguments);
//Clipping //Clipping
comp = Shape::gen(); auto comp = Shape::gen();
REQUIRE(shape->clip(comp) == Result::Success); REQUIRE(shape->clip(comp) == Result::Success);
//AlphaMask //AlphaMask

View file

@ -39,18 +39,25 @@ TEST_CASE("Pushing Paints Into Scene", "[tvgScene]")
{ {
auto scene = unique_ptr<Scene>(Scene::gen()); auto scene = unique_ptr<Scene>(Scene::gen());
REQUIRE(scene); REQUIRE(scene);
REQUIRE(scene->parent() == nullptr);
Paint* paints[3]; Paint* paints[3];
//Pushing Paints //Pushing Paints
paints[0] = Shape::gen(); paints[0] = Shape::gen();
REQUIRE(paints[0]->parent() == nullptr);
REQUIRE(scene->push(paints[0]) == Result::Success); REQUIRE(scene->push(paints[0]) == Result::Success);
REQUIRE(paints[0]->parent() == scene.get());
paints[1] = Picture::gen(); paints[1] = Picture::gen();
REQUIRE(paints[1]->parent() == nullptr);
REQUIRE(scene->push(paints[1]) == Result::Success); REQUIRE(scene->push(paints[1]) == Result::Success);
REQUIRE(paints[1]->parent() == scene.get());
paints[2] = Picture::gen(); paints[2] = Picture::gen();
REQUIRE(paints[2]->parent() == nullptr);
REQUIRE(scene->push(paints[2]) == Result::Success); REQUIRE(scene->push(paints[2]) == Result::Success);
REQUIRE(paints[2]->parent() == scene.get());
//Pushing Null Pointer //Pushing Null Pointer
REQUIRE(scene->push(nullptr) == Result::InvalidArguments); REQUIRE(scene->push(nullptr) == Result::InvalidArguments);