Using Bilinear Interpolation and Nearest Neighbor techniques to scale your images

When you zoom into an image or change your window’s resolution, you are trying to redraw whatever images were in the window without knowing all of the pixel information. In order to accurately render the scaled images, you must use information that you do know, and predict the information you do not. Two techniques commonly used for this are Bilinear Interpolation and Nearest Neighbor.

When faced with implementing these techniques, I decided to approach the task in a way that was easy to visualize and debug. I started by making a simple Pixel class, that would hold my color information and make image operations simple. It looks something like this:

class Pixel
{
    public:
    Pixel() : r(0), g(0), b(0), a(255) {}
    void operator=(const Pixel& rhs)
    {
        r = rhs.r;
        g = rhs.g;
        b = rhs.b;
        a = rhs.a;
    }

    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
}

You can add more functionality of course, but for the sake of this tutorial let’s keep it nice and simple.

Now that we have our Pixel class setup, store your loaded image into a 2D vector of Pixels. This will allow for easy visualization of the algorithms I am covering today as well as future image processing algorithms. And now, without further ado lets get into the two resizing algorithms.

Nearest Neighbor:
Nearest Neighbor is a very simple algorithm. In order to get the missing pixel information, we simply round to get the closest corresponding pixel in the original image. Here are the variables we will need:
imageIn: the 2D vector of Pixels representing the original image
imageOut: the 2D vector of Pixels that will represent the resized image
width: will be the width of the resized image
height: will be the height of the resized image
xRatio: the ratio of the amount that the window has resized in the X direction
yRatio: the ratio of the amount that the window has resized in the Y direction

void NearestNeighbor(const std::vector& imageIn, std::vector& imageOut, int& width, int& height, double xRatio, double yRatio)
{
        //calculate the new image size based on how the screen was resized
	width = (int)(originalWidth / xRatio);
	height = (int)(originalHeight / yRatio);

	imageOut.resize(height);
	 for(int y = 0; y = imageIn.size())
			 yIndex = imageIn.size() - 1;

		 imageOut[y].resize(width);
		 for (int x = 0; x = imageIn[0].size())
				 xIndex = imageIn[0].size() - 1;

			 imageOut[y][x] = (imageIn[yIndex][xIndex]);
		 }
	 }
}

Bilinear Interpolation:
Bilinear Interpolation, although more complicated, will often generate a more accurate result than Nearest Neighbor. It takes the information of pixels near the pixel we are looking at then interpolates between them, giving us a great guess of what information that pixel should have. We will use the same inputs as we did for Nearest Neighbor:
imageIn: the 2D vector of Pixels representing the original image
imageOut: the 2D vector of Pixels that will represent the resized image
width: will be the width of the resized image
height: will be the height of the resized image
xRatio: the ratio of the amount that the window has resized in the X direction
yRatio: the ratio of the amount that the window has resized in the Y direction

void BilinearInterpolation(const std::vector& imageIn, std::vector& imageOut, int& width, int& height, double xRatio, double yRatio)
{
    //calculate new image size based on how the screen was resized
    width= (int)(originalWidth / xRatio);
    height= (int)(originalHeight / yRatio);

    imageOut.resize(height);
    for (int y = 0; y = imageIn.size())
            y1 = imageIn.size() - 2;
        if (y2 >= imageIn.size())
            y2 = imageIn.size() - 1;

        //the interpolation value for the y direction
        float beta = (float)(y2 - y1) / (float)(y - y1);

        imageOut[y].resize(width);
        for (int x = 0; x = imageIn[0].size())
                x1 = imageIn[0].size() - 2;
            if (x2 >= imageIn[0].size())
                x2 = imageIn[0].size() - 1;

            Pixel pixel;
            //the interpolation value for the x direction
            float alpha = (float)(x2 - x1) / (float)(x - x1);

            Pixel fxy1, fxy2;

            for (int i = 0; i < 3; ++i)
            {
                fxy1[i] = (unsigned char)((1.0f - alpha) * (imageIn[y1][x1])[i] + alpha * (imageIn[y1][x2])[i]);
                fxy2[i] = (unsigned char)((1.0f - alpha) * (imageIn[y2][x1])[i] + alpha * (imageIn[y2][x2])[i]);
            }

            for (int i = 0; i < 3; ++i)
            {
                pixel[i] = (unsigned char)((1.0f - beta) * fxy1[i] + beta * fxy2[i]);
            }

            imageOut[y][x] = pixel;
        }
    }
}

And that's that! Now all you have to do is convert the 2D vector of Pixels to whatever you will be passing in to your graphics pipeline. Have fun!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s