3.8 KiB
Bilinear Interpolation
Bilinear interpolation (also bilinear filtering) is a simple way of creating a smooth transition (interpolation) between discrete samples (values) in 2D, it is a generalization of linear interpolation to 2 dimensions. It is used in many places, popularly e.g. in 3D computer graphics for texture filtering; bilinear interpolation allows to upscale textures to higher resolutions (i.e. compute new pixels between existing pixels) while keeping their look smooth and "non-blocky" (even though blurry). On the scale of quality vs simplicity it is kind of a middle way between a simpler nearest neighbour interpolation (which creates the "blocky" look) and more complex bicubic interpolation (which uses yet smoother curves but also requires more samples). Bilinear interpolation can further be generalized to trilinear interpolation (in computer graphics trilinear interpolation is used to also additionally interpolate between different levels of a texture's mipamap) and perhaps even bilinear extrapolation. Many frameworks/libraries/engines have bilinear filtering built-in (e.g. GL_LINEAR
in OpenGL).
The principle is simple: first linearly interpolate in one direction (e.g. horizontal), then in the other (vertical). Mathematically the order in which we take the dimensions doesn't matter (but it may matter practically due to rounding errors etc.).
Example: let's say we want to compute the value x between the four following given corner values:
1 . . . . . . 5
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . x . . .
. . . . . . . .
8 . . . . . . 3
Let's say we first interpolate horizontally: we'll compute one value, a, on the top (between 1 and 5) and one value, b, at the bottom (between 8 and 3). When computing a we interpolate between 1 and 5 by the horizontal position of x (4/7), so we get a = 1 + 4/7 * (5 - 1) = 23/7. Similartly b = 8 + 4/7 * (3 - 8) = 36/7. Now we interpolate between a and b vertically (by the vertical position of x, 5/7) to get the final value x = 23/7 + 5/7 * (36/7 - 23/7) = 226/49 ~= 4.6. If we first interpolate vertically and then horizontally, we'd get the same result (the value between 1 and 8 would be 6, the value between 5 and 3 would be 25/7 and the final value 226/49 again).
Here is a C code to compute all the inbetween values in the above, using fixed point (no float):
#include <stdio.h>
#define GRID_RESOLUTION 8
int interpolateLinear(int a, int b, int t)
{
return a + (t * (b - a)) / (GRID_RESOLUTION - 1);
}
int interpolateBilinear(int topLeft, int topRight, int bottomLeft, int bottomRight,
int x, int y)
{
#define FPP 16 // we'll use fixed point to prevent rounding errors
#if 1 // switch between the two versions, should give same results:
// horizontal first, then vertical
int a = interpolateLinear(topLeft * FPP,topRight * FPP,x);
int b = interpolateLinear(bottomLeft * FPP,bottomRight * FPP,x);
return interpolateLinear(a,b,y) / FPP;
#else
// vertical first, then horizontal
int a = interpolateLinear(topLeft * FPP,bottomLeft * FPP,y);
int b = interpolateLinear(topRight * FPP,bottomRight * FPP,y);
return interpolateLinear(a,b,x) / FPP;
#endif
}
int main(void)
{
for (int y = 0; y < GRID_RESOLUTION; ++y)
{
for (int x = 0; x < GRID_RESOLUTION; ++x)
printf("%d ",interpolateBilinear(1,5,8,3,x,y));
putchar('\n');
}
return 0;
}
The program outputs:
1 1 2 2 3 3 4 5
2 2 2 3 3 4 4 5
3 3 3 3 4 4 4 5
4 4 4 4 4 4 4 5
5 5 5 5 5 5 5 4
6 6 6 6 5 5 5 4
7 7 7 6 6 5 5 4
8 8 7 6 6 5 4 3