mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-07 21:23:32 +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
93ebd388c7
commit
e0365142a7
14 changed files with 376 additions and 100 deletions
|
@ -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]}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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<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 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<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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue