common: support Tint SceneEffect

The Tint effect in ThorVG is used to modify the overall color tone of a scene.
It works by blending a specified tint color with the existing colors of the scene.
This effect is useful for color grading, mood changes, or applying thematic filters
to vector graphics and animations.

Applied the equation is:
Result = (1 - L) * Black + L * White, where the L is Luminance.

issue: https://github.com/thorvg/thorvg/issues/2718
This commit is contained in:
Hermet Park 2024-12-16 12:42:32 +09:00
parent 1806b32971
commit b778f98206
7 changed files with 107 additions and 17 deletions

View file

@ -231,7 +231,8 @@ enum class SceneEffect : uint8_t
ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state. ClearAll = 0, ///< Reset all previously applied scene effects, restoring the scene to its original state.
GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]} GaussianBlur, ///< Apply a blur effect with a Gaussian filter. Param(3) = {sigma(float)[> 0], direction(int)[both: 0 / horizontal: 1 / vertical: 2], border(int)[duplicate: 0 / wrap: 1], quality(int)[0 - 100]}
DropShadow, ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]} DropShadow, ///< Apply a drop shadow effect with a Gaussian Blur filter. Param(8) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255], angle(float)[0 - 360], distance(float), blur_sigma(float)[> 0], quality(int)[0 - 100]}
Fill ///< Override the scene content color with a given fill information (Experimental API). Param(5) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255]} Fill, ///< Override the scene content color with a given fill information (Experimental API). Param(5) = {color_R(int)[0 - 255], color_G(int)[0 - 255], color_B(int)[0 - 255], opacity(int)[0 - 255]}
Tint ///< Tinting the current scene color with a given black, white color paramters (Experimental API). Param(7) = {black_R(int)[0 - 255], black_G(int)[0 - 255], black_B(int)[0 - 255], white_R(int)[0 - 255], white_G(int)[0 - 255], white_B(int)[0 - 255], intensity(float)[0 - 100]}
}; };

View file

@ -571,6 +571,7 @@ void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32
void rasterUnpremultiply(RenderSurface* surface); void rasterUnpremultiply(RenderSurface* surface);
void rasterPremultiply(RenderSurface* surface); void rasterPremultiply(RenderSurface* surface);
bool rasterConvertCS(RenderSurface* surface, ColorSpace to); bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
uint32_t rasterUnpremultiply(uint32_t data);
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params); bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params);
bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect); bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect);
@ -578,5 +579,8 @@ bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEff
bool effectDropShadowPrepare(RenderEffectDropShadow* effect); bool effectDropShadowPrepare(RenderEffectDropShadow* effect);
bool effectFillPrepare(RenderEffectFill* effect); bool effectFillPrepare(RenderEffectFill* effect);
bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct); bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct);
bool effectTintPrepare(RenderEffectTint* effect);
bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct);
#endif /* _TVG_SW_COMMON_H_ */ #endif /* _TVG_SW_COMMON_H_ */

View file

@ -450,3 +450,58 @@ bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct)
} }
return true; return true;
} }
/************************************************************************/
/* Tint Implementation */
/************************************************************************/
bool effectTintPrepare(RenderEffectTint* params)
{
params->valid = true;
return true;
}
bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct)
{
auto& bbox = cmp->bbox;
auto w = size_t(bbox.max.x - bbox.min.x);
auto h = size_t(bbox.max.y - bbox.min.y);
auto black = cmp->recoverSfc->join(params->black[0], params->black[1], params->black[2], 255);
auto white = cmp->recoverSfc->join(params->white[0], params->white[1], params->white[2], 255);
auto opacity = cmp->opacity;
auto luma = cmp->recoverSfc->alphas[2]; //luma function
TVGLOG("SW_ENGINE", "Tint region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %f)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->black[0], params->black[1], params->black[2], params->white[0], params->white[1], params->white[2], params->intensity);
if (direct) {
auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x);
auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
for (size_t y = 0; y < h; ++y) {
auto dst = dbuffer;
auto src = sbuffer;
for (size_t x = 0; x < w; ++x, ++dst, ++src) {
auto tmp = rasterUnpremultiply(*src);
auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity);
*dst = INTERPOLATE(val, *dst, MULTIPLY(opacity, A(tmp)));
}
dbuffer += cmp->image.stride;
sbuffer += cmp->recoverSfc->stride;
}
cmp->valid = true; //no need the subsequent composition
} else {
auto dbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
for (size_t y = 0; y < h; ++y) {
auto dst = dbuffer;
for (size_t x = 0; x < w; ++x, ++dst) {
auto tmp = rasterUnpremultiply(*dst);
auto val = INTERPOLATE(INTERPOLATE(black, white, luma((uint8_t*)&tmp)), tmp, params->intensity);
*dst = ALPHA_BLEND(val, A(tmp));
}
dbuffer += cmp->image.stride;
}
}
return true;
}

View file

