Oil Painting Filter in C#

Converting an image to make it look like an oil painting is not only a nice effect, but is also an easy to implement algorithm. This article demonstrates how to create an oil painting effect from an image. The oil painting filter consists of two main components: color gradients and pixel color intensities. Result images express a lesser degree of detail and tend to appear to have smaller color ranges.

Implementation

The following steps are required for the algorithm:

  1. Iterate each pixel – Every pixel forming part of the source image should be iterated. When iterating a pixel determine the neighboring pixel values based on the specified filter size (or radius).
  2. Calculate color intensityDetermine the Color Intensity of each pixel being iterated and that of the neighboring pixels. The neighboring pixels included should extend to a range determined by the Filter Size specified. The calculated value should be reduced in order to match a value ranging from zero to the number of Intensity Levels specified.
  3. Determine maximum neighborhood color intensity – When calculating the color intensities of a pixel neighborhood determine the maximum intensity value. In addition, record the occurrence of each intensity level and sum each of the Red, Green and Blue pixel color component values equating to the same intensity level.
  4. Assign the result pixel – The value assigned to the corresponding pixel in the resulting image equates to the pixel color sum total, where those pixels expressed the same intensity level. The sum total should be averaged by dividing the color sum total by the intensity level occurrence

When calculating color intensity reduced to fit the number of levels specified the formula can be expressed as follows:

colorintensityalgorithm.png

The variables in the above formula can be described as follows:

  • i – Intensity: the calculated intensity value.
  • R – Red: The value of the pixel’s red color component.
  • G – Green:
  • B – Blue:
  • l – Number of intensity levels: the maximum number of intensity levels specified.

Result Image: Filter 15, Levels 30

Oil Painting - Filter 15, Levels 30

The sample source code defines OilPaintFilter method, an extension method targeting the Bitmap class. This method determines the maximum color intensity from a pixel’s neighbors.

public static Bitmap OilPaintFilter(this Bitmap sourceBitmap, int levels, int filterSize)
        {
            Bitmap image;
            ArgbColor[] originalData;
            ArgbColor[] resultData;
            Size size;

            image = sourceBitmap;
            size = image.Size;

            originalData = image.GetPixelsFrom32BitArgbImage();
            resultData = new ArgbColor[size.Width * size.Height];

            int[] intensityBin = new int[levels];
            int[] blueBin = new int[levels];
            int[] greenBin = new int[levels];
            int[] redBin = new int[levels];

            levels = levels - 1;

            int filterOffset = (filterSize - 1) / 2;
            int byteOffset = 0;
            int calcOffset = 0;
            int currentIntensity = 0;
            int maxIntensity = 0;
            int maxIndex = 0;

            double blue = 0;
            double green = 0;
            double red = 0;

            for (int offsetY = filterOffset; offsetY < size.Height - filterOffset; offsetY++)
            {
                for (int offsetX = filterOffset; offsetX < size.Width - filterOffset; offsetX++)
                {
                    ArgbColor result;

                    blue = green = red = 0;

                    currentIntensity = maxIntensity = maxIndex = 0;

                    intensityBin = new int[levels + 1];
                    blueBin = new int[levels + 1];
                    greenBin = new int[levels + 1];
                    redBin = new int[levels + 1];

                    byteOffset = offsetY * size.Width + offsetX;

                    for (int filterY = -filterOffset; filterY <= filterOffset; filterY++)
                    {
                        for (int filterX = -filterOffset; filterX <= filterOffset; filterX++)
                        {
                            calcOffset = byteOffset + (filterX) + (filterY * size.Width);

                            currentIntensity = (int)Math.Round(((double)(originalData[calcOffset].R +
                                                originalData[calcOffset].G + originalData[calcOffset].B) / 3.0 * (levels)) / 255.0);

                            intensityBin[currentIntensity] += 1;
                            blueBin[currentIntensity] += originalData[calcOffset].B;
                            greenBin[currentIntensity] += originalData[calcOffset].G;
                            redBin[currentIntensity] += originalData[calcOffset].R;

                            if (intensityBin[currentIntensity] > maxIntensity)
                            {
                                maxIntensity = intensityBin[currentIntensity];
                                maxIndex = currentIntensity;
                            }
                        }
                    }

                    blue = blueBin[maxIndex] / maxIntensity;
                    green = greenBin[maxIndex] / maxIntensity;
                    red = redBin[maxIndex] / maxIntensity;

                    result.A = 255;
                    result.R = ClipByte(red);
                    result.G = ClipByte(green);
                    result.B = ClipByte(blue);

                    resultData[byteOffset] = result;
                }
            }

            return resultData.ToBitmap(size); ;
        }

Downloads

OilPaintingFilter.zip 13 Nov 2014 137 KB

Kuwahara Filter in C#

