6

I am trying (mostly successfully) to "read" the colors from the active ThemeColorScheme.

The subroutine below will obtain 12 colors from the theme, for example this is myAccent1:

http://i.imgur.com/ZwBRgQO.png

I need also to obtain 4 more colors from the palette. The four colors I need will be the one immediately below the color indicated above, and then the next 3 colors from left-to-right.

Because the ThemeColorScheme object holds 12 items only I get The specified value is out of range error, as expected if I try to assign a value to myAccent9 this way. I understand this error and why it occurs. What I do not know is how to access the other 40-odd colors from the palette, which are not part of the ThemeColorScheme object?

Private Sub ColorOverride()

Dim pres As Presentation
Dim thm As OfficeTheme
Dim themeColor As themeColor
Dim schemeColors As ThemeColorScheme

Set pres = ActivePresentation

Set schemeColors = pres.Designs(1).SlideMaster.Theme.ThemeColorScheme

    myDark1 = schemeColors(1).RGB         'msoThemeColorDark1
    myLight1 = schemeColors(2).RGB        'msoThemeColorLight
    myDark2 = schemeColors(3).RGB         'msoThemeColorDark2
    myLight2 = schemeColors(4).RGB        'msoThemeColorLight2
    myAccent1 = schemeColors(5).RGB       'msoThemeColorAccent1
    myAccent2 = schemeColors(6).RGB       'msoThemeColorAccent2
    myAccent3 = schemeColors(7).RGB       'msoThemeColorAccent3
    myAccent4 = schemeColors(8).RGB       'msoThemeColorAccent4
    myAccent5 = schemeColors(9).RGB       'msoThemeColorAccent5
    myAccent6 = schemeColors(10).RGB      'msoThemeColorAccent6
    myAccent7 = schemeColors(11).RGB      'msoThemeColorThemeHyperlink
    myAccent8 = schemeColors(12).RGB      'msoThemeColorFollowedHyperlink

    '## THESE LINES RAISE AN ERROR, AS EXPECTED:

    'myAccent9 = schemeColors(13).RGB     
    'myAccent10 = schemeColors(14).RGB
    'myAccent11 = schemeColors(15).RGB
    'myAccent12 = schemeColors(16).RGB

End Sub

So my question is, how might I obtain the RGB value of these colors from the palette/theme?

