renderer: support Trintone SceneEffect

The Tritone effect maps the scene's shadows, midtones, and highlights
to three specific colors, allowing for more complex and artistic color grading.

Applied Tritone Formula:
if (L < 0.5) Result = (1 - 2L) * Shadow + 2L * Midtone
else Result = (1 - 2(L - 0.5)) * Midtone + (2(L - 0.5)) * Highlight
Where the L is Luminance.

issue: https://github.com/thorvg/thorvg/issues/2718
This commit is contained in:
Hermet Park 2024-12-17 12:20:30 +09:00
parent 1eff126b40
commit e9fb478471
6 changed files with 107 additions and 2 deletions

View file

@ -232,7 +232,8 @@ enum class SceneEffect : uint8_t
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]} 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]}
Tritone ///< Apply a tritone color effect to the scene using three color parameters for shadows, midtones, and highlights (Experimental API). Param(9) = {Shadow_R(int)[0 - 255], Shadow_G(int)[0 - 255], Shadow_B(int)[0 - 255], Midtone_R(int)[0 - 255], Midtone_G(int)[0 - 255], Midtone_B(int)[0 - 255], Highlight_R(int)[0 - 255], Highlight_G(int)[0 - 255], Highlight_B(int)[0 - 255]}
}; };

View file

@ -581,6 +581,7 @@ 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 effectTintPrepare(RenderEffectTint* effect);
bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct); bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct);
bool effectTritonePrepare(RenderEffectTritone* effect);
bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* params, bool direct);
#endif /* _TVG_SW_COMMON_H_ */ #endif /* _TVG_SW_COMMON_H_ */

View file

@ -475,6 +475,8 @@ bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct)
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); 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);
/* Tint Formula: (1 - L) * Black + L * White, where the L is Luminance. */
if (direct) { if (direct) {
auto dbuffer = cmp->recoverSfc->buf32 + (bbox.min.y * cmp->recoverSfc->stride + bbox.min.x); 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); auto sbuffer = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
@ -503,5 +505,75 @@ bool effectTint(SwCompositor* cmp, const RenderEffectTint* params, bool direct)
} }
} }
return true;
}
/************************************************************************/
/* Tritone Implementation */
/************************************************************************/
static uint32_t _trintone(uint32_t s, uint32_t m, uint32_t h, int l)
{
/* Tritone Formula:
if (L < 0.5) { (1 - 2L) * Shadow + 2L * Midtone }
else { (1 - 2(L - 0.5)) * Midtone + (2(L - 0.5)) * Highlight }
Where the L is Luminance. */
if (l < 128) {
auto a = std::min(l * 2, 255);
return ALPHA_BLEND(s, 255 - a) + ALPHA_BLEND(m, a);
} else {
auto a = 2 * std::max(0, l - 128);
return ALPHA_BLEND(m, 255 - a) + ALPHA_BLEND(h, a);
}
}
bool effectTritonePrepare(RenderEffectTritone* params)
{
params->valid = true;
return true;
}
bool effectTritone(SwCompositor* cmp, const RenderEffectTritone* 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 shadow = cmp->recoverSfc->join(params->shadow[0], params->shadow[1], params->shadow[2], 255);
auto midtone = cmp->recoverSfc->join(params->midtone[0], params->midtone[1], params->midtone[2], 255);
auto highlight = cmp->recoverSfc->join(params->highlight[0], params->highlight[1], params->highlight[2], 255);
auto opacity = cmp->opacity;
auto luma = cmp->recoverSfc->alphas[2]; //luma function
TVGLOG("SW_ENGINE", "Tritone region(%ld, %ld, %ld, %ld), param(%d %d %d, %d %d %d, %d %d %d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->shadow[0], params->shadow[1], params->shadow[2], params->midtone[0], params->midtone[1], params->midtone[2], params->highlight[0], params->highlight[1], params->highlight[2]);
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);
*dst = INTERPOLATE(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), *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);
*dst = ALPHA_BLEND(_trintone(shadow, midtone, highlight, luma((uint8_t*)&tmp)), A(tmp));
}
dbuffer += cmp->image.stride;
}
}
return true; return true;
} }

View file

@ -656,6 +656,7 @@ bool SwRenderer::prepare(RenderEffect* 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)); case SceneEffect::Tint: return effectTintPrepare(static_cast<RenderEffectTint*>(effect));
case SceneEffect::Tritone: return effectTritonePrepare(static_cast<RenderEffectTritone*>(effect));
default: return false; default: return false;
} }
} }
@ -691,6 +692,9 @@ bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool
case SceneEffect::Tint: { case SceneEffect::Tint: {
return effectTint(p, static_cast<const RenderEffectTint*>(effect), direct); return effectTint(p, static_cast<const RenderEffectTint*>(effect), direct);
} }
case SceneEffect::Tritone: {
return effectTritone(p, static_cast<const RenderEffectTritone*>(effect), direct);
}
default: return false; default: return false;
} }
} }

View file

@ -348,6 +348,29 @@ struct RenderEffectTint : RenderEffect
} }
}; };
struct RenderEffectTritone : RenderEffect
{
uint8_t shadow[3]; //rgb
uint8_t midtone[3]; //rgb
uint8_t highlight[3]; //rgb
static RenderEffectTritone* gen(va_list& args)
{
auto inst = new RenderEffectTritone;
inst->shadow[0] = va_arg(args, int);
inst->shadow[1] = va_arg(args, int);
inst->shadow[2] = va_arg(args, int);
inst->midtone[0] = va_arg(args, int);
inst->midtone[1] = va_arg(args, int);
inst->midtone[2] = va_arg(args, int);
inst->highlight[0] = va_arg(args, int);
inst->highlight[1] = va_arg(args, int);
inst->highlight[2] = va_arg(args, int);
inst->type = SceneEffect::Tritone;
return inst;
}
};
class RenderMethod class RenderMethod
{ {
private: private:

View file

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