Early on I wanted to learn how the Lanczos filter works but I couldn’t find a readable explanation. I still don’t know how the Lanczos filter works. Given the (tight) similarity of the results, I may have just re-invented the Lanczos filter (after a fashion), but I don’t know.
Here is an image, origally 500 x 500 pixels.
Below is a part of the image after resizing by 3 using Lanczos.
Below is the same part of the image using the method being described.
As is evident, my method is hardly distinguishable from Lanczos. My method shows here to be slightly lighter or having lower contrast but that can be adjusted with a parameter that can be passed in during resizing.
My method employs two steps, though both steps are functional on their own. These are linear interpolation and a special kind of contrast enhancement.
I’ll only detail the interpolation part in this post.
Linear interpolation – core method
Given two values, A and B, and a proportional position, prop, (between 0 and 1) between them, the interpolated value is found as follows.
Apart = A * (1.0 – prop)
Bpart = B * prop
interp_val = Apart + Bpart
It can be seen from the pic that when prop is large, the position is closer to B, hence A’s value is proportionally less (hence the 1.0 - prop).
Or as a function (for later use):
float interp (float A, float B, float prop) {
float Apart = A * (1.0 – prop);
float Bpart = B * prop;
return Apart + Bpart;
}
or just
float interp (float A, float B, float prop) {
return (A * (1.0 – prop)) + (B * prop);
}
Linear interpolation of an image
The general method (to be improved on futher down) is:
determine the inverse scaling factor
for each dest pixel position
get decimal position in source (src)
get integer part of decimal value (srci)
get fractional part of decimal value (srcf)
interpolate for values at srci and srci+1 with srcf as prop
Or more specifically, (and without any bounds checking):
Where scale is a parameter passed in as a decimal multiple of original image, inp is input image, and outp is output image, presumed already correctly sized to accommodate new data.
float inv_scale = 1.0 / scale;
float src, srcf;
int srci;
for (int x=0; x<dest.width(); x++) {
src = x * inv_scale;
srci = floor (src);
srcf = src – srci;
outp (x) = interp (inp (srci), inp (srci+1), srcf);
}
Where the pixel’s value is
The previous method presumes that each pixel is represented on pixel boundaries.
It is however more accurate to interpret the discrete (pixel) data as a continuum, the value of a pixel then being in the abstract middle between the discrete pixel boundaries.
If each pixel’s value is in its middle then both the source and destination references must be to their respective middles.
So instead of
src = x * inv_scale
this
src = (x + 0.5) * inv_scale
Further, if srcf is less than 0.5 then the position is between that and the previous pixel, and not that and the next pixel. This is important.
And if that is the case then the value of the proportional position changes too. If it was 0.2 (that is 0.3 to the right of middle of the pixel at srci) then the proportional position is 0.7 from the left of middle of the previous pixel.
So
if (srcf < 0.5) {
srci = srci – 1;
srcf = 0.5 + srcf;
}
However if srcf is not less than 0.5 then the proportion needs to change too but differently. So we also have:
else {
srcf = srcf – 0.5
}
Edge cases
If srcf is to the left of middle of the first pixel then there is nothing to interpolate with, so use that pixel’s value as the dest value. Similarly if srcf is to the right of the middle of the last pixel then use that pixel’s value as the dest value.
Left edge
if (srci == 0 and srcf < 0.5)
dst_val = inp [srci];
Right edge
if (srci == width-1 and srcf > 0.5)
dst_val = inp [srci];
Complete(r) code
void resize (float scale, image & inp, image & outp)
for (int x=0; x<dest.width(); x++) {
src = (x + 0.5) * inv_scale;
srci = floor (src);
srcf = src – srci;
if (srci == 0 and srcf < 0.5) {
outp (x) = inp (srci);
continue;
}
if (srci == width-1 and srcf > 0.5) {
outp (x) = inp (srci);
continue;
}
if (srcf < 0.5) {
srci = srci – 1;
srcf = 0.5 + srcf;
}
else {
srcf = srcf - 0.5;
}
outp (x) = interp (inp (srci), inp (srci+1), srcf);
}
Blocking and cumulative scaling
Scaling by factors greater than two results in apparent (soft) blocks of colour. This is because many dest pixels get their values from the same source pixels (generally).
This can be addressed by cumulatively resizing an image, limiting each run to a maximum scaling factor of 2, (and a minimum of 1).
If scaling by 750%, or 7.5, then
7.5 / 2 = 3.75 // scale by 2
3.75 / 2 = 1.875 // scale by 2
1.875 // scale by 1.875
Sanity check: 2 * 2 * 1.875 = 7.5
Generally:
while 1
if scale >= 2.0
interp by 2.0
scale = scale / 2.0
else if scale > 1.0
interp by scale
break
void cum_resize (float scale, image & inp, image & outp)
while (1) {
if (scale >= 2.0) {
resize (2.0, inp, outp);
inp = outp;
scale = scale / 2.0;
}
else if (scale > 1.0) {
resize (scale, inp, outp);
break;
}
}
Resizing down
The method generally works for image reductions too - so long as the scale is > 0.5. If the factor is <= 0.5 then dest pixels will be missing source pixels.
Note however that there is a better way to resize down. (The Lanczos filter produces ‘nicer’ down-sized images than this method.) And that better way may be the topic of another post.
In the meantime, cum_resize () needs to be extended to accommodate cumulative reductions for scaling factors <= 0.5;
if (scale < 1) {
while 1 {
if (scale <= 0.5) {
resize (0.5, inp, outp);
inp = outp;
scale = scale / 0.5;
{
else if (scale < 1.0) {
resize (scale, inp, outp);
break;
{
}
else if (scale > 1) {
while 1 {
if (scale >= 2.0) {...}
}
else { // scale == 1, so nothing to do but pass outp as inp
outp = inp
}
Images are two dimensional
Indeed they are. The solution is to recognise that scaling is separable, meaning that scaling by x and y separately is fine (not just fine but correct). My personal method is to cumulatively scale by xscale, rotate (transpose) the image, cumulatively scale by yscale, rotate the image back.
Generally:
cum_resize (xscale)
rotate left
cum_resize (yscale)
rotate right
Conclusion
That's all.











