renderer: support SceneEffect DropShadow

Apply a drop shadow effect with a Gaussian Blur filter.

API Addition:
 - enum class SceneEffect::DropShadow

Parameters:
 - 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]

issue: https://github.com/thorvg/thorvg/issues/2718
This commit is contained in:
Hermet Park 2024-10-23 18:41:31 +09:00 committed by Hermet Park
parent 93ebd388c7
commit e0365142a7
14 changed files with 376 additions and 100 deletions

View file

@ -226,7 +226,8 @@ enum class BlendMethod : uint8_t
enum class SceneEffect : uint8_t
{
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]}
};

View file

@ -978,7 +978,7 @@ bool GlRenderer::prepare(TVG_UNUSED RenderEffect* effect)
}
bool GlRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect)
bool GlRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect, TVG_UNUSED bool direct)
{
TVGLOG("GL_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
return false;

View file

@ -87,7 +87,7 @@ public:
bool endComposite(RenderCompositor* cmp) override;
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
static GlRenderer* gen();
static int init(TVG_UNUSED uint32_t threads);

View file

@ -564,13 +564,17 @@ bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint
bool rasterGradientStroke(SwSurface* surface, SwShape* shape, const Fill* fdata, uint8_t opacity);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h, pixel_t val = 0);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterXYFlip(uint32_t* src, uint32_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, bool flipped);
void rasterUnpremultiply(RenderSurface* surface);
void rasterPremultiply(RenderSurface* surface);
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params);
bool effectGaussianPrepare(RenderEffectGaussian* effect);
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, bool direct);
bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* effect);
bool effectDropShadow(SwCompositor* cmp, SwSurface* surfaces[2], const RenderEffectDropShadow* params, bool direct);
bool effectDropShadowPrepare(RenderEffectDropShadow* effect);
#endif /* _TVG_SW_COMMON_H_ */

View file

