From e9fb47847106fe9e284481bce11302639c7c32e2 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Tue, 17 Dec 2024 12:20:30 +0900 Subject: [PATCH] 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 --- inc/thorvg.h | 3 +- src/renderer/sw_engine/tvgSwCommon.h | 3 +- src/renderer/sw_engine/tvgSwPostEffect.cpp | 72 ++++++++++++++++++++++ src/renderer/sw_engine/tvgSwRenderer.cpp | 4 ++ src/renderer/tvgRender.h | 23 +++++++ src/renderer/tvgScene.h | 4 ++ 6 files changed, 107 insertions(+), 2 deletions(-) diff --git a/inc/thorvg.h b/inc/thorvg.h index ec1a6e0f..72c6fecb 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -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]} 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]} - 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]} }; diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h index b1cfcddd..2adf85af 100644 --- a/src/renderer/sw_engine/tvgSwCommon.h +++ b/src/renderer/sw_engine/tvgSwCommon.h @@ -581,6 +581,7 @@ bool effectFillPrepare(RenderEffectFill* effect); bool effectFill(SwCompositor* cmp, const RenderEffectFill* params, bool direct); bool effectTintPrepare(RenderEffectTint* effect); 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_ */ diff --git a/src/renderer/sw_engine/tvgSwPostEffect.cpp b/src/renderer/sw_engine/tvgSwPostEffect.cpp index eebf051f..630ff82b 100644 --- a/src/renderer/sw_engine/tvgSwPostEffect.cpp +++ b/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -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); + /* Tint Formula: (1 - L) * Black + L * White, where the L is Luminance. */ + 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); @@ -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; } \ No newline at end of file diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index c89be2c8..7680548f 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -656,6 +656,7 @@ bool SwRenderer::prepare(RenderEffect* effect) case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(effect)); case SceneEffect::Fill: return effectFillPrepare(static_cast(effect)); case SceneEffect::Tint: return effectTintPrepare(static_cast(effect)); + case SceneEffect::Tritone: return effectTritonePrepare(static_cast(effect)); default: return false; } } @@ -691,6 +692,9 @@ bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool case SceneEffect::Tint: { return effectTint(p, static_cast(effect), direct); } + case SceneEffect::Tritone: { + return effectTritone(p, static_cast(effect), direct); + } default: return false; } } diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 0ce3b9bc..e2b950cc 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -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 { private: diff --git a/src/renderer/tvgScene.h b/src/renderer/tvgScene.h index 3ee7b193..0a89652c 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -330,6 +330,10 @@ struct Scene::Impl : Paint::Impl re = RenderEffectTint::gen(args); break; } + case SceneEffect::Tritone: { + re = RenderEffectTritone::gen(args); + break; + } default: break; }