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->lineTo(146, 143);
|
||||||
shape2->close();
|
shape2->close();
|
||||||
shape2->fill(0, 0, 255, 255);
|
shape2->fill(0, 0, 255, 255);
|
||||||
|
shape2->stroke(10);
|
||||||
|
shape2->stroke(255, 255, 255, 255);
|
||||||
|
shape2->opacity(127);
|
||||||
|
|
||||||
scene->push(move(shape2));
|
scene->push(move(shape2));
|
||||||
|
|
||||||
//Circle
|
//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 - halfRadius, cy + radius, cx - radius, cy + halfRadius, cx - radius, cy);
|
||||||
shape3->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius);
|
shape3->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius);
|
||||||
shape3->fill(255, 0, 0, 255);
|
shape3->fill(255, 0, 0, 255);
|
||||||
|
shape3->stroke(10);
|
||||||
|
shape3->stroke(0, 0, 255, 255);
|
||||||
|
shape3->opacity(200);
|
||||||
scene->push(move(shape3));
|
scene->push(move(shape3));
|
||||||
|
|
||||||
//Draw the Scene onto the Canvas
|
//Draw the Scene onto the Canvas
|
||||||
|
|
|
@ -53,7 +53,7 @@ void svgDirCallback(const char* name, const char* path, void* data)
|
||||||
}
|
}
|
||||||
|
|
||||||
cout << "SVG: " << buf << endl;
|
cout << "SVG: " << buf << endl;
|
||||||
pictures.push_back(picture.release());
|
pictures.push_back(picture.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
void tvgDrawCmds(tvg::Canvas* canvas)
|
void tvgDrawCmds(tvg::Canvas* canvas)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
#include <math.h>
|
||||||
#include "tvgSwCommon.h"
|
#include "tvgSwCommon.h"
|
||||||
#include "tvgTaskScheduler.h"
|
#include "tvgTaskScheduler.h"
|
||||||
#include "tvgSwRenderer.h"
|
#include "tvgSwRenderer.h"
|
||||||
|
@ -45,11 +46,16 @@ struct SwShapeTask : SwTask
|
||||||
{
|
{
|
||||||
SwShape shape;
|
SwShape shape;
|
||||||
const Shape* sdata = nullptr;
|
const Shape* sdata = nullptr;
|
||||||
|
bool compStroking;
|
||||||
|
|
||||||
void run(unsigned tid) override
|
void run(unsigned tid) override
|
||||||
{
|
{
|
||||||
if (opacity == 0) return; //Invisible
|
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?
|
//Valid Stroking?
|
||||||
uint8_t strokeAlpha = 0;
|
uint8_t strokeAlpha = 0;
|
||||||
auto strokeWidth = sdata->strokeWidth();
|
auto strokeWidth = sdata->strokeWidth();
|
||||||
|
@ -76,8 +82,9 @@ struct SwShapeTask : SwTask
|
||||||
/* We assume that if stroke width is bigger than 2,
|
/* We assume that if stroke width is bigger than 2,
|
||||||
shape outline below stroke could be full covered by stroke drawing.
|
shape outline below stroke could be full covered by stroke drawing.
|
||||||
Thus it turns off antialising in that condition. */
|
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;
|
if (!shapeGenRle(&shape, sdata, clip, antiAlias, compList.size() > 0 ? true : false)) goto end;
|
||||||
|
++addStroking;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,11 +100,13 @@ struct SwShapeTask : SwTask
|
||||||
shapeDelFill(&shape);
|
shapeDelFill(&shape);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Stroke
|
//Stroke
|
||||||
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
|
if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) {
|
||||||
if (strokeAlpha > 0) {
|
if (strokeAlpha > 0) {
|
||||||
shapeResetStroke(&shape, sdata, transform);
|
shapeResetStroke(&shape, sdata, transform);
|
||||||
if (!shapeGenStrokeRle(&shape, sdata, tid, transform, clip)) goto end;
|
if (!shapeGenStrokeRle(&shape, sdata, tid, transform, clip)) goto end;
|
||||||
|
++addStroking;
|
||||||
} else {
|
} else {
|
||||||
shapeDelStroke(&shape);
|
shapeDelStroke(&shape);
|
||||||
}
|
}
|
||||||
|
@ -105,17 +114,22 @@ struct SwShapeTask : SwTask
|
||||||
|
|
||||||
//Composition
|
//Composition
|
||||||
for (auto comp : compList) {
|
for (auto comp : compList) {
|
||||||
SwShape *compShape = &static_cast<SwShapeTask*>(comp.edata)->shape;
|
if (comp.method == CompositeMethod::ClipPath) {
|
||||||
if (comp.method == CompositeMethod::ClipPath) {
|
auto compShape = &static_cast<SwShapeTask*>(comp.edata)->shape;
|
||||||
//Clip to fill(path) rle
|
//Clip shape rle
|
||||||
if (shape.rle && compShape->rect) rleClipRect(shape.rle, &compShape->bbox);
|
if (shape.rle) {
|
||||||
else if (shape.rle && compShape->rle) rleClipPath(shape.rle, compShape->rle);
|
if (compShape->rect) rleClipRect(shape.rle, &compShape->bbox);
|
||||||
|
else if (compShape->rle) rleClipPath(shape.rle, compShape->rle);
|
||||||
//Clip to stroke rle
|
}
|
||||||
if (shape.strokeRle && compShape->rect) rleClipRect(shape.strokeRle, &compShape->bbox);
|
//Clip stroke rle
|
||||||
else if (shape.strokeRle && compShape->rle) rleClipPath(shape.strokeRle, compShape->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:
|
end:
|
||||||
shapeDelOutline(&shape, tid);
|
shapeDelOutline(&shape, tid);
|
||||||
}
|
}
|
||||||
|
@ -132,13 +146,13 @@ struct SwImageTask : SwTask
|
||||||
{
|
{
|
||||||
SwImage image;
|
SwImage image;
|
||||||
const Picture* pdata = nullptr;
|
const Picture* pdata = nullptr;
|
||||||
uint32_t *pixels = nullptr;
|
uint32_t* pixels = nullptr;
|
||||||
|
|
||||||
void run(unsigned tid) override
|
void run(unsigned tid) override
|
||||||
{
|
{
|
||||||
SwSize clip = {static_cast<SwCoord>(surface->w), static_cast<SwCoord>(surface->h)};
|
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;
|
auto prepareImage = false;
|
||||||
if (!imagePrepared(&image) && ((flags & RenderUpdateFlag::Image) || (opacity > 0))) prepareImage = true;
|
if (!imagePrepared(&image) && ((flags & RenderUpdateFlag::Image) || (opacity > 0))) prepareImage = true;
|
||||||
|
|
||||||
|
@ -146,22 +160,21 @@ struct SwImageTask : SwTask
|
||||||
imageReset(&image);
|
imageReset(&image);
|
||||||
if (!imagePrepare(&image, pdata, tid, clip, transform)) goto end;
|
if (!imagePrepare(&image, pdata, tid, clip, transform)) goto end;
|
||||||
|
|
||||||
|
//Composition?
|
||||||
if (compList.size() > 0) {
|
if (compList.size() > 0) {
|
||||||
if (!imageGenRle(&image, pdata, clip, false, true)) goto end;
|
if (!imageGenRle(&image, pdata, clip, false, true)) goto end;
|
||||||
|
if (image.rle) {
|
||||||
//Composition
|
for (auto comp : compList) {
|
||||||
for (auto comp : compList) {
|
if (comp.method == CompositeMethod::ClipPath) {
|
||||||
SwShape *compShape = &static_cast<SwShapeTask*>(comp.edata)->shape;
|
auto compShape = &static_cast<SwShapeTask*>(comp.edata)->shape;
|
||||||
if (comp.method == CompositeMethod::ClipPath) {
|
if (compShape->rect) rleClipRect(image.rle, &compShape->bbox);
|
||||||
//Clip to fill(path) rle
|
else if (compShape->rle) rleClipPath(image.rle, compShape->rle);
|
||||||
if (image.rle && compShape->rect) rleClipRect(image.rle, &compShape->bbox);
|
}
|
||||||
else if (image.rle && compShape->rle) rleClipPath(image.rle, compShape->rle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (pixels) image.data = pixels;
|
||||||
if (this->pixels) image.data = this->pixels;
|
|
||||||
end:
|
end:
|
||||||
imageDelOutline(&image, tid);
|
imageDelOutline(&image, tid);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +203,7 @@ SwRenderer::~SwRenderer()
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
if (surface) delete(surface);
|
if (mainSurface) delete(mainSurface);
|
||||||
|
|
||||||
--rendererCnt;
|
--rendererCnt;
|
||||||
if (!initEngine) _termEngine();
|
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 (!buffer || stride == 0 || w == 0 || h == 0) return false;
|
||||||
|
|
||||||
if (!surface) {
|
if (!mainSurface) {
|
||||||
surface = new SwSurface;
|
mainSurface = new SwSurface;
|
||||||
if (!surface) return false;
|
if (!mainSurface) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
surface->buffer = buffer;
|
mainSurface->buffer = buffer;
|
||||||
surface->stride = stride;
|
mainSurface->stride = stride;
|
||||||
surface->w = w;
|
mainSurface->w = w;
|
||||||
surface->h = h;
|
mainSurface->h = h;
|
||||||
surface->cs = cs;
|
mainSurface->cs = cs;
|
||||||
|
|
||||||
return rasterCompositor(surface);
|
return rasterCompositor(mainSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool SwRenderer::preRender()
|
bool SwRenderer::preRender()
|
||||||
{
|
{
|
||||||
return rasterClear(surface);
|
return rasterClear(mainSurface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool SwRenderer::postRender()
|
bool SwRenderer::postRender()
|
||||||
{
|
{
|
||||||
tasks.clear();
|
tasks.clear();
|
||||||
|
|
||||||
|
//Clear Composite Surface
|
||||||
|
if (compSurface) {
|
||||||
|
if (compSurface->buffer) free(compSurface->buffer);
|
||||||
|
delete(compSurface);
|
||||||
|
}
|
||||||
|
compSurface = nullptr;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,26 +264,83 @@ bool SwRenderer::render(TVG_UNUSED const Picture& picture, void *data)
|
||||||
auto task = static_cast<SwImageTask*>(data);
|
auto task = static_cast<SwImageTask*>(data);
|
||||||
task->done();
|
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)
|
bool SwRenderer::render(TVG_UNUSED const Shape& shape, void *data)
|
||||||
{
|
{
|
||||||
auto task = static_cast<SwShapeTask*>(data);
|
auto task = static_cast<SwShapeTask*>(data);
|
||||||
task->done();
|
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;
|
uint8_t r, g, b, a;
|
||||||
|
|
||||||
if (auto fill = task->sdata->fill()) {
|
if (auto fill = task->sdata->fill()) {
|
||||||
//FIXME: pass opacity to apply gradient fill?
|
//FIXME: pass opacity to apply gradient fill?
|
||||||
rasterGradientShape(surface, &task->shape, fill->id());
|
rasterGradientShape(renderTarget, &task->shape, fill->id());
|
||||||
} else{
|
} else{
|
||||||
task->sdata->fillColor(&r, &g, &b, &a);
|
task->sdata->fillColor(&r, &g, &b, &a);
|
||||||
a = static_cast<uint8_t>((task->opacity * (uint32_t) a) / 255);
|
a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
|
||||||
if (a > 0) rasterSolidShape(surface, &task->shape, r, g, b, a);
|
if (a > 0) rasterSolidShape(renderTarget, &task->shape, r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
task->sdata->strokeColor(&r, &g, &b, &a);
|
task->sdata->strokeColor(&r, &g, &b, &a);
|
||||||
a = static_cast<uint8_t>((task->opacity * (uint32_t) a) / 255);
|
a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
|
||||||
if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -299,7 +377,7 @@ void SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, u
|
||||||
}
|
}
|
||||||
|
|
||||||
task->opacity = opacity;
|
task->opacity = opacity;
|
||||||
task->surface = surface;
|
task->surface = mainSurface;
|
||||||
task->flags = flags;
|
task->flags = flags;
|
||||||
|
|
||||||
tasks.push_back(task);
|
tasks.push_back(task);
|
||||||
|
|
|
@ -49,7 +49,8 @@ public:
|
||||||
static bool term();
|
static bool term();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SwSurface* surface = nullptr;
|
SwSurface* mainSurface = nullptr;
|
||||||
|
SwSurface* compSurface = nullptr; //Composition Surface to use temporarily in the intermediate rendering
|
||||||
vector<SwTask*> tasks;
|
vector<SwTask*> tasks;
|
||||||
|
|
||||||
SwRenderer(){};
|
SwRenderer(){};
|
||||||
|
|
|
@ -46,21 +46,29 @@ struct Scene::Impl
|
||||||
|
|
||||||
void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flag)
|
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 */
|
This is necessary for scene composition */
|
||||||
void* edata = nullptr;
|
void* edata = nullptr;
|
||||||
|
|
||||||
for (auto paint : paints) {
|
for (auto paint : paints) {
|
||||||
edata = paint->pImpl->update(renderer, transform, opacity, compList, static_cast<uint32_t>(flag));
|
edata = paint->pImpl->update(renderer, transform, opacity, compList, static_cast<uint32_t>(flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
return edata;
|
return edata;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool render(RenderMethod &renderer)
|
bool render(RenderMethod &renderer)
|
||||||
{
|
{
|
||||||
|
//TODO: composition begin
|
||||||
|
//auto data = renderer.beginComp();
|
||||||
|
|
||||||
for (auto paint : paints) {
|
for (auto paint : paints) {
|
||||||
if (!paint->pImpl->render(renderer)) return false;
|
if (!paint->pImpl->render(renderer)) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: composition end
|
||||||
|
//renderer.endComp(edata);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue