James South
James South

Reputation: 10645

Correctly executing bicubic resampling

I've been experimenting with the image bicubic resampling algorithm present in the AForge framework with the idea of introducing something similar into my image processing solution. See the original algorithm here and interpolation kernel here

Unfortunately I've hit a wall. It looks to me like somehow I am calculating the sample destination position incorrectly, probably due to the algorithm being designed for Format24bppRgb images where as I am using a Format32bppPArgb format.

Here's my code:

public Bitmap Resize(Bitmap source, int width, int height)
{
    int sourceWidth = source.Width;
    int sourceHeight = source.Height;

    Bitmap destination = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
    destination.SetResolution(source.HorizontalResolution, source.VerticalResolution);

    using (FastBitmap sourceBitmap = new FastBitmap(source))
    {
        using (FastBitmap destinationBitmap = new FastBitmap(destination))
        {
            double heightFactor = sourceWidth / (double)width;
            double widthFactor = sourceHeight / (double)height;

            // Coordinates of source points
            double ox, oy, dx, dy, k1, k2;
            int ox1, oy1, ox2, oy2;

            // Width and height decreased by 1
            int maxHeight = height - 1;
            int maxWidth = width - 1;

            for (int y = 0; y < height; y++)
            {
                // Y coordinates
                oy = (y * widthFactor) - 0.5;

                oy1 = (int)oy;
                dy = oy - oy1;

                for (int x = 0; x < width; x++)
                {
                    // X coordinates
                    ox = (x * heightFactor) - 0.5f;
                    ox1 = (int)ox;
                    dx = ox - ox1;

                    // Destination color components
                    double r = 0;
                    double g = 0;
                    double b = 0;
                    double a = 0;

                    for (int n = -1; n < 3; n++)
                    {
                        // Get Y cooefficient
                        k1 = Interpolation.BiCubicKernel(dy - n);

                        oy2 = oy1 + n;
                        if (oy2 < 0)
                        {
                            oy2 = 0;
                        }

                        if (oy2 > maxHeight)
                        {
                            oy2 = maxHeight;
                        }

                        for (int m = -1; m < 3; m++)
                        {
                            // Get X cooefficient
                            k2 = k1 * Interpolation.BiCubicKernel(m - dx);

                            ox2 = ox1 + m;
                            if (ox2 < 0)
                            {
                                ox2 = 0;
                            }

                            if (ox2 > maxWidth)
                            {
                                ox2 = maxWidth;
                            }

                            Color color = sourceBitmap.GetPixel(ox2, oy2);

                            r += k2 * color.R;
                            g += k2 * color.G;
                            b += k2 * color.B;
                            a += k2 * color.A;
                        }
                    }

                    destinationBitmap.SetPixel(
                        x, 
                        y, 
                        Color.FromArgb(a.ToByte(), r.ToByte(), g.ToByte(), b.ToByte()));
                }
            }

        }
    }

    source.Dispose();
    return destination;
}

And the kernel which should represent the given equation on Wikipedia

public static double BiCubicKernel(double x)
{
    if (x < 0)
    {
        x = -x;
    }

    double bicubicCoef = 0;

    if (x <= 1)
    {
        bicubicCoef = (1.5 * x - 2.5) * x * x + 1;
    }
    else if (x < 2)
    {
        bicubicCoef = ((-0.5 * x + 2.5) * x - 4) * x + 2;
    }

    return bicubicCoef;
}

Here's the original image at 500px x 667px.

picture of a tree

And the image resized to 400px x 543px.

tree with bicubic algorithm applied

Visually it appears that the image is over reduced and then the same pixels are repeatedly applied once we hit a particular point.

Can anyone give me some pointers here to solve this?

Note FastBitmap is a wrapper for Bitmap that uses LockBits to manipulate pixels in memory. It works well with everything else I apply it to.

Edit

As per request here's the methods involved in ToByte

public static byte ToByte(this double value)
{
    return Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
}

public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
    if (value.CompareTo(min) < 0)
    {
        return min;
    }

    if (value.CompareTo(max) > 0)
    {
        return max;
    }

    return value;
}

Upvotes: 2

Views: 1991

Answers (2)

vgru
vgru

Reputation: 51282

You are limiting your ox2 and oy2 to destination image dimensions, instead of source dimensions.

Change this:

// Width and height decreased by 1
int maxHeight = height - 1;
int maxWidth = width - 1;

to this:

// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
int maxWidth = sourceWidth - 1;

Upvotes: 2

netaholic
netaholic

Reputation: 1385

Well, I've met a very strange thing, which might be or might be not a souce of the problem. I've started to try implementing convolution matrix by myself and encountered strange behaviour. I was testing code on a small image 4x4 pixels. The code is following:

 var source = Bitmap.FromFile(@"C:\Users\Public\Pictures\Sample Pictures\Безымянный.png");
 using (FastBitmap sourceBitmap = new FastBitmap(source))
        {
            for (int TY = 0; TY < 4; TY++)
            {
                for (int TX = 0; TX < 4; TX++)
                {
                    Color color = sourceBitmap.GetPixel(TX, TY);
                    Console.Write(color.B.ToString().PadLeft(5));
                }
                Console.WriteLine();
            }
        }

The output

Althought I'm printing out only blue channel value, it's still clearly incorrect.

On the other hand, your solution partitially works, what makes the thing I've found kind of irrelevant. One more guess I have: what is your system's DPI?

From what I have found helpfull, here are some links:

That's my answer so far, but I will try further.

Upvotes: 1

Related Questions