Evas filters: Implement blur functions

Currently supported:
- Box blur for Alpha and RGBA
- Gaussian blur for Alpha and RGBA

Motion blur is not implemented.

These effects are SLOW. Gaussian blur is based on a true gaussian
curve for small radii, or a sine-based approximation.
The true gaussian might need to be removed for consistency, since
it gives slightly different results from the sine one (less blur).

It is not possible to mix Alpha and RGBA surfaces in this filter.
This commit is contained in:
Jean-Philippe Andre 2013-12-09 16:26:02 +09:00
parent 65d779c7a3
commit 84caa5902e
1 changed files with 741 additions and 0 deletions

View File

@ -0,0 +1,741 @@
#include "evas_filter.h"
#include "evas_filter_private.h"
#include <math.h>
#include <time.h>
#define DEBUG_TIME 1
#if DIV_USING_BITSHIFT
static int
_smallest_pow2_larger_than(int val)
{
int n;
for (n = 0; n < 32; n++)
if (val <= (1 << n)) return n;
ERR("Value %d is too damn high!", val);
return 32;
}
/* Input:
* const int pow2 = _smallest_pow2_larger_than(divider * 1024);
* const int numerator = (1 << pow2) / divider;
* Result:
* r = ((val * numerator) >> pow2);
*/
# define DEFINE_DIAMETER(rad) const int pow2 = _smallest_pow2_larger_than((radius * 2 + 1) << 10); const int numerator = (1 << pow2) / (radius * 2 + 1);
# define DIVIDE_BY_DIAMETER(val) (((val) * numerator) >> pow2)
#else
# define DEFINE_DIAMETER(rad) const int diameter = radius * 2 + 1;
# define DIVIDE_BY_DIAMETER(val) ((val) / diameter)
#endif
// Switch from Pascal Triangle based gaussian to Sine
#define MAX_GAUSSIAN_RADIUS 5
#if MAX_GAUSSIAN_RADIUS > 12
# error Impossible value
#endif
#if DEBUG_TIME
# define DEBUG_TIME_BEGIN() \
struct timespec ts1, ts2; \
clock_gettime(CLOCK_MONOTONIC, &ts1);
# define DEBUG_TIME_END() \
clock_gettime(CLOCK_MONOTONIC, &ts2); \
long long int t = 1000000LL * (ts2.tv_sec - ts1.tv_sec) \
+ (ts2.tv_nsec - ts1.tv_nsec) / 1000LL; \
INF("TIME SPENT: %lldus", t);
#else
# define DEBUG_TIME_BEGIN() do {} while(0)
# define DEBUG_TIME_END() do {} while(0)
#endif
/* RGBA functions */
static void
_box_blur_step_rgba(DATA32 *src, DATA32 *dst, int radius, int len, int step)
{
DEFINE_DIAMETER(radius);
int acc[4] = {0};
DATA8 *d, *rs, *ls;
int x, k;
int divider;
d = (DATA8 *) dst;
ls = (DATA8 *) src;
rs = (DATA8 *) src;
// Read-ahead
for (x = radius; x; x--)
{
for (k = 0; k < 4; k++)
acc[k] += rs[k];
rs += step;
}
// Left
for (x = 0; x < radius; x++)
{
for (k = 0; k < 4; k++)
acc[k] += rs[k];
rs += step;
divider = x + radius + 1;
d[ALPHA] = acc[ALPHA] / divider;
d[RED] = acc[RED] / divider;
d[GREEN] = acc[GREEN] / divider;
d[BLUE] = acc[BLUE] / divider;
d += step;
}
// Main part
for (x = len - (2 * radius); x > 0; x--)
{
for (k = 0; k < 4; k++)
acc[k] += rs[k];
rs += step;
d[ALPHA] = DIVIDE_BY_DIAMETER(acc[ALPHA]);
d[RED] = DIVIDE_BY_DIAMETER(acc[RED]);
d[GREEN] = DIVIDE_BY_DIAMETER(acc[GREEN]);
d[BLUE] = DIVIDE_BY_DIAMETER(acc[BLUE]);
d += step;
for (k = 0; k < 4; k++)
acc[k] -= ls[k];
ls += step;
}
// Right part
for (x = radius; x; x--)
{
divider = x + radius;
d[ALPHA] = acc[ALPHA] / divider;
d[RED] = acc[RED] / divider;
d[GREEN] = acc[GREEN] / divider;
d[BLUE] = acc[BLUE] / divider;
d += step;
for (k = 0; k < 4; k++)
acc[k] -= ls[k];
ls += step;
}
}
static void
_box_blur_horiz_rgba(DATA32 *src, DATA32 *dst, int radius, int w, int h)
{
int y;
int step = sizeof(DATA32);
DEBUG_TIME_BEGIN();
for (y = 0; y < h; y++)
{
_box_blur_step_rgba(src, dst, radius, w, step);
src += w;
dst += w;
}
DEBUG_TIME_END();
}
static void
_box_blur_vert_rgba(DATA32 *src, DATA32 *dst, int radius, int w, int h)
{
int x;
int step = w * sizeof(DATA32);
DEBUG_TIME_BEGIN();
for (x = 0; x < w; x++)
{
_box_blur_step_rgba(src, dst, radius, h, step);
src += 1;
dst += 1;
}
DEBUG_TIME_END();
}
static Eina_Bool
_box_blur_horiz_apply_rgba(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->image.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->image.data, EINA_FALSE);
_box_blur_horiz_rgba(in->image.data, out->image.data, cmd->blur.dx,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
static Eina_Bool
_box_blur_vert_apply_rgba(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->image.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->image.data, EINA_FALSE);
_box_blur_vert_rgba(in->image.data, out->image.data, cmd->blur.dy,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
/* Alpha only functions */
/* Box blur */
static void
_box_blur_step_alpha(DATA8 *src, DATA8 *dst, int radius, int len, int step)
{
int k;
int acc = 0;
DATA8 *sr = src, *sl = src, *d = dst;
DEFINE_DIAMETER(radius);
for (k = radius; k; k--)
{
acc += *sr;
sr += step;
}
for (k = 0; k < radius; k++)
{
acc += *sr;
*d = acc / (k + radius + 1);
sr += step;
d += step;
}
for (k = len - (2 * radius); k; k--)
{
acc += *sr;
*d = DIVIDE_BY_DIAMETER(acc);
acc -= *sl;
sl += step;
sr += step;
d += step;
}
for (k = radius; k; k--)
{
*d = acc / (k + radius);
acc -= *sl;
d += step;
sl += step;
}
}
static void
_box_blur_horiz_alpha(DATA8 *src, DATA8 *dst, int radius, int w, int h)
{
int k;
DEBUG_TIME_BEGIN();
for (k = h; k; k--)
{
_box_blur_step_alpha(src, dst, radius, w, 1);
dst += w;
src += w;
}
DEBUG_TIME_END();
}
static void
_box_blur_vert_alpha(DATA8 *src, DATA8 *dst, int radius, int w, int h)
{
int k;
DEBUG_TIME_BEGIN();
for (k = w; k; k--)
{
_box_blur_step_alpha(src, dst, radius, h, w);
dst += 1;
src += 1;
}
DEBUG_TIME_END();
}
static Eina_Bool
_box_blur_horiz_apply_alpha(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->mask.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->mask.data, EINA_FALSE);
_box_blur_horiz_alpha(in->mask.data, out->mask.data, cmd->blur.dx,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
static Eina_Bool
_box_blur_vert_apply_alpha(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->mask.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->mask.data, EINA_FALSE);
_box_blur_vert_alpha(in->mask.data, out->mask.data, cmd->blur.dy,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
/* Gaussian blur */
static void
_gaussian_blur_weights_get(int *weights, int *pow2_divider, int radius)
{
int even[radius + 1];
int odd[radius + 1];
int k, j;
EINA_SAFETY_ON_FALSE_RETURN(radius >= 0 && radius <= 12);
/* Uses Pascal's Triangle to compute the integer gaussian weights:
*
* 0 1 / 1 [1]
* 1 1 [1 1]
* 1 1 2 1 / 4 [1 2]
* 1 3 3 1 [1 3 3]
* 2 1 4 6 4 1 / 16 [1 4 6]
* 1 ..................1
*
* Limitation: max radius is 12 when using 32 bits integers:
* pow2_divider = 24, leaving exactly 8 bits for the data
*/
if (pow2_divider)
*pow2_divider = radius * 2;
memset(odd, 0, sizeof(odd));
memset(even, 0, sizeof(even));
odd[0] = 1;
even[0] = 1;
for (k = 1; k <= radius; k++)
{
for (j = 1; j <= k; j++)
odd[j] = even[j] + even[j - 1];
odd[k] = 2 * even[k - 1];
for (j = 1; j <= k; j++)
even[j] = odd[j] + odd[j - 1];
}
for (k = 0; k <= radius; k++)
weights[k] = odd[k];
for (k = 0; k <= radius; k++)
weights[k + radius] = weights[radius - k];
}
static void
_sin_blur_weights_get(int *weights, int *pow2_divider, int radius)
{
const int diameter = 2 * radius + 1;
double x, divider, sum = 0.0;
double dweights[diameter];
int k, nextpow2, isum = 0;
const int FAKE_PI = 3.0;
/* Base curve:
* f(x) = sin(x+pi/2)/2+1/2
*/
for (k = 0; k < diameter; k++)
{
x = ((double) k / (double) (diameter - 1)) * FAKE_PI * 2.0 - FAKE_PI;
dweights[k] = ((sin(x + M_PI_2) + 1.0) / 2.0) * 1024.0;
sum += dweights[k];
}
// Now we need to normalize to have a 2^N divider.
nextpow2 = log2(2 * sum);
divider = (double) (1 << nextpow2);
for (k = 0; k < diameter; k++)
{
weights[k] = round(dweights[k] * divider / sum);
isum += weights[k];
}
// Final correction. The difference SHOULD be small...
weights[radius] += (int) divider - isum;
if (pow2_divider)
*pow2_divider = nextpow2;
}
static void
_gaussian_blur_step_alpha(DATA8 *src, DATA8 *dst, int radius, int len, int step,
int *weights, int pow2_divider)
{
int j, k, acc, divider;
DATA8 *s = src;
const int diameter = 2 * radius + 1;
// left
for (k = 0; k < radius; k++, dst += step)
{
acc = 0;
divider = 0;
s = src;
for (j = 0; j <= k + radius; j++, s += step)
{
acc += (*s) * weights[j + radius - k];
divider += weights[j + radius - k];
}
*dst = acc / divider;
}
// middle
for (k = radius; k < (len - radius); k++, src += step, dst += step)
{
acc = 0;
s = src;
for (j = 0; j < diameter; j++, s += step)
acc += (*s) * weights[j];
*dst = acc >> pow2_divider;
}
// right
for (k = 0; k < radius; k++, dst += step, src += step)
{
acc = 0;
divider = 0;
s = src;
for (j = 0; j < 2 * radius - k; j++, s += step)
{
acc += (*s) * weights[j];
divider += weights[j];
}
*dst = acc / divider;
}
}
static void
_gaussian_blur_step_rgba(DATA32 *src, DATA32 *dst, int radius, int len, int step,
int *weights, int pow2_divider)
{
const int diameter = 2 * radius + 1;
int j, k;
// left
for (k = 0; k < radius; k++, dst += step)
{
int acc[4] = {0};
int divider = 0;
DATA32 *s = src;
for (j = 0; j <= k + radius; j++, s += step)
{
const int weightidx = j + radius - k;
acc[ALPHA] += A_VAL(s) * weights[weightidx];
acc[RED] += R_VAL(s) * weights[weightidx];
acc[GREEN] += G_VAL(s) * weights[weightidx];
acc[BLUE] += B_VAL(s) * weights[weightidx];
divider += weights[weightidx];
}
A_VAL(dst) = acc[ALPHA] / divider;
R_VAL(dst) = acc[RED] / divider;
G_VAL(dst) = acc[GREEN] / divider;
B_VAL(dst) = acc[BLUE] / divider;
}
// middle
for (k = len - (2 * radius); k > 0; k--, src += step, dst += step)
{
int acc[4] = {0};
DATA32 *s = src;
for (j = 0; j < diameter; j++, s += step)
{
acc[ALPHA] += A_VAL(s) * weights[j];
acc[RED] += R_VAL(s) * weights[j];
acc[GREEN] += G_VAL(s) * weights[j];
acc[BLUE] += B_VAL(s) * weights[j];
}
A_VAL(dst) = acc[ALPHA] >> pow2_divider;
R_VAL(dst) = acc[RED] >> pow2_divider;
G_VAL(dst) = acc[GREEN] >> pow2_divider;
B_VAL(dst) = acc[BLUE] >> pow2_divider;
}
// right
for (k = 0; k < radius; k++, dst += step, src += step)
{
int acc[4] = {0};
int divider = 0;
DATA32 *s = src;
for (j = 0; j < 2 * radius - k; j++, s += step)
{
acc[ALPHA] += A_VAL(s) * weights[j];
acc[RED] += R_VAL(s) * weights[j];
acc[GREEN] += G_VAL(s) * weights[j];
acc[BLUE] += B_VAL(s) * weights[j];
divider += weights[j];
}
A_VAL(dst) = acc[ALPHA] / divider;
R_VAL(dst) = acc[RED] / divider;
G_VAL(dst) = acc[GREEN] / divider;
B_VAL(dst) = acc[BLUE] / divider;
}
}
static void
_gaussian_blur_horiz_alpha(DATA8 *src, DATA8 *dst, int radius, int w, int h)
{
int *weights;
int k, pow2_div;
weights = alloca((2 * radius + 1) * sizeof(int));
if (radius <= MAX_GAUSSIAN_RADIUS)
_gaussian_blur_weights_get(weights, &pow2_div, radius);
else
_sin_blur_weights_get(weights, &pow2_div, radius);
for (k = h; k; k--)
{
_gaussian_blur_step_alpha(src, dst, radius, w, 1, weights, pow2_div);
dst += w;
src += w;
}
}
static void
_gaussian_blur_vert_alpha(DATA8 *src, DATA8 *dst, int radius, int w, int h)
{
int *weights;
int k, pow2_div;
weights = alloca((2 * radius + 1) * sizeof(int));
if (radius <= MAX_GAUSSIAN_RADIUS)
_gaussian_blur_weights_get(weights, &pow2_div, radius);
else
_sin_blur_weights_get(weights, &pow2_div, radius);
for (k = w; k; k--)
{
_gaussian_blur_step_alpha(src, dst, radius, h, w, weights, pow2_div);
dst += 1;
src += 1;
}
}
static void
_gaussian_blur_horiz_rgba(DATA32 *src, DATA32 *dst, int radius, int w, int h)
{
int *weights;
int k, pow2_div;
weights = alloca((2 * radius + 1) * sizeof(int));
if (radius <= MAX_GAUSSIAN_RADIUS)
_gaussian_blur_weights_get(weights, &pow2_div, radius);
else
_sin_blur_weights_get(weights, &pow2_div, radius);
for (k = h; k; k--)
{
_gaussian_blur_step_rgba(src, dst, radius, w, 1, weights, pow2_div);
dst += w;
src += w;
}
}
static void
_gaussian_blur_vert_rgba(DATA32 *src, DATA32 *dst, int radius, int w, int h)
{
int *weights;
int k, pow2_div;
weights = alloca((2 * radius + 1) * sizeof(int));
if (radius <= MAX_GAUSSIAN_RADIUS)
_gaussian_blur_weights_get(weights, &pow2_div, radius);
else
_sin_blur_weights_get(weights, &pow2_div, radius);
for (k = w; k; k--)
{
_gaussian_blur_step_rgba(src, dst, radius, h, w, weights, pow2_div);
dst += 1;
src += 1;
}
}
static Eina_Bool
_gaussian_blur_horiz_apply_alpha(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->mask.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->mask.data, EINA_FALSE);
_gaussian_blur_horiz_alpha(in->mask.data, out->mask.data, cmd->blur.dx,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
static Eina_Bool
_gaussian_blur_vert_apply_alpha(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->mask.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->mask.data, EINA_FALSE);
_gaussian_blur_vert_alpha(in->mask.data, out->mask.data, cmd->blur.dy,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
static Eina_Bool
_gaussian_blur_horiz_apply_rgba(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->image.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->image.data, EINA_FALSE);
_gaussian_blur_horiz_rgba(in->image.data, out->image.data, cmd->blur.dx,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
static Eina_Bool
_gaussian_blur_vert_apply_rgba(Evas_Filter_Command *cmd)
{
RGBA_Image *in, *out;
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->input->backing, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd->output->backing, EINA_FALSE);
in = cmd->input->backing;
out = cmd->output->backing;
EINA_SAFETY_ON_NULL_RETURN_VAL(in->image.data, EINA_FALSE);
EINA_SAFETY_ON_NULL_RETURN_VAL(out->image.data, EINA_FALSE);
_gaussian_blur_vert_rgba(in->image.data, out->image.data, cmd->blur.dy,
in->cache_entry.w, in->cache_entry.h);
return EINA_TRUE;
}
/* Main entry point */
Evas_Filter_Apply_Func
evas_filter_blur_cpu_func_get(Evas_Filter_Command *cmd)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL(cmd->mode == EVAS_FILTER_MODE_BLUR, NULL);
switch (cmd->blur.type)
{
case EVAS_FILTER_BLUR_BOX:
if (!cmd->input->alpha_only && !cmd->output->alpha_only)
{
if (cmd->blur.dx)
return _box_blur_horiz_apply_rgba;
else if (cmd->blur.dy)
return _box_blur_vert_apply_rgba;
}
else if (cmd->input->alpha_only && cmd->output->alpha_only)
{
if (cmd->blur.dx)
return _box_blur_horiz_apply_alpha;
else if (cmd->blur.dy)
return _box_blur_vert_apply_alpha;
}
CRIT("Unsupported operation: mixing RGBA and Alpha surfaces.");
return NULL;
case EVAS_FILTER_BLUR_GAUSSIAN:
if (!cmd->input->alpha_only && !cmd->output->alpha_only)
{
if (cmd->blur.dx)
return _gaussian_blur_horiz_apply_rgba;
else if (cmd->blur.dy)
return _gaussian_blur_vert_apply_rgba;
}
else if (cmd->input->alpha_only && cmd->output->alpha_only)
{
if (cmd->blur.dx)
return _gaussian_blur_horiz_apply_alpha;
else if (cmd->blur.dy)
return _gaussian_blur_vert_apply_alpha;
}
CRIT("Unsupported operation: mixing RGBA and Alpha surfaces.");
return NULL;
default:
CRIT("Not implemented yet!");
return NULL;
}
}