If you have any problems with this web page, please first read my browser notesbrowser notes [link to ../../Miscellany/Browsers/Browsers.php] and if you still have issues, feel free to e-mail mee-mail me [link to e-mail the author at mailto:Tony@WordArticles.com]

Journeys in Colour Space

Colour Space

Journeys in Colour Space


I am an alien in Colour Space; I do words, not pictures. Please forgive me if my ramblings are not 100% technically accurate; if you'd care to help me out, you can e-mail me herehere [link to e-mail the author at mailto:Tony@WordArticles.com]

This page is part of my continuing, and probably inept, attempts to maintain clarity in articles by moving my sometimes verbose descriptions of details out to one side. It describes in detail the differences between two different representations of colours (RGB and HSL), tells you why you need to know, and offers conversion routines for you to use. In case you have come here from somewhere other than my main article on colours in Word 2007, I have endeavoured to make this complete and self-contained as far as it goes.

Red, Green, and Blue

Most anybody working in Microsoft Office, probably most anybody working with computers, will be aware of the Red, Green, Blue (RGB) colour model used to represent colours in the RGB, or, more correctly, standard RGB, colour space. This is not the place, even if I had the knowledge, to go into the differences between colour spaces and colour models, and it is sufficient to know that RGB is a method used to represent colours input to computers from, or output by computers to, optical devices such as cameras and monitors.

The RGB colour model is an additive model, a type used when working with light (as opposed to the, perhaps more common, subtractive models used when working with paints, dyes, inks, etc.). The more of a colour one adds to the melting pot, the lighter, or brighter, that colour becomes in the overall result. When there is none of any of the colours, the result is black (dark) and when there is full intensity of all of the colours the result is white (light).

