Take the integral of the gaussian function.

integral e^(-1/2 ((x-μ)/σ)^2)/(σ sqrt(2 π)) dx = 1/2 erf((x-μ)/(sqrt(2) σ))+constant

with erf being the error function: https://en.wikipedia.org/wiki/Error_function. I gave it a try, works fine:

//from http://picomath.org/javascript/erf.js.html

function erf(x) {

// constants

var a1 = 0.254829592;

var a2 = -0.284496736;

var a3 = 1.421413741;

var a4 = -1.453152027;

var a5 = 1.061405429;

var p = 0.3275911;

// Save the sign of x

var sign = 1;

if (x < 0)

sign = -1;

x = Math.abs(x);

// A&S formula 7.1.26

var t = 1.0/(1.0 + p*x);

var y = 1.0 – (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);

return sign*y;

}

var sqrt_2 = Math.sqrt(2);

function def_int_gaussian(x, mu, sigma) {

return 0.5 * erf((x-mu)/(sqrt_2 * sigma));

}

var sigma = 1;

var mu = 0;

var kernel_size = 5;

var start_x = -(kernel_size/2);

var end_x = (kernel_size/2);

var step = 1;

coeff = []

var last_int = def_int_gaussian(start_x, mu, sigma);

for (var xi = start_x; xi < end_x; xi+=step) {

var new_int = def_int_gaussian(xi+step, mu, sigma)

coeff.push(new_int-last_int);

last_int = new_int;

}

sum = 0;

for (var i in coeff) {

sum += coeff[i]

}

//normalize

for (var i in coeff) {

coeff[i] /= sum;

}

Very useful and helpful! ]]>

Say you have a kernel of width 5 with weights a, b, c, d, e corresponding to pixels with values p0, p1, p2, p3, p4. The positions of the samples are -2, -1, 0, 1, 2.

The total kernel result is k = ap0 + bp1 + cp2 + dp3 + ep4.

You can evaluate this kernel equivalently with only 3 samples, instead of 5. 1 in the center, and 1 each somewhere between p0 and p1, and p3 and p4 respectively.

The task is to figure out WHERE that somewhere is, and what the WEIGHT of that sample should be.

The contribution of the first two samples to the kernel total is

ap0 + bp1 = (a+b)( a/(a+b)p0 + b/(a+b)p1 )

Bilinear filtering p0 and p1 in one axis with weight c is:

(c)p0 + (1-c)p1

setting c = a/(a+b), we get

1-c = (a+b)/(a+b) – a/(a+b) = b/(a+b)

Now that we know that a/(a+b)p0 + b/(a+b)p1 can be expressed as (c)p0 + (1-c)p1, and

ap0 + bp1 = (a+b)( a/(a+b)p0 + b/(a+b)p1 ) = (a+b)( cp0 + (1-c)p1 )

We use c = a/(a+b) as our uv offset, and a+b as the weight of the dual sample. We know that the sample needs to be somewhere between -2 and -1. So we set it to -1 – c = -1 – a/(a+b).

As an example, for a 5 tap kernel of sigma=1, the calculator gives us these weights:

0.06136 0.24477 0.38774 0.24477 0.06136

Plugging these into the equations,

c = 0.06136 / (0.06136 + 0.24477) = 0.2004, therefore

-1 – c = -1.2004

and

a+b = 0.30613

So the new kernel that evaluates to the same result would have weights:

0.30613 0.38774 0.30613

With sample offsets

-1.2004 0 1.2004

Notice that the sample offset -1.2004 is closer to p1 (-1) than p0 (-2). This makes sense, because the weight of p1 is higher than the weight of p0, and lerping gives us the correct proportion between the two weights.

It would be cool if you updated your calculator to calculate optimal weights and offsets in this way. I found your page at the top of the google search results, so I think enough people might be using this as a reference to be a useful addition.

Good luck, and thanks for the article!

Steve

]]>