mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
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:
parent
8daf0b7b94
commit
2324f6e75d
14 changed files with 375 additions and 99 deletions
|
@ -216,7 +216,8 @@ enum class BlendMethod : uint8_t
|
||||||
enum class SceneEffect : uint8_t
|
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]}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
TVGLOG("GL_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -87,7 +87,7 @@ public:
|
||||||
bool endComposite(RenderCompositor* cmp) override;
|
bool endComposite(RenderCompositor* cmp) override;
|
||||||
|
|
||||||
bool prepare(RenderEffect* effect) 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 GlRenderer* gen();
|
||||||
static int init(TVG_UNUSED uint32_t threads);
|
static int init(TVG_UNUSED uint32_t threads);
|
||||||
|
|
|
@ -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 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);
|
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 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 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 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 rasterUnpremultiply(RenderSurface* surface);
|
||||||
void rasterPremultiply(RenderSurface* surface);
|
void rasterPremultiply(RenderSurface* surface);
|
||||||
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
|
bool rasterConvertCS(RenderSurface* surface, ColorSpace to);
|
||||||
|
|
||||||
bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params);
|
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, bool direct);
|
||||||
bool effectGaussianPrepare(RenderEffectGaussian* effect);
|
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_ */
|
#endif /* _TVG_SW_COMMON_H_ */
|
||||||
|
|
|
@ -20,10 +20,11 @@
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "tvgMath.h"
|
||||||
#include "tvgSwCommon.h"
|
#include "tvgSwCommon.h"
|
||||||
|
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
/* Gaussian Filter Implementation */
|
/* Gaussian Blur Implementation */
|
||||||
/************************************************************************/
|
/************************************************************************/
|
||||||
|
|
||||||
struct SwGaussianBlur
|
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
|
auto r = idx % end;
|
||||||
if (border == 1) return idx % end;
|
return (r < 0) ? end + r : r;
|
||||||
|
}
|
||||||
|
|
||||||
//duplicate
|
|
||||||
|
static int _gaussianEdgeExtend(int end, int idx)
|
||||||
|
{
|
||||||
if (idx < 0) return 0;
|
if (idx < 0) return 0;
|
||||||
else if (idx >= end) return end - 1;
|
else if (idx >= end) return end - 1;
|
||||||
return idx;
|
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?
|
//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) {
|
if (flipped) {
|
||||||
src += ((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;
|
dst += (bbox.min.x * stride + bbox.min.y) << 2;
|
||||||
} else {
|
} else {
|
||||||
src += ((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;
|
dst += (bbox.min.y * stride + bbox.min.x) << 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto iarr = 1.0f / (dimension + dimension + 1);
|
auto iarr = 1.0f / (dimension + dimension + 1);
|
||||||
|
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
for (int x = 0; x < h; x++) {
|
for (int y = 0; y < h; ++y) {
|
||||||
auto p = x * stride;
|
auto p = y * stride;
|
||||||
auto i = p * 4; //current index
|
auto i = p * 4; //current index
|
||||||
auto l = -(dimension + 1); //left index
|
auto l = -(dimension + 1); //left index
|
||||||
auto r = dimension; //right index
|
auto r = dimension; //right index
|
||||||
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
|
int acc[4] = {0, 0, 0, 0}; //sliding accumulator
|
||||||
|
|
||||||
//initial acucmulation
|
//initial accumulation
|
||||||
for (int x2 = l; x2 < r; ++x2) {
|
for (int x = l; x < r; ++x) {
|
||||||
auto id = (_gaussianRemap(w, x2, border) + p) * 4;
|
auto id = (_gaussianRemap(w, x, border) + p) * 4;
|
||||||
acc[0] += src[id++];
|
acc[0] += src[id++];
|
||||||
acc[1] += src[id++];
|
acc[1] += src[id++];
|
||||||
acc[2] += src[id++];
|
acc[2] += src[id++];
|
||||||
acc[3] += src[id];
|
acc[3] += src[id];
|
||||||
}
|
}
|
||||||
//perform filtering
|
//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 rid = (_gaussianRemap(w, r, border) + p) * 4;
|
||||||
auto lid = (_gaussianRemap(w, l, border) + p) * 4;
|
auto lid = (_gaussianRemap(w, l, border) + p) * 4;
|
||||||
acc[0] += src[rid++] - src[lid++];
|
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;
|
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);
|
auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1);
|
||||||
if (wl % 2 == 0) --wl;
|
if (wl % 2 == 0) --wl;
|
||||||
auto wu = wl + 2;
|
auto wu = wl + 2;
|
||||||
|
@ -118,92 +133,286 @@ static int _gaussianInit(int* kernel, float sigma, int level)
|
||||||
auto m = int(mi + 0.5f);
|
auto m = int(mi + 0.5f);
|
||||||
auto extends = 0;
|
auto extends = 0;
|
||||||
|
|
||||||
for (int i = 0; i < level; i++) {
|
for (int i = 0; i < data->level; i++) {
|
||||||
kernel[i] = ((i < m ? wl : wu) - 1) / 2;
|
data->kernel[i] = ((i < m ? wl : wu) - 1) / 2;
|
||||||
extends += kernel[i];
|
extends += data->kernel[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return extends;
|
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
|
auto extends = _gaussianInit(rd, params->sigma * params->sigma, params->quality);
|
||||||
data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1;
|
|
||||||
auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level);
|
|
||||||
|
|
||||||
//skip, if the parameters are invalid.
|
//invalid
|
||||||
if (extends == 0) {
|
if (extends == 0) {
|
||||||
params->invalid = true;
|
params->invalid = true;
|
||||||
free(data);
|
free(rd);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_gaussianExtendRegion(params->extend, extends, params->direction);
|
_gaussianExtendRegion(params->extend, extends, params->direction);
|
||||||
|
|
||||||
params->rd = data;
|
params->rd = rd;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* It is best to take advantage of the Gaussian blur’s separable property
|
bool effectGaussianBlur(SwCompositor* cmp, SwSurface* surface, const RenderEffectGaussianBlur* params, TVG_UNUSED bool direct)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (params->invalid) return false;
|
if (cmp->image.channelSize != sizeof(uint32_t)) {
|
||||||
|
|
||||||
if (image.channelSize != sizeof(uint32_t)) {
|
|
||||||
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
|
TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto& buffer = surface->compositor->image;
|
||||||
auto data = static_cast<SwGaussianBlur*>(params->rd);
|
auto data = static_cast<SwGaussianBlur*>(params->rd);
|
||||||
|
auto& bbox = cmp->bbox;
|
||||||
auto w = (bbox.max.x - bbox.min.x);
|
auto w = (bbox.max.x - bbox.min.x);
|
||||||
auto h = (bbox.max.y - bbox.min.y);
|
auto h = (bbox.max.y - bbox.min.y);
|
||||||
auto stride = image.stride;
|
auto stride = cmp->image.stride;
|
||||||
auto front = image.buf8;
|
auto front = cmp->image.buf32;
|
||||||
auto back = buffer.buf8;
|
auto back = buffer.buf32;
|
||||||
auto swapped = false;
|
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);
|
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
|
//horizontal
|
||||||
if (params->direction == 0 || params->direction == 1) {
|
if (params->direction != 2) {
|
||||||
for (int i = 0; i < data->level; ++i) {
|
for (int i = 0; i < data->level; ++i) {
|
||||||
auto k = data->kernel[i] / threshold;
|
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, w, h, bbox, data->kernel[i], params->border, false);
|
||||||
if (k == 0) continue;
|
|
||||||
_gaussianBlur(front, back, stride, w, h, bbox, k, params->border, false);
|
|
||||||
std::swap(front, back);
|
std::swap(front, back);
|
||||||
swapped = !swapped;
|
swapped = !swapped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
|
//vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
|
||||||
if (params->direction == 0 || params->direction == 2) {
|
if (params->direction != 1) {
|
||||||
rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, w, h, bbox, false);
|
rasterXYFlip(front, back, stride, w, h, bbox, false);
|
||||||
std::swap(front, back);
|
std::swap(front, back);
|
||||||
|
|
||||||
for (int i = 0; i < data->level; ++i) {
|
for (int i = 0; i < data->level; ++i) {
|
||||||
auto k = data->kernel[i] / threshold;
|
_gaussianFilter(reinterpret_cast<uint8_t*>(back), reinterpret_cast<uint8_t*>(front), stride, h, w, bbox, data->kernel[i], params->border, true);
|
||||||
if (k == 0) continue;
|
|
||||||
_gaussianBlur(front, back, stride, h, w, bbox, k, params->border, true);
|
|
||||||
std::swap(front, back);
|
std::swap(front, back);
|
||||||
swapped = !swapped;
|
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);
|
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;
|
return true;
|
||||||
}
|
}
|
|
@ -869,16 +869,7 @@ static bool _rasterDirectRleImage(SwSurface* surface, const SwImage* image, uint
|
||||||
auto dst = &surface->buf32[span->y * surface->stride + span->x];
|
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 img = image->buf32 + (span->y + image->oy) * image->stride + (span->x + image->ox);
|
||||||
auto alpha = MULTIPLY(span->coverage, opacity);
|
auto alpha = MULTIPLY(span->coverage, opacity);
|
||||||
if (alpha == 255) {
|
rasterTranslucentPixel32(dst, img, span->len, alpha);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1144,27 +1135,14 @@ static bool _rasterDirectImage(SwSurface* surface, const SwImage* image, const S
|
||||||
//32bits channels
|
//32bits channels
|
||||||
if (surface->channelSize == sizeof(uint32_t)) {
|
if (surface->channelSize == sizeof(uint32_t)) {
|
||||||
auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x];
|
auto dbuffer = &surface->buf32[region.min.y * surface->stride + region.min.x];
|
||||||
|
|
||||||
for (auto y = region.min.y; y < region.max.y; ++y) {
|
for (auto y = region.min.y; y < region.max.y; ++y) {
|
||||||
auto dst = dbuffer;
|
rasterTranslucentPixel32(dbuffer, sbuffer, region.max.x - region.min.x, opacity);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dbuffer += surface->stride;
|
dbuffer += surface->stride;
|
||||||
sbuffer += image->stride;
|
sbuffer += image->stride;
|
||||||
}
|
}
|
||||||
//8bits grayscale
|
//8bits grayscale
|
||||||
} else if (surface->channelSize == sizeof(uint8_t)) {
|
} else if (surface->channelSize == sizeof(uint8_t)) {
|
||||||
auto dbuffer = &surface->buf8[region.min.y * surface->stride + region.min.x];
|
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) {
|
for (auto y = region.min.y; y < region.max.y; ++y, dbuffer += surface->stride, sbuffer += image->stride) {
|
||||||
auto dst = dbuffer;
|
auto dst = dbuffer;
|
||||||
auto src = sbuffer;
|
auto src = sbuffer;
|
||||||
|
@ -1598,6 +1576,19 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRle* rle, const
|
||||||
/* External Class Implementation */
|
/* 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)
|
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,6 +20,38 @@
|
||||||
* SOFTWARE.
|
* 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>
|
template<typename PIXEL_T>
|
||||||
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len)
|
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len)
|
||||||
{
|
{
|
||||||
|
|
|
@ -649,19 +649,32 @@ bool SwRenderer::endComposite(RenderCompositor* cmp)
|
||||||
bool SwRenderer::prepare(RenderEffect* effect)
|
bool SwRenderer::prepare(RenderEffect* effect)
|
||||||
{
|
{
|
||||||
switch (effect->type) {
|
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;
|
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 p = static_cast<SwCompositor*>(cmp);
|
||||||
auto& buffer = request(surface->channelSize)->compositor->image;
|
|
||||||
|
|
||||||
switch (effect->type) {
|
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;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ public:
|
||||||
void clearCompositors();
|
void clearCompositors();
|
||||||
|
|
||||||
bool prepare(RenderEffect* effect) 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 SwRenderer* gen();
|
static SwRenderer* gen();
|
||||||
static bool init(uint32_t threads);
|
static bool init(uint32_t threads);
|
||||||
|
|
|
@ -276,20 +276,17 @@ struct RenderEffect
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RenderEffectGaussian : RenderEffect
|
struct RenderEffectGaussianBlur : RenderEffect
|
||||||
{
|
{
|
||||||
float sigma;
|
float sigma;
|
||||||
uint8_t direction; //0: both, 1: horizontal, 2: vertical
|
uint8_t direction; //0: both, 1: horizontal, 2: vertical
|
||||||
uint8_t border; //0: duplicate, 1: wrap
|
uint8_t border; //0: duplicate, 1: wrap
|
||||||
uint8_t quality; //0 ~ 100 (optional)
|
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);
|
auto inst = new RenderEffectGaussianBlur;
|
||||||
if (sigma <= 0) return nullptr;
|
inst->sigma = std::max((float) va_arg(args, double), 0.0f);
|
||||||
|
|
||||||
auto inst = new RenderEffectGaussian;
|
|
||||||
inst->sigma = sigma;
|
|
||||||
inst->direction = std::min(va_arg(args, int), 2);
|
inst->direction = std::min(va_arg(args, int), 2);
|
||||||
inst->border = std::min(va_arg(args, int), 1);
|
inst->border = std::min(va_arg(args, int), 1);
|
||||||
inst->quality = std::min(va_arg(args, int), 100);
|
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
|
class RenderMethod
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -331,7 +352,7 @@ public:
|
||||||
virtual bool endComposite(RenderCompositor* cmp) = 0;
|
virtual bool endComposite(RenderCompositor* cmp) = 0;
|
||||||
|
|
||||||
virtual bool prepare(RenderEffect* effect) = 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)
|
static inline bool MASK_REGION_MERGING(CompositeMethod method)
|
||||||
|
|
|
@ -121,7 +121,11 @@ Result Scene::push(SceneEffect effect, ...) noexcept
|
||||||
|
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case SceneEffect::GaussianBlur: {
|
case SceneEffect::GaussianBlur: {
|
||||||
re = RenderEffectGaussian::gen(args);
|
re = RenderEffectGaussianBlur::gen(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SceneEffect::DropShadow: {
|
||||||
|
re = RenderEffectDropShadow::gen(args);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
|
|
|
@ -143,8 +143,9 @@ struct Scene::Impl
|
||||||
if (cmp) {
|
if (cmp) {
|
||||||
//Apply post effects if any.
|
//Apply post effects if any.
|
||||||
if (effects) {
|
if (effects) {
|
||||||
|
auto direct = effects->count == 1 ? true : false;
|
||||||
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
for (auto e = effects->begin(); e < effects->end(); ++e) {
|
||||||
renderer->effect(cmp, *e);
|
renderer->effect(cmp, *e, direct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderer->endComposite(cmp);
|
renderer->endComposite(cmp);
|
||||||
|
|
|
@ -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);
|
TVGLOG("WG_ENGINE", "SceneEffect(%d) is not supported", (int)effect->type);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -53,7 +53,7 @@ public:
|
||||||
bool endComposite(RenderCompositor* cmp) override;
|
bool endComposite(RenderCompositor* cmp) override;
|
||||||
|
|
||||||
bool prepare(RenderEffect* effect) 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 WgRenderer* gen();
|
||||||
static bool init(uint32_t threads);
|
static bool init(uint32_t threads);
|
||||||
|
|
Loading…
Add table
Reference in a new issue