There are three primary colours (red, green, and blue, if you hadn't guessed!), colours that cannot be made by mixing other colours, corresponding, roughly, to the three types of colour receptors, or cones, in the human eye. All other colours can be made by mixing the three primary colours in varying proportions. The RGB model used by PCs allows for the amount of each of the primary colours to be specified as an integer in the range 0 — 255, giving 256 × 256 × 256 possible combinations for a total of 16,777,216, colloquially referred to as 16 million, or just millions of, colours.

The RGB colour model can be thought of, and is sometimes graphically represented as, a cube, with each of the primary colours being measured along a single dimension. The eight vertices of the cube represent black and white, the three primary colours, red, green, and blue, and the three secondary colours obtained by taking combinations of full intensity of two of the three primary colours, yellow, magenta, and cyan or what Word calls yellow, purple and aqua.

If, like me, you just use normal colour facilities on your computer, the RGB colour model is the one you will have been using, and be familiar with, up to now.

Hue, Saturation, and Luminance

Hue, Saturation, Luminance (sometimes referred to as Luminosity, or Lightness) — HSL — is a different colour model that can be used to represent the same colours as those in the RGB colour space. Instead of the cube used to represent RGB colours, HSL model colours are, strictly, points in or on a cylinder, but are usually represented as a bicone, generally incorrectly referred to as a double cone, two cones placed base to base. Black sits at the bottom vertex and the intensity, or luminosity, rises up the axis to arrive at white at the top vertex. What you may think of as the actual colour, the hue, is measured round the circumference, and the depth of the colour, the saturation, is measured by the distance from the central axis.

Hue, round the circumference, is normally given as an angle from 0° — 360°, 0° (and 360°) being red and running, roughly, through all the colours of the rainbow, with green at 120° and blue at 240°, as the angle increases. Saturation, from grey to chosen hue, is normally specified either as a percentage or as a value in the range zero to one, as is luminance, which runs from black (0) through the chosen hue and saturation (0.5, or 50%) to white (1, or 100%). Translated into values in Word dialogues, all three are specified, just as with the RGB values, as integers in the range 0 — 255. Just to keep you on your toes, Windows allows specification of hue as an integer in the range 0 — 239 and saturation and luminance as integers in the range 0 — 240.

The RGB and HSL models map the same colour space; they simply present it in different ways, and one or other may be more preferable for certain operations. In particular, and of interest at the moment, is the ease with which a colour can be made lighter or darker in the HSL model simply by adjusting its luminance. In theory it should be possible to convert from one to the other without losing infomation. This ability is, however, limited by constraints of implementation, in particular the fact that all the component values are rounded to integers in some range or other.


Converting from RGB to HSL is relatively straightforward. The algorithms, and code samples in many languages, are freely available on the web. The VBA code presented here isn't special; I hope it is accurate. I have tried many variations on the theme in order to try to match exactly what Word does but, as at the time of writing, I have been unable to manage it. The small differences I have seen (of 1 in one or more of the components) could, perhaps, be explained by rounding. Word (and, indeed, Windows, as far as I can tell) does not make HSL values available to code, only to the User Interface, so I have not been able to do any automated testing but, in at least one case, Word does not successfully round-trip values; that is, converting from RGB to HSL and back to RGB results in a different colour (although I could not claim it to be visually different).

Without further ado, here is some code that converts between RGB values buried in a Long variable and the HSL equivalent, in a User-Defined Type. For the simple purposes required here, and to avoid as many rounding differences as possible, the HSL component values are all maintained as values in the range 0 — 1, and the RGB values are worked on as values in the same range. Firstly the Type definition to go at the start of the module:

Type HSL
    H As Double As Double ' Range 0 - 1
    S As Double As Double ' Range 0 - 1
    L As Double As Double ' Range 0 - 1
End Type
' From RGB to HSL

Function RGBtoHSL(RGB As Long) As HSL

    Dim R As Double ' Range 0 - 1
    Dim G As Double ' Range 0 - 1
    Dim B As Double ' Range 0 - 1

    Dim RGB_Max  As Double
    Dim RGB_Min  As Double
    Dim RGB_Diff As Double

    Dim HexString As String

    HexString = Right$(String$(7, "0") & Hex$(RGB), 8)
    R = CDbl("&H" & Mid$(HexString, 7, 2)) / 255
    G = CDbl("&H" & Mid$(HexString, 5, 2)) / 255
    B = CDbl("&H" & Mid$(HexString, 3, 2)) / 255

    RGB_Max = R
    If G > RGB_Max Then RGB_Max = G
    If B > RGB_Max Then RGB_Max = B

    RGB_Min = R
    If G < RGB_Min Then RGB_Min = G
    If B < RGB_Min Then RGB_Min = B

    RGB_Diff = RGB_Max - RGB_Min

    With RGBtoHSL
        .L = (RGB_Max + RGB_Min) / 2

        If RGB_Diff = 0 Then
            .S = 0
            .H = 0

            Select Case RGB_Max
                Case R: .H = (1 / 6) * (G - B) / RGB_Diff - (B > G)
                Case G: .H = (1 / 6) * (B - R) / RGB_Diff + (1 / 3)
                Case B: .H = (1 / 6) * (R - G) / RGB_Diff + (2 / 3)
            End Select
            Select Case .L
                Case Is < 0.5: .S = RGB_Diff / (2 * .L)
                Case Else:     .S = RGB_Diff / (2 - (2 * .L))
            End Select
        End If

    End With
End Function

' .. and back again

Function HSLtoRGB(HSL As HSL) As Long

    Dim R As Double
    Dim G As Double
    Dim B As Double

    Dim X As Double
    Dim Y As Double

    With HSL
        If .S = 0 Then
            R = .L
            G = .L
            B = .L
            Select Case .L
                Case Is < 0.5: X = .L * (1 + .S)
                Case Else:     X = .L + .S - (.L * .S)
            End Select
            Y = 2 * .L - X

            R = H2C(X, Y, IIf(.H > 2 / 3, .H - 2 / 3, .H + 1 / 3))
            G = H2C(X, Y, .H)
            B = H2C(X, Y, IIf(.H < 1 / 3, .H + 2 / 3, .H - 1 / 3))
        End If
    End With
    HSLtoRGB = CLng("&H00" & _
                    Right$("0" & Hex$(Round(B * 255)), 2) & _
                    Right$("0" & Hex$(Round(G * 255)), 2) & _
                    Right$("0" & Hex$(Round(R * 255)), 2))

End Function
Function H2C(X As Double, Y As Double, HC As Double) As Double Select Case HC Case Is < 1 / 6: H2C = Y + ((X - Y) * 6 * HC) Case Is < 1 / 2: H2C = X Case Is < 2 / 3: H2C = Y + ((X - Y) * ((2 / 3) - HC) * 6) Case Else: H2C = Y End Select End Function

Tint and Shade

The current reason for wanting to use the HSL representation is to simplify determination of the results of using tints (lighter variants of the colour) and shades (darker variants). You may remember that a tint of 40% (equivalent to being 60% lighter), say, is a colour 40% of the way between white and the starting colour, and that that is equivalent to a colour with luminance 40% of the way between its maximum (1, on the 0 — 1 scale you are using here) and its current value.

In case that wasn't entirely clear, Microsoft define a tint (staying with the example of 40%) as being 40% of the base colour with 60% white, and a corresponding shade as being 40% colour with 60% black. To calculate the results you can substitute luminance for colour, for example, if a colour has a luminance of 0.3, a 40% tint is 40% of 0.3 (0.12) plus 60% of 1 (0.6), which is a luminance of 0.72.

Code for this is but a single line, which is shown in context in the code in the next section. Given Luminance and a TintAndShade with values between 0 and 1 representing tints (0.4 = 40% tint, say), and values between 0 and -1 representing shades (-0.4 = 40% shade), this will do the adjusting:

Luminance = (Luminance * (Abs(TintAndShade))) + _
            (Abs(TintAndShade > 0) * (1 - TintAndShade))

The expression Abs(TintAndShade > 0), perhaps, deserves explanation. The Abs Function first coerces the Boolean result of the comparison to a number: True = -1, False = 0, and then strips the sign, so the expression as a whole resolves to 1 when TintAndShade is positive (i.e. a tint) and 0 when it is negative (a shade), giving the appropriate multiplicand as described above.

Using the Routines

The reason for my writing this page was that these routines were needed as part of the larger project of determining RGB values for theme colours, and the routines will be seen in context on the main page. Briefly, an RGB element will be added to the ColourDetails UDT, and it will be populated by a call to the GetRGB procedure from the QueryThemeColor routine, after which it will be passed back to the top-level Colours2 procedure for it to do as it will.

All that I am going to provide here is some sample superstructure that can be used for testing. Needed here, and shown below as a reference, is a procedure, based on the conversion table in the main article herehere [link to the table in the main article at 2007.php#EnumerationConversion]), that converts Word's idea of a theme colour index to the Theme’'s idea of one, and which enables access to the colour information in the theme.

Suppose you have a colour you want to identify and you know its Theme Color Index (one of the wdThemeColorIndex enumeration constants) and its Tint or Shade, if any, as a value from -1 to +1, in other words you have the values that you have seen can be extracted from what Word makes available for font, and other, colours through its object model (and which Word more directly makes available for objects that have ColorFormat objects).

' (HSL User-defined Type as above)
Sub MickeyMouseDriver() Dim ResultRGB As Long Dim ResultHex As String
    ' =================================================
    ' Put test (Word) theme index and TintAndShade here
    ' =================================================
    '                  =================  ===
    ResultRGB = GetRGB(wdThemeColorText2, 0.3)
    '                  =================  ===
    ResultHex = Right("00000" & Hex(ResultRGB), 6)
    MsgBox " Result is RGB(" & CLng("&H" & Mid(ResultHex, 5, 2)) & _
                        ", " & CLng("&H" & Mid(ResultHex, 3, 2)) & _
                        ", " & CLng("&H" & Mid(ResultHex, 1, 2)) & ")"

End Sub
Function GetRGB(ThemeColorIndex As WdThemeColorIndex, _ TintAndShade As Double) _ As String Dim ColorSchemeIndex As MsoThemeColorSchemeIndex Dim ColorSchemeRGB As Long Dim ColorSchemeHSL As HSL Dim TintedAndShadedRGB As Long ColorSchemeIndex = ThemeColorSchemeIndex(ThemeColorIndex) ColorSchemeRGB = ActiveDocument.DocumentTheme. _ ThemeColorScheme(ColorSchemeIndex).RGB ColorSchemeHSL = RGBtoHSL(ColorSchemeRGB) ColorSchemeHSL.L = (ColorSchemeHSL.L * (Abs(TintAndShade))) + _ (Abs(TintAndShade > 0) * (1 - TintAndShade)) TintedAndShadedRGB = HSLtoRGB(ColorSchemeHSL) GetRGB = TintedAndShadedRGB End Function
' (ThemeColorSchemeIndex routine as on main page)
' (RGBtoHSL routine as above)
' (HSLtoRGB (and H2C) routines as above)

For the moment, at least, that is all I have to say about colour space. Your trip can be continued by returning to the main articlereturning to the main article [link to back to the main article at 2007.php#ThemeColorSchemeIndex]).