The Kuwahara filter is probably one of those things that you haven’t heard of much, if ever. It’s a noise reduction/blurring technique for images that preserves edges, similar to a median filter. It acts like a box blur, except we take that box around the center pixel and divide that into four smaller boxes (with some overlap since the number of items in the box is usually odd along the x and y axis). These smaller boxes are calculated in the same fashion as the box blur for the average value. While we’re doing that, we’re also performing another step: Finding the variance for the box. We want to know which box has the least amount of variance. The variance can be calculated in a number of ways but the easiest is simply finding the minimum and maximum values (for r, g and b) for a box and subtracting the min from the max. The bit of code below is based on my Visual Basic source used in RasterWave:

public static Bitmap KuwaharaBlur(Bitmap Image, int Size)
{
    System.Drawing.Bitmap TempBitmap = Image;
    System.Drawing.Bitmap NewBitmap = new System.Drawing.Bitmap(TempBitmap.Width, TempBitmap.Height);
    System.Drawing.Graphics NewGraphics = System.Drawing.Graphics.FromImage(NewBitmap);
    NewGraphics.DrawImage(TempBitmap, new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), new System.Drawing.Rectangle(0, 0, TempBitmap.Width, TempBitmap.Height), System.Drawing.GraphicsUnit.Pixel);
    NewGraphics.Dispose();
    Random TempRandom = new Random();
    int[] ApetureMinX = { -(Size / 2), 0, -(Size / 2), 0 };
    int[] ApetureMaxX = { 0, (Size / 2), 0, (Size / 2) };
    int[] ApetureMinY = { -(Size / 2), -(Size / 2), 0, 0 };
    int[] ApetureMaxY = { 0, 0, (Size / 2), (Size / 2) };
    for (int x = 0; x < NewBitmap.Width; ++x)
    {
        for (int y = 0; y < NewBitmap.Height; ++y)
        {
            int[] RValues = { 0, 0, 0, 0 };
            int[] GValues = { 0, 0, 0, 0 };
            int[] BValues = { 0, 0, 0, 0 };
            int[] NumPixels = { 0, 0, 0, 0 };
            int[] MaxRValue = { 0, 0, 0, 0 };
            int[] MaxGValue = { 0, 0, 0, 0 };
            int[] MaxBValue = { 0, 0, 0, 0 };
            int[] MinRValue = { 255, 255, 255, 255 };
            int[] MinGValue = { 255, 255, 255, 255 };
            int[] MinBValue = { 255, 255, 255, 255 };
            for (int i = 0; i < 4; ++i)
            {
                for (int x2 = ApetureMinX[i]; x2 < ApetureMaxX[i]; ++x2)
                {
                    int TempX = x + x2;
                    if (TempX >= 0 && TempX < NewBitmap.Width)
                    {
                        for (int y2 = ApetureMinY[i]; y2 < ApetureMaxY[i]; ++y2)
                        {
                            int TempY = y + y2;
                            if (TempY >= 0 && TempY < NewBitmap.Height)
                            {
                                Color TempColor = TempBitmap.GetPixel(TempX, TempY);
                                RValues[i] += TempColor.R;
                                GValues[i] += TempColor.G;
                                BValues[i] += TempColor.B;
                                if (TempColor.R > MaxRValue[i])
                                {
                                    MaxRValue[i] = TempColor.R;
                                }
                                else if (TempColor.R < MinRValue[i])
                                {
                                    MinRValue[i] = TempColor.R;
                                }
 
                                if (TempColor.G > MaxGValue[i])
                                {
                                    MaxGValue[i] = TempColor.G;
                                }
                                else if (TempColor.G < MinGValue[i])
                                {
                                    MinGValue[i] = TempColor.G;
                                }
 
                                if (TempColor.B > MaxBValue[i])
                                {
                                    MaxBValue[i] = TempColor.B;
                                }
                                else if (TempColor.B < MinBValue[i])
                                {
                                    MinBValue[i] = TempColor.B;
                                }
                                ++NumPixels[i];
                            }
                        }
                    }
                }
            }
            int j = 0;
            int MinDifference = 10000;
            for (int i = 0; i < 4; ++i)
            {
                int CurrentDifference = (MaxRValue[i] - MinRValue[i]) + (MaxGValue[i] - MinGValue[i]) + (MaxBValue[i] - MinBValue[i]);
                if (CurrentDifference < MinDifference && NumPixels[i] > 0)
                {
                    j = i;
                    MinDifference = CurrentDifference;
                }
            }
 
            Color MeanPixel = Color.FromArgb(RValues[j] / NumPixels[j],
                GValues[j] / NumPixels[j],
                BValues[j] / NumPixels[j]);
            NewBitmap.SetPixel(x, y, MeanPixel);
        }
    }
    return NewBitmap;
}

The code above takes in an image as well as the size that you want the aperture to be. In turn it gives you a smoothed image. This filter technique is quite different from other blur filters as it produces a lot more stylized look. Hopefully this helps you out in some way.