Click here to Skip to main content
12,814,411 members (39,200 online)
Click here to Skip to main content
Add your own
alternative version


178 bookmarked
Posted 14 Apr 2002

Image Processing for Dummies with C# and GDI+ Part 4 - Bilinear Filters and Resizing

, 14 Apr 2002 CPOL
Rate this:
Please Sign up or sign in to vote.
The fourth installment covers how to write a filter that resizes an image, and uses bilinear filtering

Sample Image

Here we go again...

Well, this is the fourth installment in the series, and I thank you for sticking around this long.  I am wanting to do some groundwork for a future article, which will be involved enough that I didn't want to add bilinear filtering to the mix at that stage, so I'm covering it here.  In a nutshell, bilinear filtering is a method of increasing the accuracy with which we can select pixels, by allowing the selection of pixels in between the ones we draw.  Honest!!! To illustrate it's effect, we are going to write a resize filter first, then we are going to add a bilinear filter to see the effect it has on the final result.

A resize filter.

If you want to resize an image arbitrarily, the easiest way to do it is to calculate a factor for the difference between the source and destination in both x and y axes, then use that factor to figure out which pixel on the source image maps to the colour being placed on the destination image.  Note for this filter I step through the destination image and calculate the source pixels from there, this ensures that no pixels in the destination image are not filled.

SetPixel ?

Before I show you the code, you'll notice that I've chosen to use Set/GetPixel this time around instead of getting a pointer to my bitmap data.  This does two things for me, firstly it means my code is not 'unsafe', and secondly, it makes the code a lot simpler, which will help when we add the bilinear filter, which does enough work without my sample being cluttered by all the pointer lookup code that would also be required, as you will see.

The code

Here then is my code for a function that resizes a bitmap, fills it with data from a copy that was made prior, and then returns it.  Note that unlike my other functions, I found I had to return the new Bitmap because when I create one of a new size, it is no longer the same bitmap that is referred to by the 'in' parameter, and therefore I am unable to return a bool to indicate success.

public static Bitmap Resize(Bitmap b, int nWidth, int nHeight, bool bBilinear)
    Bitmap bTemp = (Bitmap)b.Clone();
    b = new Bitmap(nWidth, nHeight, bTemp.PixelFormat);

    double nXFactor = (double)bTemp.Width/(double)nWidth;
    double nYFactor = (double)bTemp.Height/(double)nHeight;

    if (bBilinear)
        // Not yet 80)
        for (int x = 0; x < b.Width; ++x)
            for (int y = 0; y < b.Height; ++y)
                b.SetPixel(x, y, bTemp.GetPixel((int)(Math.Floor(x * nXFactor)),
                          (int)(Math.Floor(y * nYFactor))));

    return b;

In order to highlight the artifacts we get from such a filter, I have taken an image of Calvin and increased the width while decreasing the height ( both by 10 pixels ) several times, to get the following:

As you can see, things start to deteriorate fairly rapidly.

Bilinear Filtering

The problem we are having above is that we are not grabbing the pixels we want a lot of the time.  If we resize an image of 100 x 100 to 160 x 110, for example, then our X scale is 100/160, or .625.  In other words, to fill column 43, we need to look up column (43 * .625), or 26.875.  Obviously, we are not able to look up such a value, we will end up with column 27.  In this case, the difference is slight, but we can obviously end up with decimal values including .5, right in the middle between two existing pixels.  The image above shows how such small rounding of values can accumulate to cause image quality to deteriorate.  The solution is obviously to look up the values without rounding.  How do we look up a pixel that does not exist ? We interpolate it from the values we can look up.  By reading the values of all the surrounding pixels, and then weighting those values according to the decimal part of the pixel value, we can construct the value of the sub pixel.  For example, in the above example, we would multiply the values of column 26 by .875, and the values of column 27 by .125 to find the exact value required.

In order to make the example clearer, I have used GetPixel to read the four pixels in the area surrounding the subpixel we want to find.  In a future example I will use direct pixel access, which will be faster, but also a lot more complex.  The variable names have been chosen to help clarify what is going on.  Here is the missing code from above, the code which is executed when bBilinear = true .

if (bBilinear)
    double fraction_x, fraction_y, one_minus_x, one_minus_y;
    int ceil_x, ceil_y, floor_x, floor_y;
    Color c1 = new Color();
    Color c2 = new Color();
    Color c3 = new Color();
    Color c4 = new Color();
    byte red, green, blue;

    byte b1, b2;

    for (int x = 0; x < b.Width; ++x)
        for (int y = 0; y < b.Height; ++y)
            // Setup

            floor_x = (int)Math.Floor(x * nXFactor);
            floor_y = (int)Math.Floor(y * nYFactor);
            ceil_x = floor_x + 1;
            if (ceil_x >= bTemp.Width) ceil_x = floor_x;
            ceil_y = floor_y + 1;
            if (ceil_y >= bTemp.Height) ceil_y = floor_y;
            fraction_x = x * nXFactor - floor_x;
            fraction_y = y * nYFactor - floor_y;
            one_minus_x = 1.0 - fraction_x;
            one_minus_y = 1.0 - fraction_y;

            c1 = bTemp.GetPixel(floor_x, floor_y);
            c2 = bTemp.GetPixel(ceil_x, floor_y);
            c3 = bTemp.GetPixel(floor_x, ceil_y);
            c4 = bTemp.GetPixel(ceil_x, ceil_y);

            // Blue
            b1 = (byte)(one_minus_x * c1.B + fraction_x * c2.B);

            b2 = (byte)(one_minus_x * c3.B + fraction_x * c4.B);
            blue = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

            // Green
            b1 = (byte)(one_minus_x * c1.G + fraction_x * c2.G);

            b2 = (byte)(one_minus_x * c3.G + fraction_x * c4.G);
            green = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

            // Red
            b1 = (byte)(one_minus_x * c1.R + fraction_x * c2.R);

            b2 = (byte)(one_minus_x * c3.R + fraction_x * c4.R);
            red = (byte)(one_minus_y * (double)(b1) + fraction_y * (double)(b2));

            b.SetPixel(x,y, System.Drawing.Color.FromArgb(255, red, green, blue));

