From af8c278c5e2a47c21f44440cdb0a7aaf6f6d3baa Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Wed, 2 Dec 2020 17:16:15 +0900 Subject: [PATCH] sw_engine raster: support opacity composition. This implementation supports shape + stroke opacity composition. Currently, tvg shape provides individual alpha values for filling & stroking These alpha values are working individually, meaning that if stroking is half translucent, user can see that translucent stroking is crossed the shape outlines. Sometimes this result can be expected but user also expects the shape filling is invisible behind of translucent stroking. For this reason, Paint provides an additional api opacity() that applies opacity value to whole paint attributes. This is a little expensive job, please consider if you can possibly avoid that usage. See Opacity example. @Issues: 94 --- src/examples/Opacity.cpp | 7 ++ src/examples/Stress.cpp | 2 +- src/lib/sw_engine/tvgSwRenderer.cpp | 160 +++++++++++++++++++++------- src/lib/sw_engine/tvgSwRenderer.h | 3 +- src/lib/tvgSceneImpl.h | 12 ++- 5 files changed, 139 insertions(+), 45 deletions(-) diff --git a/src/examples/Opacity.cpp b/src/examples/Opacity.cpp index 3b9dd44e..53a2bab1 100644 --- a/src/examples/Opacity.cpp +++ b/src/examples/Opacity.cpp @@ -35,6 +35,10 @@ void tvgDrawCmds(tvg::Canvas* canvas) shape2->lineTo(146, 143); shape2->close(); shape2->fill(0, 0, 255, 255); + shape2->stroke(10); + shape2->stroke(255, 255, 255, 255); + shape2->opacity(127); + scene->push(move(shape2)); //Circle @@ -52,6 +56,9 @@ void tvgDrawCmds(tvg::Canvas* canvas) shape3->cubicTo(cx - halfRadius, cy + radius, cx - radius, cy + halfRadius, cx - radius, cy); shape3->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius); shape3->fill(255, 0, 0, 255); + shape3->stroke(10); + shape3->stroke(0, 0, 255, 255); + shape3->opacity(200); scene->push(move(shape3)); //Draw the Scene onto the Canvas diff --git a/src/examples/Stress.cpp b/src/examples/Stress.cpp index 235d563f..9e2d556b 100644 --- a/src/examples/Stress.cpp +++ b/src/examples/Stress.cpp @@ -53,7 +53,7 @@ void svgDirCallback(const char* name, const char* path, void* data) } cout << "SVG: " << buf << endl; - pictures.push_back(picture.release()); + pictures.push_back(picture.release()); } void tvgDrawCmds(tvg::Canvas* canvas) diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp index 5607c78d..d70db789 100644 --- a/src/lib/sw_engine/tvgSwRenderer.cpp +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -19,6 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ +#include #include "tvgSwCommon.h" #include "tvgTaskScheduler.h" #include "tvgSwRenderer.h" @@ -45,11 +46,16 @@ struct SwShapeTask : SwTask { SwShape shape; const Shape* sdata = nullptr; + bool compStroking; void run(unsigned tid) override { if (opacity == 0) return; //Invisible + /* Valid filling & stroking each increases the value by 1. + This value is referenced for compositing shape & stroking. */ + uint32_t addStroking = 0; + //Valid Stroking? uint8_t strokeAlpha = 0; auto strokeWidth = sdata->strokeWidth(); @@ -76,8 +82,9 @@ struct SwShapeTask : SwTask /* We assume that if stroke width is bigger than 2, shape outline below stroke could be full covered by stroke drawing. Thus it turns off antialising in that condition. */ - auto antiAlias = (strokeAlpha > 0 && strokeWidth > 2) ? false : true; + auto antiAlias = (strokeAlpha == 255 && strokeWidth > 2) ? false : true; if (!shapeGenRle(&shape, sdata, clip, antiAlias, compList.size() > 0 ? true : false)) goto end; + ++addStroking; } } } @@ -93,11 +100,13 @@ struct SwShapeTask : SwTask shapeDelFill(&shape); } } + //Stroke if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { if (strokeAlpha > 0) { shapeResetStroke(&shape, sdata, transform); if (!shapeGenStrokeRle(&shape, sdata, tid, transform, clip)) goto end; + ++addStroking; } else { shapeDelStroke(&shape); } @@ -105,17 +114,22 @@ struct SwShapeTask : SwTask //Composition for (auto comp : compList) { - SwShape *compShape = &static_cast(comp.edata)->shape; - if (comp.method == CompositeMethod::ClipPath) { - //Clip to fill(path) rle - if (shape.rle && compShape->rect) rleClipRect(shape.rle, &compShape->bbox); - else if (shape.rle && compShape->rle) rleClipPath(shape.rle, compShape->rle); - - //Clip to stroke rle - if (shape.strokeRle && compShape->rect) rleClipRect(shape.strokeRle, &compShape->bbox); - else if (shape.strokeRle && compShape->rle) rleClipPath(shape.strokeRle, compShape->rle); - } + if (comp.method == CompositeMethod::ClipPath) { + auto compShape = &static_cast(comp.edata)->shape; + //Clip shape rle + if (shape.rle) { + if (compShape->rect) rleClipRect(shape.rle, &compShape->bbox); + else if (compShape->rle) rleClipPath(shape.rle, compShape->rle); + } + //Clip stroke rle + if (shape.strokeRle) { + if (compShape->rect) rleClipRect(shape.strokeRle, &compShape->bbox); + else if (compShape->rle) rleClipPath(shape.strokeRle, compShape->rle); + } + } } + if (addStroking == 2 && opacity < 255) compStroking = true; + else compStroking = false; end: shapeDelOutline(&shape, tid); } @@ -132,13 +146,13 @@ struct SwImageTask : SwTask { SwImage image; const Picture* pdata = nullptr; - uint32_t *pixels = nullptr; + uint32_t* pixels = nullptr; void run(unsigned tid) override { SwSize clip = {static_cast(surface->w), static_cast(surface->h)}; - //Invisiable shape turned to visible by alpha. + //Invisible shape turned to visible by alpha. auto prepareImage = false; if (!imagePrepared(&image) && ((flags & RenderUpdateFlag::Image) || (opacity > 0))) prepareImage = true; @@ -146,22 +160,21 @@ struct SwImageTask : SwTask imageReset(&image); if (!imagePrepare(&image, pdata, tid, clip, transform)) goto end; + //Composition? if (compList.size() > 0) { if (!imageGenRle(&image, pdata, clip, false, true)) goto end; - - //Composition - for (auto comp : compList) { - SwShape *compShape = &static_cast(comp.edata)->shape; - if (comp.method == CompositeMethod::ClipPath) { - //Clip to fill(path) rle - if (image.rle && compShape->rect) rleClipRect(image.rle, &compShape->bbox); - else if (image.rle && compShape->rle) rleClipPath(image.rle, compShape->rle); + if (image.rle) { + for (auto comp : compList) { + if (comp.method == CompositeMethod::ClipPath) { + auto compShape = &static_cast(comp.edata)->shape; + if (compShape->rect) rleClipRect(image.rle, &compShape->bbox); + else if (compShape->rle) rleClipPath(image.rle, compShape->rle); + } } } } } - - if (this->pixels) image.data = this->pixels; + if (pixels) image.data = pixels; end: imageDelOutline(&image, tid); } @@ -190,7 +203,7 @@ SwRenderer::~SwRenderer() { clear(); - if (surface) delete(surface); + if (mainSurface) delete(mainSurface); --rendererCnt; if (!initEngine) _termEngine(); @@ -210,30 +223,38 @@ bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t { if (!buffer || stride == 0 || w == 0 || h == 0) return false; - if (!surface) { - surface = new SwSurface; - if (!surface) return false; + if (!mainSurface) { + mainSurface = new SwSurface; + if (!mainSurface) return false; } - surface->buffer = buffer; - surface->stride = stride; - surface->w = w; - surface->h = h; - surface->cs = cs; + mainSurface->buffer = buffer; + mainSurface->stride = stride; + mainSurface->w = w; + mainSurface->h = h; + mainSurface->cs = cs; - return rasterCompositor(surface); + return rasterCompositor(mainSurface); } bool SwRenderer::preRender() { - return rasterClear(surface); + return rasterClear(mainSurface); } bool SwRenderer::postRender() { tasks.clear(); + + //Clear Composite Surface + if (compSurface) { + if (compSurface->buffer) free(compSurface->buffer); + delete(compSurface); + } + compSurface = nullptr; + return true; } @@ -243,26 +264,83 @@ bool SwRenderer::render(TVG_UNUSED const Picture& picture, void *data) auto task = static_cast(data); task->done(); - return rasterImage(surface, &task->image, task->transform, task->opacity); + return rasterImage(mainSurface, &task->image, task->transform, task->opacity); } + bool SwRenderer::render(TVG_UNUSED const Shape& shape, void *data) { auto task = static_cast(data); task->done(); + + if (task->opacity == 0) return true; + SwSurface* renderTarget; + uint32_t opacity; + bool composite; + + //Do Composition + if (task->compStroking) { + //Setup Composition Surface + if (!compSurface) { + compSurface = new SwSurface; + if (!compSurface) return false; + *compSurface = *mainSurface; + compSurface->buffer = (uint32_t*) malloc(sizeof(uint32_t) * mainSurface->stride * mainSurface->h); + if (!compSurface->buffer) { + delete(compSurface); + compSurface = nullptr; + return false; + } + } + rasterClear(compSurface); + renderTarget = compSurface; + opacity = 255; + composite = true; + //No Composition + } else { + renderTarget = mainSurface; + opacity = task->opacity; + composite = false; + } + + //Main raster stage uint8_t r, g, b, a; + if (auto fill = task->sdata->fill()) { //FIXME: pass opacity to apply gradient fill? - rasterGradientShape(surface, &task->shape, fill->id()); + rasterGradientShape(renderTarget, &task->shape, fill->id()); } else{ task->sdata->fillColor(&r, &g, &b, &a); - a = static_cast((task->opacity * (uint32_t) a) / 255); - if (a > 0) rasterSolidShape(surface, &task->shape, r, g, b, a); + a = static_cast((opacity * (uint32_t) a) / 255); + if (a > 0) rasterSolidShape(renderTarget, &task->shape, r, g, b, a); } + task->sdata->strokeColor(&r, &g, &b, &a); - a = static_cast((task->opacity * (uint32_t) a) / 255); - if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a); + a = static_cast((opacity * (uint32_t) a) / 255); + if (a > 0) rasterStroke(renderTarget, &task->shape, r, g, b, a); + + //Composition (Shape + Stroke) stage + if (composite) { + SwImage image; + image.data = compSurface->buffer; + image.w = compSurface->w; + image.h = compSurface->h; + image.rle = nullptr; + + //Add stroke size to bounding box. + auto strokeWidth = static_cast(ceilf(task->sdata->strokeWidth() * 0.5f)); + image.bbox.min.x = task->shape.bbox.min.x - strokeWidth; + image.bbox.min.y = task->shape.bbox.min.y - strokeWidth; + image.bbox.max.x = task->shape.bbox.max.x + strokeWidth; + image.bbox.max.y = task->shape.bbox.max.y + strokeWidth; + if (image.bbox.min.x < 0) image.bbox.min.x = 0; + if (image.bbox.min.y < 0) image.bbox.min.y = 0; + if (image.bbox.max.x > compSurface->w) image.bbox.max.x = compSurface->w; + if (image.bbox.max.y > compSurface->h) image.bbox.max.y = compSurface->h; + + rasterImage(mainSurface, &image, nullptr, task->opacity); + } return true; } @@ -299,7 +377,7 @@ void SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, u } task->opacity = opacity; - task->surface = surface; + task->surface = mainSurface; task->flags = flags; tasks.push_back(task); diff --git a/src/lib/sw_engine/tvgSwRenderer.h b/src/lib/sw_engine/tvgSwRenderer.h index 3601eae6..475ca585 100644 --- a/src/lib/sw_engine/tvgSwRenderer.h +++ b/src/lib/sw_engine/tvgSwRenderer.h @@ -49,7 +49,8 @@ public: static bool term(); private: - SwSurface* surface = nullptr; + SwSurface* mainSurface = nullptr; + SwSurface* compSurface = nullptr; //Composition Surface to use temporarily in the intermediate rendering vector tasks; SwRenderer(){}; diff --git a/src/lib/tvgSceneImpl.h b/src/lib/tvgSceneImpl.h index bb37c328..4b0eb774 100644 --- a/src/lib/tvgSceneImpl.h +++ b/src/lib/tvgSceneImpl.h @@ -46,21 +46,29 @@ struct Scene::Impl void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, vector& compList, RenderUpdateFlag flag) { - /* FXIME: it requires to return list of childr engine data + /* FXIME: it requires to return list of children engine data This is necessary for scene composition */ void* edata = nullptr; for (auto paint : paints) { edata = paint->pImpl->update(renderer, transform, opacity, compList, static_cast(flag)); } + return edata; } bool render(RenderMethod &renderer) { + //TODO: composition begin + //auto data = renderer.beginComp(); + for (auto paint : paints) { if (!paint->pImpl->render(renderer)) return false; - } + } + + //TODO: composition end + //renderer.endComp(edata); + return true; }