Pixels en masse

In this post I’ll show you how to do image processing in 50 lines of C# code, or less.

To do this, we’ll need a few trustees:

  • Microsoft .Net, our software platform of choice
  • LinqPad, our software environment of choice

First, fire up LinqPad and start by importing System.Drawing. All other needed namespaces are imported by LinqPad.

Then we need to change the script type from Expression to Program. This gives us the full expressive power of C#, including the ability to do statements, create methods, classes, structs, etc.

Then we define a main method:

void Main()
	var desktop = Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop);
	var droplet = Path.Combine(desktop, "droplet.jpg");
	Render(new Bitmap(droplet), PixelShader1).Dump();

The first lines defines the desktop folder path. The second line defines the path to an image I have on my desktop. The third is where the action starts. This line calls the Render method, passing along the droplet image and then a method delegate. The result of the call is dumped to the output. The Dump method is a special LinqPad extension method that normally outputs a textual representation of the state of the object being dumped out. It does however handle XML data and image data specially. In the case of image data it will show the data as an image.

Next we define the Render method. Note that this is a straight-forward higher-order method that takes in a bitmap and a shader and gives back a new and shaded copy of the source bitmap.

IEnumerable<Bitmap> Render(Bitmap source, PixelShader shader)
	var w = source.Width;
	var h = source.Height;

	var pixels =
		from x in Enumerable.Range(0, w)
		from y in Enumerable.Range(0, h)
		let  p = source.GetPixel(x, y)
		select new { X = x, Y = y, Color = p };

	var target = (Bitmap)source.Clone();

	foreach (var p in pixels)
		target.SetPixel(p.X, p.Y, shader(p.X, p.Y, w, h, p.Color));

	yield return source;
	yield return target;

Well, that description is not entirely accurate. I’ve set it up so that it actually returns a stream of images. So far it will only return the source and target images but it can now be easily extended to generates animations, courtesy of iterators (see “yield return”).

There’s a couple of things to notice about this code

  • the code is sequential
  • the code uses linq

These are by no means opposites but in 50 lines of code, I’ll not show you parallel LINQ or parallel code. It is however easy to bolt on and I’ll leave this as an exercise to the reader. Beware that the Bitmap class is not thread-safe!

Now it’s time to define what a pixel shader is (in this narrow context). It’s simple

delegate Color PixelShader(int x, int y, int w, int h, Color source);

The PixelShader is just a function from a coordinate set (x,y), a dimension (w,h) and a color to a new color. This representation lets you do a lot of magic but you can easily extend it to include a pixel matrix and a time parameter for added power.

Now let’s define a simple pixel shader that just swaps the two color channels red and green:

Color PixelShader1(int x, int y, int w, int h, Color source)
	var a = source.A;
	var r = source.R;
	var g = source.G;
	var b = source.B;

	Swap(ref r, ref g);

	return Color.FromArgb(a, r, g, b);

First we read out the alpha, red, green and blue pixel values, then we swap red and green and then we construct a new color from that.

Last but not least, a little helper function for swapping values:

void Swap<T>(ref T a, ref T b)
	var t = a;
	a = b;
	b = t;

I’ve previously written against using methods with ref and out parameters but in some cases, they are quite useful.

That’s it! – A compositional image processing script in 50 lines of code.

The output looks like this (full image):

Some extensions left as an exercise for the reader:

  • multi-threading (Threads/ThreadPool or better Tasks/Parallel or perhaps PLINQ)
  • animation
  • color space conversion (RGB, HSL, LAB, CMYK, …)
  • 3D/4D image processing and synthesis

About xosfaere

Software Developer
This entry was posted in Uncategorized and tagged , , , , , , , , . Bookmark the permalink.

1 Response to Pixels en masse

  1. xosfaere says:

    Note, you’ll need LinqPad version 4.28.3 or higher to do image processing with inline output. See the beta page.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s