@ -1668,6 +1668,20 @@ bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_
} }
uint32_t rasterUnpremultiply(uint32_t data)
{
uint8_t a = data >> 24;
if (a == 255 || a == 0) return data;
uint16_t r = ((data >> 8) & 0xff00) / a;
uint16_t g = ((data) & 0xff00) / a;
uint16_t b = ((data << 8) & 0xff00) / a;
if (r > 0xff) r = 0xff;
if (g > 0xff) g = 0xff;
if (b > 0xff) b = 0xff;
return (a << 24) | (r << 16) | (g << 8) | (b);
}
void rasterUnpremultiply(RenderSurface* surface) void rasterUnpremultiply(RenderSurface* surface)
{ {
if (surface->channelSize != sizeof(uint32_t)) return; if (surface->channelSize != sizeof(uint32_t)) return;
@ -1678,20 +1692,7 @@ void rasterUnpremultiply(RenderSurface* surface)
for (uint32_t y = 0; y < surface->h; y++) { for (uint32_t y = 0; y < surface->h; y++) {
auto buffer = surface->buf32 + surface->stride * y; auto buffer = surface->buf32 + surface->stride * y;
for (uint32_t x = 0; x < surface->w; ++x) { for (uint32_t x = 0; x < surface->w; ++x) {
uint8_t a = buffer[x] >> 24; buffer[x] = rasterUnpremultiply(buffer[x]);
if (a == 255) {
continue;
} else if (a == 0) {
buffer[x] = 0x00ffffff;
} else {
uint16_t r = ((buffer[x] >> 8) & 0xff00) / a;
uint16_t g = ((buffer[x]) & 0xff00) / a;
uint16_t b = ((buffer[x] << 8) & 0xff00) / a;
if (r > 0xff) r = 0xff;
if (g > 0xff) g = 0xff;
if (b > 0xff) b = 0xff;
buffer[x] = (a << 24) | (r << 16) | (g << 8) | (b);
}
} }
} }
surface->premultiplied = false; surface->premultiplied = false;

View file

@ -655,6 +655,7 @@ bool SwRenderer::prepare(RenderEffect* effect)
case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast<RenderEffectGaussianBlur*>(effect)); case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast<RenderEffectGaussianBlur*>(effect));
case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast<RenderEffectDropShadow*>(effect)); case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast<RenderEffectDropShadow*>(effect));
case SceneEffect::Fill: return effectFillPrepare(static_cast<RenderEffectFill*>(effect)); case SceneEffect::Fill: return effectFillPrepare(static_cast<RenderEffectFill*>(effect));
case SceneEffect::Tint: return effectTintPrepare(static_cast<RenderEffectTint*>(effect));
default: return false; default: return false;
} }
} }
@ -687,6 +688,9 @@ bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool
case SceneEffect::Fill: { case SceneEffect::Fill: {
return effectFill(p, static_cast<const RenderEffectFill*>(effect), direct); return effectFill(p, static_cast<const RenderEffectFill*>(effect), direct);
} }
case SceneEffect::Tint: {
return effectTint(p, static_cast<const RenderEffectTint*>(effect), direct);
}
default: return false; default: return false;
} }
} }

View file

@ -301,7 +301,7 @@ struct RenderEffectDropShadow : RenderEffect
inst->color[0] = va_arg(args, int); inst->color[0] = va_arg(args, int);
inst->color[1] = va_arg(args, int); inst->color[1] = va_arg(args, int);
inst->color[2] = va_arg(args, int); inst->color[2] = va_arg(args, int);
inst->color[3] = std::min(va_arg(args, int), 255); inst->color[3] = va_arg(args, int);
inst->angle = (float) va_arg(args, double); inst->angle = (float) va_arg(args, double);
inst->distance = (float) va_arg(args, double); inst->distance = (float) va_arg(args, double);
inst->sigma = std::max((float) va_arg(args, double), 0.0f); inst->sigma = std::max((float) va_arg(args, double), 0.0f);
@ -321,12 +321,33 @@ struct RenderEffectFill : RenderEffect
inst->color[0] = va_arg(args, int); inst->color[0] = va_arg(args, int);
inst->color[1] = va_arg(args, int); inst->color[1] = va_arg(args, int);
inst->color[2] = va_arg(args, int); inst->color[2] = va_arg(args, int);
inst->color[3] = std::min(va_arg(args, int), 255); inst->color[3] = va_arg(args, int);
inst->type = SceneEffect::Fill; inst->type = SceneEffect::Fill;
return inst; return inst;
} }
}; };
struct RenderEffectTint : RenderEffect
{
uint8_t black[3]; //rgb
uint8_t white[3]; //rgb
float intensity; //0 - 100
static RenderEffectTint* gen(va_list& args)
{
auto inst = new RenderEffectTint;
inst->black[0] = va_arg(args, int);
inst->black[1] = va_arg(args, int);
inst->black[2] = va_arg(args, int);
inst->white[0] = va_arg(args, int);
inst->white[1] = va_arg(args, int);
inst->white[2] = va_arg(args, int);
inst->intensity = std::min((float)va_arg(args, double), 100.0f) * 2.55f;
inst->type = SceneEffect::Tint;
return inst;
}
};
class RenderMethod class RenderMethod
{ {
private: private:

View file

@ -326,6 +326,10 @@ struct Scene::Impl : Paint::Impl
re = RenderEffectFill::gen(args); re = RenderEffectFill::gen(args);
break; break;
} }
case SceneEffect::Tint: {
re = RenderEffectTint::gen(args);
break;
}
default: break; default: break;
} }