common: spec out Scene Clipper

Scene Clipper is an unusual feature
that is too unstable and ambiguous in ThorVG.

Users can achieve the same functionality
with multiple composed shapes instead of scene clipping.

size: -2.5kb

issues:
- https://github.com/thorvg/thorvg/issues/1548
- https://github.com/thorvg/thorvg/issues/1549
- https://github.com/thorvg/thorvg/issues/1573
This commit is contained in:
Hermet Park 2024-08-12 21:36:13 +09:00 committed by Hermet Park
parent e50bf002de
commit 407fc84796
16 changed files with 15 additions and 281 deletions

View file

@ -1,91 +0,0 @@
/*
* Copyright (c) 2023 - 2024 the ThorVG project. All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "Example.h"
/************************************************************************/
/* ThorVG Drawing Contents */
/************************************************************************/
struct UserExample : tvgexam::Example
{
bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
if (!canvas) return false;
/* Using a single shape is sufficient to clip multiple regions.
The disabled code below performs the same function. Please try it. */
#if 1
//A scene clipper
auto clipper = tvg::Scene::gen();
auto shape1 = tvg::Shape::gen();
shape1->appendCircle(200, 200, 100, 100);
clipper->push(std::move(shape1));
auto shape2 = tvg::Shape::gen();
shape2->appendCircle(400, 400, 150, 150);
clipper->push(std::move(shape2));
auto shape3 = tvg::Shape::gen();
shape3->appendCircle(150, 300, 60, 60);
clipper->push(std::move(shape3));
auto shape4 = tvg::Shape::gen();
shape4->appendCircle(400, 100, 125, 125);
clipper->push(std::move(shape4));
auto shape5 = tvg::Shape::gen();
shape5->appendCircle(150, 500, 100, 100);
clipper->push(std::move(shape5));
#else
//A single clipper with multiple regions
auto clipper = tvg::Shape::gen();
clipper->appendCircle(200, 200, 100, 100);
clipper->appendCircle(400, 400, 150, 150);
clipper->appendCircle(150, 300, 60, 60);
clipper->appendCircle(400, 100, 125, 125);
clipper->appendCircle(150, 500, 100, 100);
#endif
//Source
auto shape = tvg::Shape::gen();
shape->appendRect(100, 100, 400, 400, 50, 50);
shape->strokeFill(0, 0, 255);
shape->strokeWidth(10);
shape->fill(255, 255, 255);
shape->composite(std::move(clipper), tvg::CompositeMethod::ClipPath);
canvas->push(std::move(shape));
return true;
}
};
/************************************************************************/
/* Entry Point */
/************************************************************************/
int main(int argc, char **argv)
{
return tvgexam::main(new UserExample, argc, argv);
}

View file

@ -59,7 +59,6 @@ source_file = [
'Retaining.cpp',
'Scene.cpp',
'SceneBlending.cpp',
'SceneClipper.cpp',
'SceneTransform.cpp',
'Shape.cpp',
'Stroke.cpp',

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.
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.
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

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.
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_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

View file

@ -1131,13 +1131,6 @@ RenderData GlRenderer::prepare(Surface* image, const RenderMesh* mesh, RenderDat
}
RenderData GlRenderer::prepare(TVG_UNUSED const Array<RenderData>& scene, TVG_UNUSED RenderData data, TVG_UNUSED const Matrix& transform, TVG_UNUSED Array<RenderData>& clips, TVG_UNUSED uint8_t opacity, TVG_UNUSED RenderUpdateFlag flags)
{
//TODO:
return nullptr;
}
RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
// If prepare for clip, only path is meaningful.

View file

@ -54,7 +54,6 @@ public:
};
RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;

View file

@ -199,59 +199,6 @@ struct SwShapeTask : SwTask
};
struct SwSceneTask : SwTask
{
Array<RenderData> scene; //list of paints render data (SwTask)
SwRle* sceneRle = nullptr;
bool clip(SwRle* target) override
{
//Only one shape
if (scene.count == 1) {
return static_cast<SwTask*>(*scene.data)->clip(target);
}
//More than one shapes
if (sceneRle) rleClipPath(target, sceneRle);
else TVGLOG("SW_ENGINE", "No clippers in a scene?");
return true;
}
SwRle* rle() override
{
return sceneRle;
}
void run(unsigned tid) override
{
//TODO: Skip the run if the scene hasn't changed.
if (!sceneRle) sceneRle = static_cast<SwRle*>(calloc(1, sizeof(SwRle)));
else rleReset(sceneRle);
//Merge shapes if it has more than one shapes
if (scene.count > 1) {
//Merge first two clippers
auto clipper1 = static_cast<SwTask*>(*scene.data);
auto clipper2 = static_cast<SwTask*>(*(scene.data + 1));
rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
//Unify the remained clippers
for (auto rd = scene.begin() + 2; rd < scene.end(); ++rd) {
auto clipper = static_cast<SwTask*>(*rd);
rleMerge(sceneRle, sceneRle, clipper->rle());
}
}
}
void dispose() override
{
rleFree(sceneRle);
}
};
struct SwImageTask : SwTask
{
SwImage image;
@ -759,26 +706,6 @@ RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
}
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwSceneTask*>(data);
if (!task) task = new SwSceneTask;
else task->done();
task->scene = scene;
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto task = scene.begin(); task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task

