diff --git a/inc/thorvg.h b/inc/thorvg.h index 28bd7602..1ee898ca 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -216,7 +216,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]} }; diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index f7ec6ee3..20984975 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -1079,7 +1079,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; diff --git a/src/renderer/gl_engine/tvgGlRenderer.h b/src/renderer/gl_engine/tvgGlRenderer.h index e676c565..8c8368ff 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.h +++ b/src/renderer/gl_engine/tvgGlRenderer.h @@ -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); diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h index 74b38330..b6973076 100644 --- a/src/renderer/sw_engine/tvgSwCommon.h +++ b/src/renderer/sw_engine/tvgSwCommon.h @@ -568,13 +568,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_ */ diff --git a/src/renderer/sw_engine/tvgSwPostEffect.cpp b/src/renderer/sw_engine/tvgSwPostEffect.cpp index 4aea7d5c..46b021d9 100644 --- a/src/renderer/sw_engine/tvgSwPostEffect.cpp +++ b/src/renderer/sw_engine/tvgSwPostEffect.cpp @@ -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 blur’s 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(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 blur’s 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(back), reinterpret_cast(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(front), reinterpret_cast(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(back), reinterpret_cast(front), stride, h, w, bbox, data->kernel[i], params->border, true); std::swap(front, back); swapped = !swapped; } - rasterXYFlip(reinterpret_cast(front), reinterpret_cast(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(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(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; } \ No newline at end of file diff --git a/src/renderer/sw_engine/tvgSwRaster.cpp b/src/renderer/sw_engine/tvgSwRaster.cpp index fd1238be..18ffc18e 100644 --- a/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/src/renderer/sw_engine/tvgSwRaster.cpp @@ -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) { diff --git a/src/renderer/sw_engine/tvgSwRasterC.h b/src/renderer/sw_engine/tvgSwRasterC.h index 258edf84..8bea1b13 100644 --- a/src/renderer/sw_engine/tvgSwRasterC.h +++ b/src/renderer/sw_engine/tvgSwRasterC.h @@ -20,6 +20,38 @@ * SOFTWARE. */ + +template +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 +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 static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len) { diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index d4db78fb..0cdadb2a 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -649,19 +649,32 @@ bool SwRenderer::endComposite(RenderCompositor* cmp) bool SwRenderer::prepare(RenderEffect* effect) { switch (effect->type) { - case SceneEffect::GaussianBlur: return effectGaussianPrepare(static_cast(effect)); + case SceneEffect::GaussianBlur: return effectGaussianBlurPrepare(static_cast(effect)); + case SceneEffect::DropShadow: return effectDropShadowPrepare(static_cast(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(cmp); - auto& buffer = request(surface->channelSize)->compositor->image; switch (effect->type) { - case SceneEffect::GaussianBlur: return effectGaussianBlur(p->image, buffer, p->bbox, static_cast(effect)); + case SceneEffect::GaussianBlur: { + return effectGaussianBlur(p, request(surface->channelSize), static_cast(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(effect), direct); + cmp1->compositor->valid = true; + return ret; + } default: return false; } } diff --git a/src/renderer/sw_engine/tvgSwRenderer.h b/src/renderer/sw_engine/tvgSwRenderer.h index a5d63028..f3f7088d 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.h +++ b/src/renderer/sw_engine/tvgSwRenderer.h @@ -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); diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 030e7822..003c7d3b 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -276,20 +276,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); @@ -298,6 +295,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: @@ -331,7 +352,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(CompositeMethod method) diff --git a/src/renderer/tvgScene.cpp b/src/renderer/tvgScene.cpp index 38a4f058..ce169d33 100644 --- a/src/renderer/tvgScene.cpp +++ b/src/renderer/tvgScene.cpp @@ -121,7 +121,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; diff --git a/src/renderer/tvgScene.h b/src/renderer/tvgScene.h index cb2ef7f6..47d0f5b6 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -143,8 +143,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); diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index a418b285..867f652a 100755 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -431,7 +431,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; diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index 043f5df2..3b948144 100755 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -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);