@ -20,10 +20,11 @@
* SOFTWARE.
*/
#include "tvgMath.h"
#include "tvgSwCommon.h"
/************************************************************************/
/* Gaussian Filter Implementation */
/* Gaussian Blur Implementation */
/************************************************************************/
struct SwGaussianBlur
@ -48,49 +49,59 @@ static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direct
}
static int _gaussianRemap(int end, int idx, int border)
static int _gaussianEdgeWrap(int end, int idx)
{
//wrap
if (border == 1) return idx % end;
auto r = idx % end;
return (r < 0) ? end + r : r;
}
//duplicate
static int _gaussianEdgeExtend(int end, int idx)
{
if (idx < 0) return 0;
else if (idx >= end) return end - 1;
return idx;
}
static int _gaussianRemap(int end, int idx, int border)
{
if (border == 1) return _gaussianEdgeWrap(end, idx);
return _gaussianEdgeExtend(end, idx);
}
//TODO: SIMD OPTIMIZATION?
static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped)
static void _gaussianFilter(uint8_t* dst, uint8_t* src, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped)
{
if (flipped) {
src += ((bbox.min.x * stride) + bbox.min.y) << 2;
dst += ((bbox.min.x * stride) + bbox.min.y) << 2;
src += (bbox.min.x * stride + bbox.min.y) << 2;
dst += (bbox.min.x * stride + bbox.min.y) << 2;
} else {
src += ((bbox.min.y * stride) + bbox.min.x) << 2;
dst += ((bbox.min.y * stride) + bbox.min.x) << 2;
src += (bbox.min.y * stride + bbox.min.x) << 2;
dst += (bbox.min.y * stride + bbox.min.x) << 2;
}
auto iarr = 1.0f / (dimension + dimension + 1);
#pragma omp parallel for
for (int x = 0; x < h; x++) {
auto p = x * stride;
for (int y = 0; y < h; ++y) {
auto p = y * stride;
auto i = p * 4; //current index
auto l = -(dimension + 1); //left index
auto r = dimension; //right index
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
//initial acucmulation
for (int x2 = l; x2 < r; ++x2) {
auto id = (_gaussianRemap(w, x2, border) + p) * 4;
//initial accumulation
for (int x = l; x < r; ++x) {
auto id = (_gaussianRemap(w, x, border) + p) * 4;
acc[0] += src[id++];
acc[1] += src[id++];
acc[2] += src[id++];
acc[3] += src[id];
}
//perform filtering
for (int x2 = 0; x2 < w; ++x2, ++r, ++l) {
for (int x = 0; x < w; ++x, ++r, ++l) {
auto rid = (_gaussianRemap(w, r, border) + p) * 4;
auto lid = (_gaussianRemap(w, l, border) + p) * 4;
acc[0] += src[rid++] - src[lid++];
@ -106,11 +117,15 @@ static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w,
}
static int _gaussianInit(int* kernel, float sigma, int level)
static int _gaussianInit(SwGaussianBlur* data, float sigma, int quality)
{
const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL;
//compute the kernel
if (tvg::zero(sigma)) return 0;
data->level = int(SwGaussianBlur::MAX_LEVEL * ((quality - 1) * 0.01f)) + 1;
//compute box kernel sizes
auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1);
if (wl % 2 == 0) --wl;
auto wu = wl + 2;
@ -118,92 +133,286 @@ static int _gaussianInit(int* kernel, float sigma, int level)
auto m = int(mi + 0.5f);
auto extends = 0;
for (int i = 0; i < level; i++) {
kernel[i] = ((i < m ? wl : wu) - 1) / 2;
extends += kernel[i];
for (int i = 0; i < data->level; i++) {
data->kernel[i] = ((i < m ? wl : wu) - 1) / 2;
extends += data->kernel[i];
}
return extends;
}
bool effectGaussianPrepare(RenderEffectGaussian* params)
bool effectGaussianBlurPrepare(RenderEffectGaussianBlur* params)
{
auto data = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
auto rd = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
//compute box kernel sizes
data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1;
auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level);
auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality);
//skip, if the parameters are invalid.
//invalid
if (extends == 0) {
params->invalid = true;
free(data);
free(rd);
return false;
}
_gaussianExtendRegion(params->extend, extends, params->direction);
params->rd = data;
params->rd = rd;
return true;
}
/* It is best to take advantage of the Gaussian blurs separable property
by dividing the process into two passes. horizontal and vertical.
We can expect fewer calculations. */
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params)
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, TVG_UNUSED bool direct)
{
if (params->invalid) return false;
if (image.channelSize != sizeof(uint32_t)) {
if (cmp->image.channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
return false;
}
auto& buffer = surface->compositor->image;
auto data = static_cast<SwGaussianBlur*>(params->rd);
auto& bbox = cmp->bbox;
auto w = (bbox.max.x - bbox.min.x);
auto h = (bbox.max.y - bbox.min.y);
auto stride = image.stride;
auto front = image.buf8;
auto back = buffer.buf8;
auto stride = cmp->image.stride;
auto front = cmp->image.buf32;
auto back = buffer.buf32;
auto swapped = false;
//fine-tuning for low-quality (experimental)
auto threshold = (std::min(w, h) < 300) ? 2 : 1;
TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level);
/* It is best to take advantage of the Gaussian blurs separable property
by dividing the process into two passes. horizontal and vertical.
We can expect fewer calculations. */
//horizontal
if (params->direction == 0 || params->direction == 1) {
if (params->direction != 2) {
for (int i = 0; i < data->level; ++i) {
auto k = data->kernel[i] / threshold;
if (k == 0) continue;
_gaussianBlur(front, back, stride, w, h, bbox, k, params->border, false);
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, w, h, bbox, data->kernel[i], params->border, false);
std::swap(front, back);
swapped = !swapped;
}
}
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
if (params->direction == 0 || params->direction == 2) {
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, w, h, bbox, false);
if (params->direction != 1) {
rasterXYFlip(front, back, stride, w, h, bbox, false);
std::swap(front, back);
for (int i = 0; i < data->level; ++i) {
auto k = data->kernel[i] / threshold;
if (k == 0) continue;
_gaussianBlur(front, back, stride, h, w, bbox, k, params->border, true);
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, h, w, bbox, data->kernel[i], params->border, true);
std::swap(front, back);
swapped = !swapped;
}
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, h, w, bbox, true);
rasterXYFlip(front, back, stride, h, w, bbox, true);
std::swap(front, back);
}
if (swapped) std::swap(image.buf8, buffer.buf8);
if (swapped) std::swap(cmp->image.buf8, buffer.buf8);
return true;
}
/************************************************************************/
/* Drop Shadow Implementation */
/************************************************************************/
struct SwDropShadow : SwGaussianBlur
{
SwPoint offset;
};
//TODO: SIMD OPTIMIZATION?
static void _dropShadowFilter(uint32_t* dst, uint32_t* src, int stride, int w, int h, const SwBBox& bbox, int32_t dimension, uint32_t color, bool flipped)
{
if (flipped) {
src += (bbox.min.x * stride + bbox.min.y);
dst += (bbox.min.x * stride + bbox.min.y);
} else {
src += (bbox.min.y * stride + bbox.min.x);
dst += (bbox.min.y * stride + bbox.min.x);
}
auto iarr = 1.0f / (dimension + dimension + 1);
//#pragma omp parallel for
for (int y = 0; y < h; ++y) {
auto p = y * stride;
auto i = p; //current index
auto l = -(dimension + 1); //left index
auto r = dimension; //right index
int acc = 0; //sliding accumulator
//initial accumulation
for (int x = l; x < r; ++x) {
auto id = _gaussianEdgeExtend(w, x) + p;
acc += A(src[id]);
}
//perform filtering
for (int x = 0; x < w; ++x, ++r, ++l) {
auto rid = _gaussianEdgeExtend(w, r) + p;
auto lid = _gaussianEdgeExtend(w, l) + p;
acc += A(src[rid]) - A(src[lid]);
dst[i++] = ALPHA_BLEND(color, static_cast<uint8_t>(acc * iarr + 0.5f));
}
}
}
static void _dropShadowShift(uint32_t* dst, uint32_t* src, int stride, SwBBox& region, SwPoint& offset, uint8_t opacity, bool direct)
{
src += (region.min.y * stride + region.min.x);
dst += (region.min.y * stride + region.min.x);
auto w = region.max.x - region.min.x;
auto h = region.max.y - region.min.y;
auto translucent = (direct || opacity < 255);
//shift offset
if (region.min.x + offset.x < 0) {
src -= offset.x;
} else {
dst += offset.x;
w -= offset.x;
}
if (region.min.y + offset.y < 0) {
src -= (offset.y * stride);
} else {
dst += (offset.y * stride);
h -= offset.y;
}
for (auto y = 0; y < h; ++y) {
if (translucent) rasterTranslucentPixel32(dst, src, w, opacity);
else rasterPixel32(dst, src, w, opacity);
src += stride;
dst += stride;
}
}
static void _dropShadowExtendRegion(RenderRegion& region, int extra, SwPoint& offset)
{
//bbox region expansion for feathering
region.x = -extra;
region.w = extra * 2;
region.y = -extra;
region.h = extra * 2;
region.x = std::min(region.x + (int32_t)offset.x, region.x);
region.y = std::min(region.y + (int32_t)offset.y, region.y);
region.w += abs(offset.x);
region.h += abs(offset.y);
}
bool effectDropShadowPrepare(RenderEffectDropShadow* params)
{
auto rd = (SwDropShadow*)malloc(sizeof(SwDropShadow));
//compute box kernel sizes
auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality);
//invalid
if (extends == 0 || params->color[3] == 0) {
params->invalid = true;
free(rd);
return false;
}
//offset
if (params->distance > 0.0f) {
auto radian = tvg::deg2rad(90.0f - params->angle);
rd->offset = {(SwCoord)(params->distance * cosf(radian)), (SwCoord)(-1.0f * params->distance * sinf(radian))};
} else {
rd->offset = {0, 0};
}
//bbox region expansion for feathering
_dropShadowExtendRegion(params->extend, extends, rd->offset);
params->rd = rd;
return true;
}
//A quite same integration with effectGaussianBlur(). See it for detailed comments.
//surface[0]: the original image, to overlay it into the filtered image.
//surface[1]: temporary buffer for generating the filtered image.
bool effectDropShadow(SwCompositor* cmp, SwSurface* surface[2], const RenderEffectDropShadow* params, bool direct)
{
if (cmp->image.channelSize != sizeof(uint32_t)) {
TVGERR("SW_ENGINE", "Not supported grayscale Drop Shadow!");
return false;
}
//FIXME: if the body is partially visible due to clipping, the shadow also becomes partially visible.
auto data = static_cast<SwDropShadow*>(params->rd);
auto& bbox = cmp->bbox;
auto w = (bbox.max.x - bbox.min.x);
auto h = (bbox.max.y - bbox.min.y);
//outside the screen
if (abs(data->offset.x) >= w || abs(data->offset.y) >= h) return true;
SwImage* buffer[] = {&surface[0]->compositor->image, &surface[1]->compositor->image};
auto color = cmp->recoverSfc->join(params->color[0], params->color[1], params->color[2], 255);
auto stride = cmp->image.stride;
auto front = cmp->image.buf32;
auto back = buffer[1]->buf32;
auto opacity = params->color[3];
TVGLOG("SW_ENGINE", "DropShadow region(%ld, %ld, %ld, %ld) params(%f %f %f), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->angle, params->distance, params->sigma, data->level);
//saving the original image in order to overlay it into the filtered image.
_dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[0], color, false);
std::swap(front, buffer[0]->buf32);
std::swap(front, back);
//horizontal
for (int i = 1; i < data->level; ++i) {
_dropShadowFilter(back, front, stride, w, h, bbox, data->kernel[i], color, false);
std::swap(front, back);
}
//vertical
rasterXYFlip(front, back, stride, w, h, bbox, false);
std::swap(front, back);
for (int i = 0; i < data->level; ++i) {
_dropShadowFilter(back, front, stride, h, w, bbox, data->kernel[i], color, true);
std::swap(front, back);
}
rasterXYFlip(front, back, stride, h, w, bbox, true);
std::swap(cmp->image.buf32, back);
//draw to the main surface directly
if (direct) {
_dropShadowShift(cmp->recoverSfc->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct);
std::swap(cmp->image.buf32, buffer[0]->buf32);
return true;
}
//draw to the intermediate surface
rasterClear(surface[1], bbox.min.x, bbox.min.y, w, h);
_dropShadowShift(buffer[1]->buf32, cmp->image.buf32, stride, bbox, data->offset, opacity, direct);
std::swap(cmp->image.buf32, buffer[1]->buf32);
//compositing shadow and body
auto s = buffer[0]->buf32 + (bbox.min.y * buffer[0]->stride + bbox.min.x);
auto d = cmp->image.buf32 + (bbox.min.y * cmp->image.stride + bbox.min.x);
for (auto y = 0; y < h; ++y) {
rasterTranslucentPixel32(d, s, w, 255);
s += buffer[0]->stride;
d += cmp->image.stride;
}
return true;
}

