Click here to Skip to main content
Licence CPOL
First Posted 11 Mar 2003
Views 290,206
Bookmarked 81 times

ColorMatrix Basics - Simple Image Color Adjustment

By | 2 Apr 2003 | Article
A beginners guide to using the GDI+ ColorMatrix.

Color Image Grayscale Image Blue Image

Introduction

This article discusses color operations on digital images, using the new ColorMatrix class provided by GDI+. The ColorMatrix is a welcome addition to the GDI library, especially with the increase in demand of digital imaging applications, as more and more consumer products are made available. This class, as well as many other new GDI classes, provide more control to the developer and reduce dependence on 3rd party applications such as LEAD tools, and others. Some basic knowledge of matrix operations (multiplication, addition, etc), the RGBA colorspace and GDI+ is assumed.

Background

ColorMatrix operations are performed in the RGBA colorspace (red, green, blue, alpha). A ColorMatrix consists of a 5x5 matrix, with color values normalized to 1 for full intensity (255 -> 1.0). You might expect the matrix to be 4x4 ( [R, G, B, A] ), which would be sufficient if we only needed to perform linear transformations (multiplication: scaling, rotation, etc). However, one of the most frequent color manipulations, color adjustment, requires adding color values. This is a non-linear operation, referred to as a translation. Adding a 5th element to the color vector ( [R, G, B, A, w] ) combines these two operations, linear and non-linear, into a single operation called an affine transformation. The 5th element of the color vector is simply a dummy element, always with a value of 1, which only serves to allow a translation (addition) of the color vector.

The example below scales the color vector [255, 128, 102, 255] by .5 and then adds a value of 26 to the R, G and B components, leaving the A component at full intensity. Remember that the component values are normalized, with full intensity, 255, equal to 1.0 (values have been rounded to the nearest tenth). Also notice the addition of the 5th element to the color vector, which is simply ignored in the resultant color vector.

This takes the color and transforms it to .

Now that we've covered the basic principle of the ColorMatrix and it's operations on color vectors, we can start exploring some practical uses.

Applying the code

Applying a ColorMatrix to an image is quite simple. You must first associate a ColorMatrix object with an ImageAttributes object. Then you simply pass the ImageAttributes object as a parameter to the Graphics.DrawImage method.

Color adjustment is one of the more common color operations applied to digital images. The code to do this might look as follows:

Public Function translate(ByVal img As Image, ByVal red As Single, _
                       ByVal green As Single, ByVal blue As Single, _
                       Optional ByVal alpha As Single = 0) As Boolean

    
    Dim sr, sg, sb, sa As Single
    
    ' noramlize the color components to 1
    sr = red / 255
    sg = green / 255
    sb = blue / 255
    sa = alpha / 255
 
    ' create the color matrix
    dim New ColorMatrix(New Single()() _
                       {New Single() {1, 0, 0, 0, 0}, _
                        New Single() {0, 1, 0, 0, 0}, _
                        New Single() {0, 0, 1, 0, 0}, _
                        New Single() {0, 0, 0, 1, 0}, _
                        New Single() {sr, sg, sb, sa, 1}})

    ' apply the matrix to the image
    Return draw_adjusted_image(img, cm)

End Function
        
                               
Private Function draw_adjusted_image(ByVal img As Image, _
                ByVal cm As ColorMatrix) As Boolean


    Try
        Dim bmp As New Bitmap(img) ' create a copy of the source image 
        Dim imgattr As New ImageAttributes()
        Dim rc As New Rectangle(0, 0, img.Width, img.Height)
        Dim g As Graphics = Graphics.FromImage(img)

        ' associate the ColorMatrix object with an ImageAttributes object
        imgattr.SetColorMatrix(cm) 

        ' draw the copy of the source image back over the original image, 
        'applying the ColorMatrix
        g.DrawImage(bmp, rc, 0, 0, img.Width, img.Height, _
                               GraphicsUnit.Pixel, imgattr)

        g.Dispose()

        Return True

    Catch
        Return False
    End Try

