Other Posts in Image Editing

  1. Perlin Noise
  2. Fault Formation
  3. Cellular Textures
  4. Resizing an Image in C#
  5. Box Blur and Gaussian Blur... Sort of...
  6. Thermal Erosion
  7. Using Mid Point Displacement to Create Cracks
  8. Fluvial Erosion
  9. Creating Marble Like Textures Procedurally
  10. Procedural Textures and Dilation
  11. Converting Image to Black and White in C#
  12. Getting an HTML Based Color Palette from an Image in C#
  13. Adding Noise/Jitter to an Image in C#
  14. Creating Pixelated Images in C#
  15. Edge detection in C#
  16. Using Sin to Get What You Want... In C#...
  17. Noise Reduction of an Image in C# using Median Filters
  18. Image Dilation in C#
  19. Sepia Tone in C#
  20. Kuwahara Filter in C#
  21. Matrix Convolution Filters in C#
  22. Symmetric Nearest Neighbor in C#
  23. Bump Map Creation Using C#
  24. Normal Map Creation Using C#
  25. Creating Negative Images using C#
  26. Red, Blue, and Green Filters in C#
  27. Converting an Image to ASCII Art in C#
  28. Adjusting Brightness of an Image in C#
  29. Adding Noise to an Image in C#
  30. Adjusting the Gamma of an Image Using C#
  31. Adjusting Contrast of an Image in C#
  32. Drawing a Box With Rounded Corners in C#
  33. Anding Two Images Together Using C#
  34. Motion Detection in C#
  35. Creating Thermometer Chart in C#
  36. Colorizing a Black and White Image in C#
  37. Extracting an Icon From a File
  38. Setting the Pixel Format and Image Format of an Image in .Net
  39. Using Unsafe Code for Faster Image Manipulation
  40. Sobel Edge Detection and Laplace Edge Detection in C#

Matrix Convolution Filters in C#

2/17/2009

I've shown you a couple filters that take a pixel and modify it based on its surrounding pixels. This includes the box blur, Gaussian blur, etc. however, even though they might qualify, I'm leaving out some like the Kuwahara and Median filters. I'm specifically talking about those where we averaged the pixels based on some sort of weighting system. These filters are called Convolution Filters. While I've been hard coding most of these items, you can simply look at each of these filters as a matrix. For example the Box Blur would look like:

1 1 1
1 1 1
1 1 1

The 3x3 matrix, if used, would take each pixel and multiply it by 1 and add it to the total. The total is then divided by the total value of the matrix (in this case 9), which gives us our final value. However in an emboss filter (what you would use in creating a bump map), we would use something like the following:

-1 -1 -1
-1  9 -1
-1 -1 -1

The matrix above is treated in the same manner of the box blur matrix but instead of dividing by 9, we'd divide by 1 ((8*-1)+9=1)... So really we ignore the division portion. That being said, we might want to add a value to this (if you use it, you'll notice that it's rather dark), 127 is usually the default (to red, green, and blue individually).

Anyway, as you can see these types of filters are rather generic (only the matrix changes and maybe the value we add). As such, I've gone back and written a class to help with these types of filters:

   1: /*
   2: Copyright (c) 2010 <a href="http://www.gutgames.com">James Craig</a>
   3: 
   4: Permission is hereby granted, free of charge, to any person obtaining a copy
   5: of this software and associated documentation files (the "Software"), to deal
   6: in the Software without restriction, including without limitation the rights
   7: to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   8: copies of the Software, and to permit persons to whom the Software is
   9: furnished to do so, subject to the following conditions:
  10: 
  11: The above copyright notice and this permission notice shall be included in
  12: all copies or substantial portions of the Software.
  13: 
  14: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15: IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16: FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17: AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20: THE SOFTWARE.*/
  21:  
  22: #region Usings
  23: using System.Drawing;
  24: using System.Drawing.Imaging;
  25: using Utilities.Math;
  26:  
  27: #endregion
  28:  
  29: namespace Utilities.Media.Image
  30: {
  31:     /// <summary>
  32:     /// Used when applying convolution filters to an image
  33:     /// </summary>
  34:     public class Filter
  35:     {
  36:         #region Constructor
  37:  
  38:         /// <summary>
  39:         /// Constructor
  40:         /// </summary>
  41:         public Filter()
  42:         {
  43:             MyFilter = new int[3, 3];
  44:             Width = 3;
  45:             Height = 3;
  46:             Offset = 0;
  47:             Absolute = false;
  48:         }
  49:  
  50:         /// <summary>
  51:         /// Constructor
  52:         /// </summary>
  53:         /// <param name="Width">Width</param>
  54:         /// <param name="Height">Height</param>
  55:         public Filter(int Width, int Height)
  56:         {
  57:             MyFilter = new int[Width, Height];
  58:             this.Width = Width;
  59:             this.Height = Height;
  60:             Offset = 0;
  61:             Absolute = false;
  62:         }
  63:  
  64:         #endregion
  65:  
  66:         #region Public Properties
  67:  
  68:         /// <summary>
  69:         /// The actual filter array
  70:         /// </summary>
  71:         public int[,] MyFilter { get; set; }
  72:  
  73:         /// <summary>
  74:         /// Width of the filter box
  75:         /// </summary>
  76:         public int Width { get; set; }
  77:  
  78:         /// <summary>
  79:         /// Height of the filter box
  80:         /// </summary>
  81:         public int Height { get; set; }
  82:  
  83:         /// <summary>
  84:         /// Amount to add to the red, blue, and green values
  85:         /// </summary>
  86:         public int Offset { get; set; }
  87:  
  88:         /// <summary>
  89:         /// Determines if we should take the absolute value prior to clamping
  90:         /// </summary>
  91:         public bool Absolute { get; set; }
  92:  
  93:         #endregion
  94:  
  95:         #region Public Functions
  96:  
  97:         /// <summary>
  98:         /// Applies the filter to the input image
  99:         /// </summary>
 100:         /// <param name="Input">input image</param>
 101:         /// <returns>Returns a separate image with the filter applied</returns>
 102:         public Bitmap ApplyFilter(Bitmap Input)
 103:         {
 104:             Bitmap NewBitmap = new Bitmap(Input.Width, Input.Height);
 105:             BitmapData NewData = Image.LockImage(NewBitmap);
 106:             BitmapData OldData = Image.LockImage(Input);
 107:             int NewPixelSize = Image.GetPixelSize(NewData);
 108:             int OldPixelSize = Image.GetPixelSize(OldData);
 109:             for (int x = 0; x < Input.Width; ++x)
 110:             {
 111:                 for (int y = 0; y < Input.Height; ++y)
 112:                 {
 113:                     int RValue = 0;
 114:                     int GValue = 0;
 115:                     int BValue = 0;
 116:                     int Weight = 0;
 117:                     int XCurrent = -Width / 2;
 118:                     for (int x2 = 0; x2 < Width; ++x2)
 119:                     {
 120:                         if (XCurrent + x < Input.Width && XCurrent + x >= 0)
 121:                         {
 122:                             int YCurrent = -Height / 2;
 123:                             for (int y2 = 0; y2 < Height; ++y2)
 124:                             {
 125:                                 if (YCurrent + y < Input.Height && YCurrent + y >= 0)
 126:                                 {
 127:                                     Color Pixel = Image.GetPixel(OldData, XCurrent + x, YCurrent + y, OldPixelSize);
 128:                                     RValue += MyFilter[x2, y2] * Pixel.R;
 129:                                     GValue += MyFilter[x2, y2] * Pixel.G;
 130:                                     BValue += MyFilter[x2, y2] * Pixel.B;
 131:                                     Weight += MyFilter[x2, y2];
 132:                                 }
 133:                                 ++YCurrent;
 134:                             }
 135:                         }
 136:                         ++XCurrent;
 137:                     }
 138:                     Color MeanPixel = Image.GetPixel(OldData, x, y, OldPixelSize);
 139:                     if (Weight == 0)
 140:                         Weight = 1;
 141:                     if (Weight > 0)
 142:                     {
 143:                         if (Absolute)
 144:                         {
 145:                             RValue = System.Math.Abs(RValue);
 146:                             GValue = System.Math.Abs(GValue);
 147:                             BValue = System.Math.Abs(BValue);
 148:                         }
 149:                         RValue = (RValue / Weight) + Offset;
 150:                         RValue = MathHelper.Clamp(RValue, 255, 0);
 151:                         GValue = (GValue / Weight) + Offset;
 152:                         GValue = MathHelper.Clamp(GValue, 255, 0);
 153:                         BValue = (BValue / Weight) + Offset;
 154:                         BValue = MathHelper.Clamp(BValue, 255, 0);
 155:                         MeanPixel = Color.FromArgb(RValue, GValue, BValue);
 156:                     }
 157:                     Image.SetPixel(NewData, x, y, MeanPixel, NewPixelSize);
 158:                 }
 159:             }
 160:             Image.UnlockImage(NewBitmap, NewData);
 161:             Image.UnlockImage(Input, OldData);
 162:             return NewBitmap;
 163:         }
 164:  
 165:         #endregion
 166:     }
 167: }

One thing to note is that the class uses a couple of functions from my utility library specifically locking/unlocking the images to speed things up and a clamp function from my MathHelper class. You can simply remove the locking/unlocking code and replace the SetPixel/GetPixel functions with the built in equivalent. The MathHelper function simply clamps a value between two values (inclusive). Anyway, the class above allows you to set the filter and then send in a bitmap object and it will run on it.  And since it has the copyright notice at the top, it is going into the Utilities library. In fact I've gone back and rewritten some of the Image class's functions to use it. So you're going to see this class a bit more in the future. By the way, if you want to try it out, try the following matrices:

Motion Blur, left to right

0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

Gaussian Blur:

1 2 1
2 4 2
1 2 1

Sharpen:

-1 -2 -1
-2 16 -2
-1 -2 -1

Anyway, hopefully this will help you out. So give it a try, leave feedback, and happy coding.



Comments