Background
In about 20 years of working with GDI, I never needed to do image filters. "If not now, when?" I decided to start with brightness and contrast. After a relatively extensive research, I came up with three reasonable approaches, and since I was already doing it, I decided to check the GetPixel
/ SetPixel
speed – is it as slow as everyone claims?
Goals
- To be open minded and persistent - even with great tools like AForge, you can often write a better, simpler, and faster solution yourself, with relatively little effort.
- For more advanced members: it seems to me - for some strange reason, the unsafe mode is very popular. Very often, there is no need for it – you can accomplish the same with simple marshaling, or by other means.
- Most important – translation of the QColorMatrix from C++ to C#. While doing this research – I did not see a better approach to image filtering, compared to the use of the QColorMatrix approach. This subject is very advanced; while the code itself is very simple and very readable, the math is relatively complicated. I can not explain the code unless I explain how the ColorMatrix works. But that is not the goal of this article. If your math level is strong enough, and you understand matrix multiplications and rotations, you can very easily understand and follow the code. If your math level is not strong enough, you can just use it as shown below - it works :).
Method 1
At first, my search brought me to this article: Image Processing Lab in C#. It uses the AForge library. I was too lazy to look for the source. It was much easier for me to use Refractory and get the objects that control brightness and contracts – that was how the AForgeFilter
object was created. There are three things I do not like about the AForge way:
- Unsafe mode, you might have a business requirement not to use unsafe mode.
- Seems to me: sometimes you might want to apply brightness and contrast filters at the same time. With AForge, you can’t do it.
- As my follow up research indicates, it is too slow.
The first problem I solved very easily – just use marshaling instead of unsafe mode. The attached demo shows how long it took to process the image. I could not notice that marshaling works any slower compared to the unsafe mode, so why use unsafe?
Method 2
Unfortunately, I did not keep the link of where I found the second method's algorithms. (The OtherFilter
object). It is much simpler compared with AForge, and it is faster.
Method 3
I was not completely satisfied. There is a ColorMatrix
object. There must be a way to use it for image filtering. The next article I hit was: Playing with ColorMatrix by Sjaak Priester. Fortunately, I am equally fluent in C++ and C#. QColorMatrix
is a more or less direct translation of the original C++ QColorMatrix
. How fast is it? Applying four filters and Gamma is ~2 times faster compared to the second method, and ~7 times faster compared to AForge…
Method 4
If any one is not yet convinced not to use the GetPixel
/ SetPixel
methods (the OtherFilterSlow
object) – take a look at how slow it is: ~10 times slower. Still want to use it?
Using the code
Please note: if you are going to use the AForgeFilter
object, you will need to check with the AForge community. I do not know what their license is, and my code is just a refractory from aforge.dll. Otherwise, the code is used as explained below. To adjust brightness:
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
AForgeFilter pFilterBrightness = new AForgeFilter();
pFilterBrightness.AdjustValue = (double)TrackBarBrightness1.Value / 1000;
Bitmap pBitmapBrightness = pFilterBrightness.Apply(pSource);
To adjust contrast:
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
AForgeFilter pFilterContrast= new AForgeFilter();
pFilterContrast.Factor = (double)TrackBarContrast1.Value / 1000;
Bitmap pBitmapBrightness = pFilterContrast.Apply(pSource);
In the example, I wanted to compare safe mode to unsafe mode, and changed brightness and contrast at the same time. Here is the code:
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
AForgeFilter pFilterBrightness = new AForgeFilter();
pFilterBrightness.AdjustValue = (double)TrackBarBrightness1.Value / 1000;
Bitmap pBitmapBrightness = checkBoxSafe.Checked ?
pFilterBrightness.ApplySafe(pSource) : pFilterBrightness.Apply(pSource);
AForgeFilter pFilterContrast = new AForgeFilter();
pFilterContrast.Factor = (double)TrackBarContrast1.Value / 1000;
Bitmap pBitmapContrast = checkBoxSafe.Checked ?
pFilterContrast.ApplySafe(pBitmapBrightness) :
pFilterContrast.Apply(pBitmapBrightness);
For the second method:
Brightness
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
Bitmap pBitmap = pSource.Clone(new Rectangle(0, 0, pSource.Width, pSource.Height),
pSource.PixelFormat);
new Brightness().Adjust(pBitmap, TrackBarBrightness2.Value);
Contrast
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
Bitmap pBitmap = pSource.Clone(new Rectangle(0, 0, pSource.Width, pSource.Height),
pSource.PixelFormat);
Contrast().Adjust(pBitmap, TrackBarContrast2.Value);
Brightness and Contrast
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
Bitmap pBitmap = pSource.Clone(new Rectangle(0, 0, pSource.Width, pSource.Height),
pSource.PixelFormat);
new BrightnessContrast().Adjust(pBitmap, TrackBarBrightness2.Value, TrackBarContrast2.Value);
The third method allows you to change four filters and gamma:
QColorMatrix pQColorMatrix = new QColorMatrix();
pQColorMatrix.ScaleColors(TrackBarContrast3.Value * 0.05f,
QColorMatrix.MatrixOrder.MatrixOrderPrepend);
pQColorMatrix.TranslateColors(TrackBarBrightness3.Value * 0.05f,
QColorMatrix.MatrixOrder.MatrixOrderAppend);
pQColorMatrix.SetSaturation(TrackBarSaturation3.Value * 0.05f,
QColorMatrix.MatrixOrder.MatrixOrderAppend);
pQColorMatrix.RotateHue(TrackBarHue3.Value * 4.0f);
Bitmap pSource = global::WindowsFormsApplication14.Properties.Resources.untitled;
Bitmap pResult = pQColorMatrix.Adjust(pSource, TrackBarGamma3.Value * 0.05f);