Click here to Skip to main content
15,920,603 members
Articles / Programming Languages / Visual Basic
Article

ColorMatrix Basics - Simple Image Color Adjustment

Rate me:
Please Sign up or sign in to vote.
4.75/5 (50 votes)
2 Apr 2003CPOL3 min read 476.7K   95   68
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.

Image 4

This takes the color Image 5 and transforms it to Image 6.

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:

VB
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.

VB
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:

VB
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)


Written By
Architect
United States United States
Michael has been developing software for about 19 years primarily in C#, C/C++, Fortran, and Visual Basic. His previous experience includes Internet data services (communication protocols, data storage and GIS) 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.

Comments and Discussions

 
GeneralRe: Saving the new changed image Pin
thefellow3j3-Feb-07 20:36
thefellow3j3-Feb-07 20:36 
Generalavg rgb values Pin
wiseleyb1-Jul-03 13:25
wiseleyb1-Jul-03 13:25 
GeneralRe: avg rgb values Pin
Michael Combs2-Jul-03 4:50
Michael Combs2-Jul-03 4:50 
GeneralRe: avg rgb values Pin
MikeDC28-Sep-06 2:38
MikeDC28-Sep-06 2:38 
GeneralImage Manipulation Pin
tantang1215-Jun-03 18:20
susstantang1215-Jun-03 18:20 
GeneralRe: Image Manipulation Pin
Anonymous27-Aug-03 21:34
Anonymous27-Aug-03 21:34 
GeneralRe: Image Manipulation Pin
len00x13-Sep-03 5:59
len00x13-Sep-03 5:59 
GeneralRe: Image Manipulation Pin
headspin30-Jan-06 8:50
headspin30-Jan-06 8:50 
Option Explicit On
Option Strict On

Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Public Class Colourise
' Provide public access to the picture's byte data.
Public ImageBytes() As Byte
Public RowSizeBytes As Integer
Public Const PixelDataSize As Integer = 32

' Save a reference to the bitmap.
Public Sub New(ByVal bm As Bitmap)
m_Bitmap = bm
End Sub

' A reference to the Bitmap.
Private m_Bitmap As Bitmap

' Bitmap data.
Private m_BitmapData As BitmapData

