common paint: introduce opacity() method.

We introduced separate opacity interface to adjust alpha value by paint.
This opacity will affect to whole paint image if paint is a group of paints.

Also, this opacity is to multipy with fill/stroke alpha values.
This means if the opacity is valid, the paint might deal with a composition step,
which is very expensive due to additional rendering step.

One tip is, if you want to toggle on/off for a certian paint,
you can set opacity to 255 or 0.

@API Additions:

Result Paint::opacity(uint8_t o) noexcept;
uint8_t Paint::opacity() const noexcept;

@Examples: examples/Opacity

@Issues: 94
This commit is contained in:
Hermet Park 2020-10-26 18:57:11 +09:00 committed by Hermet Park
parent da4b57e763
commit 0399d84478
14 changed files with 223 additions and 21 deletions

View file

@ -93,10 +93,13 @@ public:
Result translate(float x, float y) noexcept;
Result transform(const Matrix& m) noexcept;
Result bounds(float* x, float* y, float* w, float* h) const noexcept;
Result opacity(uint8_t o) noexcept;
Paint* duplicate() const noexcept;
Result composite(std::unique_ptr<Paint> target, CompositeMethod method) const noexcept;
uint8_t opacity() const noexcept;
_TVG_DECLARE_ACCESSOR();
_TVG_DECLARE_PRIVATE(Paint);
};

166
src/examples/Opacity.cpp Normal file
View file

@ -0,0 +1,166 @@
#include "Common.h"
/************************************************************************/
/* Drawing Commands */
/************************************************************************/
void tvgDrawCmds(tvg::Canvas* canvas)
{
if (!canvas) return;
//Prepare Circle
auto shape1 = tvg::Shape::gen();
shape1->appendCircle(400, 400, 250, 250);
shape1->fill(255, 255, 0, 255);
canvas->push(move(shape1));
//Create a Scene
auto scene = tvg::Scene::gen();
scene->opacity(127); //Apply opacity to scene (0 - 255)
scene->reserve(2);
//Star
auto shape2 = tvg::Shape::gen();
//Appends Paths
shape2->moveTo(199, 34);
shape2->lineTo(253, 143);
shape2->lineTo(374, 160);
shape2->lineTo(287, 244);
shape2->lineTo(307, 365);
shape2->lineTo(199, 309);
shape2->lineTo(97, 365);
shape2->lineTo(112, 245);
shape2->lineTo(26, 161);
shape2->lineTo(146, 143);
shape2->close();
shape2->fill(0, 0, 255, 255);
scene->push(move(shape2));
//Circle
auto shape3 = tvg::Shape::gen();
auto cx = 550.0f;
auto cy = 550.0f;
auto radius = 125.0f;
auto halfRadius = radius * 0.552284f;
//Append Paths
shape3->moveTo(cx, cy - radius);
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 - 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);
scene->push(move(shape3));
//Draw the Scene onto the Canvas
canvas->push(move(scene));
}
/************************************************************************/
/* Sw Engine Test Code */
/************************************************************************/
static unique_ptr<tvg::SwCanvas> swCanvas;
void tvgSwTest(uint32_t* buffer)
{
//Create a Canvas
swCanvas = tvg::SwCanvas::gen();
swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888);
/* Push the shape into the Canvas drawing list
When this shape is into the canvas list, the shape could update & prepare
internal data asynchronously for coming rendering.
Canvas keeps this shape node unless user call canvas->clear() */
tvgDrawCmds(swCanvas.get());
}
void drawSwView(void* data, Eo* obj)
{
if (swCanvas->draw() == tvg::Result::Success) {
swCanvas->sync();
}
}
/************************************************************************/
/* GL Engine Test Code */
/************************************************************************/
static unique_ptr<tvg::GlCanvas> glCanvas;
void initGLview(Evas_Object *obj)
{
static constexpr auto BPP = 4;
//Create a Canvas
glCanvas = tvg::GlCanvas::gen();
glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT);
/* Push the shape into the Canvas drawing list
When this shape is into the canvas list, the shape could update & prepare
internal data asynchronously for coming rendering.
Canvas keeps this shape node unless user call canvas->clear() */
tvgDrawCmds(glCanvas.get());
}
void drawGLview(Evas_Object *obj)
{
auto gl = elm_glview_gl_api_get(obj);
gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
gl->glClear(GL_COLOR_BUFFER_BIT);
if (glCanvas->draw() == tvg::Result::Success) {
glCanvas->sync();
}
}
/************************************************************************/
/* Main Code */
/************************************************************************/
int main(int argc, char **argv)
{
tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw;
if (argc > 1) {
if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl;
}
//Initialize ThorVG Engine
if (tvgEngine == tvg::CanvasEngine::Sw) {
cout << "tvg engine: software" << endl;
} else {
cout << "tvg engine: opengl" << endl;
}
//Threads Count
auto threads = std::thread::hardware_concurrency();
//Initialize ThorVG Engine
if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) {
elm_init(argc, argv);
if (tvgEngine == tvg::CanvasEngine::Sw) {
createSwView();
} else {
createGlView();
}
elm_run();
elm_shutdown();
//Terminate ThorVG Engine
tvg::Initializer::term(tvgEngine);
} else {
cout << "engine is not supported" << endl;
}
return 0;
}

