api: Introduced Paint::clip() API

Separate clip function from the Composite()
clipping and composition can be used together.

This helps avoid the introduction of nested scenes
when composition and clipping overlap.

Deprecated:
- enum class CompositeMethod::ClipPath
- enum Tvg_Composite_Method::TVG_COMPOSITE_METHOD_CLIP_PATH

Experimental API:
- Result Paint::clip(std::unique_ptr<Paint> clipper)
- Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper)

Issue: https://github.com/thorvg/thorvg/issues/1496
This commit is contained in:
Hermet Park 2024-08-16 14:27:07 +09:00
parent b9b1336817
commit b0683a26ec
10 changed files with 127 additions and 85 deletions

View file

@ -157,7 +157,7 @@ enum class FillRule : uint8_t
enum class CompositeMethod : uint8_t enum class CompositeMethod : uint8_t
{ {
None = 0, ///< No composition is applied. None = 0, ///< No composition is applied.
ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. @deprecated Use Paint::clip() instead.
AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value. AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value.
InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value. InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value.
LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9 LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9
@ -339,7 +339,6 @@ public:
* @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. * @param[in] o The opacity value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque.
* *
* @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible. * @note Setting the opacity with this API may require multiple render pass for composition. It is recommended to avoid changing the opacity if possible.
* @note ClipPath won't use the opacity value. (see: enum class CompositeMethod::ClipPath)
*/ */
Result opacity(uint8_t o) noexcept; Result opacity(uint8_t o) noexcept;
@ -351,6 +350,20 @@ public:
*/ */
Result composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept; Result composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept;
/**
* @brief Clip the drawing region of the paint object.
*
* This function restricts the drawing area of the paint object to the specified shape's paths.
*
* @param[in] clipper The shape object as the clipper.
*
* @retval Result::NonSupport If the @p clipper type is not Shape.
*
* @note @p clipper only supports the Shape type.
* @note Experimental API
*/
Result clip(std::unique_ptr<Paint> clipper) noexcept;
/** /**
* @brief Sets the blending method for the paint object. * @brief Sets the blending method for the paint object.
* *
@ -1050,7 +1063,6 @@ public:
* @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0. * @param[in] a The alpha channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. The default value is 0.
* *
* @note Either a solid color or a gradient fill is applied, depending on what was set as last. * @note Either a solid color or a gradient fill is applied, depending on what was set as last.
* @note ClipPath won't use the fill values. (see: enum class CompositeMethod::ClipPath)
*/ */
Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept; Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept;

View file

@ -138,7 +138,7 @@ typedef enum {
*/ */
typedef enum { typedef enum {
TVG_COMPOSITE_METHOD_NONE = 0, ///< No composition is applied. TVG_COMPOSITE_METHOD_NONE = 0, ///< No composition is applied.
TVG_COMPOSITE_METHOD_CLIP_PATH, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. TVG_COMPOSITE_METHOD_CLIP_PATH, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered. Note that ClipPath only supports the Shape type. @deprecated Use Paint::clip() instead.
TVG_COMPOSITE_METHOD_ALPHA_MASK, ///< The pixels of the source and the target are alpha blended. As a result, only the part of the source, which intersects with the target is visible. TVG_COMPOSITE_METHOD_ALPHA_MASK, ///< The pixels of the source and the target are alpha blended. As a result, only the part of the source, which intersects with the target is visible.
TVG_COMPOSITE_METHOD_INVERSE_ALPHA_MASK, ///< The pixels of the source and the complement to the target's pixels are alpha blended. As a result, only the part of the source which is not covered by the target is visible. TVG_COMPOSITE_METHOD_INVERSE_ALPHA_MASK, ///< The pixels of the source and the complement to the target's pixels are alpha blended. As a result, only the part of the source which is not covered by the target is visible.
TVG_COMPOSITE_METHOD_LUMA_MASK, ///< The source pixels are converted to grayscale (luma value) and alpha blended with the target. As a result, only the part of the source which intersects with the target is visible. \since 0.9 TVG_COMPOSITE_METHOD_LUMA_MASK, ///< The source pixels are converted to grayscale (luma value) and alpha blended with the target. As a result, only the part of the source which intersects with the target is visible. \since 0.9
@ -994,6 +994,23 @@ TVG_API Tvg_Result tvg_paint_set_composite_method(Tvg_Paint* paint, Tvg_Paint* t
TVG_API Tvg_Result tvg_paint_get_composite_method(const Tvg_Paint* paint, const Tvg_Paint** target, Tvg_Composite_Method* method); TVG_API Tvg_Result tvg_paint_get_composite_method(const Tvg_Paint* paint, const Tvg_Paint** target, Tvg_Composite_Method* method);
/*!
* \brief Clip the drawing region of the paint object.
*
* This function restricts the drawing area of the paint object to the specified shape's paths.
*
* \param[in] paint The target object of the clipping.
* \param[in] clipper The shape object as the clipper.
*
* \return Tvg_Result enumeration.
* \retval TVG_RESULT_INVALID_ARGUMENT In case a @c nullptr is passed as the argument.
* \retval TVG_RESULT_NOT_SUPPORTED If the @p clipper type is not Shape.
*
* \note Experimental API
*/
TVG_API Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper);
/** /**
* \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

@ -260,6 +260,13 @@ TVG_API Tvg_Result tvg_paint_get_type(const Tvg_Paint* paint, Tvg_Type* type)
} }
TVG_API Tvg_Result tvg_paint_set_clip(Tvg_Paint* paint, Tvg_Paint* clipper)
{
if (!paint) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Paint*>(paint)->clip(unique_ptr<Paint>((Paint*)(clipper)));
}
TVG_DEPRECATED TVG_API Tvg_Result tvg_paint_get_identifier(const Tvg_Paint* paint, Tvg_Identifier* identifier) TVG_DEPRECATED TVG_API Tvg_Result tvg_paint_get_identifier(const Tvg_Paint* paint, Tvg_Identifier* identifier)
{ {
return tvg_paint_get_type(paint, (Tvg_Type*) identifier); return tvg_paint_get_type(paint, (Tvg_Type*) identifier);

View file

@ -961,17 +961,10 @@ void LottieBuilder::updatePrecomp(LottieComposition* comp, LottieLayer* precomp,
if (!child->matteSrc) updateLayer(comp, precomp->scene, child, frameNo); if (!child->matteSrc) updateLayer(comp, precomp->scene, child, frameNo);
} }
//TODO: remove the intermediate scene....
if (precomp->scene->composite(nullptr) != CompositeMethod::None) {
auto cscene = Scene::gen().release();
cscene->push(cast(precomp->scene));
precomp->scene = cscene;
}
//clip the layer viewport //clip the layer viewport
auto clipper = precomp->statical.pooling(true); auto clipper = precomp->statical.pooling(true);
clipper->transform(precomp->cache.matrix); clipper->transform(precomp->cache.matrix);
precomp->scene->composite(cast(clipper), CompositeMethod::ClipPath); precomp->scene->clip(cast(clipper));
} }
@ -1396,5 +1389,5 @@ void LottieBuilder::build(LottieComposition* comp)
//viewport clip //viewport clip
auto clip = Shape::gen(); auto clip = Shape::gen();
clip->appendRect(0, 0, comp->w, comp->h); clip->appendRect(0, 0, comp->w, comp->h);
comp->root->scene->composite(std::move(clip), CompositeMethod::ClipPath); comp->root->scene->clip(std::move(clip));
} }

View file

@ -272,8 +272,7 @@ static void _applyComposition(SvgLoaderData& loaderData, Paint* paint, const Svg
if (valid) { if (valid) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath); Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
comp->transform(finalTransform); comp->transform(finalTransform);
paint->clip(std::move(comp));
paint->composite(std::move(comp), CompositeMethod::ClipPath);
} }
node->style->clipPath.applying = false; node->style->clipPath.applying = false;
@ -714,7 +713,6 @@ static Matrix _calculateAspectRatioMatrix(AspectRatioAlign align, AspectRatioMee
static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite) static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNode* node, const Box& vBox, const string& svgPath, int depth, bool* isMaskWhite)
{ {
unique_ptr<Scene> finalScene;
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite); auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
// mUseTransform = mUseTransform * mTranslate // mUseTransform = mUseTransform * mTranslate
@ -751,9 +749,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
mSceneTransform = mUseTransform * mSceneTransform; mSceneTransform = mUseTransform * mSceneTransform;
scene->transform(mSceneTransform); scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) { if (!node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = std::move(scene);
} else {
auto viewBoxClip = Shape::gen(); auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0); viewBoxClip->appendRect(0, 0, width, height, 0, 0);
@ -764,21 +760,13 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
} }
viewBoxClip->transform(mClipTransform); viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen(); scene->clip(std::move(viewBoxClip));
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(scene));
auto root = Scene::gen();
root->push(std::move(compositeLayer));
finalScene = std::move(root);
} }
} else { } else {
scene->transform(mUseTransform); scene->transform(mUseTransform);
finalScene = std::move(scene);
} }
return finalScene; return scene;
} }
@ -937,7 +925,7 @@ Scene* svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, float h, Aspe
viewBoxClip->appendRect(0, 0, w, h); viewBoxClip->appendRect(0, 0, w, h);
auto compositeLayer = Scene::gen(); auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath); compositeLayer->clip(std::move(viewBoxClip));
compositeLayer->push(std::move(docNode)); compositeLayer->push(std::move(docNode));
auto root = Scene::gen(); auto root = Scene::gen();

View file

@ -542,8 +542,8 @@ SwRle* rleRender(const SwBBox* bbox);
void rleFree(SwRle* rle); void rleFree(SwRle* rle);
void rleReset(SwRle* rle); void rleReset(SwRle* rle);
void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2); void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2);
void rleClipPath(SwRle* rle, const SwRle* clip); void rleClip(SwRle* rle, const SwRle* clip);
void rleClipRect(SwRle* rle, const SwBBox* clip); void rleClip(SwRle* rle, const SwBBox* clip);
SwMpool* mpoolInit(uint32_t threads); SwMpool* mpoolInit(uint32_t threads);
bool mpoolTerm(SwMpool* mpool); bool mpoolTerm(SwMpool* mpool);

View file

@ -66,8 +66,6 @@ struct SwTask : Task
virtual void dispose() = 0; virtual void dispose() = 0;
virtual bool clip(SwRle* target) = 0; virtual bool clip(SwRle* target) = 0;
virtual SwRle* rle() = 0;
virtual ~SwTask() {} virtual ~SwTask() {}
}; };
@ -102,21 +100,13 @@ struct SwShapeTask : SwTask
bool clip(SwRle* target) override bool clip(SwRle* target) override
{ {
if (shape.fastTrack) rleClipRect(target, &bbox); if (shape.fastTrack) rleClip(target, &bbox);
else if (shape.rle) rleClipPath(target, shape.rle); else if (shape.rle) rleClip(target, shape.rle);
else return false; else return false;
return true; return true;
} }
SwRle* rle() override
{
if (!shape.rle && shape.fastTrack) {
shape.rle = rleRender(&shape.bbox);
}
return shape.rle;
}
void run(unsigned tid) override void run(unsigned tid) override
{ {
if (opacity == 0 && !clipper) return; //Invisible if (opacity == 0 && !clipper) return; //Invisible
@ -209,12 +199,6 @@ struct SwImageTask : SwTask
return true; return true;
} }
SwRle* rle() override
{
TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?");
return nullptr;
}
void run(unsigned tid) override void run(unsigned tid) override
{ {
auto clipRegion = bbox; auto clipRegion = bbox;

View file

@ -990,7 +990,7 @@ void rleFree(SwRle* rle)
} }
void rleClipPath(SwRle *rle, const SwRle *clip) void rleClip(SwRle *rle, const SwRle *clip)
{ {
if (rle->size == 0 || clip->size == 0) return; if (rle->size == 0 || clip->size == 0) return;
auto spanCnt = rle->size > clip->size ? rle->size : clip->size; auto spanCnt = rle->size > clip->size ? rle->size : clip->size;
@ -999,11 +999,11 @@ void rleClipPath(SwRle *rle, const SwRle *clip)
_replaceClipSpan(rle, spans, spansEnd - spans); _replaceClipSpan(rle, spans, spansEnd - spans);
TVGLOG("SW_ENGINE", "Using ClipPath!"); TVGLOG("SW_ENGINE", "Using Path Clipping!");
} }
void rleClipRect(SwRle *rle, const SwBBox* clip) void rleClip(SwRle *rle, const SwBBox* clip)
{ {
if (rle->size == 0) return; if (rle->size == 0) return;
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (rle->size))); auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (rle->size)));
@ -1011,5 +1011,5 @@ void rleClipRect(SwRle *rle, const SwBBox* clip)
_replaceClipSpan(rle, spans, spansEnd - spans); _replaceClipSpan(rle, spans, spansEnd - spans);
TVGLOG("SW_ENGINE", "Using ClipRect!"); TVGLOG("SW_ENGINE", "Using Box Clipping!");
} }

View file

@ -164,6 +164,7 @@ Paint* Paint::Impl::duplicate(Paint* ret)
ret->pImpl->opacity = opacity; ret->pImpl->opacity = opacity;
if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method); if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
if (clipper) ret->pImpl->clip(clipper->duplicate());
return ret; return ret;
} }
@ -209,9 +210,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
Compositor* cmp = nullptr; Compositor* cmp = nullptr;
/* Note: only ClipPath is processed in update() step. if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
RenderRegion region; RenderRegion region;
PAINT_METHOD(region, bounds(renderer)); PAINT_METHOD(region, bounds(renderer));
@ -248,43 +247,47 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<R
RenderData trd = nullptr; //composite target render data RenderData trd = nullptr; //composite target render data
RenderRegion viewport; RenderRegion viewport;
Result compFastTrack = Result::InsufficientCondition; Result compFastTrack = Result::InsufficientCondition;
bool childClipper = false;
if (compData) { if (compData) {
auto target = compData->target; auto target = compData->target;
auto method = compData->method; auto method = compData->method;
P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset
/* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle, /* If the transformation has no rotational factors and the Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */ we can optimize by using the viewport instead of the regular AlphaMasking sequence for improved performance. */
auto tryFastTrack = false;
if (target->type() == Type::Shape) { if (target->type() == Type::Shape) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true; auto shape = static_cast<Shape*>(target);
else { uint8_t a;
auto shape = static_cast<Shape*>(target); shape->fillColor(nullptr, nullptr, nullptr, &a);
uint8_t a; //no gradient fill & no compositions of the composition target.
shape->fillColor(nullptr, nullptr, nullptr, &a); if (!shape->fill() && !(PP(shape)->compData)) {
//no gradient fill & no compositions of the composition target. if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) {
if (!shape->fill() && !(PP(shape)->compData)) { viewport = renderer->viewport();
if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true; if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true; P(target)->ctxFlag |= ContextFlag::FastTrack;
} }
}
if (tryFastTrack) {
viewport = renderer->viewport();
if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
P(target)->ctxFlag |= ContextFlag::FastTrack;
} }
} }
} }
if (compFastTrack == Result::InsufficientCondition) { if (compFastTrack == Result::InsufficientCondition) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false; trd = P(target)->update(renderer, pm, clips, 255, pFlag, false);
trd = P(target)->update(renderer, pm, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
} }
} }
/* 2. Main Update */ /* 2. Clipping */
if (this->clipper) {
P(this->clipper)->ctxFlag &= ~ContextFlag::FastTrack; //reset
viewport = renderer->viewport();
if ((compFastTrack = _compFastTrack(renderer, this->clipper, pm, viewport)) == Result::Success) {
P(this->clipper)->ctxFlag |= ContextFlag::FastTrack;
}
if (compFastTrack == Result::InsufficientCondition) {
trd = P(this->clipper)->update(renderer, pm, clips, 255, pFlag, true);
clips.push(trd);
}
}
/* 3. Main Update */
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag); auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
renderFlag = RenderUpdateFlag::None; renderFlag = RenderUpdateFlag::None;
opacity = MULTIPLY(opacity, this->opacity); opacity = MULTIPLY(opacity, this->opacity);
@ -294,9 +297,9 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const Matrix& pm, Array<R
tr.cm = pm * tr.m; tr.cm = pm * tr.m;
PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper)); PAINT_METHOD(rd, update(renderer, tr.cm, clips, opacity, newFlag, clipper));
/* 3. Composition Post Processing */ /* 4. Composition Post Processing */
if (compFastTrack == Result::Success) renderer->viewport(viewport); if (compFastTrack == Result::Success) renderer->viewport(viewport);
else if (childClipper) clips.pop(); else if (this->clipper) clips.pop();
return rd; return rd;
} }
@ -351,6 +354,11 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
void Paint::Impl::reset() void Paint::Impl::reset()
{ {
if (clipper) {
delete(clipper);
clipper = nullptr;
}
if (compData) { if (compData) {
if (P(compData->target)->unref() == 0) delete(compData->target); if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData); free(compData);
@ -431,15 +439,27 @@ Paint* Paint::duplicate() const noexcept
} }
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept Result Paint::clip(std::unique_ptr<Paint> clipper) noexcept
{ {
if (method == CompositeMethod::ClipPath && target && target->type() != Type::Shape) { auto p = clipper.release();
TVGERR("RENDERER", "ClipPath only allows the Shape!");
if (p && p->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!");
return Result::NonSupport; return Result::NonSupport;
} }
pImpl->clip(p);
return Result::Success;
}
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
{
//TODO: remove. Keep this for the backward compatibility
if (method == CompositeMethod::ClipPath) return clip(std::move(target));
auto p = target.release(); auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success; if (pImpl->composite(this, p, method)) return Result::Success;
delete(p); delete(p);
return Result::InvalidArguments; return Result::InvalidArguments;
} }
@ -451,6 +471,11 @@ CompositeMethod Paint::composite(const Paint** target) const noexcept
if (target) *target = pImpl->compData->target; if (target) *target = pImpl->compData->target;
return pImpl->compData->method; return pImpl->compData->method;
} else { } else {
//TODO: remove. Keep this for the backward compatibility
if (pImpl->clipper) {
if (target) *target = pImpl->clipper;
return CompositeMethod::ClipPath;
}
if (target) *target = nullptr; if (target) *target = nullptr;
return CompositeMethod::None; return CompositeMethod::None;
} }

