Use the ColorMatrix and ImageAttributes classes to quickly modify image colors in C#

[modify image colors]

This example shows how to use the ColorMatrix and ImageAttribute classes to quickly modify image colors. I’ve made several posts that use those classes to modify image colors in a few specific ways. I’ve also posted other posts that modify colors by looping through an image’s pixels.

This post describes the ColorMatrix and ImageAttribute classes and explains how you can use them to modify image colors in certain ways extremely quickly.

Using ColorMatrix and ImageAttributes

When you use the Graphics class’s DrawImage method, you can pass in an optional ImageAttributes object that can modify the image. For example, the following code snippet applies the ImageAttributes object named attr to the image that is being drawn.

gr.DrawImage(image, dest_points,
     source_rect, GraphicsUnit.Pixel, attr);

One of the things that the ImageAttributes object can do is apply a ColorMatrix to the image’s pixels. The ColorMatrix is a five-by-five matrix of values that are multiplied by each of the image’s pixels. Each pixel is treated as a vector of values that include its red, green, blue, and alpha (opacity) components, plus a fifth value that is set to 1.

The following picture shows how a pixel’s component vector is multiplied by the matrix.


[modify image colors]

The system performs normal vector/matrix multiplication to get the result pixel’s component values. For example, multiplying the vector by the matrix’s first column gives the red component.

    red = R * m00 + G * m10 + B * m20 + A * m30 + 1 * m40

Building the ColorMatrix

There are several ways that you can build a ColorMatrix. The following code shows how you can explicitly set each of the object’s matrix entries.

ColorMatrix color_matrix = new ColorMatrix(
    new float[][]
    {
        new float[] {-1,  0,  0,  0,  0},
        new float[] { 0, -1,  0,  0,  0},
        new float[] { 0,  0, -1,  0,  0},
        new float[] { 0,  0,  0,  1,  0},
        new float[] { 1,  1,  1,  0,  1}
    });

If you multiply the pixel component vector by this matrix, the new pixel value is <1 – R, 1 – G, 1 – B, A, 1>.

That result inverts the pixel’s red, green, and blue color components so you get a negative of the original image. Shortly I’ll talk more about the component values and why values such as 1 – R inverts the red component. I’ll also show several specific matrices that you might want to use.

Another approach to building a ColorMatrix is to start with the default identity matrix and then modify any entries that you want to change. The following code demonstrates that approach.

ColorMatrix color_matrix = new ColorMatrix();
color_matrix.Matrix33 = 0.75f;

The first statement initializes the matrix to a default, which is the identity matrix. that matrix, which is all zeros except for all ones on the upper-left to lower-right diagonal, is shown in the following picture. If you multiply a pixel’s component vector by an identity matrix, the result is the same as the original vector.


[modify image colors]

The code snippet then sets the matrix’s [3][3] entry to 0.75 to give the following matrix. Now if you component multiply a vector by the matrix, the vector’s alpha component is multiplied by 0.75 so the result is less opaque.


[modify image colors]

Component Values

Often we think of a pixel’s component values as ranging from 0 to 255, but that really only makes sense if you’re using a color model that uses 8 bits for each color component. These days many images use 24-bit color with 8 bits for each of the red, green, and blue components. Other images use 32 bits with 8 bits for red, green, blue, and alpha. In theory, however, there’s no reason why you couldn’t use some other color model. Older images may use color models with fewer bits such as 8, 15, 16, or 18 bits. Deep color models may use more bits such as 30, 36, or 48 bits.

To make working with colors as flexible as possible, a ColorMatrix represents each color component as a value between zero and one. For example, if you’re using a 32-bit color system and a pixel has RGBA values (255, 128, 0, 255), then the color component vector used by the ColorMatrix is <1, 0.5, 0, 1>.

One consequence of this is that the values in a ColorMatrix are usually between zero and one. You can use larger or smaller values, but they may not make sense.

Note that the ColorMatrix pegs the resulting component values so they lie between zero and one. If a result of the matrix multiplication is less than zero, then the output color component is zero. Similarly if the result is greater than one, the result is one.

Useful Matrices

The following picture shows some useful matrices.


[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]
[modify image colors]

You can use similar matrices to adjust different color components. For example, take a look at the “Set Alpha” matrix. To use a similar matrix to set red values, start with an identity matrix, set the [0][0] entry to 0, and set the [4][0] entry to the value that you want to use for the red component as shown in the following picture.


