Cedar City Group: A Time For Reflection (Factory Pattern in Action part 3)

Sunday, January 4, 2009

A Time For Reflection (Factory Pattern in Action part 3)

For a preview of the application being discussed here please visit this page

The preceding portions of this series can be found here and here.

Setting properties on Factory Generated Objects
The grayscale and sepia objects were unique in that they do not require any parameters to operate. They know what they are supposed to do and can do it with no input provided by the controlling object. But, it should be obvious that to be useful, some objects will need to have some input provided. If the a factory returns only a base type, how can you set a property on a returned object without breaking the barrier of anonymity that the factory pattern provides?

Interfaces provide a possible solution, and possibly a better solution from a type safety perspective. However, in the interest of making the coupling as loose as possible, I have chosen to use reflection as the means of communicating with the object returned from the factory pattern.

For this portion, I selected to use a couple of filters that modify the colors in bitmap. The first filter can lighten or darken a photo using a gamma correction. The gamma value must be greater than 0. A gamma value of 1.0 leaves the image unchanged, values less than 1 lighten the images, values greater than 1.0 darken the image. Following is the code for my LightenDarken filter.

using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Drawing.Imaging;
using System.Drawing;

namespace ccg.imaging
{
///
/// Performs a gamma correction on the processed image
///

///
[Serializable]
[NameAttribute("Ligthens or Darkens an image")]
[DescriptionAttribute("Adjusts the brightness of the entire image.")]
public class LightenDarken: ImageProcessBase
{
public LightenDarken()
{

}

public LightenDarken(float gamma)
{
_gamma = gamma;
}

float _gamma = 1.0f;

public float Gamma
{
set { _gamma = value; }
get { return _gamma; }
}

protected override System.Drawing.Bitmap ProcessSafe(System.Drawing.Bitmap bitmapin)
{
System.Drawing.Bitmap outmap = new System.Drawing.Bitmap(bitmapin.Width,bitmapin.Height);
Graphics g = Graphics.FromImage(outmap);
ImageAttributes ia = new ImageAttributes();
ia.SetGamma(_gamma);
g.DrawImage(bitmapin,new Rectangle(0,0,outmap.Width, outmap.Height),0,0,
bitmapin.Width,bitmapin.Height,GraphicsUnit.Pixel,ia);
g.Dispose();

return outmap;
}
}
}


The class allows the gamma value to be set in the constructor, but also allows it to be set as a property. Since the factory returns an ImageProcessBase type, the Gamma property is not available directly. The property could be set directly, but to do that, the controlling code would need to know the name of the class returned by the factory. In my opinion, this forces the controlling code to know too much about the underlying implementation of the filter. The most loosely couple approach is to set the property using reflection. The controlling code can retrieve the Type information from the class and use that to set the property using reflection.


ImageProcessBase proc = filterfactory.ActivateFilter("lightendarken");
Type t = proc.GetType();
object[] parms = { gamma }; // gamma declared and initialized earlier
t.InvokeMember("Gamma", System.Reflection.BindingFlags.SetProperty,
null, proc, parms);
output = proc.Process(output);


This approach has its downfalls, the main one being that if the property name is changed on the LightenDarken class, then the problem will show up at run time instead of compile time. However, this approach requires the least amount of information to be known be the controlling code. Casting or using interfaces requires the controlling code to know the name of the class or interface and the name of the property. Using reflection requires only the name of the property to be known. Since loose coupling is my primary design goal, this is the approach I selected.

The second filter uses the ColorMap property of the ImageAttributes class in the .NET framework to modify the colors in the bitmap. The ColorMap property of the ImageAttribute class allows you to specify a 'from' color and a 'to' color. When the bitmap is regenerated in the DrawImage call, this color mapping is used. My original intentions was to use it to reduce the number of shades of grey in an image my mapping multiple 'from' colors to the same 'to' color. However, I found it to be much more useful than just that. By supplying different ColorMaps, this filter can achieve some pretty cool effects. To make this simpler, I created an abstract class called ColorMapper. The only abstract method is the protected initializecolormap method.


using System;
using System.Drawing;
using System.Collections;
using System.Drawing.Imaging;

namespace ccg.imaging
{
///
/// This is an abstract class to allow the creation of multiple colormapping
/// classes
///

///
[NameAttribute("ColorMapper")]
[DescriptionAttribute("Maps Colors within a bitmap to different colors")]
[Serializable]
public abstract class ColorMapper: ImageProcessBase
{

#region Constructors
public ColorMapper()
{
}

#endregion

//--- create the color mapping
protected abstract ColorMap[] initializecolormap(Bitmap bitmapin);

///
/// process the incoming image to map colors to different colors
///

protected override System.Drawing.Bitmap ProcessSafe(System.Drawing.Bitmap
bitmapin)
{
ImageAttributes ia = new ImageAttributes();
ia.SetRemapTable(initializecolormap(bitmapin));
Bitmap outimg = new Bitmap(bitmapin.Width,bitmapin.Height);
Graphics g = Graphics.FromImage(outimg);
g.DrawImage(bitmapin, new Rectangle(0, 0, outimg.Width,
outimg.Height), 0, 0, bitmapin.Width, bitmapin.Height,
GraphicsUnit.Pixel, ia);
g.Dispose();
return outimg;
}
}
}


