What is the exact function or algorithm Windows 10 is using to calculate taskbar underline color from Registry values, based on data I have collected?

146 Views Asked by At

In earlier versions of Windows, it was easy for users to set the exact colors of interface elements. For example, in Windows 7, you could go to [Control Panel -> Personalization -> Window Color], which would bring up a dialog box letting you select specific interface elements and set the color for each of them. In Windows 10, much of that functionality was removed; you can go to [Settings -> Personalization -> Colors], but it will only let you choose an accent color as an overall theme, and the exact colors of interface elements are then all automatically calculated as variations from your accent color.

However, as explained in such links as...:

...it is possible to edit the Registry binary value

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Accent\AccentPalette

to set the colors of elements of the Windows taskbar. To summarize the Reddit link and the results of my own experiments, AccentPalette consists of the bytes

GG GG GG 00 HH HH HH 00
JJ JJ JJ 00 KK KK KK 00
LL LL LL 00 MM MM MM 00
NN NN NN 00 PP PP PP 00

These are RGB triplets delimited by a 00 null byte. Each RGB triplet is used by Windows to determine the color of a different element of the interface. These RGB triplets are generally applied to the interface without any modification to their value. For example, LL LL LL is used as the exact background color of the active taskbar button (the button in the taskbar corresponding to the active window), and MM MM MM is used as the exact background color of inactive taskbar buttons (buttons in the taskbar corresponding to inactive windows).

The HH HH HH RGB triplet determines the color of the underline beneath taskbar buttons with an open window. The same underline color is used for all open windows, both active and inactive. That underline is a light blue line in the example images here (re-used from the Superuser questions above):

Taskbar Example Image 1

Taskbar Example Image 2

The difficulty is that unlike the other RGB triplets, Windows 10 does not use this HH HH HH value without modification. If all three color components are greater than or equal to the hex value B3, then Windows uses the triplet as the color of the underline without modification, but if any of the three color components are less than B3, then Windows applies some sort of lightening blend/transformation to the triplet to get the final color.

Let us designate the three color components (red, green, and blue) of this HH HH HH Registry AccentPalette triplet as $S_R$, $S_G$, and $S_B$, and the three color components of the final taskbar underline color as $T_R$, $T_G$, and $T_B$.

My question is, based on the data below, what is the exact function or algorithm $f(S_R, S_G, S_B) = (T_R, T_G, T_B)$ of this blend/transformation? Based on the data below, how exactly does Windows 10 calculate the final taskbar underline RGB color from the HH HH HH triplet?

Here is a table of data I have manually gathered by editing the Registry, restarting Explorer so that Windows 10 will apply the change, taking screenshots, and then using an image editor's eyedropper tool to determine the final resulting color. The values are all in hex. This is overall an extremely tedious process, hence the limited data (and obviously, I can't manually test all 256 x 256 x 256 = 16,777,216 possible RGB triplets).

$S_R$ $S_G$ $S_B$ $T_R$ $T_G$ $T_B$
00 00 00 B3 B3 B3
00 00 01-B3 75 75 B3
00 00 B4 75 75 B4
00 00 B5 76 76 B5
00 00 B6 76 76 B6
00 00 B7 77 77 B7
00 00 B8 93 93 C6
00 00 B9 78 78 B9
00 00 BA 79 79 BA
00 00 BB 7A 7A BB
00 00 C0 7D 7D C0
00 00 C8 82 82 C8
00 00 CC 85 85 CC
00 00 D0 87 87 D0
00 00 D8 8D 8D D8
00 00 DD 90 90 DD
00 00 E0 92 92 E0
00 00 E8 97 97 E8
00 00 F0 9C 9C F0
00 00 F8 A1 A1 F8
00 00 FF A6 A6 FF
00 01 00 75 B3 75
00 01 01 75 B3 B3
00 01 02 75 94 B3
00 01 03 75 89 B3
00 01 04 75 84 B3
00 01 05 91 9A C2
00 01 06 75 7F B3
00 01 07 75 7D B3
00 01 08 75 7C B3
00 01 12-15 75 78 B3
00 01 16-1F 75 77 B3
00 01 20-40 75 76 B3
00 01 41-B3 75 75 B3
00 01 B4 75 76 B4
00 01 FF A6 A6 FF
74 75 76 B0 B1 B3
B0 B1 B2 B1 B2 B3
B1 B1 B1 B3 B3 B3
B1 B2 B3 B1 B2 B3
B1 B3 B4 B1 B3 B4
B2 B3 B4 B2 B3 B4
B3 00 B3 B3 75 B3
C0 D0 F0 C0 D0 F0
FF 00 FF FF A6 FF
FF FF FF FF FF FF

Some observations from my experimentation:

  • As mentioned previously, if all three color components are greater than or equal to the hex value B3, then no change is applied. But if any of the three color components are less than B3, then Windows applies some sort of lightening blend/transformation to the triplet to get the final color.
  • The result is deterministic. The same S values always result in the same T values.
  • The result is independent in the sense that it is not affected by any other triplets in the AccentPalette value.
  • Any permutation applied to the S values results in the same permutation being applied to the T values. For example, if $f(S_1, S_2, S_3) = (T_1, T_2, T_3)$, then $f(S_1, S_3, S_2) = (T_1, T_3, T_2)$. That is, the red, green, and blue components are interchangeable in the sense that any of them can be swapped, and the same components will be swapped in the result.
  • The mapping is many-to-one. One example from the table above is that if $S_R$ = 00 and $S_G$ = 00, then any value of $S_B$ from 01 to B3 will give the result value (75, 75, B3). That is, $f(00, 00, 01-B3) = (75, 75, B3)$.
  • The function might look linear at a glance within some ranges, but a closer examination shows it is not truly linear. For example, look in the table above at the results in the range $f(00, 00, B4-B7)$. Within that range, $T_B$ is simply $S_B$, but for $T_R$ and $T_G$, it increments first from 75 to 76, but then stays at 76 for another step before incrementing again to 77.
  • The function generally is fairly smooth, but it has some wild discontinuities. As seen in the table above, it is smooth-ish in the ranges $f(00, 00, B4-B7)$ and $f(00, 00, B9-BB)$, but $f(00, 00, B8)$ gives a wild jump that is far from the values surrounding it.
1

There are 1 best solutions below

3
On

First, I agree with Fshrike and Stinking Bishop that math stack exchange is probably not the appropriate forum for posting these kind of questions. Nevertheless, I gave it a shot.

I played a bit with the dataset, and I came up with a simple algorithm which almost works. It fails with very small errors for 8 of the given inputs, and it completely fails for the 2 weird inputs (00 00 B8 and 00 01 05).

  • "test_function" is just a util to compare the result of the function to the given data.

  • "algorithm" is the function that I came up with, that gets 3 RGB values, and calculate 3 new RGB values.

See the provided code:

# pairs is a list where each object is a pair of input and expected output.
# For example: ((0, 1, 7) , (117, 125, 179))

def test_function(f):
    total_distance_squared = 0
    for pair in pairs:
        res = f(pair[0])
        if res != pair[1]:
            distance_squared = sum([(res[i] - pair[1][i])**2 for i in [0,1,2]])
            print(f"input: {pair[0]}, res: {res}, expected_res: {pair[1]}")
            total_distance_squared += distance_squared

    print(f"total error: {total_distance_squared}")
    return total_distance_squared

special_val = 0xb3
constant = 0.65084  # quite close to 41/63, but I don't know...
def algorithm(triplet):
    a, b, c = triplet[0], triplet[1], triplet[2]
    if a == 0 and b == 0 and c == 0:
        return special_val, special_val, special_val

    max_val = max(a,b,c)
    if max_val < special_val:
        r = special_val/max_val
        a, b, c = (round(a*r), round(b*r), round(c*r))

    new_max_val = max(a, b, c)
    if a < special_val:
        a = round(constant * new_max_val + (1-constant) * a)
    if b < special_val:
        b = round(constant * new_max_val + (1-constant) * b)
    if c < special_val:
        c = round(constant * new_max_val + (1-constant) * c)

    return a, b, c

test_function(algorithm)

Running this code with your data (excluding the two weird inputs) prints:

input: (0, 1, 7), res: (117, 126, 179), expected_res: (117, 125, 179)
input: (0, 1, 32), res: (117, 119, 179), expected_res: (117, 118, 179)
input: (0, 1, 65), res: (117, 118, 179), expected_res: (117, 117, 179)
input: (116, 117, 118), res: (178, 178, 179), expected_res: (176, 177, 179)
input: (176, 177, 178), res: (178, 179, 179), expected_res: (177, 178, 179)
input: (177, 178, 179), res: (178, 179, 179), expected_res: (177, 178, 179)
input: (177, 179, 180), res: (179, 179, 180), expected_res: (177, 179, 180)
input: (178, 179, 180), res: (179, 179, 180), expected_res: (178, 179, 180)
total error: 17

To try and explain the steps of the algorithm in words:

  • If all values are $0$, return 0xB3, 0xB3, 0xB3.

  • If all values are smaller than 0xB3, normalize all of the values with the same constant such that the largest value is 0xB3.

  • Now, for every value $v$, if it's smaller than 0xB3, replace it with: $0.65084 \cdot M + (1-0.65084)\cdot v$, where $M$ is the maximum of the three numbers.

A couple of comments:

  • I have no explanation for the 2 weird inputs.
  • I believe that the small errors are some kind of rounding errors (I couldn't find a constant that would perfectly work).
  • As mentioned in the code, the value of the magic constant is very close to $\frac{41}{63}$ but I don't know if it's just by chance.
  • A bit unrelated but still - If for some reason you want a very accurate solution, that would match your data on the nose, I think it would make more sense to attack this problem with reverse engineering of the code of Windows.