View file

@ -869,16 +869,7 @@ static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox);
auto alpha = MULTIPLY(span->coverage, opacity);
if (alpha == 255) {
for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) {
*dst = *img + ALPHA_BLEND(*dst, IA(*img));
}
} else {
for (uint32_t x = 0; x < span->len; ++x, ++dst, ++img) {
auto src = ALPHA_BLEND(*img, alpha);
*dst = src + ALPHA_BLEND(*dst, IA(src));
}
}
rasterTranslucentPixel32(dst, img, span->len, alpha);
}
return true;
}
@ -1144,27 +1135,14 @@ static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const S
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x];
for (auto y = region.min.y; y < region.max.y; ++y) {
auto dst = dbuffer;
auto src = sbuffer;
if (opacity == 255) {
for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) {
*dst = *src + ALPHA_BLEND(*dst, IA(*src));
}
} else {
for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) {
auto tmp = ALPHA_BLEND(*src, opacity);
*dst = tmp + ALPHA_BLEND(*dst, IA(tmp));
}
}
rasterTranslucentPixel32(dbuffer, sbuffer, region.max.x - region.min.x, opacity);
dbuffer += surface->stride;
sbuffer += image->stride;
}
//8bits grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x];
for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride, sbuffer += image->stride) {
auto dst = dbuffer;
auto src = sbuffer;
@ -1598,6 +1576,19 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRle* rle, const
/* External Class Implementation */
/************************************************************************/
void rasterTranslucentPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity)
{
//TODO: Support SIMD accelerations
cRasterTranslucentPixels(dst, src, len, opacity);
}
void rasterPixel32(uint32_t* dst, uint32_t* src, uint32_t len, uint8_t opacity)
{
//TODO: Support SIMD accelerations
cRasterPixels(dst, src, len, opacity);
}
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len)
{
@ -1625,7 +1616,7 @@ void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
bool rasterCompositor(SwSurface* surface)
{
//See MaskMethod, Alpha:3, InvAlpha:4, Luma:5, InvLuma:6
//See MaskMethod, Alpha:1, InvAlpha:2, Luma:3, InvLuma:4
surface->alphas[0] = _alpha;
surface->alphas[1] = _ialpha;

View file

@ -20,6 +20,38 @@
* SOFTWARE.
*/
template<typename PIXEL_T>
static void inline cRasterTranslucentPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity)
{
//TODO: 64bits faster?
if (opacity == 255) {
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
*dst = *src + ALPHA_BLEND(*dst, IA(*src));
}
} else {
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
auto tmp = ALPHA_BLEND(*src, opacity);
*dst = tmp + ALPHA_BLEND(*dst, IA(tmp));
}
}
}
template<typename PIXEL_T>
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T* src, uint32_t len, uint32_t opacity)
{
//TODO: 64bits faster?
if (opacity == 255) {
for (uint32_t x = 0; x < len; ++x, ++dst, ++src) {
*dst = *src;
}
} else {
cRasterTranslucentPixels(dst, src, len, opacity);
}
}
template<typename PIXEL_T>
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len)
{

View file

@ -642,19 +642,32 @@ bool SwRenderer::endComposite(RenderCompositor* cmp)
bool SwRenderer::prepare(RenderEffect* effect)
{
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast<RenderEffectGaussian*>(effect));
case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast<RenderEffectGaussianBlur*>(effect));
case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast<RenderEffectDropShadow*>(effect));
default: return false;
}
}
bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect)
bool SwRenderer::effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct)
{
if (effect->invalid) return false;
auto p = static_cast<SwCompositor*>(cmp);
auto& buffer = request(surface->channelSize)->compositor->image;
switch (effect->type) {
case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast<const RenderEffectGaussian*>(effect));
case SceneEffect::GaussianBlur: {
return effectGaussianBlur(p, request(surface->channelSize), static_cast<const RenderEffectGaussianBlur*>(effect), direct);
}
case SceneEffect::DropShadow: {
auto cmp1 = request(surface->channelSize);
cmp1->compositor->valid = false;
auto cmp2 = request(surface->channelSize);
SwSurface* surfaces[] = {cmp1, cmp2};
auto ret = effectDropShadow(p, surfaces, static_cast<const RenderEffectDropShadow*>(effect), direct);
cmp1->compositor->valid = true;
return ret;
}
default: return false;
}
}