With this base class, we can easily produce a couple of color mapping effects. ShadeReducer maps multiple 'from' colors to the same 'to' color to reduce the number of shades in a grayscale bitmap. ColorShift uses the specified BaseColor as the color to shade rather than Black.


using System;
using System.Drawing;
using System.Collections;
using System.Drawing.Imaging;

namespace ccg.imaging
{
///
/// Summary description for ShadeReducer
///

///
[NameAttribute("Shade Reducer")]
[DescriptionAttribute("Reduces the image to the specified number of shades")]
[Serializable]
public class ShadeReducer: ColorMapper
{
//--- default number of shades of no value is provided
int _numberofshades = 255;

#region Constructors
public ShadeReducer()
{
}

public ShadeReducer(int numberofshades)
{
_numberofshades = numberofshades;
}
#endregion

///
/// Property to set the number of shades to use
///

public int NumberOfShades
{
get { return this._numberofshades; }
set { this._numberofshades = value; }
}

//--- create the color mapping
protected override ColorMap[] initializecolormap(Bitmap bitmapin)
{
ColorMap[] cmarray = new ColorMap[255];
int newvalue;
int shadeincrement = (255 / _numberofshades);
for (int i = 254; i >= 0; i--)
{
ColorMap cm = new ColorMap();
cm.OldColor = Color.FromArgb(i, i, i);
newvalue = 255-(shadeincrement * ((254-i) / shadeincrement));
cm.NewColor = Color.FromArgb(newvalue, newvalue, newvalue);
cmarray[i] = cm;
}
return cmarray;
}
}
}



using System;
using System.Drawing;
using System.Collections;
using System.Drawing.Imaging;

namespace ccg.imaging
{
///
/// Summary description for ShadeReducer
///

///
[NameAttribute("Color Shifter")]
[DescriptionAttribute("Converts a grayscale bitmap to shades of the specified Base Color")]
[Serializable]
public class ColorShifter: ColorMapper
{
Color _basecolor = Color.FromArgb(0x000000);

#region Constructors
public ColorShifter()
{
}
#endregion

public Color BaseColor
{
get { return _basecolor; }
set { _basecolor = value; }
}

///
/// This method produces as many shades of the basecolor as possible
/// The BaseColor will be the darkest color in the bitmap.
/// The method will produce as many shades as possible of the color
/// without changing it
///

private Color GetColorShade(Color colortoshade, int shadevalue)
{
int redvalue = colortoshade.R + shadevalue;
int greenvalue = colortoshade.G + shadevalue;
int bluevalue = colortoshade.B + shadevalue;

if (redvalue > 255)
{
greenvalue -= redvalue - 255;
bluevalue -= redvalue - 255;
redvalue = 255;
}

if (greenvalue > 255)
{
bluevalue -= greenvalue - 255;
redvalue -= greenvalue - 255;
greenvalue = 255;
}

if (bluevalue > 255)
{
redvalue -= bluevalue - 255;
greenvalue -= bluevalue - 255;
bluevalue = 255;
}
return Color.FromArgb(redvalue, greenvalue, bluevalue);
}

//--- create the color mapping
protected override ColorMap[] initializecolormap(Bitmap bitmapin)
{
ColorMap[] cmarray = new ColorMap[255];
int newvalue;
int shadeincrement = (255 / _numberofshades);
for (int i = 0; i < 255; i++)
{
ColorMap cm = new ColorMap();
cm.OldColor = Color.FromArgb(i, i, i);
cm.NewColor = GetColorShade(_basecolor, i);
cmarray[i] = cm;
}
return cmarray;
}
}
}


The controlling code can use reflection to set the NumberOfShades and BaseColor properties as needed.

The example of these filters in action is available at http://www.cedarcitygroup.com/digitaldarkroom/uncolormyworld.aspx. The .NET framework provides a tremendous amount of flexibility in processing images. Breaking different operations down into filters allows them to be mixed and matched into a processing pipeline that can be applied to a large number of images easily.

I hope that this series has been beneficial. Be on the lookout for an image processing utility for webmasters available here soon.

I just got my hands on some numerical analysis code that I wrote 20 years ago in Pascal. I'm dying to convert it to C#, there will be some posts about it soon as well.

0 comments: