nr-filter-gaussian.cpp revision db898371a8af9d0104344ba54d197a5b02131200
#define __NR_FILTER_GAUSSIAN_CPP__
/*
* Gaussian blur renderer
*
* Authors:
* Niko Kiirala <niko@kiirala.com>
* bulia byak
* Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
*
* Copyright (C) 2006-2008 authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include "config.h" // Needed for HAVE_OPENMP
#include <algorithm>
#include <cmath>
#include <complex>
#include <cstdlib>
#include <glib.h>
#include <limits>
#if HAVE_OPENMP
#include <omp.h>
#endif //HAVE_OPENMP
#include "display/nr-filter-primitive.h"
#include "display/nr-filter-gaussian.h"
#include "display/nr-filter-types.h"
#include "display/nr-filter-units.h"
#include "libnr/nr-pixblock.h"
#include "util/fixed_point.h"
#include "preferences.h"
#ifndef INK_UNUSED
#define INK_UNUSED(x) ((void)(x))
#endif
// IIR filtering method based on:
// L.J. van Vliet, I.T. Young, and P.W. Verbeek, Recursive Gaussian Derivative Filters,
// in: A.K. Jain, S. Venkatesh, B.C. Lovell (eds.),
// ICPR'98, Proc. 14th Int. Conference on Pattern Recognition (Brisbane, Aug. 16-20),
// IEEE Computer Society Press, Los Alamitos, 1998, 509-514.
//
// Using the backwards-pass initialization procedure from:
// Boundary Conditions for Young - van Vliet Recursive Filtering
// Bill Triggs, Michael Sdika
// IEEE Transactions on Signal Processing, Volume 54, Number 5 - may 2006
// Number of IIR filter coefficients used. Currently only 3 is supported.
// "Recursive Gaussian Derivative Filters" says this is enough though (and
// some testing indeed shows that the quality doesn't improve much if larger
// filters are used).
static size_t const N = 3;
}
// Type used for IIR filter coefficients (can be 10.21 signed fixed point, see Anisotropic Gaussian Filtering Using Fixed Point Arithmetic, Christoph H. Lampert & Oliver Wirjadi, 2006)
typedef double IIRValue;
// Type used for FIR filter coefficients (can be 16.16 unsigned fixed point, should have 8 or more bits in the fractional part, the integer part should be capable of storing approximately 20*255)
template<typename T> static inline T sqr(T const& v) { return v*v; }
template<typename T> static inline T clip(T const& v, T const& a, T const& b) {
if ( v < a ) return a;
if ( v > b ) return b;
return v;
}
}
template<>
inline unsigned char round_cast(double v) {
// This (fast) rounding method is based on:
double const dmr = 6755399441055744.0;
v = v + dmr;
return ((unsigned char*)&v)[0];
double const dmr = 6755399441055744.0;
v = v + dmr;
return ((unsigned char*)&v)[7];
#else
static double const rndoffset(.5);
return static_cast<unsigned char>(v+rndoffset);
#endif
}
if ( v < minval ) return minval_rounded;
if ( v > maxval ) return maxval_rounded;
return round_cast<Tt>(v);
}
if ( v < minval ) return minval_rounded;
if ( v > maxval ) return maxval_rounded;
return round_cast<Tt>(v);
}
namespace Inkscape {
namespace Filters {
{
}
{
return new FilterGaussian();
}
{
// Nothing to do here
}
static int
_effect_area_scr(double const deviation)
{
}
static void
{
double k[scr_len+1]; // This is only called for small kernel sizes (above approximately 10 coefficients the IIR filter is used)
// Compute kernel and sum of coefficients
// Note that actually only half the kernel is computed, as it is symmetric
double sum = 0;
for ( int i = scr_len; i >= 0 ; i-- ) {
if ( i > 0 ) sum += k[i];
}
// the sum of the complete kernel is twice as large (plus the center element which we skipped above to prevent counting it twice)
// Normalize kernel (making sure the sum is exactly 1)
double ksum = 0;
for ( int i = scr_len; i >= 1 ; i-- ) {
}
}
// Return value (v) should satisfy:
// 2^(2*v)*255<2^32
// 255<2^(32-2*v)
// 2^8<=2^(32-2*v)
// 8<=32-2*v
// 2*v<=24
// v<=12
static int
{
// To make sure FIR will always be used (unless the kernel is VERY big):
// deviation/3 <= step
// log(deviation/3) <= log(step)
// So when x below is >= 1/3 FIR will almost always be used.
// This means IIR is almost only used with the modes BETTER or BEST.
int stepsize_l2;
switch (quality) {
case BLUR_QUALITY_WORST:
// 2 == log(x*8/3))
// 2^2 == x*2^3/3
// x == 3/2
break;
case BLUR_QUALITY_WORSE:
// 2 == log(x*16/3))
// 2^2 == x*2^4/3
// x == 3/2^2
break;
case BLUR_QUALITY_BETTER:
// 2 == log(x*32/3))
// 2 == x*2^5/3
// x == 3/2^4
break;
case BLUR_QUALITY_BEST:
stepsize_l2 = 0; // no subsampling at all
break;
case BLUR_QUALITY_NORMAL:
default:
// 2 == log(x*16/3))
// 2 == x*2^4/3
// x == 3/2^3
break;
}
return stepsize_l2;
}
/**
* Sanity check function for indexing pixblocks.
* Catches reading and writing outside the pixblock area.
* When enabled, decreases filter rendering speed massively.
*/
static inline void
{
if (false) {
}
}
static void calcFilter(double const sigma, double b[N]) {
assert(N==3);
double const d3_org = 1.85132;
double qbeg = 1; // Don't go lower than sigma==2 (we'd probably want a normal convolution in that case anyway)
double s;
do { // Binary search for right q (a linear interpolation scheme is suggested, but this should work fine as well)
// Compute scaled filter coefficients
// Compute actual sigma^2
qbeg = q;
} else {
qend = q;
}
// Compute filter coefficients
b[2] = -bscale;
}
static void calcTriggsSdikaM(double const b[N], double M[N*N]) {
assert(N==3);
for(unsigned int i=0; i<9; i++) M[i] *= Mscale;
}
template<unsigned int SIZE>
static void calcTriggsSdikaInitialization(double const M[N*N], IIRValue const uold[N][SIZE], IIRValue const uplus[SIZE], IIRValue const vplus[SIZE], IIRValue const alpha, IIRValue vold[N][SIZE]) {
for(unsigned int c=0; c<SIZE; c++) {
double uminp[N];
for(unsigned int i=0; i<N; i++) {
double voldf = 0;
for(unsigned int j=0; j<N; j++) {
}
// Properly takes care of the scaling coefficient alpha and vplus (which is already appropriately scaled)
// This was arrived at by starting from a version of the blur filter that ignored the scaling coefficient
// (and scaled the final output by alpha^2) and then gradually reintroducing the scaling coefficient.
}
}
}
// Filters over 1st dimension
static void
{
#if HAVE_OPENMP
#else
#endif // HAVE_OPENMP
#if HAVE_OPENMP
unsigned int tid = omp_get_thread_num();
#else
unsigned int tid = 0;
#endif // HAVE_OPENMP
// corresponding line in the source and output buffer
// Border constants
// Forward pass
for(unsigned int c=0; c<PC; c++) u[0][c] *= b[0];
for(unsigned int i=1; i<N+1; i++) {
for(unsigned int c=0; c<PC; c++) u[0][c] += u[i][c]*b[i];
}
}
// Backward pass
if ( PREMULTIPLIED_ALPHA ) {
for(unsigned int c=0; c<PC-1; c++) dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], v[0][PC-1], dstimg[PC-1]);
} else {
}
while(c1-->0) {
for(unsigned int c=0; c<PC; c++) v[0][c] *= b[0];
for(unsigned int i=1; i<N+1; i++) {
for(unsigned int c=0; c<PC; c++) v[0][c] += v[i][c]*b[i];
}
if ( PREMULTIPLIED_ALPHA ) {
for(unsigned int c=0; c<PC-1; c++) dstimg[c] = clip_round_cast_varmax<PT>(v[0][c], v[0][PC-1], dstimg[PC-1]);
} else {
}
}
}
}
// Filters over 1st dimension
// Assumes kernel is symmetric
// Kernel should have scr_len+1 elements
static void
{
// Past pixels seen (to enable in-place operation)
#if HAVE_OPENMP
#else
#endif // HAVE_OPENMP
// corresponding line in the source buffer
// current line in the output buffer
// history initialization
// update history
// for all bytes of the pixel
int last_in = -1;
int different_count = 0;
// go over our point's neighbours in the history
for ( int i = 0 ; i <= scr_len ; i++ ) {
// value at the pixel
// is it the same as last one we saw?
// sum pixels weighted by the kernel
}
// go over our point's neighborhood on x axis in the in buffer
for ( int i = 1 ; i <= scr_len ; i++ ) {
// the pixel we're looking at
} else {
nb_src_disp += sstr1;
}
// value at the pixel
// is it the same as last one we saw?
// sum pixels weighted by the kernel
}
// store the result in bufx
// optimization: if there was no variation within this point's neighborhood,
// skip ahead while we keep seeing the same last_in byte:
// blurring flat color would not change it anyway
if (different_count <= 1) { // note that different_count is at least 1, because last_in is initialized to -1
pos++;
nb_src_disp += sstr1;
nb_dst_disp += dstr1;
}
}
}
}
}
}
static void
{
}
}
}
}
}
}
}
static void
upsample(PT *const dst, int const dstr1, int const dstr2, unsigned int const dn1, unsigned int const dn2,
PT const *const src, int const sstr1, int const sstr2, unsigned int const sn1, unsigned int const sn2,
{
assert(((sn1-1)<<step1_l2)>=dn1 && ((sn2-1)<<step2_l2)>=dn2); // The last pixel of the source image should fall outside the destination image
// get 4 values at the corners of the pixel from src
// initialize values for linear interpolation
// iterate over the rectangle to be interpolated
// prepare linear interpolation for this row
// simple linear interpolation
// compute a = a0*(ix-1)+a1*(xi+1)+round_offset
}
// compute a0 = a00*(iy-1)+a01*(yi+1) and similar for a1
}
}
}
}
}
{
// TODO: Meaningful return values? (If they're checked at all.)
/* in holds the input pixblock */
/* If to either direction, the standard deviation is zero or
* input image is not defined,
* a transparent black image should be returned. */
// A bit guessing here, but source graphic is likely to be of
// right size
}
}
return 0;
}
// Gaussian blur is defined to operate on non-premultiplied color values.
// So, convert the input first it uses non-premultiplied color values.
// And please note that this should not be done AFTER resampling, as resampling a non-premultiplied image
// does not simply yield a non-premultiplied version of the resampled premultiplied image!!!
false);
if (!in) {
// ran out of memory
return 0;
}
}
// Some common constants
#if HAVE_OPENMP
int const NTHREADS = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
#else
int const NTHREADS = 1;
#endif // HAVE_OPENMP
// Subsampling constants
int const width = resampling ? static_cast<int>(ceil(static_cast<double>(width_org)/x_step))+1 : width_org;
int const height = resampling ? static_cast<int>(ceil(static_cast<double>(height_org)/y_step))+1 : height_org;
// Decide which filter to use for X and Y
// This threshold was determined by trial-and-error for one specific machine,
// so there's a good chance that it's not optimal.
// Whatever you do, don't go below 1 (and preferrably not even below 2), as
// the IIR filter gets unstable there.
// new buffer for the subsampled output
// alas, we've accomplished a lot, but ran out of memory - so abort
delete out;
return 0;
}
// Temporary storage for IIR filter
// NOTE: This can be eliminated, but it reduces the precision a bit
for(int i=0; i<NTHREADS; i++) {
while(i-->0) {
delete[] tmpdata[i];
}
delete out;
return 0;
}
}
}
// Resampling (if necessary), goes from in -> out (setting ssin to out if used)
if ( resampling ) {
// Downsample
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
downsample<unsigned char,1>(NR_PIXBLOCK_PX(out), 1, out->rs, width, height, NR_PIXBLOCK_PX(in), 1, in->rs, width_org, height_org, x_step_l2, y_step_l2);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
downsample<unsigned char,3>(NR_PIXBLOCK_PX(out), 3, out->rs, width, height, NR_PIXBLOCK_PX(in), 3, in->rs, width_org, height_org, x_step_l2, y_step_l2);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// downsample<unsigned char,4>(NR_PIXBLOCK_PX(out), 4, out->rs, width, height, NR_PIXBLOCK_PX(in), 4, in->rs, width_org, height_org, x_step_l2, y_step_l2);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
downsample<unsigned char,4>(NR_PIXBLOCK_PX(out), 4, out->rs, width, height, NR_PIXBLOCK_PX(in), 4, in->rs, width_org, height_org, x_step_l2, y_step_l2);
break;
default:
assert(false);
};
}
// Horizontal filtering, goes from ssin -> out (ssin might be equal to out, but these algorithms can be used in-place)
if (use_IIR_x) {
// Filter variables
double bf[N]; // computed filter coefficients
double M[N*N]; // matrix used for initialization procedure (has to be double)
// Compute filter (x)
b[0] = 1; // b[0] == alpha (scaling coefficient)
for(size_t i=0; i<N; i++) {
b[i+1] = bf[i];
b[0] -= b[i+1];
}
// Compute initialization matrix (x)
calcTriggsSdikaM(bf, M);
// Filter (x)
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
filter2D_IIR<unsigned char,1,false>(NR_PIXBLOCK_PX(out), 1, out->rs, NR_PIXBLOCK_PX(ssin), 1, ssin->rs, width, height, b, M, tmpdata, NTHREADS);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
filter2D_IIR<unsigned char,3,false>(NR_PIXBLOCK_PX(out), 3, out->rs, NR_PIXBLOCK_PX(ssin), 3, ssin->rs, width, height, b, M, tmpdata, NTHREADS);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// filter2D_IIR<unsigned char,4,false>(NR_PIXBLOCK_PX(out), 4, out->rs, NR_PIXBLOCK_PX(ssin), 4, ssin->rs, width, height, b, M, tmpdata, NTHREADS);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
filter2D_IIR<unsigned char,4,true >(NR_PIXBLOCK_PX(out), 4, out->rs, NR_PIXBLOCK_PX(ssin), 4, ssin->rs, width, height, b, M, tmpdata, NTHREADS);
break;
default:
assert(false);
};
} else if ( scr_len_x > 0 ) { // !use_IIR_x
// Filter kernel for x direction
// Filter (x)
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
filter2D_FIR<unsigned char,1>(NR_PIXBLOCK_PX(out), 1, out->rs, NR_PIXBLOCK_PX(ssin), 1, ssin->rs, width, height, kernel, scr_len_x, NTHREADS);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
filter2D_FIR<unsigned char,3>(NR_PIXBLOCK_PX(out), 3, out->rs, NR_PIXBLOCK_PX(ssin), 3, ssin->rs, width, height, kernel, scr_len_x, NTHREADS);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// filter2D_FIR<unsigned char,4>(NR_PIXBLOCK_PX(out), 4, out->rs, NR_PIXBLOCK_PX(ssin), 4, ssin->rs, width, height, kernel, scr_len_x, NTHREADS);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
filter2D_FIR<unsigned char,4>(NR_PIXBLOCK_PX(out), 4, out->rs, NR_PIXBLOCK_PX(ssin), 4, ssin->rs, width, height, kernel, scr_len_x, NTHREADS);
break;
default:
assert(false);
};
}
// Vertical filtering, goes from out -> out
if (use_IIR_y) {
// Filter variables
double bf[N]; // computed filter coefficients
double M[N*N]; // matrix used for initialization procedure (has to be double)
// Compute filter (y)
b[0] = 1; // b[0] == alpha (scaling coefficient)
for(size_t i=0; i<N; i++) {
b[i+1] = bf[i];
b[0] -= b[i+1];
}
// Compute initialization matrix (y)
calcTriggsSdikaM(bf, M);
// Filter (y)
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
filter2D_IIR<unsigned char,1,false>(NR_PIXBLOCK_PX(out), out->rs, 1, NR_PIXBLOCK_PX(out), out->rs, 1, height, width, b, M, tmpdata, NTHREADS);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
filter2D_IIR<unsigned char,3,false>(NR_PIXBLOCK_PX(out), out->rs, 3, NR_PIXBLOCK_PX(out), out->rs, 3, height, width, b, M, tmpdata, NTHREADS);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// filter2D_IIR<unsigned char,4,false>(NR_PIXBLOCK_PX(out), out->rs, 4, NR_PIXBLOCK_PX(out), out->rs, 4, height, width, b, M, tmpdata, NTHREADS);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
filter2D_IIR<unsigned char,4,true >(NR_PIXBLOCK_PX(out), out->rs, 4, NR_PIXBLOCK_PX(out), out->rs, 4, height, width, b, M, tmpdata, NTHREADS);
break;
default:
assert(false);
};
} else if ( scr_len_y > 0 ) { // !use_IIR_y
// Filter kernel for y direction
// Filter (y)
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
filter2D_FIR<unsigned char,1>(NR_PIXBLOCK_PX(out), out->rs, 1, NR_PIXBLOCK_PX(out), out->rs, 1, height, width, kernel, scr_len_y, NTHREADS);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
filter2D_FIR<unsigned char,3>(NR_PIXBLOCK_PX(out), out->rs, 3, NR_PIXBLOCK_PX(out), out->rs, 3, height, width, kernel, scr_len_y, NTHREADS);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// filter2D_FIR<unsigned char,4>(NR_PIXBLOCK_PX(out), out->rs, 4, NR_PIXBLOCK_PX(out), out->rs, 4, height, width, kernel, scr_len_y, NTHREADS);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
filter2D_FIR<unsigned char,4>(NR_PIXBLOCK_PX(out), out->rs, 4, NR_PIXBLOCK_PX(out), out->rs, 4, height, width, kernel, scr_len_y, NTHREADS);
break;
default:
assert(false);
};
}
for(int i=0; i<NTHREADS; i++) {
delete[] tmpdata[i]; // deleting a nullptr has no effect, so this is safe
}
// Upsampling, stores (the upsampled) out using slot.set(_output, ...)
if ( !resampling ) {
// No upsampling needed
} else {
// New buffer for the final output, same resolution as the in buffer
// alas, we've accomplished a lot, but ran out of memory - so abort
delete out;
return 0;
}
// Upsample
case NR_PIXBLOCK_MODE_A8: ///< Grayscale
upsample<unsigned char,1>(NR_PIXBLOCK_PX(finalout), 1, finalout->rs, width_org, height_org, NR_PIXBLOCK_PX(out), 1, out->rs, width, height, x_step_l2, y_step_l2);
break;
case NR_PIXBLOCK_MODE_R8G8B8: ///< 8 bit RGB
upsample<unsigned char,3>(NR_PIXBLOCK_PX(finalout), 3, finalout->rs, width_org, height_org, NR_PIXBLOCK_PX(out), 3, out->rs, width, height, x_step_l2, y_step_l2);
break;
//case NR_PIXBLOCK_MODE_R8G8B8A8N: ///< Normal 8 bit RGBA
// upsample<unsigned char,4>(NR_PIXBLOCK_PX(finalout), 4, finalout->rs, width_org, height_org, NR_PIXBLOCK_PX(out), 4, out->rs, width, height, x_step_l2, y_step_l2);
// break;
case NR_PIXBLOCK_MODE_R8G8B8A8P: ///< Premultiplied 8 bit RGBA
upsample<unsigned char,4>(NR_PIXBLOCK_PX(finalout), 4, finalout->rs, width_org, height_org, NR_PIXBLOCK_PX(out), 4, out->rs, width, height, x_step_l2, y_step_l2);
break;
default:
assert(false);
};
// We don't need the out buffer anymore
delete out;
// The final out buffer gets returned
}
// If we downsampled the input, clean up the downsampled data
return 0;
}
{
// maximum is used because rotations can mix up these directions
// TODO: calculate a more tight-fitting rendering area
}
return TRAIT_PARALLER;
}
{
}
}
void FilterGaussian::set_deviation(double x, double y)
{
_deviation_x = x;
_deviation_y = y;
}
}
} /* namespace Filters */
} /* namespace Inkscape */
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :