mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
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
This commit is contained in:
parent
1743db705b
commit
af8c278c5e
5 changed files with 139 additions and 45 deletions
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <math.h>
|
||||
#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<SwShapeTask*>(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<SwShapeTask*>(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<SwCoord>(surface->w), static_cast<SwCoord>(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<SwShapeTask*>(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<SwShapeTask*>(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<SwImageTask*>(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<SwShapeTask*>(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<uint8_t>((task->opacity * (uint32_t) a) / 255);
|
||||
if (a > 0) rasterSolidShape(surface, &task->shape, r, g, b, a);
|
||||
a = static_cast<uint8_t>((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<uint8_t>((task->opacity * (uint32_t) a) / 255);
|
||||
if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
|
||||
a = static_cast<uint8_t>((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<SwCoord>(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);
|
||||
|
|
|
@ -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<SwTask*> tasks;
|
||||
|
||||
SwRenderer(){};
|
||||
|
|
|
@ -46,21 +46,29 @@ struct Scene::Impl
|
|||
|
||||
void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite>& 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<uint32_t>(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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue