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
{
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.
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
@ -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.
*
* @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;
@ -351,6 +350,20 @@ public:
*/
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.
*
@ -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.
*
* @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;

View file

@ -138,7 +138,7 @@ typedef enum {
*/
typedef enum {
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_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
@ -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);
/*!
* \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.
*

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)
{
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);
}
//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
auto clipper = precomp->statical.pooling(true);
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
auto clip = Shape::gen();
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) {
Matrix finalTransform = _compositionTransform(paint, node, compNode, SvgNodeType::ClipPath);
comp->transform(finalTransform);
paint->composite(std::move(comp), CompositeMethod::ClipPath);
paint->clip(std::move(comp));
}
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)
{
unique_ptr<Scene> finalScene;
auto scene = _sceneBuildHelper(loaderData, node, vBox, svgPath, false, depth + 1, isMaskWhite);
// mUseTransform = mUseTransform * mTranslate
@ -751,9 +749,7 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
mSceneTransform = mUseTransform * mSceneTransform;
scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = std::move(scene);
} else {
if (!node->node.use.symbol->node.symbol.overflowVisible) {
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0);
@ -764,21 +760,13 @@ static unique_ptr<Scene> _useBuildHelper(SvgLoaderData& loaderData, const SvgNod
}
viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen();
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);
scene->clip(std::move(viewBoxClip));
}
} else {
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);
auto compositeLayer = Scene::gen();
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->clip(std::move(viewBoxClip));
compositeLayer->push(std::move(docNode));
auto root = Scene::gen();

View file

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

View file

@ -66,8 +66,6 @@ struct SwTask : Task
virtual void dispose() = 0;
virtual bool clip(SwRle* target) = 0;
virtual SwRle* rle() = 0;
virtual ~SwTask() {}
};
@ -102,21 +100,13 @@ struct SwShapeTask : SwTask
bool clip(SwRle* target) override
{
if (shape.fastTrack) rleClipRect(target, &bbox);
else if (shape.rle) rleClipPath(target, shape.rle);
if (shape.fastTrack) rleClip(target, &bbox);
else if (shape.rle) rleClip(target, shape.rle);
else return false;
return true;
}
SwRle* rle() override
{
if (!shape.rle && shape.fastTrack) {
shape.rle = rleRender(&shape.bbox);
}
return shape.rle;
}
void run(unsigned tid) override
{
if (opacity == 0 && !clipper) return; //Invisible
@ -209,12 +199,6 @@ struct SwImageTask : SwTask
return true;
}
SwRle* rle() override
{
TVGERR("SW_ENGINE", "Image is used as Scene ClipPath?");
return nullptr;
}
void run(unsigned tid) override
{
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;
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);
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;
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);
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;
if (compData) ret->pImpl->composite(ret, compData->target->duplicate(), compData->method);
if (clipper) ret->pImpl->clip(clipper->duplicate());
return ret;
}
@ -209,9 +210,7 @@ bool Paint::Impl::render(RenderMethod* renderer)
Compositor* cmp = nullptr;
/* Note: only ClipPath is processed in update() step.
Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
if (compData && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
RenderRegion region;
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
RenderRegion viewport;
Result compFastTrack = Result::InsufficientCondition;
bool childClipper = false;
if (compData) {
auto target = compData->target;
auto method = compData->method;
P(target)->ctxFlag &= ~ContextFlag::FastTrack; //reset
/* If the transformation has no rotational factors and the ClipPath/Alpha(InvAlpha)Masking involves a simple rectangle,
we can optimize by using the viewport instead of the regular ClipPath/AlphaMasking sequence for improved performance. */
auto tryFastTrack = false;
/* 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 AlphaMasking sequence for improved performance. */
if (target->type() == Type::Shape) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true;
else {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
//no gradient fill & no compositions of the composition target.
if (!shape->fill() && !(PP(shape)->compData)) {
if (method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) tryFastTrack = true;
else if (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0)) tryFastTrack = true;
}
}
if (tryFastTrack) {
viewport = renderer->viewport();
if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
P(target)->ctxFlag |= ContextFlag::FastTrack;
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
//no gradient fill & no compositions of the composition target.
if (!shape->fill() && !(PP(shape)->compData)) {
if ((method == CompositeMethod::AlphaMask && a == 255 && PP(shape)->opacity == 255) || (method == CompositeMethod::InvAlphaMask && (a == 0 || PP(shape)->opacity == 0))) {
viewport = renderer->viewport();
if ((compFastTrack = _compFastTrack(renderer, target, pm, viewport)) == Result::Success) {
P(target)->ctxFlag |= ContextFlag::FastTrack;
}
}
}
}
if (compFastTrack == Result::InsufficientCondition) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
trd = P(target)->update(renderer, pm, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
trd = P(target)->update(renderer, pm, clips, 255, pFlag, false);
}
}
/* 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);
renderFlag = RenderUpdateFlag::None;
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;
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);
else if (childClipper) clips.pop();
else if (this->clipper) clips.pop();
return rd;
}
@ -351,6 +354,11 @@ bool Paint::Impl::bounds(float* x, float* y, float* w, float* h, bool transforme
void Paint::Impl::reset()
{
if (clipper) {
delete(clipper);
clipper = nullptr;
}
if (compData) {
if (P(compData->target)->unref() == 0) delete(compData->target);
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) {
TVGERR("RENDERER", "ClipPath only allows the Shape!");
auto p = clipper.release();
if (p && p->type() != Type::Shape) {
TVGERR("RENDERER", "Clipping only supports the Shape!");
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();
if (pImpl->composite(this, p, method)) return Result::Success;
delete(p);
return Result::InvalidArguments;
}
@ -451,6 +471,11 @@ CompositeMethod Paint::composite(const Paint** target) const noexcept
if (target) *target = pImpl->compData->target;
return pImpl->compData->method;
} else {
//TODO: remove. Keep this for the backward compatibility
if (pImpl->clipper) {
if (target) *target = pImpl->clipper;
return CompositeMethod::ClipPath;
}
if (target) *target = nullptr;
return CompositeMethod::None;
}

View file

@ -49,6 +49,7 @@ namespace tvg
{
Paint* paint = nullptr;
Composite* compData = nullptr;
Paint* clipper = nullptr;
RenderMethod* renderer = nullptr;
struct {
Matrix m; //input matrix
@ -88,6 +89,7 @@ namespace tvg
if (P(compData->target)->unref() == 0) delete(compData->target);
free(compData);
}
if (clipper && P(clipper)->unref() == 0) delete(clipper);
if (renderer && (renderer->unref() == 0)) delete(renderer);
}
@ -120,6 +122,20 @@ namespace tvg
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)
{
//Invalid case