View file

@ -49,6 +49,7 @@ namespace tvg
{ {
Paint* paint = nullptr; Paint* paint = nullptr;
Composite* compData = nullptr; Composite* compData = nullptr;
Paint* clipper = nullptr;
RenderMethod* renderer = nullptr; RenderMethod* renderer = nullptr;
struct { struct {
Matrix m; //input matrix Matrix m; //input matrix
@ -88,6 +89,7 @@ namespace tvg
if (P(compData->target)->unref() == 0) delete(compData->target); if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData); free(compData);
} }
if (clipper && P(clipper)->unref() == 0) delete(clipper);
if (renderer && (renderer->unref() == 0)) delete(renderer); if (renderer && (renderer->unref() == 0)) delete(renderer);
} }
@ -120,6 +122,20 @@ namespace tvg
return tr.m; return tr.m;
} }
void clip(Paint* clipper)
{
if (this->clipper) {
P(this->clipper)->unref();
if (this->clipper != clipper && P(this->clipper)->refCnt == 0) {
delete(this->clipper);
}
}
this->clipper = clipper;
if (!clipper) return;
P(clipper)->ref();
}
bool composite(Paint* source, Paint* target, CompositeMethod method) bool composite(Paint* source, Paint* target, CompositeMethod method)
{ {
//Invalid case //Invalid case