[modify image colors]

There are many other ways that you can use the ColorMatrix. For example, you could set the red and green components to the average of those values and set blue components to zero. Or you could switch the image’s red and blue components. You can also use techniques used in three-dimensional graphics to rotate, translate, or skew the colors, although exactly what it means to rotate or skew colors isn’t obvious. Feel free to download the example and experiment.

The Example Program

When you load an image file, the program saves it in the Image variable named TransformedImage. When you apply the commands in the program’s Picture menu, the program performs those commands on that image and displays the result.

Probably the most interesting part of the example program is the following ApplyColorMatrix method, which copies an image into a new bitmap while applying a ColorMatrix to it.


private Image ApplyColorMatrix(Image image, ColorMatrix color_matrix)
{
    // Make the result bitmap.
    Bitmap bm = new Bitmap(image.Width, image.Height);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        // Create an ImageAttributes object and use it to
        // draw the original image onto the result image.
        ImageAttributes attr = new ImageAttributes();
        attr.SetColorMatrix(color_matrix);

        Point[] dest_points =
        {
            new Point(0, 0),
            new Point(image.Width, 0),
            new Point(0, image.Height),
        };
        Rectangle source_rect = new Rectangle(
            0, 0, image.Width, image.Height);
        gr.DrawImage(image, dest_points,
            source_rect, GraphicsUnit.Pixel, attr);
    }
    return bm;
}

The method first creates a new bitmap with the same size as the original image and makes an associated Graphics object. It then creates a new ImageAttributes object and sets its ColorMatrix to the one passed in as a parameter.

The code then creates an array giving the upper left, upper right, and lower left corners of the rectangle where the image should be drawn. It also creates a source rectangle indicating the part of the image that should be drawn. The values used here copy the entire input image onto the entire output image.

Next the method draws the input image onto the new bitmap, passing the DrawImage method the ImageAttributes object. (There are simpler overloaded versions of the DrawImage method, but they don’t include an ImageAttributes object.

The method finishes by returning the new bitmap.

The example program uses the ApplyColorMatrix method to perform each of its operations. For example, the following code shows how the program decreases the picture’s alpha value.

private void mnuPictureDecreaseAlpha_Click(object sender, EventArgs e)
{
    ColorMatrix color_matrix = new ColorMatrix(
        new float[][]
        {
            new float[] {1, 0, 0,    0, 0},
            new float[] {0, 1, 0,    0, 0},
            new float[] {0, 0, 1,    0, 0},
            new float[] {0, 0, 0, 0.8f, 0},
            new float[] {0, 0, 0,    0, 1}
        });
    TransformedImage = ApplyColorMatrix(TransformedImage, color_matrix);
    ShowImage();
}

This code simply creates an appropriate ColorMatrix. It then passes it and the current TransformedImage into the ApplyColorMatrix method and stores the result in variable TransformedImage. It finishes by calling the following ShowImage method to display the result.

// Display the transformed image drawn on top of a checkerboard.
private void ShowImage()
{
    int wid = TransformedImage.Width;
    int hgt = TransformedImage.Height;
    Bitmap bm = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        // Draw the checkerboard.
        const int step = 20;
        for (int x = 0; x < wid; x += step)
        {
            for (int y = 0; y < hgt; y += step)
            {
                if (((x / step) + (y / step)) % 2 == 0)
                    gr.FillRectangle(Brushes.Red,
                        x, y, step, step);
                else
                    gr.FillRectangle(Brushes.Yellow,
                        x, y, step, step);
            }
        }

        // Draw the image on top.
        gr.DrawImage(TransformedImage, new Point(0, 0));
    }

    picImage.Image = bm;
}

This method creates a new bitmap that is the same size as the TransformedImage. It then uses nested loops to fill the bitmap with a checkerboard.

The code then draws the TransformedImage onto the bitmap and displays the result in the picImage PictureBox. If the pixels’ alpha components are one (255 in a 24- or 32-bit color system), then the image completely covers the checkerboard. If the pixels’ alpha components are less than one, then the checkerboard will show through.

Conclusion

The program includes a lot of other details such as how it loads and saves images, reverts to the original image, and enables and disables menu items when appropriate. Download the example to see additional details and to experiment with the program. If you come up with some particularly interesting images, feel free to post a note below.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in graphics, image processing and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.