(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
I recently wrote about Kenny Kerr's Window Clippings utility. I thought I'd write a bit about how it generates transparent screenshots. The technique isn't completely obvious. (Although Kenny and I both came up with the same idea independently, so perhaps it's moderately obvious.)
You might think that the DWM API in Vista just provides you with the whole window, alpha (transparency) channel and all, but unfortunately not. Certainly, the DWM needs to maintain a copy of each window's contents, including the alpha, in order to keep the screen up to date - Greg Schechter wrote a blog entry describing the DWM, which mentions use of off-screen buffers. However, there is no documented way to obtain a copy of this buffer, and even if there were, it probably wouldn't contain the glass and shadow, which are important for screenshots.
The only tool we have is the ability to grab an area of what's on the screen. However, it turns out that we can use this to recover the alpha channel of the original window.
There are two tricks involved in recovering the alpha channel. The first is to control what's behind the window: if you open a white background behind a semi-transparent window, this removes all influence of things like the desktop or other windows that were previously behind the window. And by creating a background of uniform colour, you can remove glass's blur from the equation. However, that's not enough.
The problem is that given a white background, you can't tell the difference between an opaque white pixel, a semi-opaque white pixel, and a completely transparent pixel. So the second trick is pretty obvious: take a second screenshot with a different background colour. Using a black and a white background for the two shots is the simplest approach. Then it's a simple matter of solving a simultaneous equation for each pixel to recover the original alpha channel value.
Let's call the red, green, blue, and alpha values for a pixel from the source image RS, GS, BS, and AS. These are the values we are trying to recover in order to reconstruct the pixel from the window. What we actually have are pixel values from two screenshots. Let's call the values from the image with the black background R0, G0 and B0. The values from the image with the white background are R1, G1, and B1.
To generate an output red value, Vista will alpha blend the source value RS onto the background red value which we'll call RB. Assuming linear alpha blending, and assuming pixel values ranging from 0 to 1, the calculation will be:
Output Red = RS*AS + (1-AS)*RB
So we can now write equations for the the two known output values: R0 and R1. Since R0 is the pixel value for the snapshot taken with the black background, RB will be 0 for that image. And since R1 is the pixel value for the snapshot taken with the white background, RB will be 1. (Again, this assumes that pixel values range from 0 to 1.) So we have:
R0 = RS*AS + (1-AS)*0 = RS*AS R1 = RS*AS + (1-AS)*1 = RS*AS + 1 - AS
Since the first equation gives us R0 = RS*AS
we can substitute that into the second equation, giving us:
R1 = R0 + 1 - AS
Remember, we know R0 and R1 since those came from our screenshot. AS is the value we're trying to discover: the alpha channel for the source image, i.e. the window itself. We can rearrange this equation to calculate AS from the known R0 and R1 values:
AS = R0 - R1 + 1
We're not quite done yet. We've recovered the alpha value from the source, but we don't yet have the original red, green, and blue values. In the case where the alpha is 1 (the pixel is not transparent), it's simple: the colour values in both snapshots will be the same as each other and as the source. But if alpha is not 1, it's more complex. Recall earlier we worked out that:
R0 = RS*AS
Well we now know AS, and we always knew R0. So we can calculate the original source value for red, and also green and blue:
RS = R0/AS GS = G0/AS BS = B0/AS
The one last snag is what to do if AS is 0. To avoid a divide by zero we'd need to special-case that. Since this
indicates a completely transparent pixel, it doesn't greatly matter what the colour channel values are. That's just
as well since we have no way of finding them out. As a WPF-head, I'd suggest choosing 1 for the red, green and blue
channels, since that's how Colors.Transparent
is defined. But for this screenshot app, there's a case for choosing
all 0: the area most commonly completely transparent in these snapshots is right at the edge of the shadow, and the
shadow is black.