Click here to Skip to main content
Click here to Skip to main content

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

By , 14 Apr 2002
 

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)
    }
    else
    {
        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.

License

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
Member
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 asp.net 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 now I work for the new owners.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralLicensememberCaptain Groovy4 Feb '10 - 13:59 
I noticed there is no license specified for this code. Can you kindly provide license terms? Thanks.
GeneralLicense - BumpmemberCaptain Groovy11 Feb '10 - 12:47 
Hi Christian,
 
I’m interested in using your Image Processing code. My employer requires we get an explicit license to use any third party code and I didn’t see any kind of license on your site. Would you be so kind as to reply to this message “Yes” indicating you are willing to grant my company and its successors the right to use your Image Processing algorithm under the BSD license set forth below.
 
Thanks,
Jeff Cutler-Stamm
 
_________________
 
BSD 2.0
Copyright (c) 2010, Christian Graus
All rights reserved.
 
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the Christian Graus nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
GeneralRe: License - BumpmemberRavi Bhavnani7 May '10 - 16:43 
Jeff, you may also want to check out AForge[^] (GLPL).
 
/ravi
My new year resolution: 2048 x 1536
Home | Articles | My .NET bits | Freeware
 
ravib(at)ravib(dot)com

GeneralRe: LicensemvpChristian Graus24 Aug '10 - 23:44 
Sorry, I just never did it. I've done it now.
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

QuestionCreate a bmpmemberJollyMansArt28 Aug '09 - 7:46 
I would like to create a bmp file 1 pixel by 1 pixel that has the same name as the file I am creating. THe issue I am having is simply the part of creating a bmp file in code. The fact that it is 1 pixel by 1 pixel means this image I really do not care for the end user to be able to see, as it's purpose is to block MS Access Splash Screen from showing when the mdb files are opening.
 
I can handle everything but the creation of a bmp file that is 1 pixel by 1 pixel in code. Can some one help me or point me in the direction of how to build such a bmp file in code without coping a recreated file.
AnswerRe: Create a bmpmvpChristian Graus24 Aug '10 - 23:45 
Bitmap b = new Bitmap(1,1,ColorSpaceInfo); ? I forget how to specify the bit depth.
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

Answerfaster method than setpixel and get pixelmemberMortezai29 Jun '09 - 8:07 
dear sir
 
i need a high speed method to make 4 image from one, that the first image have for example 2048*1536 pixel, and the four made is each 1024*768 , so i use the getpixel and setpixel method that is too slow :
 
vb.net code ::
 
For y = 0 To picy.Height - 1 Step 2
For x = 0 To picy.Width - 1 Step 2
pic(0).SetPixel(Int(x / 2), Int(y / 2), picy.GetPixel(x, y))
pic(1).SetPixel(Int(x / 2), Int(y / 2), picy.GetPixel(x + 1, y))
pic(2).SetPixel(Int(x / 2), Int(y / 2), picy.GetPixel(x + 1, y + 1))
pic(3).SetPixel(Int(x / 2), Int(y / 2), picy.GetPixel(x, y + 1))
Next x
Next y
 
please help me to find a very faster method in any languages, vc++ or vb or vc# or others , its not important i need a fast method,
 
thank you for attentions and cooperations.
GeneralRe: faster method than setpixel and get pixelmvpChristian Graus24 Aug '10 - 23:45 
Had you read my articles, you'd have found the answer to your question. All my articles use direct pixel access.
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

GeneralMy vote of 1groupiamhycljc30 Mar '09 - 16:46 
Bilinear Filtering is invalid for the binary image
GeneralRe: My vote of 1mvpChristian Graus24 Aug '10 - 23:46 
What on earth are you talking about ?
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

QuestionDoes not work on asp.netmemberRob Gaudet28 Jan '09 - 17:15 
I challenge anyone to show how to make this work in asp.net.
AnswerRe: Does not work on asp.netmemberyassir hannoun3 Feb '09 - 13:29 
well it is done as if you are doing it with windows forms
AnswerRe: Does not work on asp.netmvpChristian Graus24 Aug '10 - 23:46 
How bizarre. This will work fine in ASP.NET, although it's not an ASP.NET article, and you'd need to process images on the server, then send them back to the client. I do't see any reason to do that, but it works just fine. Perhaps you just don't understand ASP.NET ?
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

QuestionSurely this algorithm can't be right?memberShrog13 Dec '08 - 7:22 
As far as I can tell, this will only work for images that you resize slightly - between 50% and 100% of the original. Unless I am misunderstanding how bilinear resizing is supposed to work.
 
If I have an image that is 50x50 pixels in size and I want to resize it to 10x10, then each pixel in the smaller image must be the average of 25 pixels (5x5). This algorithm assumes that your "ceiling" and "floor" will never be more than 1 pixel appart.
 
As soon as you start resizing to less than 50% of the original size, you start dropping pixels and the one_minus_x and one_minus_y values become totally incorrect.
AnswerRe: Surely this algorithm can't be right?memberAaron Sinclair3 Feb '10 - 15:27 
It is correct - you don't take a sample of more than 1 pixel, you calculate the exact pixel point of where the new pixel would be on the old image. You'll get a decimal value (eg: 26.675), and then you only have to take a certain percentage of the pixel on either side of it. This one stumped me too, and I thought the same as you Big Grin | :-D however now I understand. I know it's 2 years ago, but maybe this will help someone else make sense of this :P
AnswerRe: Surely this algorithm can't be right?mvpChristian Graus24 Aug '10 - 23:48 
As someone else said, the whole point of a bilinear filter is to work out fuzzy values, where you do a resize that isn't perfect ( as 50x50 to 10x10 is, what if it was 11x11 ) ? It's used to calculate the color of an imaginary pixel that exists between two actual ones.
Christian Graus
 
Driven to the arms of OSX by Vista.
 
Read my blog to find out how I've worked around bugs in Microsoft tools and frameworks.

GeneralResizing indexed colors picturememberMathieuGardere25 Mar '08 - 7:51 
I just had an error when trying to resize a GIF and an exception was thrown because it is impossible to call SetPixel method on an indexed color image.
 
To fix this, just update a line in the Resize method:
b = new Bitmap(nWidth, nHeight, bTemp.PixelFormat);
You can use any PixelFormat enumeration value which doesnt stand for an indexed format.
For instance:
b = new Bitmap(nWidth, nHeight, PixelFormat.Format24bppRgb);
 
______________________
Mathieu Gardère
wam@mathieugardere.co.uk

GeneralThanks a lotmemberahmedsayed_ig7 Oct '06 - 14:44 
A great Article from a great Author. Thanks Christian Graus.
I read many articles that you wrote, and they really rock!! Smile | :)
GeneralNice - especially for GreyscalesmemberTrevor Larkum1 Jul '06 - 10:51 
I have been struggling to use GDI+ for bilinear resizing of 16 bit greyscale bitmaps, as used often for terrain heightmaps, but have given up because it is so buggy (I understand now that MS have admitted the greyscale handling was never completed and wasn't intended to be shipped). I tried your code (adjusted for 16bit pixel data) and it worked great! 5 from me.
Generalabout the sphere algorithmmemberniannian20 Dec '05 - 20:56 
Hello, I have read your artical about GDI+ in www.codeproject.com
And I learn many from your articals.Thank you.
Now I have a question .Can you help me?
 
In your artical "Image Processing for Dummies with C# and GDI+ Part 5 - Displacement filters, including swirl" , there is a sphere effect .
Now I want to know how to make the concave effect, which is opposed to sphere effect. Can you help me?
 
niannina
GeneralRe: about the sphere algorithmmemberChristian Graus21 Dec '05 - 11:12 
The sphere effect, and most of these effects, where really the result of my playing with the framework I came up with, and so this does not represent my trying to write the best sphere filter in the world.
 
What you're talking about is texture mapping, you want to map the texture to the inside of a sphere. A google on texture mapping may give you some help in this area, also a barrel filter goes some of the way to doing something like this, that may be another term that will help you. I'm sorry not to give a more detailed answer, but right now I am really flooded with work. If you still need help in the new year, feel free to ask for more.

 
Christian Graus - Microsoft MVP - C++
QuestionNice Work :) But how about an Article about Dithering?memberlordk2k14 Apr '05 - 3:20 
Hi,
 