View file

@ -14,6 +14,7 @@ source_file = [
'LinearGradient.cpp',
'MultiCanvas.cpp',
'MultiShapes.cpp',
'Opacity.cpp',
'PathCopy.cpp',
'Path.cpp',
'RadialGradient.cpp',

View file

@ -140,7 +140,7 @@ bool GlRenderer::dispose(TVG_UNUSED const Shape& shape, void *data)
}
void* GlRenderer::prepare(const Shape& shape, void* data, TVG_UNUSED const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag flags)
void* GlRenderer::prepare(const Shape& shape, void* data, TVG_UNUSED const RenderTransform* transform, TVG_UNUSED uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flags)
{
//prepare shape data
GlShape* sdata = static_cast<GlShape*>(data);

View file

@ -30,7 +30,7 @@ class GlRenderer : public RenderMethod
public:
Surface surface = {nullptr, 0, 0, 0};
void* prepare(const Shape& shape, void* data, const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag flags) override;
void* prepare(const Shape& shape, void* data, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flags) override;
bool dispose(const Shape& shape, void *data) override;
bool preRender() override;
bool render(const Shape& shape, void *data) override;

View file

@ -37,9 +37,12 @@ struct SwTask : Task
SwSurface* surface = nullptr;
RenderUpdateFlag flags = RenderUpdateFlag::None;
vector<Composite> compList;
uint32_t opacity;
void run() override
{
if (opacity == 0) return; //Invisible
//Valid Stroking?
uint8_t strokeAlpha = 0;
auto strokeWidth = sdata->strokeWidth();
@ -51,12 +54,13 @@ struct SwTask : Task
//Invisiable shape turned to visible by alpha.
auto prepareShape = false;
if (!shapePrepared(&shape) && (flags & RenderUpdateFlag::Color)) prepareShape = true;
if (!shapePrepared(&shape) && ((flags & RenderUpdateFlag::Color) || (opacity > 0))) prepareShape = true;
//Shape
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
uint8_t alpha = 0;
sdata->fillColor(nullptr, nullptr, nullptr, &alpha);
alpha = static_cast<uint8_t>(static_cast<uint32_t>(alpha) * opacity / 255);
bool renderShape = (alpha > 0 || sdata->fill());
if (renderShape || strokeAlpha) {
shapeReset(&shape);
@ -181,12 +185,15 @@ bool SwRenderer::render(const Shape& shape, void *data)
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());
} 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);
}
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);
return true;
@ -207,7 +214,7 @@ bool SwRenderer::dispose(TVG_UNUSED const Shape& sdata, void *data)
}
void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag flags)
void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwTask*>(data);
@ -237,6 +244,7 @@ void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform*
task->transform = nullptr;
}
task->opacity = opacity;
task->surface = surface;
task->flags = flags;

View file

@ -34,7 +34,7 @@ namespace tvg
class SwRenderer : public RenderMethod
{
public:
void* prepare(const Shape& shape, void* data, const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag flags) override;
void* prepare(const Shape& shape, void* data, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flags) override;
bool dispose(const Shape& shape, void *data) override;
bool preRender() override;
bool postRender() override;

View file

@ -81,11 +81,11 @@ struct Canvas::Impl
//Update single paint node
if (paint) {
paint->pImpl->update(*renderer, nullptr, compList, RenderUpdateFlag::None);
paint->pImpl->update(*renderer, nullptr, 255, compList, RenderUpdateFlag::None);
//Update all retained paint nodes
} else {
for (auto paint: paints) {
paint->pImpl->update(*renderer, nullptr, compList, RenderUpdateFlag::None);
paint->pImpl->update(*renderer, nullptr, 255, compList, RenderUpdateFlag::None);
}
}
return Result::Success;

View file

@ -67,20 +67,39 @@ Result Paint::transform(const Matrix& m) noexcept
return Result::FailedAllocation;
}
Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept
{
if (pImpl->bounds(x, y, w, h)) return Result::Success;
return Result::InsufficientCondition;
}
Paint* Paint::duplicate() const noexcept
{
return pImpl->duplicate();
}
Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) const noexcept
{
if (pImpl->composite(target.release(), method)) return Result::Success;
return Result::InsufficientCondition;
}
Result Paint::opacity(uint8_t o) noexcept
{
if (pImpl->opacity == o) return Result::Success;
pImpl->opacity = o;
pImpl->flag |= RenderUpdateFlag::Color;
return Result::Success;
}
uint8_t Paint::opacity() const noexcept
{
return pImpl->opacity;
}

View file

@ -33,7 +33,7 @@ namespace tvg
virtual ~StrategyMethod(){}
virtual bool dispose(RenderMethod& renderer) = 0;
virtual void* update(RenderMethod& renderer, const RenderTransform* transform, vector<Composite> compList, RenderUpdateFlag pFlag) = 0; //Return engine data if it has.
virtual void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite> compList, RenderUpdateFlag pFlag) = 0; //Return engine data if it has.
virtual bool render(RenderMethod& renderer) = 0;
virtual bool bounds(float* x, float* y, float* w, float* h) const = 0;
virtual Paint* duplicate() = 0;
@ -48,6 +48,8 @@ namespace tvg
Paint* compTarget = nullptr;
CompositeMethod compMethod = CompositeMethod::None;
uint8_t opacity = 255;
~Impl() {
if (smethod) delete(smethod);
if (rTransform) delete(rTransform);
@ -127,7 +129,7 @@ namespace tvg
return smethod->dispose(renderer);
}
void* update(RenderMethod& renderer, const RenderTransform* pTransform, vector<Composite>& compList, uint32_t pFlag)
void* update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, vector<Composite>& compList, uint32_t pFlag)
{
if (flag & RenderUpdateFlag::Transform) {
if (!rTransform) return nullptr;
@ -140,20 +142,21 @@ namespace tvg
void *compdata = nullptr;
if (compTarget && compMethod == CompositeMethod::ClipPath) {
compdata = compTarget->pImpl->update(renderer, pTransform, compList, pFlag);
compdata = compTarget->pImpl->update(renderer, pTransform, opacity, compList, pFlag);
if (compdata) compList.push_back({compdata, compMethod});
}
void *edata = nullptr;
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | flag);
flag = RenderUpdateFlag::None;
opacity = (opacity * this->opacity) / 255;
if (rTransform && pTransform) {
RenderTransform outTransform(pTransform, rTransform);
edata = smethod->update(renderer, &outTransform, compList, newFlag);
edata = smethod->update(renderer, &outTransform, opacity, compList, newFlag);
} else {
auto outTransform = pTransform ? pTransform : rTransform;
edata = smethod->update(renderer, outTransform, compList, newFlag);
edata = smethod->update(renderer, outTransform, opacity, compList, newFlag);
}
if (compdata) compList.pop_back();
@ -179,6 +182,8 @@ namespace tvg
}
}
ret->pImpl->opacity = opacity;
return ret;
}
@ -210,9 +215,9 @@ namespace tvg
return inst->dispose(renderer);
}
void* update(RenderMethod& renderer, const RenderTransform* transform, vector<Composite> compList, RenderUpdateFlag flag) override
void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite> compList, RenderUpdateFlag flag) override
{
return inst->update(renderer, transform, compList, flag);
return inst->update(renderer, transform, opacity, compList, flag);
}
bool render(RenderMethod& renderer) override

View file

@ -58,13 +58,13 @@ struct Picture::Impl
}
void* update(RenderMethod &renderer, const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag flag)
void* update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag flag)
{
reload();
if (!paint) return nullptr;
return paint->pImpl->update(renderer, transform, compList, flag);
return paint->pImpl->update(renderer, transform, opacity, compList, flag);
}
bool render(RenderMethod &renderer)

View file

@ -65,7 +65,7 @@ class RenderMethod
{
public:
virtual ~RenderMethod() {}
virtual void* prepare(TVG_UNUSED const Shape& shape, TVG_UNUSED void* data, TVG_UNUSED const RenderTransform* transform, TVG_UNUSED vector<Composite>& compList, TVG_UNUSED RenderUpdateFlag flags) { return nullptr; }
virtual void* prepare(TVG_UNUSED const Shape& shape, TVG_UNUSED void* data, TVG_UNUSED const RenderTransform* transform, uint32_t opacity, TVG_UNUSED vector<Composite>& compList, TVG_UNUSED RenderUpdateFlag flags) { return nullptr; }
virtual bool dispose(TVG_UNUSED const Shape& shape, TVG_UNUSED void *data) { return true; }
virtual bool preRender() { return true; }
virtual bool render(TVG_UNUSED const Shape& shape, TVG_UNUSED void *data) { return true; }

View file

@ -44,14 +44,14 @@ struct Scene::Impl
return true;
}
void* update(RenderMethod &renderer, const RenderTransform* transform, 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
This is necessary for scene composition */
void* edata = nullptr;
for (auto paint: paints) {
edata = paint->pImpl->update(renderer, transform, compList, static_cast<uint32_t>(flag));
edata = paint->pImpl->update(renderer, transform, opacity, compList, static_cast<uint32_t>(flag));
}
return edata;
}

View file

@ -222,9 +222,9 @@ struct Shape::Impl
return renderer.render(*shape, edata);
}
void* update(RenderMethod& renderer, const RenderTransform* transform, vector<Composite>& compList, RenderUpdateFlag pFlag)
void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, vector<Composite>& compList, RenderUpdateFlag pFlag)
{
this->edata = renderer.prepare(*shape, this->edata, transform, compList, static_cast<RenderUpdateFlag>(pFlag | flag));
this->edata = renderer.prepare(*shape, this->edata, transform, opacity, compList, static_cast<RenderUpdateFlag>(pFlag | flag));
flag = RenderUpdateFlag::None;
return this->edata;
}