' Lock the bitmap's data.
Private Sub LockBitmap()
' Lock the bitmap data.
Dim bounds As Rectangle = New Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height)
m_BitmapData = m_Bitmap.LockBits(bounds, Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
RowSizeBytes = m_BitmapData.Stride

' Allocate room for the data.
Dim total_size As Integer = m_BitmapData.Stride * m_BitmapData.Height
ReDim ImageBytes(total_size)

' Copy the data into the ImageBytes array.
Marshal.Copy(m_BitmapData.Scan0, ImageBytes, 0, total_size)
End Sub

' Copy the data back into the Bitmap
' and release resources.
Private Sub UnlockBitmap()
' Copy the data back into the bitmap.
Dim total_size As Integer = m_BitmapData.Stride * m_BitmapData.Height
Marshal.Copy(ImageBytes, 0, m_BitmapData.Scan0, total_size)

' Unlock the bitmap.
m_Bitmap.UnlockBits(m_BitmapData)

' Release resources.
ImageBytes = Nothing
m_BitmapData = Nothing
End Sub

Private Sub RGBToHLS(ByVal r As Long, ByVal g As Long, ByVal b As Long, ByRef h As Single, ByRef s As Single, ByRef l As Single)
Dim Max As Single
Dim Min As Single
Dim delta As Single
Dim rR As Single, rG As Single, rB As Single

rR = CSng(r / 255) : rG = CSng(g / 255) : rB = CSng(b / 255)

'{Given: rgb each in [0,1].
' Desired: h in [0,360] and s in [0,1], except if s=0, then h=UNDEFINED.}
Max = Maximum(rR, rG, rB)
Min = Minimum(rR, rG, rB)
l = (Max + Min) / 2 '{This is the lightness}
'{Next calculate saturation}
If Max = Min Then
'begin {Acrhomatic case}
s = 0
h = 0
'end {Acrhomatic case}
Else
'begin {Chromatic case}
'{First calculate the saturation.}
If l <= 0.5 Then
s = (Max - Min) / (Max + Min)
Else
s = (Max - Min) / (2 - Max - Min)
End If
'{Next calculate the hue.}
delta = Max - Min
If rR = Max Then
h = (rG - rB) / delta '{Resulting color is between yellow and magenta}
ElseIf rG = Max Then
h = 2 + (rB - rR) / delta '{Resulting color is between cyan and yellow}
ElseIf rB = Max Then
h = 4 + (rR - rG) / delta '{Resulting color is between magenta and cyan}
End If
'Debug.Print h
'h = h * 60
'If h < 0# Then
' h = h + 360 '{Make degrees be nonnegative}
'End If
'end {Chromatic Case}
End If
'end {RGB_to_HLS}
End Sub

Private Sub HLSToRGB(ByVal h As Single, ByVal s As Single, ByVal l As Single, ByRef r As Long, ByRef g As Long, ByRef b As Long)
Dim rR As Single, rG As Single, rB As Single
Dim Min As Single, Max As Single

If s = 0 Then
' Achromatic case:
rR = l : rG = l : rB = l
Else
' Chromatic case:
' delta = Max-Min
If l <= 0.5 Then
's = (Max - Min) / (Max + Min)
' Get Min value:
Min = l * (1 - s)
Else
's = (Max - Min) / (2 - Max - Min)
' Get Min value:
Min = l - s * (1 - l)
End If
' Get the Max value:
Max = 2 * l - Min

' Now depending on sector we can evaluate the h,l,s:
If (h < 1) Then
rR = Max
If (h < 0) Then
rG = Min
rB = rG - h * (Max - Min)
Else
rB = Min
rG = h * (Max - Min) + rB
End If
ElseIf (h < 3) Then
rG = Max
If (h < 2) Then
rB = Min
rR = rB - (h - 2) * (Max - Min)
Else
rR = Min
rB = (h - 2) * (Max - Min) + rR
End If
Else
rB = Max
If (h < 4) Then
rR = Min
rG = rR - (h - 4) * (Max - Min)
Else
rG = Min
rR = (h - 4) * (Max - Min) + rG
End If
End If
End If
r = CLng(rR * 255) : g = CLng(rG * 255) : b = CLng(rB * 255)
End Sub

Private Function Maximum(ByVal rR As Single, ByVal rG As Single, ByVal rB As Single) As Single
If (rR > rG) Then
If (rR > rB) Then
Maximum = rR
Else
Maximum = rB
End If
Else
If (rB > rG) Then
Maximum = rB
Else
Maximum = rG
End If
End If
End Function

Private Function Minimum(ByVal rR As Single, ByVal rG As Single, ByVal rB As Single) As Single
If (rR < rG) Then
If (rR < rB) Then
Minimum = rR
Else
Minimum = rB
End If
Else
If (rB < rG) Then
Minimum = rB
Else
Minimum = rG
End If
End If
End Function

Public Sub ColouriseBitmap(ByVal lHue As Long, ByVal lSaturation As Long)
' Perform colourisation
Dim x As Long
Dim y As Long
Dim xEnd As Long
Dim yEnd As Integer
Dim Jump As Integer
Dim h As Single
Dim s As Single
Dim l As Single
Dim lR As Long
Dim lG As Long
Dim lB As Long
Dim hDN As Single
Dim sDN As Single

' Calculate denormalized Hue, Saturation & Intensity:
hDN = CSng(((lHue * 6.0#) / 255.0#) - 1.0#)
sDN = CSng(lSaturation / 255.0#)

' Lock the bitmap.
LockBitmap()

xEnd = RowSizeBytes
yEnd = m_Bitmap.Height
Jump = PixelDataSize \ 8

For x = 0 To xEnd - 1 Step Jump
For y = 0 To yEnd - 1
' Obtain the luminance:
RGBToHLS(ImageBytes(CInt(x + 2 + (y * xEnd))), ImageBytes(CInt(x + 1 + (y * xEnd))), ImageBytes(CInt(x + (y * xEnd))), h, s, l)
' Now get the new colour using the input hue and saturation
HLSToRGB(hDN, sDN, l, lR, lG, lB)
ImageBytes(CInt(x + 2 + (y * xEnd))) = CByte(lR)
ImageBytes(CInt(x + 1 + (y * xEnd))) = CByte(lG)
ImageBytes(CInt(x + (y * xEnd))) = CByte(lB)
Next y
Next x
UnlockBitmap()
End Sub

Public Sub RotateHue(ByVal incHue As Single)
' Saturation only applies to grey scale images. Otherwise saturation
' is taken from the colour.
Dim x As Long, y As Long
Dim xEnd As Long, yEnd As Long
Dim lB As Long, lG As Long, lR As Long
Dim h As Single, s As Single, l As Single
Dim Jump As Integer
Dim hDN As Single

' Lock the bitmap.
LockBitmap()

xEnd = RowSizeBytes
yEnd = m_Bitmap.Height
Jump = PixelDataSize \ 8

For x = 0 To xEnd - 1 Step Jump
For y = 0 To yEnd - 1
RGBToHLS(ImageBytes(CInt(x + 2 + (y * xEnd))), ImageBytes(CInt(x + 1 + (y * xEnd))), ImageBytes(CInt(x + (y * xEnd))), h, s, l)
h = h + incHue

If (h > 5) Then h = h - 6
If (h < -1) Then h = h + 6

HLSToRGB(h, s, l, lR, lG, lB)
ImageBytes(CInt(x + 2 + (y * xEnd))) = CByte(lR)
ImageBytes(CInt(x + 1 + (y * xEnd))) = CByte(lG)
ImageBytes(CInt(x + (y * xEnd))) = CByte(lB)
Next y
Next x
UnlockBitmap()
End Sub
End Class
GeneralActually I think MS missed a chance here Pin
yvdh8-Apr-03 13:22
yvdh8-Apr-03 13:22 
GeneralRe: Actually I think MS missed a chance here Pin
Michael Combs14-Apr-03 7:30
Michael Combs14-Apr-03 7:30 
GeneralRe: Actually I think MS missed a chance here Pin
yvdh26-Apr-03 6:30
yvdh26-Apr-03 6:30 
GeneralGood! Pin
NormDroid3-Apr-03 20:56
professionalNormDroid3-Apr-03 20:56 
GeneralXP Toolbar Alpha Transparency Example Pin
PSCRyan15-Mar-03 8:37
PSCRyan15-Mar-03 8:37 
GeneralRe: XP Toolbar Alpha Transparency Example Pin
Michael Combs16-Mar-03 6:06
Michael Combs16-Mar-03 6:06 
GeneralRe: XP Toolbar Alpha Transparency Example Pin
Abnormalcy7-Aug-03 11:19
Abnormalcy7-Aug-03 11:19 
GeneralAny Chance of some more examples Pin
FruitBatInShades13-Mar-03 10:33
FruitBatInShades13-Mar-03 10:33 
GeneralRe: Any Chance of some more examples Pin
Michael Combs13-Mar-03 11:11
Michael Combs13-Mar-03 11:11 
GeneralRe: Any Chance of some more examples Pin
FruitBatInShades13-Mar-03 23:01
FruitBatInShades13-Mar-03 23:01 
GeneralRe: Any Chance of some more examples Pin
MrPolite5-Sep-03 21:45
MrPolite5-Sep-03 21:45 
GeneralRe: Any Chance of some more examples Pin
Michael Combs8-Sep-03 4:56
Michael Combs8-Sep-03 4:56 
GeneralRe: Any Chance of some more examples Pin
Olivier DALET16-Feb-05 22:42
Olivier DALET16-Feb-05 22:42 
GeneralSample please... Pin
Paul Selormey12-Mar-03 12:50
Paul Selormey12-Mar-03 12:50 

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

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