David Zemens
  • 53,033
  • 11
  • 81
  • 130
  • 1
    [This article](http://www.wordarticles.com/Articles/Colours/2007.php) contains a ton of information about doing this in Word. I looked over it since this question is a great question but I don't have the time to figure out all the details to convert it to PPT - might be a good reference though. It looks like the additional theme colors are actually variations of TintAndShade on the main theme color. – enderland Jan 15 '14 at 17:57
  • 1
    @enderland they are variations using `TintAndShade` property, which is the route I will take if need be... although that initially sounded like a PITA, I think it might be the easiest approach that will work with the least intereference to the user's experience with PPT. – David Zemens Jan 15 '14 at 18:04

3 Answers3

7

At first sight Floris' solution seems to work, but if you're concerned with accuracy you'll soon realize that the previous solution matches the office color calculations for just a minor part of the color space.

A Proper solution - Using HSL color space

Office seems to use HSL color mode while calculating tinting and shading, and using this technique gives us almost 100% accurate color calculations (tested on Office 2013).

The methodology for calculating the values correctly seems to be:

  1. Convert the base RGB color to HSL
  2. Find the tint and shade values to use for the five sub-colors
  3. Apply tint/shade values
  4. Convert back from HSL to RGB color space

To find the tint/shade values (step #3), you look at the Luminosity-value of the HSL color and uses this table (found by trial & error):

| [0.0] | <0.0 - 0.2> | [0.2 - 0.8] | <0.8 - 1.0> | [1.0] |
|:-----:|:-----------:|:-----------:|:-----------:|:-----:|
| + .50 |    + .90    |    + .80    |    - .10    | - .05 |
| + .35 |    + .75    |    + .60    |    - .25    | - .15 |
| + .25 |    + .50    |    + .40    |    - .50    | - .25 |
| + .10 |    + .25    |    - .25    |    - .75    | - .35 |
| + .05 |    + .10    |    - .50    |    - .90    | - .50 |

Positive values are tinting the color (making it lighter), and negative values are shading the color (making it darker). There are five groups; 1 group for completely black and 1 group for completely white. These will just match these specific values (and not e.g. RGB = {255, 255, _254_}). Then there are two small ranges of very dark and very light colors that are treated separately, and finally a big range for all of the rest colors.

Note: A value of +0.40 means that the value will get 40% lighter, not that it is a 40% tint of the original color (which actually means that it is 60% lighter). This might be confusing to someone, but this is the way Office uses these values internally (i.e. in Excel through the TintAndShade property of the Cell.Interior).

PowerPoint VBA code to implement the solution

[Disclaimer]: I've built upon Floris' solution to create this VBA. A lot of the HSL translation code is also copied from a Word article mentioned in the comments already.

The output from the code below is the following color variations:

Program output, calculated color variations

At first glance, this looks very similar to Floris' solution, but on closer inspection you can clearly see the difference in many situations. Office theme colors (and thus this solution) is generally more saturated the the plain RGB lighten/darken technique.

Comparison of the different solutions. This matches office very well!

Option Explicit

Public Type HSL
    h As Double ' Range 0 - 1
    S As Double ' Range 0 - 1
    L As Double ' Range 0 - 1
End Type

Public Type RGB
    R As Byte
    G As Byte
    B As Byte
End Type

Sub CalcColor()
    Dim ii As Integer, jj As Integer
    Dim pres As Presentation
    Dim schemeColors As ThemeColorScheme
    Dim ts As Double
    Dim c, c2 As Long
    Dim hc As HSL, hc2 As HSL

    Set pres = ActivePresentation
    Set schemeColors = pres.Designs(1).SlideMaster.Theme.ThemeColorScheme

    ' For all colors
    For ii = 0 To 11
      c = schemeColors(ii + 1).RGB

      ' Generate all the color variations
      For jj = 0 To 5
        hc = RGBtoHSL(c)
        ts = SelectTintOrShade(hc, jj)
        hc2 = ApplyTintAndShade(hc, ts)
        c2 = HSLtoRGB(hc2)
        Call CreateShape(pres.Slides(1), ii, jj, c2)
      Next jj
    Next ii

End Sub

' The tint and shade value is a value between -1.0 and 1.0, where
' -1.0 means fully shading (black), and 1.0 means fully tinting (white)
' A tint/shade value of 0.0 will not change the color
Public Function SelectTintOrShade(hc As HSL, variationIndex As Integer) As Double

    Dim shades(5) As Variant
    shades(0) = Array(0#, 0.5, 0.35, 0.25, 0.15, 0.05)
    shades(1) = Array(0#, 0.9, 0.75, 0.5, 0.25, 0.1)
    shades(2) = Array(0#, 0.8, 0.6, 0.4, -0.25, -0.5)
    shades(3) = Array(0#, -0.1, -0.25, -0.5, -0.75, -0.9)
    shades(4) = Array(0#, -0.05, -0.15, -0.25, -0.35, -0.5)

    Select Case hc.L
        Case Is < 0.001: SelectTintOrShade = shades(0)(variationIndex)
        Case Is < 0.2:   SelectTintOrShade = shades(1)(variationIndex)
        Case Is < 0.8:   SelectTintOrShade = shades(2)(variationIndex)
        Case Is < 0.999: SelectTintOrShade = shades(3)(variationIndex)
        Case Else:       SelectTintOrShade = shades(4)(variationIndex)
    End Select
End Function

Public Function ApplyTintAndShade(hc As HSL, TintAndShade As Double) As HSL

    If TintAndShade > 0 Then
        hc.L = hc.L + (1 - hc.L) * TintAndShade
    Else
        hc.L = hc.L + hc.L * TintAndShade
    End If

    ApplyTintAndShade = hc

End Function

Sub CreateShape(slide As slide, xIndex As Integer, yIndex As Integer, color As Long)

    Dim newShape As Shape
    Dim xStart As Integer, yStart As Integer
    Dim xOffset As Integer, yOffset As Integer
    Dim xSize As Integer, ySize As Integer
    xStart = 100
    yStart = 100
    xOffset = 30
    yOffset = 30
    xSize = 25
    ySize = 25

    Set newShape = slide.Shapes.AddShape(msoShapeRectangle, xStart + xOffset * xIndex, yStart + yOffset * yIndex, xSize, ySize)
    newShape.Fill.BackColor.RGB = color
    newShape.Fill.ForeColor.RGB = color
    newShape.Line.ForeColor.RGB = 0
    newShape.Line.BackColor.RGB = 0

End Sub

' From RGB to HSL

Function RGBtoHSL(ByVal 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

        Else

            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(ByRef 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

        Else

            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
Community
  • 1
  • 1
Gedde
  • 1,150
  • 10
  • 19
  • Very nice. It is just possible that Office 2010 has a different palettes than 2013... It's good to have this solution here - thanks for posting it! – Floris Nov 01 '14 at 02:51
  • Yes, they've added a new color theme for Office 2013. Which can be seen [here](http://peltiertech.com/using-colors-in-excel/). – Profex Dec 05 '14 at 21:37
  • The differences are not due to a new color theme. The color theme changes the base colors only. This is about calculating the different variations based on the base color. You can do that in the RGB color space as Floris did, with quite good results in many situations, but definately not all. Or you can do the calculations using the HSL color space as showed in this answer, which will give perfect replication of the variation colors compared to Office's calculations. This is the same in both Office 2010 and 2013 (and I guess also in 2007, but I haven't had the possibility to try that yet). – Gedde Dec 05 '14 at 22:10
  • True, I didn't read Floris' comment with the full context of what he was asking in mind. FYI, 2007 & 2010 share the same default theme. – Profex Dec 08 '14 at 19:01
3

If you use VBA for excel, you can record your keystrokes. Selecting another color (from below the theme) shows:

    .Pattern = xlSolid
    .PatternColorIndex = xlAutomatic
    .ThemeColor = xlThemeColorLight2
    .TintAndShade = 0.599993896298105
    .PatternTintAndShade = 0

The .TintAndShade factor modifies the defined color. Different colors in the theme use different values for .TintAndShade - sometimes the numbers are negative (to make light colors darker).

Incomplete table of .TintAndShade (for the theme I happened to have in Excel, first two colors):

 0.00  0.00
-0.05  0.50
-0.15  0.35
-0.25  0.25
-0.35  0.15
-0.50  0.05

EDIT some code that "more or less" does the conversion - you need to make sure that you have the right values in your shades, but otherwise the conversion of colors seems to work

updated to be pure PowerPoint code, with output shown at the end

Option Explicit

Sub calcColor()
Dim ii As Integer, jj As Integer
Dim pres As Presentation
Dim thm As OfficeTheme
Dim themeColor As themeColor
Dim schemeColors As ThemeColorScheme
Dim shade
Dim shades(12) As Variant
Dim c, c2 As Long
Dim newShape As Shape

Set pres = ActivePresentation
Set schemeColors = pres.Designs(1).SlideMaster.Theme.ThemeColorScheme
shades(0) = Array(0, -0.05, -0.15, -0.25, -0.35, -0.5)
shades(1) = Array(0, 0.05, 0.15, 0.25, 0.35, 0.5)
shades(2) = Array(-0.1, -0.25, -0.5, -0.75, -0.9)
For ii = 3 To 11
  shades(ii) = Array(-0.8, -0.6, -0.4, 0.25, 0.5)
Next

For ii = 0 To 11
  c = schemeColors(ii + 1).RGB
  For jj = 0 To 4
    c2 = fadeRGB(c, shades(ii)(jj))
    Set newShape = pres.Slides(1).Shapes.AddShape(msoShapeRectangle, 200 + 30 * ii, 200 + 30 * jj, 25, 25)
    newShape.Fill.BackColor.RGB = c2
    newShape.Fill.ForeColor.RGB = c2
    newShape.Line.ForeColor.RGB = 0
    newShape.Line.BackColor.RGB = 0
  Next jj
Next ii

End Sub

Function fadeRGB(ByVal c, s) As Long
Dim r, ii
r = toRGB(c)
For ii = 0 To 2
  If s < 0 Then
    r(ii) = Int((r(ii) - 255) * s + r(ii))
  Else
    r(ii) = Int(r(ii) * (1 - s))
  End If
Next ii
fadeRGB = r(0) + 256& * (r(1) + 256& * r(2))

End Function

Function toRGB(c)
Dim retval(3), ii

For ii = 0 To 2
  retval(ii) = c Mod 256
  c = (c - retval(ii)) / 256
Next

toRGB = retval

End Function

enter image description here

Floris
  • 45,857
  • 6
  • 70
  • 122
  • Above provides the `.ThemeColorIndex` and a `TintAndShade` factor. I need to get the unique long/RGB value for this color. – David Zemens Jan 15 '14 at 17:03
  • @DavidZemens - I believe the "subcolors" are the same color as the theme color, with a different saturation. I suppose you can calculate the RGB yourself from that if RGB value is what you need. I will do an experiment and get back to you. – Floris Jan 15 '14 at 17:49
  • I believe you're correct. I wasn't able to find any ready formula or function that would do the conversion based on the saturation/tint factor. – David Zemens Jan 15 '14 at 17:53
  • I have updated my answer with the calculation (a bit of guessing was involved but the results looked convincing to me). Function `toRGB` converts the `long` into an array of three bytes; `fadeRGB` takes the color and the "fade factor", and modifies the color accordingly. – Floris Jan 15 '14 at 18:46
  • Good stuff and +1 but it looks like it is really a factor of the `.Brightness` as opposed to `.TintAndShade`. These properties do something similar (in Excel I get something like `.ForeColor.TintAndShade = 0` and `.ForeColor.Brightness = 0.400000006 ` when applying a palette color to a `Shape`.). The two properties yield noticeably different results for me... I'll have to tinker with this further. – David Zemens Jan 15 '14 at 20:34
  • I think tintAndShade is akin to saturation while brightness scales RGB components by the same amount. Definitely needs more tinkering I am sure but I hope it gave you a little nudge in an interesting direction. Microsoft doesn't spend a whole lot of time documenting this stuff, do they... – Floris Jan 15 '14 at 23:10
  • 2
    MS's documentation is befuddling. Some things, it's *really* good. Others, it's practically non-existent. I think I'm ultimately going to re-work my existing code to use the `.ObjectThemeColor` instead of the `.RGB`. More work for me (which I was trying to avoid with this question!) but it will be a little more intuitive and better for the end user. – David Zemens Jan 16 '14 at 03:28
  • I stumbled upon this post after having tried to create (almost) the exact same implementation, however, after investigating further it wasn't really as accurate when you select colors outside the mid-tones. Since similarity to the office colors was very important for me I found another method which seems to be more accurate. I added the implementation as a separate answer. – Gedde Nov 01 '14 at 02:47
0

Based on the above solution with HSL values adding here a demo that works in Excel. Works in conjunction with the above listed HSL solution.

Sub DemoExcelThemecolorsHSL()
   Dim rng As Range
   Dim n As Integer, m As Integer
   Dim arrNames
   Dim arrDescriptions
   Dim arrValues
   Dim schemeColors As ThemeColorScheme
   Dim dblTintShade As Double
   Dim lngColorRGB As Long, lngColorRGBshaded As Long
   Dim ColorHSL As HSL, ColorHSLshaded As HSL

   Set schemeColors = ActiveWorkbook.Theme.ThemeColorScheme

   arrNames = Array("xlThemeColorDark1", "xlThemeColorLight1", "xlThemeColorDark2", "xlThemeColorLight2", "xlThemeColorAccent1", "xlThemeColorAccent2", _
                    "xlThemeColorAccent3", "xlThemeColorAccent4", "xlThemeColorAccent5", "xlThemeColorAccent6", "xlThemeColorHyperlink", "xlThemeColorFollowedHyperlink")
   arrDescriptions = Array("Dark1", "Light1", "Dark2", "Light2", "Accent1", "Accent2", "Accent3", "Accent4", "Accent5", "Accent6", "Hyperlink", "Followed hyperlink")
   arrValues = Array(2, 1, 4, 3, 5, 6, 7, 8, 9, 10, 11, 12)

   ' New sheet, title row
   ActiveWorkbook.Worksheets.Add
   Set rng = Cells(1, 2)
   rng(1, 1).Value2 = "ThemeColor Name"
   rng(1, 2).Value2 = "Value"
   rng(1, 3).Value2 = "Description"
   rng(1, 4).Value2 = "TintAndShade"
   rng.Resize(1, 4).Font.Bold = True

   Set rng = rng(3, 1)
   ' color matrix
   For n = 0 To 11
      rng(n * 2, 1).Value = arrNames(n)
      rng(n * 2, 2).Value = arrValues(n)
      rng(n * 2, 3).Value = arrDescriptions(n)

      lngColorRGB = schemeColors(n + 1).RGB
      For m = 0 To 5
         ColorHSL = RGBtoHSL(lngColorRGB)
         dblTintShade = SelectTintOrShade(ColorHSL, m)
         ColorHSLshaded = ApplyTintAndShade(ColorHSL, dblTintShade)
         lngColorRGBshaded = HSLtoRGB(ColorHSLshaded)

         With rng(n * 2, m + 4)
            .Value = dblTintShade
            If ColorHSLshaded.L < 0.5 Then .Font.ColorIndex = 2

            ' fixed color, not changing when a new Color scheme is being selected
            .Interior.color = lngColorRGBshaded

            ' cell color dependent on selected color palette
            .Offset(1, 0).Interior.ThemeColor = arrValues(n)
            .Offset(1, 0).Interior.TintAndShade = dblTintShade

         End With
      Next m
   Next n
   rng.Resize(1, 3).EntireColumn.AutoFit

End Sub