your Articles about Image Processing are very good, but
i need some help about dithering Images in C#.
 
I mean something like reducing Images to 1bit Bmps with
dithers like Floyd-Steinberg or Bayer 4x4...
 
Maybe you can do another article about dithering Wink | ;)
 
Greets
 
LordK
AnswerRe: Nice Work :) But how about an Article about Dithering?memberChristian Graus14 Apr '05 - 12:09 
Good idea. I didn't think anyone used that stuff anymore, I thought we were all on 24 bit displays and looking forward to HDR Smile | :) I'll consider doing something sometime soonish.
 
Christian
 
I have several lifelong friends that are New Yorkers but I have always gravitated toward the weirdo's. - Richard Stringer
GeneralRe: Nice Work :) But how about an Article about Dithering?memberlordk2k15 Apr '05 - 2:13 
Thanks a lot Smile | :)
 
I need 1bit bmps for a 5,2" monochrom Graphic LCD, which is external connected to my PC via USB.
I also found the algorythms for the dithers from the cxImage Project, but only in C++ and
my programming skill in c++ / c# is not high enough to convert it to c#.
 
So if you could help a bit, it would be very nice Big Grin | :-D
 
greetings
 
LordK
GeneralRe: Nice Work :) But how about an Article about Dithering?membernot_in_use5 Aug '07 - 9:55 
I know this post was made quite some time ago, but I thought I'd reply anyway:
 
Dithering is actually still necessary from time to time, even in 24 bit. For example, if your program draws a gradient and you want to avoid banding artifacts, there is no way around it, especially if the colors you are blending are very similar.
 
Even with 16 bit per channel and HDR, it is still an issue. For example, whenever an application displays a 16-Bit or HDR image on an 8-bit-per-channel screen (or saves an 8-bit JPEG for that matter), it should do dithering in order to get the highest quality possible. As far as I know Photoshop handles things that way.
 
Peter

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

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