http://www.cedarcitygroup.com/digitaldarkroom/uncolormyworld.aspx
In the days of film photography, black and white photos were considered of higher quality than color photos. Many professional photographers, most notably Ansel Adams, would only shoot black and white photos. In the film days, color negatives could be used to make black and white prints by using black and white paper. To achieve the highest quality prints, however, black and white negatives were used.
But in the days of digital photography, how do you convert a color photograph to black and white?
First attempt, Pixel by Pixel
Just to review, each pixel in color jpeg bitmap is represented by four bytes, the color of the pixel is represented by three bytes, one byte for each of the colors (red, green and blue) The fourth bytes is used to control the opacity of the pixel, commonly referred to as the alpha value. Each byte contains a value from 0 to 255. When the red, green and blue components are the same, the result is a shade of gray ranging from 0x000000 for black to 0xFFFFFF for white. The task of converting a color bitmap to a grayscale bitmap involves using the values of the red, green and blue components of a pixel to compute a shade of gray to be used to accurately represent that color. Based on some analysis that I don't completely understand, a formula to do this turns out to be:
shadeofgray = 0.212671f * red + 0.715160f * green + 0.072169f * blue;
When I initially wrote this filter, I implemented it using a brute force method of computing the grayscale value pixel by pixel. This was the primary reason that I added the code to the ImageProcessBase class to allow unsafe code. After doing some research, I found that the .NET framework provides a high speed method of achieving the same effect, using only managed code.
Dust off your Linear Algebra textbook
The System.Drawing namespace provides the ColorMatrix class that can be used to manipulate colors during the rendering of a bitmap. You can find a pretty good explanation of the ColorMatrix class and the mathematics behind it here. For our purposes, I'll just show the code for my grayscale class.
using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Drawing.Imaging;
using System.Drawing;
namespace ccg.imaging
{
///
/// Converts a color image into a grayscale image
///
///
[Serializable]
[NameAttribute("Convert To Grayscale")]
[DescriptionAttribute("Converts the incoming image to a grayscale image.")]
public class Grayscale: ImageProcessBase
{
public Grayscale()
{
}
protected override System.Drawing.Bitmap ProcessSafe(System.Drawing.Bitmap bitmapin)
{
ColorMatrix cm = new ColorMatrix();
cm.Matrix00 = 0.212671f;
cm.Matrix01 = 0.212671f;
cm.Matrix02 = 0.212671f;
cm.Matrix10 = 0.715160f;
cm.Matrix11 = 0.715160f;
cm.Matrix12 = 0.715160f;
cm.Matrix20 = 0.072169f;
cm.Matrix21 = 0.072169f;
cm.Matrix22 = 0.072169f;
System.Drawing.Bitmap outmap =
new System.Drawing.Bitmap(bitmapin.Width,bitmapin.Height);
Graphics g = Graphics.FromImage(outmap);
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(cm);
g.DrawImage(bitmapin,new Rectangle(0,0,outmap.Width,
outmap.Height),0,0,bitmapin.Width,
bitmapin.Height,GraphicsUnit.Pixel,ia);
g.Dispose();
return outmap;
}
}
}
What can Brown do for you?
In the early days of photography, prints were toned with Sepia as a preservation technique. The original black and white prints were soaked in a solution made from the Sepia officinalis cuttlefish that produced prints that were more resistant to breakdown over time. The result was a brown tinted printed instead of gray. The result was beautiful in many ways. Today, many photos are converted to a Sepia tone to simulate this old fashioned beauty. By changing the values in the ColorMatrix, the same code will generate a Sepia toned bitmap.
using System;
using System.Collections;
using System.Runtime.Serialization;
using System.Drawing.Imaging;
using System.Drawing;
namespace ccg.imaging
{
///
/// Converts a color image into a sepia image
///
///
[Serializable]
[NameAttribute("Convert To Sepia")]
[DescriptionAttribute("Converts the incoming image to a sepia image.")]
public class Sepia: ImageProcessBase
{
public Sepia()
{
}
protected override System.Drawing.Bitmap ProcessSafe(System.Drawing.Bitmap bitmapin)
{
ColorMatrix cm = new ColorMatrix();
cm.Matrix00 = 0.393f;
cm.Matrix01 = 0.349f;
cm.Matrix02 = 0.272f;
cm.Matrix10 = 0.769f;
cm.Matrix11 = 0.686f;
cm.Matrix12 = 0.534f;
cm.Matrix20 = 0.189f;
cm.Matrix21 = 0.168f;
cm.Matrix22 = 0.131f;
System.Drawing.Bitmap outmap = new
System.Drawing.Bitmap(bitmapin.Width,bitmapin.Height);
Graphics g = Graphics.FromImage(outmap);
ImageAttributes ia = new ImageAttributes();
ia.SetColorMatrix(cm);
g.DrawImage(bitmapin,new Rectangle(0,0,outmap.Width,
outmap.Height),0,0,bitmapin.Width,bitmapin.Height,GraphicsUnit.Pixel,ia);
g.Dispose();
return outmap;
}
}
}
Keeping it loose
Now that we have a couple of classes that actually manipulate images, it's time to take a look at the factory itself. 'Loose Coupling' is a term that you may hear a lot in development circles. The basic idea is to reduce the amount of information shared between components of an application. This allows changes to be made to one component without forcing changes or even recompilation of other components. This is what the factory pattern is all about.
In this application, I have elected to place all of the shared information in a separate dll called ccg.imaging.foundation. This dll contains all of the base classes and the factory itself (for now). A reference to this dll will need to be added to any application (winforms or webforms) that intends to use the ccg.imaging components. Any dll projects that implement the abstract classes will also need to reference the foundation dll. The important point here is that it is not necessary for the application to directly reference any other imaging dlls. The factory class can use reflection to activate the processing components providing the ability to update the component dlls without recompiling the entire application. I've created a FactoryBase class to provide some basic Factory services.
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace ccg.imaging
{
public class FilterFactoryBase
{
// a dictionary used to store object specifications
private Dictionary<string, string> _filters = null;
protected FilterFactoryBase()
{
_filters = new Dictionary<string,string>();
}
// a method provided so that the subclassed factory can provide an
// application dependent id for the class, the fully qualified name of
// the class and the path to the assembly that contains the class
protected void AddFilter(string id, string classname, string assemblypath)
{
// store the path and classname in the dictionary using a delimited string
_filters.Add(id, assemblypath + "|" + classname);
}
// a method to activate a class identified by the application specified id
public ImageProcessBase ActivateFilter(string id)
{
ImageProcessBase objfilter = null;
// retrieve the specification from the dictionary
string classspec = _filters[id];
try
{
// if the classspec is found
if (!string.IsNullOrEmpty(classspec))
{
// break the stored spec down into its components (see AddFilter() method)
string[] typespec = classspec.Split('|');
string assemblypath = typespec[0].Trim();
string classname = typespec[1].Trim();
// make sure the needed assembly is loaded
Assembly remoteassembly = Assembly.LoadFrom(assemblypath);
// retrieve the Type from the assembly
Type remotetype = remoteassembly.GetType(classname);
// create an instance of the class
objfilter = (ImageProcessBase)Activator.CreateInstance(remotetype);
}
}
catch
{
}
// will be null if the class is not found or cannot be activated
return objfilter;
}
}
}
Now all the FilterFactory subclass needs to do is provide the application defined id, the class name and assembly path for each class that it wants to activate.
using System;
using System.Collections;
using System.Collections.Generic;
namespace ccg.imaging
{
///
/// This Class provides a means to find out what filters are available
/// and what they do.
///
public class FilterFactory: FilterFactoryBase
{
public FilterFactory()
{
string dllpath = AppDomain.CurrentDomain.BaseDirectory +
"bin\\ccg.imaging.dll";
AddFilter("grayscale", "ccg.imaging.Grayscale", dllpath );
AddFilter("sepia", "ccg.imaging.Sepia", dllpath);
}
}
}
Pulling it all together
http://www.cedarcitygroup.com/digitaldarkroom/uncolormyworld.aspx is an example web page that uses these classes to manipulate a fixed image according to gestures made on the user interface.
The main aspx page places the name of the image to be used in a session variable and provides the user input controls. An image tag is present with its source set to a second aspx page where all the magic happens. When the process button is pressed, the source for the image tag is changed so that the user's selections are passed to the processing page in the form of url parameters. Following is the code for the processing page.
using System.Configuration;
using System.Collections;
using System.Web;
using ccg.imaging;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
public partial class digitaldarkroom_darkroomprocess : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// load the image using a utility class to resolve pathname and whatnot
darkroombl bl = new darkroombl();
Bitmap output = bl.GetImage(Request["image1"].ToString());
// use the colorization command to obtain the object to use
//to implement the command
string colorcommand = Request["color"].ToString().ToLower();
// if the user specifies "full color", then we need to do nothing
if (!string.IsNullOrEmpty(colorcommand) && !colorcommand.Equals("full color"))
{
// instantiate the factory
FilterFactoryBase filterfactory = new FilterFactory();
// activate the class based on the user's input
ImageProcessBase proc = filterfactory.ActivateFilter(colorcommand);
//process the image
output = proc.Process(output);
}
// send the resulting image back as a jpeg
Response.ContentType = "image/jpeg";
MemoryStream ms = new MemoryStream();
output.Save(ms,ImageFormat.Jpeg);
ms.WriteTo(Response.OutputStream);
}
}
What does all this banjo work give us?
So, what is the final result?
1. The application that uses the image process classes does not need to have a reference to the assembly they are located in. Only the ccg.imaging.foundation is referenced by the top level project. Additional filters can be added to the dll without reguiring the application to be rebuilt.
2. The factory class only requires string values to be initialized. Right now, the class specifications are hard coded in the factory's constructor. They could be read from the web.config file or even loaded from a database.
3. The actual processing classes can be moved to different assemblies easily. The factory is the only class that would needed to be updated.
In the next post I'll implement some more filters and look at how reflection can be used to provide some more customizable capabilities.

