views:

460

answers:

3

Hey Folks,

I am trying to take a screenshot of an application and I would like to make the parts of the rectangle that are not part of the applications region be transparent. So for instance on a standard windows application I would like to make the rounded corners transparent.

I wrote a quick test application which works on on XP (or vista/windows 7 with aero turned off):

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        Graphics g = e.Graphics;           

        // Just find a window to test with
        IntPtr hwnd = FindWindowByCaption(IntPtr.Zero, "Calculator");

        WINDOWINFO info = new WINDOWINFO();
        info.cbSize = (uint)Marshal.SizeOf(info);
        GetWindowInfo(hwnd, ref info);


        Rectangle r = Rectangle.FromLTRB(info.rcWindow.Left, info.rcWindow.Top, info.rcWindow.Right, info.rcWindow.Bottom);
        IntPtr hrgn = CreateRectRgn(info.rcWindow.Left, info.rcWindow.Top, info.rcWindow.Right, info.rcWindow.Bottom);
        GetWindowRgn(hwnd, hrgn);

        // fill a rectangle which would be where I would probably 
        // write some mask color
        g.FillRectangle(Brushes.Red, r);

        // fill the region over the top, all I am trying to do here 
        // is show the contrast between the applications region and 
        // the rectangle that the region would be placed in
        Region region = Region.FromHrgn(hrgn);
        region.Translate(info.rcWindow.Left, info.rcWindow.Top);
        g.FillRegion(Brushes.Blue, region);
    }

When I run this test app on XP (or Vista/Windows 7 with Aero off), I get something like this, which is great because I can eek an xor mask out of this that can be used later with BitBlt.

Screenshot

Here is the problem, on Vista or Windows 7 with Aero enabled, there isn't necessarily a region on the window, in fact in most cases there isn't. Can anybody help me figure out some way to build a mask like this on these platforms?

Here are some of the approaches I have already tried...

1. Using the PrintWindow function: This doesn't work because it gives back a screenshot taken of the window with Aero off and this window is a different shape from the window returned with Aero on

2 Using the Desktop Window Manager API to get a full size thumbnail: This didn't work because it draws directly to the screen and from what I can tell you can't get a screenshot directly out of this api. Yeah, I could open a window with a pink background, show the thumbnail, take a screenshot then hide this temporary window but thats a horrible user experience and a complete hack I would rather not have my name on.

3. Using Graphics.CopyFromScreen or some other pinvoke variant of this: This doesn't work because I can't assume that the window I need information from is at the top of the z-order on the screen.

Right now, the best solution I can think of is to special case Aero on Windows 7 and Vista to manually rub out the corners by hard coding some graphics paths I paint out but this solution would suck since any application that performs custom skinning will break this.

Can you think of another or better solution?

If you are here, thanks for taking time to read this post, I appreciate any help or direction that you can offer!

+1  A: 

Removed idea that is terrible but would have been awesome back in the '90s

You say that using the DWM API only allows you to capture directly to the screen... could you create a window offscreen (say, X = -100000px, Y = -100000px) but visible (maybe even hidden?) and draw the screenshot to it? Since when using the DWM each window has a backing texture, I'm thinking it might still get drawn fine even though the target isn't directly onscreen.

Also, if you want to go the DirectX route and access the actual DX texture backing the window, I found a few leads that might help (especially the first link):

zildjohn01
Tried that unfortunately it doesn't work. As mentioned above, when Aero is turned on, the window is a different shape.
Steve Sheldon
Hey I looked at the samples and again unfortunately there are two problems with this approach... 1. The API's used to get the Direct3D surface are unsupported so no way I could ever use those functions and 2. (Going back to 1), they seem to have changed between windows 7 and Vista. So the samples shown don't actually work on Windows 7. I appreciate the heads up though. I was hoping to get to something using DirectX7 directly....
Steve Sheldon
+4  A: 

If you are looking for a finished application, there is 7capture, which captures also the translucency, so images can be saved to PNG format for later compositing.

EDIT:

The original question and comments indicate you are looking to produce a region on Windows Vista/7 that you can then use to mask out parts the captured image, as is done with Windows XP and non-Aero UIs. Using a region is not going to give you the result you are looking for, since the window outline is not computed as a region, but as an image with variable transparency - RGBA. The Alpha channel in that image is your mask, but it's not an on-off mask like a region, but a gradual mask with a range of values from pixels being fully included to being fully masked out.

Although it uses undocumented APIs, the code at http://spazzarama.wordpress.com/2009/02/12/screen-capture-with-vista-dwm/ will capture to a RGBA buffer which you can then use to render or save the image with the shadow and other translucency effects intact.

In DwmCapture.cs Change

BackBufferFormat = Format.X8R8G8B8

to

BackBufferFormat = Format.A8R8G8B8

(X8->A8)

And you should then be able to access both the usual RGB data plus transparency from the captured buffer. This can then be saved as a PNG or other format with alpha-channel for composing.

mdma
Thanks for the heads up, I saw that application but I need to write it myself.
Steve Sheldon
I am starting to think these guys cheated too and do this through a heuristic rather than a programmatic approach :)
Steve Sheldon
Perhaps, or they are ruthless programmers that don't mind using unpublished APIs. The DWM does compute a RGBA buffer for later compositing, but it doesn't seem that this texture is made available via a public API. E.g. the 3d task switch uses this.
mdma
I actually tried this code last night, it works on vista but not windows 7 for some reason.
Steve Sheldon
This was a good answer, thankyou - still I know better than to use undocumented API's, they are that way for a reason. I worked at Microsoft for a long time, there is usually a good reason when they do this.
Steve Sheldon
A: 
  1. Using Graphics.CopyFromScreen or some other pinvoke variant of this: This doesn't work because I can't assume that the window I need information from is at the top of the z-order on the screen.

If you know which window you need the information from, can you bring it to the front, call Graphics.CopyFromScreen, and then reset its z-index? I know from experience that Aero does odd things when items are in the background in order to make their glass interface work correctly (partial rendering etc). This may not be great UX; however, it would be a special case and used only when Aero is turned on.

Nate Noonen
Hey Nate, Yeah, I thought to do that but unfortunately the special case is most likely the default case and the UX would be horrible in this situation. Also, I believe that Graphics.CopyFromScreen still doesn't give me the region so I can round out the corners.
Steve Sheldon