End Function

Conversion to grayscale is another common conversion. Grayscale values are determined by calculating the luminosity of a color, which is a weighted average of the R, G and B color components. The average is weighted according to the sensitivity of the human eye to each color component. The weights used here are as given by the NTSC (North America Television Standards Committee) and are widely accepted.

Public Function grayscale(ByVal img As Image) As Boolean


    Dim cm As ColorMatrix = New ColorMatrix(New Single()() _
                           {New Single() {0.299, 0.299, 0.299, 0, 0}, _
                            New Single() {0.587, 0.587, 0.587, 0, 0}, _
                            New Single() {0.114, 0.114, 0.114, 0, 0}, _
                            New Single() {0, 0, 0, 1, 0}, _
                            New Single() {0, 0, 0, 0, 1}})


    Return draw_adjusted_image(img, cm)
    
End Function

The code below creates a digital negative:

Public Function negative(ByVal img As Image) As Boolean

    Dim cm As ColorMatrix = New ColorMatrix(New Single()() _
                           {New Single() {-1, 0, 0, 0, 0}, _
                            New Single() {0, -1, 0, 0, 0}, _
                            New Single() {0, 0, -1, 0, 0}, _
                            New Single() {0, 0, 0, 1, 0}, _
                            New Single() {0, 0, 0, 0, 1}})

    Return draw_adjusted_image(img, cm)

End Function

Color channel separations, alpha transparency adjustment, image toning (Sepia, etc) are just a few more common operations that can be easily performed with a ColorMatrix.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Michael Combs

Architect

United States United States

Member

Michael has been developing software for about 12 years primarily in Fortran, C/C++, Visual Basic and now .NET. His previous experience includes Internet data services (communication, data storage and mapping) for the mortgage industry, oil platform instrumentation and explosives simulation and testing. He holds a B.S. in astrophysics and computer science. He is currently working for Global Software in Oklahoma City developing law enforcement and emergency services related software.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 1 PinmemberXmen W.K.3:01 6 Jul '10  
GeneralMy vote of 4 Pinmemberjoerg-schumann1:46 29 Jun '10  
GeneralI have an error "A Graphics object cannot be created from an image that has an index" Pinmemberrpiniones0:46 7 Apr '10  
Questionaspx file ? How to call in a page to see resluts ? PinmemberAlain Van haecke3:00 9 Jul '09  
GeneralColorize Image Pinmemberdanielku159:46 31 May '09  
GeneralSub OffsetMatrix incorrect PinmemberThomas Nichols6:19 17 Mar '09  
AnswerAlpha Channel Bug PinmemberAndy Davies22:09 27 Feb '09  
GeneralMethod for producing a "Color Match"? [modified] PinmemberMichael Schäuble3:29 21 Sep '08  
QuestionPure Black and White [modified] PinmemberLuca Crisi, MCP23:44 22 May '08  
AnswerRe: Pure Black and White [modified] PinmemberLuca Crisi, MCP2:19 23 May '08  
GeneralRe: Pure Black and White PinmemberAamir Mustafa17:43 9 Oct '08  
GeneralRe: Pure Black and White PinmemberLuca Crisi, MCP8:27 13 Oct '08  
QuestionSaving Changed Image Format Pinmemberstixoffire0:16 19 Sep '07  
QuestionHow to Apply Pinmember*shona0:53 13 Aug '07  
QuestionHow to do softlight on an image? Pinmemberfreecoke6:41 12 Apr '07  
QuestionSave File [modified] PinmemberCarlHackman13:22 11 Mar '07  
Questionabaout grayscale Pinmemberaimaniez13:22 10 Mar '07  
GeneralUnderstanding The Matrix PinmemberJonFrost23:32 25 Nov '06  
QuestionColoring a grayscale image Pinmemberkspaun2:17 28 Sep '06  
AnswerRe: Coloring a grayscale image Pinmemberthefellow3j20:32 3 Feb '07  
Questionhow to do sharpness and blur [modified] Pinmembersingam13:35 7 Jul '06  
AnswerRe: how to do sharpness and blur [modified] Pinmemberwlh21cn23:44 19 Jul '06  
GeneralStrange bug while printing PinmemberPugwash20040:23 10 May '06  
GeneralComplete Grayscale Class PinsussAnonymous10:49 3 May '05  
This class can be used to convert bitmaps (as well as color objects) to grayscale using several semi-standard formulas.
 