View file

@ -61,7 +61,7 @@ public:
void clearCompositors();
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
static SwRenderer* gen();
static bool init(uint32_t threads);

View file

@ -263,20 +263,17 @@ struct RenderEffect
}
};
struct RenderEffectGaussian : RenderEffect
struct RenderEffectGaussianBlur : RenderEffect
{
float sigma;
uint8_t direction; //0: both, 1: horizontal, 2: vertical
uint8_t border; //0: duplicate, 1: wrap
uint8_t quality; //0 ~ 100 (optional)
static RenderEffectGaussian* gen(va_list& args)
static RenderEffectGaussianBlur* gen(va_list& args)
{
auto sigma = (float) va_arg(args, double);
if (sigma <= 0) return nullptr;
auto inst = new RenderEffectGaussian;
inst->sigma = sigma;
auto inst = new RenderEffectGaussianBlur;
inst->sigma = std::max((float) va_arg(args, double), 0.0f);
inst->direction = std::min(va_arg(args, int), 2);
inst->border = std::min(va_arg(args, int), 1);
inst->quality = std::min(va_arg(args, int), 100);
@ -285,6 +282,30 @@ struct RenderEffectGaussian : RenderEffect
}
};
struct RenderEffectDropShadow : RenderEffect
{
uint8_t color[4]; //rgba
float angle;
float distance;
float sigma;
uint8_t quality; //0 ~ 100 (optional)
static RenderEffectDropShadow* gen(va_list& args)
{
auto inst = new RenderEffectDropShadow;
inst->color[0] = va_arg(args, int);
inst->color[1] = va_arg(args, int);
inst->color[2] = va_arg(args, int);
inst->color[3] = va_arg(args, int);
inst->angle = (float) va_arg(args, double);
inst->distance = (float) va_arg(args, double);
inst->sigma = std::max((float) va_arg(args, double), 0.0f);
inst->quality = std::min(va_arg(args, int), 100);
inst->type = SceneEffect::DropShadow;
return inst;
}
};
class RenderMethod
{
private:
@ -318,7 +339,7 @@ public:
virtual bool endComposite(RenderCompositor* cmp) = 0;
virtual bool prepare(RenderEffect* effect) = 0;
virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect) = 0;
virtual bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0;
};
static inline bool MASK_REGION_MERGING(MaskMethod method)

