Welcome to Atalasoft Community Sign in | Help

More Image Processing with C# Lambdas

This is a continuation of the earlier post on image processing with C# lambda expressions.

The previous blog describes a simple way to define image processing in terms of lambda expressions, and while efficient, it’s missing the ability to do commands that examine pixels outside of the source pixel in order to compute the destination pixel.  The way around this is to provide more information to the lambda expression.

To do this, I created and aggregate structure that contains more than the color and coordinates that were previously available:

public struct LambdaPixel
{
    private Color _color;
    private int _x, _y;
    private AtalaImage _sourceImage;
    private Rectangle _area;
    public LambdaPixel(Color color, int x, int y, AtalaImage sourceImage, Rectangle area)
    {
        _color = color;
        _x = x;
        _y = y;
        _sourceImage = sourceImage;
        _area = area;
    }
    public Color Color { get { return _color; } }
    public int X { get { return _x; } }
    public int Y { get { return _y; } }
    public AtalaImage SourceImage { get { return _sourceImage; } }
    public Rectangle Area { get { return _area; } }
}

This is a straight forward structure that contains the same color and coordinates as were passed to the original lambda expression but now contain the source image as well as the area that is being operated upon.

Now, I have to redefine my delegate:

public delegate Color GreaterPixelDelegate(LambdaPixel pixel);

Now the delegate gets passed a LambdaPixel with the same x, y, and color and returns a color.  Ignoring the rest of the information in the LambdaPixel, this delegate represents a superset of all operations performable by the previous one.  We like this.

The only change, other than types, is the set up and call of the delegate:

    // get color byte values for pixel
    LambdaPixel sourcePixel = new LambdaPixel(sourceColor, x, y, source, imageArea);
    // call the delegate
    Color destColor = _pixelDelegate(sourcePixel);

Given the new definition of a LambdaCommand (which in my testing, I’m calling a GreaterLambdaCommand), we can write the “hello world” of image processing, flip:

AtalaImage finalImage = _greaterLambdaCommand.Apply(image,
    (LambdaPixel p) =>
        p.SourceImage.GetPixelColor(p.X, p.Area.Bottom - p.Y - 1)).Image;

This works exactly as expected, with one problem – flip takes 2.6 seconds for my standard test image.  The built-in flip in dotImage dpes that same work in .15 seconds.  So instead of being half the speed, this command is 1/20th the speed.  The culprit is GetPixelColor().  As I predicted in the previous blog, it adds a fair amount of overhead: GetPixelColor works on every pixel format, range checks, null checks, and a host of other things.  It’s possible to write your own GetPixelColor that eliminates all the safety checks and runs only in this bit depth, but the gains aren’t stellar – I get less than a 30% gain by using a specialized method and that’s not good enough to sacrifice all the error checking.

Further, if I reimplement my channel swapper using the GreaterLambdaCommand, it takes about 40% longer than the simpler case.  This the price paid for making a struct and passing it into the lambda.  If I use a Point in the struct it takes closer to 45% longer.

This illustrates the crux of performance in image processing: whatever work you do per pixel will get magnified by the number of pixels being processed.  Normally, new-ing up a struct and passing it to a method is cheap, but if you do it 2.7 million times in succession, it adds up.  I could probably eliminate this cost by making the struct mutable and using precisely one in the lifetime of the Apply method.  I suppose that using a class at that point is more efficient in that I could allocate one from the heap and reuse it.  If I made the set methods internal, it would look to the outside world to be immutable and look outwardly pretty (but be inwardly ugly), which unfortunately is what we have to do all too often to get performance.

Still, this is a nice way to make it east to experiment with image processing.

Published Thursday, December 04, 2008 11:27 AM by Steve Hawley

Comments

Thursday, December 04, 2008 2:48 PM by Rick Minerich's Development Wonderland

# More Cores requires More Abstraction, What Does this Mean for Image Processing?

Compilers and programmers are good at very different things.  This is why they must come together

Tuesday, December 09, 2008 11:05 AM by DotNetKicks.com

# More Image Processing with C# Lambdas

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Tuesday, December 09, 2008 4:10 PM by eknacks

# re: More Image Processing with C# Lambdas

You've been knacked. Keep up the good work.

Anonymous comments are disabled