Wednesday, May 09, 2007

[Software] Translucent Selection Rectangle Color

It took over two years but I finally figured out how Microsoft calculates the color of the selection marquee in Windows.

Windows XP implemented a fancy new feature whereby dragging a selection box around files in Explorer windows will draw a transparent rectangle instead of a gray-dotted box.

This is a feature of the Common Controls 6 and can be added to list controls by setting the preprocessor define _WIN32_WINNT to 0x0501 or higher and setting the LVS_EX_DOUBLEBUFFER style of the list control.

Its no secret that the color of the outline of the selection rectangle is set to COLOR_HIGHLIGHT which is the color set for selected items. But, what about the rest of the rectangle? Where does the color for the inside of the rectangle come from?

Obviously it’s calculated dynamically from the selected color and the color of the window background since it’s “translucent”. A little testing confirms this. But, what is the equation? What formula is used to blend these two colors?

A few years ago I determined to find out. I began by collecting data. This meant using the Display Properties dialog to set the selected and the window background colors, then drawing a selection rectangle and examining what the resulting color is. After doing this for dozens of pairs of colors, I had a data set and decided to figure out what they have in common. This turned out to be trickier than expected and I put it aside and occasionally looked at it briefly over the next two or three years.

This month when I learned of the LVS_EX_DOUBLEBUFFER style, I became more determined than ever to figure it out. Luckily I could not find the original data set (which would have surely side-tracked me for a while), so I decided to compile a new one with a controlled set of values instead of the random ones I had previously used. I used the following values: 0,64,128,192,255 in (almost) all the permutations of R/G/B and selected/background. I created a macro to automate the process of:

This automation allowed me to complete all 289 (17x17) permutations in just a couple of hours instead of a couple of months, since the only thing I needed to do manually was to click the correct boxes in the color picker.

Now I had a fresh, new set of data points that ranged from the min to the max in all directions. It was time to figure out the equation once and for all.

A good place to start was to plot the data points as a 3D graph where X=selected color, Y=background color, Z=blended color to see if there is any kind of visible relationship. A few minutes later and BAM! The plot was clearly a slanted plane:

Plot forms a slanted plane in 3D space.

Now I needed to figure out what the equation of the plane was. This was also easier said than done.

I decided that the most obvious next step was to use Cramer’s rule to determine the planar coefficients A, B, C, and D, then plug them into the equation of a plane and rearrange for Z. This meant taking three points (I had 289 to choose from) and calculating the planar coefficients. Plugging those into the plane equation would result in this z=-(Ax+By+D)/C where ABCD are replaced by numbers. This leaves an equation with two unknowns (two input variables). That would be the solution.

Unfortunately, this did not work. I was using COLORREFs (DWORDs) which combine all three color components in the following manner: C=R+256*G+256*256*B. The problem with doing it this way is that any math will cause carry errors between the components (eg: C*2 would cause the R component to wrap and the carry would overflow into the G component). The solution was to determine three separate equations, one for each of R, G, and B.

Luckily this did not mean triple the work because they were all the same equation as the data shows. Ignoring the G and B components, I took the 289 data points for the R component and recalculated everything. Again, it didn’t work. Even though the points clearly lay on a plane, the numbers (even the surface normals!) varied depending on which three points I used. I was stumped again (and still am; I don’t know why it works in theory but not in practice).

Then I got a stroke of “genius”. By examining the limiting factors, I could reduce the system to its base. Since f(0,0)=41, the mystery formula obviously adds 41 to whatever the rest of the results are. Also, since f(255,255)=255, I know what the limits are. Finally, I knew the other extents: f(0,255)=227 and f(255,0)=69. That turned out to be all I needed (I donated the other 285 data points to charity). I had the following system of equations:

Solving this gave the nice and simple formula z = 41 + (28*x + 186*y) / 255. A quick test showed that the results were correct (actually a few were a bit off, but almost all of those were fixed by rounding instead of truncating, and the last few off-by-ones are due to using different data sizes and order of calculation).

So there you have it. After two-and-a-half years, the solution presented itself: c=(28s+186w)/255+41 (s=selected, w=window background—or texture).

Here is a function for calculating the marquee color (selection rectangle), printed here with separate component variables for clarity’s sake:

COLORREF BlendMarquee() {
COLORREF windowbg=GetSysColor(COLOR_WINDOW);

BYTE sr=GetRValue(selected);
BYTE sg=GetGValue(selected);
BYTE sb=GetBValue(selected);

BYTE wr=GetRValue(windowbg);
BYTE wg=GetGValue(windowbg);
BYTE wb=GetBValue(windowbg);

RoundDouble(41+(sr*28+wr*186)/255, 0),
RoundDouble(41+(sg*28+wg*186)/255, 0),
RoundDouble(41+(sb*28+wg*186)/255, 0)

return c;

Comments: Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?