View file

@ -105,7 +105,11 @@ Result Scene::push(SceneEffect effect, ...) noexcept
switch (effect) {
case SceneEffect::GaussianBlur: {
re = RenderEffectGaussian::gen(args);
re = RenderEffectGaussianBlur::gen(args);
break;
}
case SceneEffect::DropShadow: {
re = RenderEffectDropShadow::gen(args);
break;
}
default: break;

View file

@ -140,8 +140,9 @@ struct Scene::Impl
if (cmp) {
//Apply post effects if any.
if (effects) {
auto direct = effects->count == 1 ? true : false;
for (auto e = effects->begin(); e < effects->end(); ++e) {
renderer->effect(cmp, *e);
renderer->effect(cmp, *e, direct);
}
}
renderer->endComposite(cmp);

View file

@ -422,7 +422,7 @@ bool WgRenderer::prepare(TVG_UNUSED RenderEffect* effect)
}
bool WgRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect)
bool WgRenderer::effect(TVG_UNUSED RenderCompositor* cmp, TVG_UNUSED const RenderEffect* effect, TVG_UNUSED bool direct)
{
TVGLOG("WG_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
return false;

View file

@ -53,7 +53,7 @@ public:
bool endComposite(RenderCompositor* cmp) override;
bool prepare(RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect) override;
bool effect(RenderCompositor* cmp, const RenderEffect* effect, bool direct) override;
static WgRenderer* gen();
static bool init(uint32_t threads);