Multicolored Gradient

Multicolor Gradients: An Introduction to Working with Palette Effects

Working with color palettes is one of the fundamental aspects of 2D rendering. I’ve gone over the various color depths used in computer graphics in a earlier post on rendering with GDI, however there are a couple of aspects I didn’t directly mention.

Specifically, the biggest advantage in using a color palette is the sizable speed advantage you gain by having a fixed amount of colors that allow for a direct index into the color table that correspond to a range of values generated by your effect.

I’ll have a more in-depth discussion on this topic in a follow-up post; for now let’s take a look at the steps needed to generate a gradient palette with a variable amount of colors, which is needed by a variety of effects, and a brief introduction to working with pointers in C# so that we can display our gradients to the screen in an efficient manner.

Basic Gradient

A simple two color gradient is generated by breaking the start and end colors down into their RGB values, 0-255 for each channel, then for each channel subtract the start color value from the end color value, multiply this by the current step (position) in the gradient, then divide that value by the total number of steps in the gradient (length), then add that total value to its respective channels start value.

Here is a C# routine that will generate a two color gradient with the specified number of steps:

static IEnumerable CreateGradient( Color start, Color end, int steps )
    for ( int i = 0; i < steps; i++ )
        yield return Color.FromArgb
            start.R + ( i * ( end.R - start.R ) / steps ),
            start.G + ( i * ( end.G - start.G ) / steps ),
            start.B + ( i * ( end.B - start.B ) / steps )

Multicolor Gradient

We can extend this functionality to create a gradient with an arbitrary number of colors by wrapping this simple function in a routine that takes the total number of desired steps (length) of the gradient, then by using the C# ‘params’ keyword our function can accept any amount of colors we desire. The ‘params‘ keyword allows us to pass an object (the type is specified as part of the param declaration) array to a function.

Note: due to the usually static nature of method signatures in C#, as well as any standard calling convention system, to allow for the flexibility of variable arguments the ‘params‘ argument must always be last in your method signature.

static Color[] CreateGradient( int size, params Color[] colors )
    List palette = new List( size );

    int colorSpan = colors.Length - 1;

    if ( colors.Length > 0 )
        int lastPadding = size % colorSpan;

        int stepSize = size / colorSpan;

        for ( int index = 0; index < colorSpan; index++ )
                    colors[ index ],
                    colors[ index + 1 ],
                    index == colorSpan - 1 ?
                        stepSize + lastPadding : stepSize

    return palette.ToArray();

Create the Gradient Buffer

If you look at the image atop this post, it looks like the gradient is one seamless image. However, it’s generated by creating a single line gradient with the size Stride x 1.

Here is some sample code that creates the initial gradient line:

int[] initialLine = new int[ display.Stride ];

for( int index = 0; index < display.Stride; index++ )
    Color current = gradient[ index ];

    initialLine[ index ] = BitConverter.ToInt32
        new byte[] { current.B, current.G, current.R, 0 }, 0

The gradient variable is created using an array of .NET color objects:

First, a global field to store the gradient colors:

static Color[] ColorSet = new Color[]
    Color.Black, Color.Teal, Color.Turquoise, Color.White

The ColorUtility is a static class that exposes the CreateGradient method discussed above.

Color[] gradient = ColorUtility.CreateGradient( display.Width, ColorSet );

Displaying the Final Result

Once we have the gradient line stored in a local variable, initialLine we just need to replicate that pre-calculated line over the entire area of our surface buffer.

The trick here is to create an in-memory array the size of the display surface, and only update the value of this array if the size changes. This offers substantial computational savings.

Here is the source code to perform this task, and a brief explanation of exactly what this code is doing for those unfamiliar with manual memory management.

    for( int line = 0; line < display.Height; line++ )
        fixed ( int* ptr = &PixelBuffer[ display.Stride * line ] )
            Marshal.Copy( initialLine, 0, ( IntPtr ) ptr, initialLine.Length );

This code accomplishes its task by “pinning” the starting address of the in-memory surface array (PixelBuffer) so that its memory is in a locked position, which makes it ineligible for garbage collection or reallocation during the execution of our loop.

Inside the fixed statement we can now calculate the offset of the starting position of each virtual ‘line’ (or row) in the array, by multiplying the stride times the current position (loop counter), this should sound familiar if you’ve read my post on GDI via C#.

Once the address has been calculated, we call upon the .NET Marshal.Copy method to copy the bytes stored in the initialLine variable and iterate them over the entire in-memory surface buffer.

After the pixel buffer has been pre-calculated, each frame we simply need to do a direct “Blt”, or more accurately a simple memory copy, from the buffer surface to the display surface:

Marshal.Copy( PixelBuffer, 0, display.Surface, PixelBuffer.Length );

Sample Application and Examples

The full source code and example applications are included in my Rendering Framework project.

3 comments on “Multicolor Gradients: An Introduction to Working with Palette Effects

Leave a Reply

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

You are commenting using your 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