乍一看,Floris' solution 似乎可行,但如果您关心准确性,您很快就会意识到,以前的解决方案只匹配办公室颜色计算的一小部分颜色空间。
正确的解决方案 - 使用 HSL 色彩空间
Office 似乎在计算着色和阴影时使用HSL color 模式,并且使用这种技术可以为我们提供几乎 100% 准确的颜色计算(在 Office 2013 上测试)。
正确计算值的方法似乎是:
- 将基本 RGB 颜色转换为 HSL
- 查找用于五种子颜色的色调和明暗度值
- 应用色调/阴影值
- 从 HSL 转换回 RGB 颜色空间
要查找色调/阴影值(第 3 步),请查看 HSL 颜色的 Luminosity-value 并使用此表(通过反复试验找到):
| [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 |
正值使颜色着色(使其更亮),负值使颜色变暗(使其更暗)。有五组; 1组全黑,1组全白。这些将只匹配这些特定值(而不是例如RGB = {255, 255, _254_})。然后是两个小范围的非常深和非常浅的颜色,分别处理,最后是所有其余颜色的大范围。
注意:+0.40 的值意味着该值将变亮 40%,而不是原始颜色的 40% 色调(实际上意味着它变亮了 60%)。这可能会让某些人感到困惑,但这是 Office 在内部使用这些值的方式(即在 Excel 中通过Cell.Interior 的TintAndShade 属性)。
实现解决方案的PowerPoint VBA代码
[免责声明]:我在 Floris 的解决方案的基础上创建了这个 VBA。很多HSL翻译代码也是从Word article mentioned in the comments复制过来的。
以下代码的输出是以下颜色变化:
乍一看,这看起来与 Floris 的解决方案非常相似,但仔细观察后,您可以清楚地看到许多情况下的差异。 Office 主题颜色(以及因此的解决方案)通常比普通的 RGB 变亮/变暗技术更加饱和。
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