View file

@ -37,7 +37,6 @@ class SwRenderer : public RenderMethod
{
public:
RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;

View file

@ -822,46 +822,6 @@ static SwSpan* _intersectSpansRect(const SwBBox *bbox, const SwRle *targetRle, S
}
static SwSpan* _mergeSpansRegion(const SwRle *clip1, const SwRle *clip2, SwSpan *outSpans)
{
auto out = outSpans;
auto spans1 = clip1->spans;
auto end1 = clip1->spans + clip1->size;
auto spans2 = clip2->spans;
auto end2 = clip2->spans + clip2->size;
//list two spans up in y order
//TODO: Remove duplicated regions?
while (spans1 < end1 && spans2 < end2) {
while (spans1 < end1 && spans1->y <= spans2->y) {
*out = *spans1;
++spans1;
++out;
}
if (spans1 >= end1) break;
while (spans2 < end2 && spans2->y <= spans1->y) {
*out = *spans2;
++spans2;
++out;
}
}
//Leftovers
while (spans1 < end1) {
*out = *spans1;
++spans1;
++out;
}
while (spans2 < end2) {
*out = *spans2;
++spans2;
++out;
}
return out;
}
void _replaceClipSpan(SwRle *rle, SwSpan* clippedSpans, uint32_t size)
{
free(rle->spans);
@ -1030,45 +990,6 @@ void rleFree(SwRle* rle)
}
void rleMerge(SwRle* rle, SwRle* clip1, SwRle* clip2)
{
if (!rle || (!clip1 && !clip2)) return;
if (clip1 && clip1->size == 0 && clip2 && clip2->size == 0) return;
TVGLOG("SW_ENGINE", "Unifying Rle!");
//clip1 is empty, just copy clip2
if (!clip1 || clip1->size == 0) {
if (clip2) {
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip2->size)));
memcpy(spans, clip2->spans, clip2->size);
_replaceClipSpan(rle, spans, clip2->size);
} else {
_replaceClipSpan(rle, nullptr, 0);
}
return;
}
//clip2 is empty, just copy clip1
if (!clip2 || clip2->size == 0) {
if (clip1) {
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * (clip1->size)));
memcpy(spans, clip1->spans, clip1->size);
_replaceClipSpan(rle, spans, clip1->size);
} else {
_replaceClipSpan(rle, nullptr, 0);
}
return;
}
auto spanCnt = clip1->size + clip2->size;
auto spans = static_cast<SwSpan*>(malloc(sizeof(SwSpan) * spanCnt));
auto spansEnd = _mergeSpansRegion(clip1, clip2, spans);
_replaceClipSpan(rle, spans, spansEnd - spans);
}
void rleClipPath(SwRle *rle, const SwRle *clip)
{
if (rle->size == 0 || clip->size == 0) return;

View file

@ -435,6 +435,11 @@ Paint* Paint::duplicate() const noexcept
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept
{
if (method == CompositeMethod::ClipPath && target && target->type() != Type::Shape) {
TVGERR("RENDERER", "ClipPath only allows the Shape!");
return Result::NonSupport;
}
auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success;
delete(p);

View file

@ -89,7 +89,7 @@ struct Picture::Impl
delete(paint);
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
auto flag = static_cast<RenderUpdateFlag>(pFlag | load());
@ -109,7 +109,7 @@ struct Picture::Impl
resizing = false;
}
needComp = needComposition(opacity) ? true : false;
rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, clipper);
rd = paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return rd;
}

View file

@ -285,7 +285,6 @@ public:
virtual ~RenderMethod() {}
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;

View file

@ -101,7 +101,7 @@ struct Scene::Impl
return true;
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper)
{
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
@ -109,21 +109,11 @@ struct Scene::Impl
this->opacity = opacity;
opacity = 255;
}
if (clipper) {
Array<RenderData> rds(paints.size());
for (auto paint : paints) {
rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true));
}
rd = renderer->prepare(rds, rd, transform, clips, opacity, flag);
return rd;
} else {
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
}
}
bool render(RenderMethod* renderer)
{

View file

@ -120,7 +120,7 @@ struct Text::Impl
return false;
}
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper)
{
if (!load()) return nullptr;
@ -142,7 +142,7 @@ struct Text::Impl
P(static_cast<RadialGradient*>(fill))->fr *= scale;
}
}
return PP(paint)->update(renderer, transform, clips, opacity, pFlag, clipper);
return PP(paint)->update(renderer, transform, clips, opacity, pFlag, false);
}
bool bounds(float* x, float* y, float* w, float* h, TVG_UNUSED bool stroking)

View file

@ -111,12 +111,6 @@ RenderData WgRenderer::prepare(const RenderShape& rshape, RenderData data, const
}
RenderData WgRenderer::prepare(TVG_UNUSED const Array<RenderData>& scene, TVG_UNUSED RenderData data, TVG_UNUSED const Matrix& transform, TVG_UNUSED Array<RenderData>& clips, TVG_UNUSED uint8_t opacity, TVG_UNUSED RenderUpdateFlag flags)
{
return nullptr;
}
RenderData WgRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
// get or create render data shape

View file

@ -29,7 +29,6 @@ class WgRenderer : public RenderMethod
{
public:
RenderData prepare(const RenderShape& rshape, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;