The result is as follows:

As you can see, this is a much better result.  It looks like it has gone through a softening filter, but it looks much better than the one above.  It is possible to get a slightly better code by going through a much more complex process called bicubic filtering, but I do not intend to cover it, simply because I've never done it.

What's Next

As I said above, the whole point of this article was to illustrate how bilinear filtering works.  A bilinear filter will be employed in my next article, which will talk about the process of writing a filter from scratch to be optimised for a particular process instead of through the sort of generic processes we've used so far.  At that point we will reimpliment the bilinear filter to be more optimised, but hopefully this version has helped you to understand that part of the process, so we can focus on other aspects in the next article.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Christian Graus
Software Developer (Senior)
Australia Australia
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and I worked for the owners for five years before leaving to get away from the travel, and spend more time with my family. I now work for a company here in Hobart, doing all sorts of Microsoft based stuff in C++ and C#, with a lot of T-SQL in the mix.

You may also be interested in...


Comments and Discussions

GeneralRe: My vote of 1 Pin
Christian Graus25-Aug-10 0:46
mvpChristian Graus25-Aug-10 0:46 
QuestionDoes not work on Pin
Rob Gaudet28-Jan-09 18:15
memberRob Gaudet28-Jan-09 18:15 
AnswerRe: Does not work on Pin
yassir hannoun3-Feb-09 14:29
memberyassir hannoun3-Feb-09 14:29 
AnswerRe: Does not work on Pin
Christian Graus25-Aug-10 0:46
mvpChristian Graus25-Aug-10 0:46 
QuestionSurely this algorithm can't be right? Pin
Shrog13-Dec-08 8:22
memberShrog13-Dec-08 8:22 
AnswerRe: Surely this algorithm can't be right? Pin
Aaron Sinclair3-Feb-10 16:27
memberAaron Sinclair3-Feb-10 16:27 
AnswerRe: Surely this algorithm can't be right? Pin
Christian Graus25-Aug-10 0:48
mvpChristian Graus25-Aug-10 0:48 
GeneralResizing indexed colors picture Pin
MathieuGardere25-Mar-08 8:51
memberMathieuGardere25-Mar-08 8:51 
GeneralThanks a lot Pin
ahmedsayed_ig7-Oct-06 15:44
memberahmedsayed_ig7-Oct-06 15:44 
GeneralNice - especially for Greyscales Pin
Trevor Larkum1-Jul-06 11:51
memberTrevor Larkum1-Jul-06 11:51 
Generalabout the sphere algorithm Pin
niannian20-Dec-05 21:56
memberniannian20-Dec-05 21:56 
GeneralRe: about the sphere algorithm Pin
Christian Graus21-Dec-05 12:12
memberChristian Graus21-Dec-05 12:12 
QuestionNice Work :) But how about an Article about Dithering? Pin
lordk2k14-Apr-05 4:20
memberlordk2k14-Apr-05 4:20 
AnswerRe: Nice Work :) But how about an Article about Dithering? Pin
Christian Graus14-Apr-05 13:09
memberChristian Graus14-Apr-05 13:09 
GeneralRe: Nice Work :) But how about an Article about Dithering? Pin
lordk2k15-Apr-05 3:13
memberlordk2k15-Apr-05 3:13 
GeneralRe: Nice Work :) But how about an Article about Dithering? Pin
not_in_use5-Aug-07 10:55
membernot_in_use5-Aug-07 10:55 
GeneralGDI image quality PNG vs JPG Pin
siftee24-Jan-05 22:52
membersiftee24-Jan-05 22:52 
GeneralRe: GDI image quality PNG vs JPG Pin
Christian Graus25-Jan-05 13:51
memberChristian Graus25-Jan-05 13:51 
GeneralRe: GDI image quality PNG vs JPG Pin
siftee25-Jan-05 21:50
membersiftee25-Jan-05 21:50 
GeneralRe: GDI image quality PNG vs JPG Pin
Christian Graus26-Jan-05 10:43
memberChristian Graus26-Jan-05 10:43 
GeneralRe: GDI image quality PNG vs JPG Pin
harningt9-May-05 14:10
sussharningt9-May-05 14:10 
GeneralNice, but... Pin
Point24-Jan-05 22:08
memberPoint24-Jan-05 22:08 
GeneralRe: Nice, but... Pin
Christian Graus25-Jan-05 11:18
memberChristian Graus25-Jan-05 11:18 
GeneralRe: Nice, but... Pin
Point25-Jan-05 12:46
memberPoint25-Jan-05 12:46 
GeneralRe: Nice, but... Pin
Christian Graus25-Jan-05 13:49
memberChristian Graus25-Jan-05 13:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170308.1 | Last Updated 15 Apr 2002
Article Copyright 2002 by Christian Graus
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid