Replaceable Image Format Conversion
In working on the 4.0 version of dotImage, I've added the ability to change how all images are converted from one pixel format to another. I thought I'd post a quick entry to show how this will work in the upcoming release. If you're interested in trying this out, be sure to get involved in our Beta program.
In order to implement this, I created a class called PixelFormatChanger, which contains one public method for changing an image to another pixel format. It also defines a protected abstract method for doing the actual work.
The public method handles all the error conditions before handing the image off to the concrete implementation.
AtalaImage now has a static property called PixelFormatChanger which is used by all of dotImage to do pixel format changes. You can take advantage of this by installing your own version to replace our defaults mechanisms.
Now, to do this properly, it's a big job. There are 12 pixel formats that should be convertible to each other. That's 132 possible conversions (12 x 12 = 144, less the 12 identity conversions that are considered illegal), which, trust me, is a lot of typing.
I'm going to show you how you can create a custom PixelFormatChanger that replaces only a portion of the possible conversions, but uses the defaults otherwise.
I'll start with the actual changer:
using System;
using Atalasoft.Imaging;
using Atalasoft.Imaging.ImageProcessing.Document;
namespace PixelFormatReplacement
{
public class ThresholdPixelChanger : PixelFormatChanger
{
private AdaptiveThresholdCommand _threshold;
private PixelFormatChanger _originalChanger;
public ThresholdPixelChanger(PixelFormatChanger originalChanger)
{
// we need an original changer to handle things
// that we can't do.
if (originalChanger == null)
{
throw new ArgumentNullException("originalChanger",
"ThresholdPixelChanger requires a base pixel format changer");
}
_originalChanger = originalChanger;
_threshold = new AdaptiveThresholdCommand();
}
protected override AtalaImage LowLevelChangePixelFormat(AtalaImage sourceImage,
PixelFormat targetPixelFormat,
Atalasoft.Imaging.ColorManagement.ColorProfile destProfile)
{
// if the target pixel format is not 1 bit or
// if the thresholding command doesn't support the
// source image pixel format, just pass it on
if (targetPixelFormat != PixelFormat.Pixel1bppIndexed ||
!_threshold.IsPixelFormatSupported(sourceImage.PixelFormat))
{
return _originalChanger.ChangePixelFormat(sourceImage, targetPixelFormat, destProfile);
}
// apply the threshold command to the
// source image, are return the resulting image
return _threshold.Apply(sourceImage).Image;
}
public PixelFormatChanger OriginalChanger { get { return _originalChanger; } }
}
}In this code, the constructor takes a copy of the original PixelFormatChanger and holds on to it. In this way, we can call its methods to implement what we can't handle. Note that instead of assuming what the threshold command can handle, I'm letting it tell me via the IsPixelFormatSupported() method. This will let me change the threshold command without breaking any code.
Essentially, if we're not going to black and white and we can't handle the pixel format, we kick it back to the older command.
So why didn't I just subclass the existing AtalaPixelFormatChanger and override what I wanted and then call base.LowLevelChangePixelFormat() ? I could have, but I lose flexibility. In this code, I can override behavior of
any exisiting PixelFormatChanger, including those written by outside vendors or any future versions created by Atalasoft. Using inheritance ties me to one particular base class.
Here's a test rig to try it out:
using System;
using System.IO;
using Atalasoft.Imaging;
using Atalasoft.Imaging.Codec;
namespace PixelFormatReplacement
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// try the built-in pixel format changer
AtalaImage originalPicture = new AtalaImage("atalapic.jpg");
AtalaImage originalChangedImage = originalPicture.GetChangedPixelFormat(PixelFormat.Pixel1bppIndexed);
SaveImage(originalChangedImage, "originalChangedImage.gif");
// install our new pixel format changer
ThresholdPixelChanger changer = new ThresholdPixelChanger(AtalaImage.PixelFormatChanger);
if (changer == null)
{
throw new Exception("Couldn't create pixel changer.");
}
AtalaImage.PixelFormatChanger = changer;
// run it.
AtalaImage thresholdChangedImage = originalPicture.GetChangedPixelFormat(PixelFormat.Pixel1bppIndexed);
SaveImage(thresholdChangedImage, "thresholdChangedImage.gif");
}
private static void SaveImage(AtalaImage image, string filename)
{
FileStream stm = new FileStream(filename, FileMode.Create);
GifEncoder encoder = new GifEncoder();
encoder.Save(stm, image, null);
stm.Close();
}
}
}And here is the output.
Original Image:
Default conversion to bitonal:
Thresholded replacement:
This capability should prove itself quite handy. In the upcoming release, nearly all of our image processing commands will have the ability to operate on any pixel format image, but with conversion. Now the client can have a say as to how this should be done.