One of my own personal interests for years has image processing. I've been taking photographs since I was a kid. Now that photographs are digital instead of chemical, it presents some really interesting possibilities for the analytical side of the brain.
I've been toying with the idea of creating an automated image processing program for years, I actually had a pretty cool version of it working a few years ago, but I've never been able to polish it up enough to make a product out of it. What I can do though, is write about what I was working on. It may give other people some ideas or teach someone something cool about the .NET framework.
Professional imaging programs like Photoshop and Fireworks provide a "batch" mode where you can determine a set of operations to perform on a group of images. This is very useful feature for webmasters. But how would you design something like that if you needed to go beyond the operations that those programs provide?
My plan was to break any possible operation on an image (resizing, convert to black and white, lighten/darken etc.) into a single object. Each operation would have a common method that performed its operation. The "Process" method would accept an image as a parameter and return the processed image when it was done.
Once a collection of these objects was built, they could be mixed and matched into an image processing "pipeline" that could do some pretty amazing stuff on a large group of images.
Here is an ideal base class for the image processing pipeline....
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.Serialization;
namespace ccg.imaging
{
[Serializable]
public class IdealImageProcessBase
{
public virtual Bitmap Process(System.Drawing.Bitmap bitmapin)
{
return bitmapin;
}
}
}
Reality quickly sets in
This "do nothing" base class for image processing is simple and works, however, in reality it needs some work.
The problem is, when you are dealing with images, you are dealing with large volumes of data. The System.Drawing.Imaging namespace provides a ton of very powerful features for manipulating images, but at some point, it may be necessary to manipulate the image 1 pixel at a time. The System.Drawing.Bitmap object provides the GetPixel() and SetPixel() methods for doing this, but, being managed code, they get very slow when processing large images.
Take A Walk On The Wild Side
Back in the day before C had anything after it, nothing was 'safe'. Before the days of managed code, you could easily write code that stepped all over itself. The hallmark of the C language was its speed. C was a language with no boundaries, direct manipulation of memory locations provided a tremendous amount of speed and flexibility...and danger. It was very easy to write code that had unintended results, I won't bore you with the details, just suffice it to say that operations as simple as concatenation of strings presented a lot of danger to your application.
Managed code provides a lot of safety for your applications, but the trade off is a lot of overhead. Thankfully, you can still get down to processing memory directly, but it requires some special security privileges and object locking.
Here is my real-world image processing base class...
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.Serialization;
using System.Security;
namespace ccg.imaging
{
///
/// This is a base class to provide low level image
/// processing services for different
/// filters and image processors.
///
///
[Serializable]
public class ImageProcessBase
{
protected static bool _allowunsafe;
protected static bool _permissionchecked = false;
#region Bitmap Locking and Unlocking
//--- locks the bitmaps data bits so that we can access them more speedily
protected System.Drawing.Imaging.BitmapData LockBits(Bitmap b)
{
// GDI+ still lies to us - the return format is BGR, NOT RGB.
return b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
}
protected virtual void UnlockBits(Bitmap b, BitmapData bmd)
{
b.UnlockBits(bmd);
}
#endregion
public ImageProcessBase()
{
if (!_permissionchecked)
{
_permissionchecked = true;
try
{
System.Security.Permissions.SecurityPermission perm =
new System.Security.Permissions.SecurityPermission(
System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode);
_allowunsafe = true;
}
catch
{
_allowunsafe = false;
}
}
}
public virtual Bitmap Process(System.Drawing.Bitmap bitmapin)
{
if (_allowunsafe)
return ProcessUnsafe(bitmapin);
else
return ProcessSafe(bitmapin);
}
protected virtual Bitmap ProcessUnsafe(Bitmap bitmapin)
{
return ProcessSafe(bitmapin);
}
protected virtual Bitmap ProcessSafe(Bitmap bitmapin)
{
return bitmapin;
}
[DataAttribute(DataAttributeType.Hidden)]
public string FilterName
{
get
{
Type t = this.GetType();
object [] attrs = t.GetCustomAttributes(true);
if (attrs != null)
{
foreach(object attr in attrs)
if (attr is NameAttribute)
return ((NameAttribute)attr).Value;
}
return t.Name;
}
}
[DataAttribute(DataAttributeType.Hidden)]
public string FilterDescription
{
get
{
Type t = this.GetType();
object [] attrs = t.GetCustomAttributes(true);
if (attrs != null)
{
foreach(object attr in attrs)
if (attr is DescriptionAttribute)
return ((DescriptionAttribute)attr).Value;
}
return t.Name;
}
}
}
Now the first ImageProcessor object checks to see if unmanaged code is allowed. By making the _permissionchecked and _allowunsafe properties static, we know that this will only be checked once. The Process() method uses the _allowunsafe property to determine whether or not to call the ProcessUnsafe() method. Since the default implementation of ProcessUnsafe() is to call ProcessSafe(), objects are not required to have an unsafe implementation. The base class provides the methods to Lock and Unlock the bitmap object for unsafe processing and also provides some attribute properties to provide information to the GUI.
All of this will lead to Factory Pattern, I promise, this is just the initial base class implementation. The next installment will cover the implementation of a couple of real processor classes and possibly the factory...

0 comments:
Post a Comment