Nearest Neighbor Interpolation in C#

This is the first article of the series Image Scaling Algorithms in C#.

Scaling refers to resizing an image (small to large or vice versa) using mathematical formulas. Scaling images is a frequently-used task—games, image viewers, media players, and even thumbnails. The common algorithms include the following: Nearest-neighbor interpolation, Bilinear interpolation, Trilinear interpolation, and Bicubic interpolation. [1]

We will begin with the Nearest neighbor interpolation which is also the simplest and fastest scaling algorithm.

How Does It Work?

Nearest Neighbor Interpolation is very simple to implement and is still commonly used today. It works by simply copying the nearest neighboring pixel, hence the name, disregarding the values of all other neighboring pixels. However, this process would result in a jagged image because it simply gets a copied constant value. On upscaling (scaling from smaller to a larger size), it copies pixels. On downscaling (larger to smaller), it discards pixels. [2]

A visualization of Nearest Neighbor Interpolation. Each colored cell indicates the area in which all the points have the black point in the cell as their nearest black point. [3]

C# Code to Handle Matrixes

var img = new int[,] {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12},
    {13, 14, 15, 16},
    {17, 18, 19, 20},
};

Console.WriteLine("Original:");
PrintPixels(img);

Console.WriteLine("\n16 x 16 (Nearest Neighbor)");
PrintPixels(NearestNeighbor(img, 16, 16));

static void PrintPixels(int[,] pixels) {
    var width = pixels.GetLength(0);
    var height = pixels.GetLength(1);

    // across width
    for (var x = 0; x < width; x++) {
        // height
        for (var y = 0; y < height; y++) {
            var value = pixels[x, y];
            if (value < 10) Console.Write("0" + value + " ");
            else Console.Write(value + " ");
        }

        Console.WriteLine();
    }
}


static int[,] NearestNeighbor(int[,] data, int newWidth, int newHeight) {
    if (newWidth < 1 || newHeight < 1) {
        throw new ArgumentException("Interpolation: Expected output size to be at least 1x1.");
    }

    var newImage = new int[newWidth, newHeight];

    var oldWidth = data.GetLength(0);
    var oldHeight = data.GetLength(1);

    // calculate as double for precision
    var xRatio = (double)oldWidth / newWidth;
    var yRatio = (double)oldHeight / newHeight;

    for (int x = 0; x < newWidth; x++) {
        for (int y = 0; y < newHeight; y++) {
            var xUnscaled = (int)(x * xRatio);
            var yUnscaled = (int)(y * yRatio);

            // copy pixel from old image
            newImage[x, y] = data[xUnscaled, yUnscaled];
        }
    }

    return newImage;
}

Output

Original:
01 02 03 04
05 06 07 08
09 10 11 12
13 14 15 16
17 18 19 20

16 x 16 (Nearest Neighbor)
01 01 01 01 02 02 02 02 03 03 03 03 04 04 04 04
01 01 01 01 02 02 02 02 03 03 03 03 04 04 04 04
01 01 01 01 02 02 02 02 03 03 03 03 04 04 04 04
01 01 01 01 02 02 02 02 03 03 03 03 04 04 04 04
05 05 05 05 06 06 06 06 07 07 07 07 08 08 08 08
05 05 05 05 06 06 06 06 07 07 07 07 08 08 08 08
05 05 05 05 06 06 06 06 07 07 07 07 08 08 08 08
09 09 09 09 10 10 10 10 11 11 11 11 12 12 12 12
09 09 09 09 10 10 10 10 11 11 11 11 12 12 12 12
09 09 09 09 10 10 10 10 11 11 11 11 12 12 12 12
13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16
13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16
13 13 13 13 14 14 14 14 15 15 15 15 16 16 16 16
17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20
17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20
17 17 17 17 18 18 18 18 19 19 19 19 20 20 20 20

We can break this code into two parts: first, getting the scaling ratio, and second, copying the pixel from the old image based on the ratio.

The formula for ratios and proportion is as follows:

a : b = ma : mb,
A = ma, B = mb
where m is a constant.

We can rewrite this formula to get m.

m = a / A

Applied to our code, this can be used to calculate the ratio of the new resolution against the old resolution.

var xRatio = (double)oldWidth / newWidth;

Do the same for y-value.

var yRatio = (double)oldHeight / newHeight;

Looping through each new pixel of the new matrix, we can then determine which pixel to copy from the old matrix.

var xUnscaled = (int)(x * xRatio);

And again, repeat the same process for y-value.

var yUnscaled = (int)(y * yRatio);

C# Code to Handle Bitmaps

The code to handle bitmaps is similar to how we handled a matrix. The difference is we handled each pixel and copied it to the new Bitmap.

var oldImg = new Bitmap(filename: "IMG_20190526_130340.jpg");

ResizeImageNearestNeighbor(oldImg, newWidth: oldImg.Width * 4, newHeight: oldImg.Height * 4)
.Save("IMG_20190526_130340_nearestneighbor.jpg", ImageFormat.Jpeg);

Results

Original Image
Resized Four Times Larger Using Nearest Neighbor Interpolation.

[1] Wikipedia Contributors. (2022, January 18). Image scaling. Wikipedia; Wikimedia Foundation. https://en.wikipedia.org/wiki/Image_scaling

[2] Wikipedia Contributors. (2021, September 1). Nearest-neighbor interpolation. Wikipedia; Wikimedia Foundation. https://en.wikipedia.org/wiki/Nearest-neighbor_interpolation

[3] This is an image from the Wikimedia Commons.