Creating Images from Scratch in C#

Whenever the subject of creating images from scratch comes up in .NET, people automatically start thinking "Windows Forms" and "Picturebox" or "Bitmap" manipulation. Although this is all good, there are more than a handful of ways to create interesting images in .NET

Let's take an interesting example. Let's suppose you wanted to do something like cellular automation:

http://mathworld.wolfram.com/CellularAutomaton.html

Images1
Figure 1: Cellular automation

This kind of image manipulation has many uses, from creating things like "Conway's Game of Life" through to easily being able to produce Barcodes. Even though you can do this type of image manipulation by using Winforms and GDI+, because it's easier to address pixels/cells directly, it's very often simpler to manipulate raw pixel data directly.

The good news is: It's not at all difficult to create this kind of image in .NET, and what's more, you can do it in a non-Winforms application, and yet still use various GDI+ assemblies to assist you.

A Simple Example

Here's a simple grey scale graduation I've just created using Photoshop:

Images2
Figure 2: A simple grey scale graduation

We'll start off by creating a similar image by using pure byte array orientated code.

In a 24 bit per pixel image, pixels are created using an 8-8-8 format. This means that one pixel is 8 bits for the Red component, 8 bits for the Green, and 8 bits for the Blue. The formula is something like the following:

Images3
Figure 3: The three basic colours

Having a 0 value in a given component means the final colour has none of that base colour present in it. A value of 255 means full intensity of the colour.

If you've ever had to specify colours in a web page by using the # colour format, you've done exactly the same thing; for example:

#FF0000

would equal a pure red pixel, whereas

#FF7700

would equal a colour created from full intensity red and half intensity green.

in the case of our .NET application, we'd specify this as a byte array, like so:

byte[] redPixel = new byte[3] { 255, 0, 0 }

That would give us our red pixel, and

byte[] mixedPixel = new byte[3] { 255, 127, 0 }

would give us our Red/Green mix.

In reality, you wouldn't create each individual pixel in separate 3-byte arrays, however. What you'd do is create a byte array that's big enough to hold all the pixels in your image. In this case, each pixel requites 3 bytes, so your array needs to be at least 3 times the image width, which is then multiplied by the image height.

If you start putting all this together, you should realize that we can easily make our grey scale graduation by creating lines of pixels that go from

0,0,0 to 255,255,255

and because there are 256 values, we can make our image 256 pixels in width. The height is entirely up to you. For this exercise, we'll create an image that's 100 pixels high.

The final maths required to allocate a byte array big enough for this image is therefore

(256*3)*100

or

76800 bytes

which means we can allocate our image data by using

byte[] imageBuffer = new byte[76800];

This will create an empty byte array ready for you to start populating it with the pixel data required to create your image.

A little side note: Back in days gone by, long before .NET and Windows, there was (and still is, to some extent) a movement called the demo-scene. The idea was to program dazzling music and graphics demonstrations using as many clever tricks as possible. All of this was also long before we had the 3D graphics cards we have today, and a large amount of the effects produced were created in memory buffers just as were doing here. The data was then pushed directly to the video card memory by using a process known as "Blitting," rather than saving the image. Many of the most spectacular productions (Such as 'Panic' by 'Future Crew' and 'Astral Blur' by 'The Black Lotus') were created by using this method, and although they look a little dated in today's age, back then they where way ahead of the curve.

Back to our .NET application. Fire up Visual Studio and create yourself a new console mode program. Once your project has been created, make sure you have a basic app template, something like the following:

namespace rawimage_blogpost
{
   class Program
   {
      static void Main()
      {
         byte[] imageBuffer = new byte[76800];

      }
   }
}

Before we start to fill our array, there's one more concept you need to be aware of, and that's the image stride. The stride, in theory, is just the value we've already seen (3 bytes per pixel multiplied by the image width). In reality, it's not always as simple as that, however.

Where Windows is concerned, we're running usually on a 32- or 64-bit operating system. This means that, to keep things fast, Windows tries to keep things evenly aligned to a multiple of 4 (4 bytes = 32 bits), meaning that when moving data about, the operating system and your computer's hardware doesn't have to do any strange maths to ensure that it splits up a one piece 32 bit value into 3 bytes.

There are two ways we can tackle this problem.

You might have seen images described as "RGB Alpha." Generally, this means that the image pixels are 8 bits Red, Green, and Blue as we've already seen, but have an extra 8 bit value added to them, called an "Alpha Value"

The advantage here means that our image pixel data is now always a multiple of 4, so you don't have to do anything extra to make sure one line of your image data is padded out so that it fits into a width that's divisible by 4 (which can get very horrible, very fast :-) ), and it means you also, if you want, get the ability to control an individual pixel's transparency value if you desire.

Alpha works exactly the same way as specifying a colour value, except the value describes how "See though" that pixel is, with 0 being completely invisible and 255 being not see through at all.

For the purposes of this post, we'll set all our alpha values to 255, but with a little experimentation you can create some interesting effects, especially when you start to combine images.

The formulae to work out the size of our array now becomes:

(4*256)*100

or

102400 bytes

Update your image array to use this new value:

namespace rawimage_blogpost
{
   class Program
   {
      static void Main()
      {
         byte[] imageBuffer = new byte[102400];

      }
   }
}

Plotting a Pixel

Now that we have the theory out of the way, plotting a pixel into your array is easy. All you need to do is take the X and Y positions of the pixel, and then turn that into a slot in your image buffer. To do this, you need to work out the width of a stride (in our case, 256 * 4 - the width in pixels of the image multiplied by 4 bytes per pixel); then use the Y value to multiply that stride. This gives you the offset in a downward direction to the start of the row. You then need to take your X value, multply that by 4 (bytes for each pixel), and then add that to the row start to give you the offset into the row, and ultimately the full offset into your byte array, something like the following:

int offset = ((256*4)*y)+(x*4);

This means we can simply plot a pixel as follows:

imageBuffer[offset] = redValue;
imageBuffer[offset+1] = greenValue;
imageBuffer[offset+2] = blueValue;
// Our alpha value
imageBuffer[offset+3] = 255;

There's one thing I've not mentioned yet. That's the pixel order.

The rest of the world uses the standard "r,g,b" ordering of the pixel data as described earlier, but Windows does not (go figure ☺). Instead of "r,g,b", Windows and .NET use the "b,g,r" ordering.

It's not a massive change, and if you get things the wrong way round you'll just get funky colours rather than what you usually expect. In our case, for the grey scale whichever way we do it we'll get the same result. All you need to make sure, however, is that you write the blue component and red component swapped round when creating your pixel data.

For purposes of clean coding, it's usually a better idea to wrap this up in a method rather than repeating that chunk of code every time you want to create a pixel. Alter your application so that it looks as follows:

namespace rawimage_blogpost
{
   class Program
   {
      private static readonly byte[] _imageBuffer =
      new byte[102400];

      static void PlotPixel(int x, int y, byte redValue,
         byte greenValue, byte blueValue)
      {
         int offset = ((256*4)*y)+(x*4);
         _imageBuffer[offset] = blueValue;
         _imageBuffer[offset+1] = greenValue;
         _imageBuffer[offset+2] = redValue;
         // Fixed alpha value (No transparency)
         _imageBuffer[offset+3] = 255;
      }

      static void Main()
      {

      }

   }
}

This means you can now plot pixels using something like this:

PlotPixel(10, 10, 255, 0, 0);

This will create a pure red pixel at location 10, 10 in your image data array.

Now that we have this, creating our gradient is now incredibly easy. We simply just need to loop over our width and height using the "X" loop variable to create the pixel colours, something like this:

for(int y = 0; y < 100; y++)
{
   for(int x = 0; x < 256; x++)
   {
      byte val = (byte)x;
      PlotPixel(x, y, val, val, val);
   }
}

All that remains now is for us to turn this data into a form that's usable elsewhere in .NET or can be saved to disk for use in other applications.

You can add your pixel data to a bitmap by using "Unsafe Code". A complete description of this is beyond the scope of this post, so for now make sure that you have "Allow Unsafe Code" ticked in your application properties:

Images4
Figure 4: Ensuring "Allow Unsafe Code" is selected

You then can use the following code to turn your pixels into a .NET bitmap and save the file to disk.

unsafe
{
   fixed (byte* ptr = _imageBuffer)
   {
      using (Bitmap image = new Bitmap(256, 100, 256*4,
         PixelFormat.Format32bppRgb, new IntPtr(ptr)))
      {
         image.Save(@"greyscale.png");
      }
   }
}

By default, "Bitmap.Save" will create a PNG (Portable Network Graphics file). If you want to save the file in a specific format, you easily can do so by specifying the image format in the save method, as follows:

image.Save(@"greyscale.bmp", ImageFormat.Bmp);
image.Save(@"greyscale.gif", ImageFormat.Gif);
image.Save(@"greyscale.jpg", ImageFormat.Jpeg);

Because the image is a standard GDI+ Bitmap object, you also can assign it to a picture box and/or image component both in Windows Forms and in Windows Presentation-based applications.

Got a strange .NET problem you're trying to solve, or want to know if there's "an API for that?" Come find me lurking around on the interwebs as "@shawty_ds" on Twitter, or "Shawty from Lidnug", or just leave a comment below and I'll see what I can do to cover it in future posts.



Related Articles

Comments

  • Need to add reference

    Posted by Eric on 07/20/2015 02:28pm

    In the solution explorer, right click on references. Add reference. Search for Drawing. Add System.Drawing. You should be able to resolve that problem now.

    Reply
  • Just me

    Posted by David on 06/01/2015 07:42am

    using (Bitmap image = new Bitmap(256, 100, 256 * 4, PixelFormat.Format32bppRgb, new IntPtr(ptr))) { Bitmap gets flagged in VS2013 and I don't know how to satisfy! (ctrl + .) doesn't help. Help?

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date