I had to remove the Xml documentation since it was interfering with the message format, but it should be fairly straightforward to figure out.
 
<code>
Dim oGray As New OpenBook.Graphics.Colors.Grayscale
_newImage = oGray.ConvertBitmapToGrayscale(_oldImage, OpenBook.Graphics.Colors.Grayscale.Formula.CCIRRec709)
</code>
 
You will have to replace [OpenBook] with something else.   It's simply the way I've organized the namespaces for myself.
 
Thanks to the original author of this tutorial on the help with ColorMatrix.   It makes an enormous performance difference (even if I wish it was a bit quicker still).
 
Kevin Townsend
 
<code>
Imports System.Drawing
Imports System.Drawing.Imaging
 
Namespace Graphics.Colors
 
      Public Class Grayscale
 
            Public Enum Formula
                  SimpleAverage = 0      ' Least accurate
                  WeightAverage = 1
                  NtscPal = 2               ' CCIR Recommendation 601-1 (Used in Ntsc/Pal Standards)
                  CCIRRec709 = 3            ' CCIR Recommendation 709
            End Enum
 
            Public Function ConvertRgbToGrayscale(ByVal sourceColor As Drawing.Color, Optional ByVal formula As Formula = Formula.CCIRRec709)
                  Dim newColor As New Drawing.Color
                  Dim a1, r1, g1, b1 As Int16
                  Dim a2, r2, g2, b2 As Int16
 
                  ' Get source color ARGB attributes
                  a1 = Convert.ToInt16(sourceColor.A)
                  r1 = Convert.ToInt16(sourceColor.R)
                  g1 = Convert.ToInt16(sourceColor.G)
                  b1 = Convert.ToInt16(sourceColor.B)
 
                  ' Calculate changes depending on foruma selected
                  Select Case formula
                        Case formula.SimpleAverage
                              a2 = a1
                              r2 = Convert.ToInt16((r1 + g1 + b1) / 3)
                              g2 = Convert.ToInt16((r1 + g1 + b1) / 3)
                              b2 = Convert.ToInt16((r1 + g1 + b1) / 3)
                        Case formula.WeightAverage
                              a2 = a1
                              r2 = Convert.ToInt16((3 * r1) + (4 * b1) + (2 * g1) / 9)
                              g2 = Convert.ToInt16((3 * r1) + (4 * b1) + (2 * g1) / 9)
                              b2 = Convert.ToInt16((3 * r1) + (4 * b1) + (2 * g1) / 9)
                        Case formula.NtscPal
                              a2 = a1
                              r2 = Convert.ToInt16((0.299 * r1) + (0.587 * g1) + (0.114 * b1))
                              g2 = Convert.ToInt16((0.299 * r1) + (0.587 * g1) + (0.114 * b1))
                              b2 = Convert.ToInt16((0.299 * r1) + (0.587 * g1) + (0.114 * b1))
                        Case formula.CCIRRec709
                              a2 = a1
                              r2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                              g2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                              b2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                        Case Else
                              ' Use CCIR Rec 709 as default
                              a2 = a1
                              r2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                              g2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                              b2 = Convert.ToInt16((0.2125 * r1) + (0.7154 * g1) + (0.0721 * b1))
                  End Select
 
                  ' Check limits
                  If r2 > 255 Then r2 = 255
                  If g2 > 255 Then g2 = 255
                  If b2 > 255 Then b2 = 255
                  If r2 < 0 Then r2 = 0
                  If g2 < 0 Then g2 = 0
                  If b2 < 0 Then b2 = 0
 
                  ' Set newColor
                  newColor = Drawing.Color.FromArgb(a2, r2, g2, b2)
 
                  ' Return
                  Return newColor
            End Function
 
            Public Function ConvertBitmapToGrayscale(ByVal sourceImage As Bitmap, Optional ByVal formula As Formula = Formula.CCIRRec709) As Bitmap
                  Dim output As Bitmap = New Bitmap(sourceImage.Width, sourceImage.Height)
                  Dim rect As New Rectangle(0, 0, sourceImage.Width, sourceImage.Height)
                  Dim cm As ColorMatrix
                  Dim ia As New ImageAttributes
                  ' Assign sourceBitmap to a new graphics object
                  Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(output)
 
                  ' Apply selected grayscale formula
                  Select Case formula
                        Case Colors.Grayscale.Formula.CCIRRec709
                              cm = New ColorMatrix(New Single()() _
                                    {New Single() {0.213, 0.213, 0.213, 0, 0}, _
                                    New Single() {0.715, 0.715, 0.715, 0, 0}, _
                                    New Single() {0.072, 0.072, 0.072, 0, 0}, _
                                    New Single() {0, 0, 0, 1, 0}, _
                                    New Single() {0, 0, 0, 0, 1}})
                        Case Colors.Grayscale.Formula.NtscPal
                              cm = New ColorMatrix(New Single()() _
                                    {New Single() {0.299, 0.299, 0.299, 0, 0}, _
                                    New Single() {0.587, 0.587, 0.587, 0, 0}, _
                                    New Single() {0.114, 0.114, 0.114, 0, 0}, _
                                    New Single() {0, 0, 0, 1, 0}, _
                                    New Single() {0, 0, 0, 0, 1}})
                        Case Colors.Grayscale.Formula.SimpleAverage
                              cm = New ColorMatrix(New Single()() _
                                    {New Single() {0.333, 0.333, 0.333, 0, 0}, _
                                    New Single() {0.333, 0.333, 0.333, 0, 0}, _
                                    New Single() {0.333, 0.333, 0.333, 0, 0}, _
                                    New Single() {0, 0, 0, 1, 0}, _
                                    New Single() {0, 0, 0, 0, 1}})
                        Case Colors.Grayscale.Formula.WeightAverage
                              cm = New ColorMatrix(New Single()() _
                                    {New Single() {0.333, 0.333, 0.333, 0, 0}, _
                                    New Single() {0.444, 0.444, 0.444, 0, 0}, _
                                    New Single() {0.222, 0.222, 0.222, 0, 0}, _
                                    New Single() {0, 0, 0, 1, 0}, _
                                    New Single() {0, 0, 0, 0, 1}})
                        Case Else
                              ' Use CCIR Rec. 709 as catch all
                              cm = New ColorMatrix(New Single()() _
                                    {New Single() {0.213, 0.213, 0.213, 0, 0}, _
                                    New Single() {0.715, 0.715, 0.715, 0, 0}, _
                                    New Single() {0.072, 0.072, 0.072, 0, 0}, _
                                    New Single() {0, 0, 0, 1, 0}, _
                                    New Single() {0, 0, 0, 0, 1}})
                  End Select
 
                  ' Set color matrix
                  ia.SetColorMatrix(cm)
 
                  ' Render new bitmap image
                  g.DrawImage(sourceImage, rect, 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, ia)
 
                  ' Return converted image
                  Return output
            End Function
 
      End Class
 
End Namespace
 
</code>
QuestionHow about 256-colored pictures Pinmemberoffslinker21:41 16 Apr '05  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120528.1 | Last Updated 3 Apr 2003
Article Copyright 2003 by Michael Combs
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid