乍一看
Floris' solution似乎可行,但如果你关心准确性,很快就会意识到先前的解决方案仅匹配了办公室颜色计算的一小部分颜色空间。
正确的解决方案-使用HSL颜色空间
办公室似乎在计算调色和阴影时使用HSL颜色模式,使用这种技术可以给我们几乎100%准确的颜色计算(在Office 2013上测试)。
正确计算值的方法似乎是:
- 将基本RGB颜色转换为HSL
- 找到要用于五个子颜色的色调和阴影值
- 应用色调/阴影值
- 从HSL转换回RGB颜色空间
要查找色调/阴影值(步骤#3),请查看HSL颜色的亮度值,并使用此表(通过试验和错误找到):
| [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 |
正值会使颜色变浅(增加色调),负值会使颜色变暗(减少色调)。共有五组;一组完全是黑色,一组完全是白色。它们只匹配这些特定的值(而不是例如
RGB = {255, 255, _254_}
)。然后是两个小范围的非常暗和非常浅的颜色,它们被单独处理,最后是一个大范围的其余所有颜色。
注意:+0.40的值意味着该值会变亮40%,而不是原始颜色的40%色调(实际上意味着它变亮了60%)。这可能会让某些人感到困惑,但这是Office在内部使用这些值的方式(即通过Excel中
Cell.Interior
的
TintAndShade
属性)。
PowerPoint VBA代码来实现解决方案
[免责声明]:我在Floris的解决方案基础上构建了这个VBA。很多HSL转换代码也是从
评论中提到的Word文章中复制的。
下面代码的输出是以下颜色变化:
![Program output, calculated color variations](https://istack.dev59.com/QHGSj.webp)
乍一看,这似乎与Floris的解决方案非常相似,但仔细检查后,您可以清楚地看到在许多情况下存在差异。办公室主题颜色(因此这个解决方案)通常比纯RGB亮度调整技术更饱和。
![Comparison of the different solutions. This matches office very well!](https://istack.dev59.com/V3wf7.webp)
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