Problem with BitBtl and DWM (Windows Aero) - c#

I'm developing an application that has to take screenshots of a fullscreen game. The problem is that I've tried many methods but any of them are able to take the screenshot when the game is in fullscreen mode, it will just take a screenshot of what is under the game and a black rectangle on the top left corner.
Is there any alternative to achieve this goal that doesn't involve hooking? As I'm using C#, which is kind of a bad language to make this kind of things.
This is my code right now:
Bitmap bmp = null;
SIZE size;
size.cx = Screen.PrimaryScreen.Bounds.Width;
size.cy = Screen.PrimaryScreen.Bounds.Height;
IntPtr Src = PlatformInvokeUSER32.GetDC(IntPtr.Zero);
IntPtr Dest = PlatformInvokeGDI32.CreateCompatibleDC(Src);
IntPtr HBitmap = (IntPtr)null;
IntPtr hOld = IntPtr.Zero;
HBitmap = PlatformInvokeGDI32.CreateCompatibleBitmap(Src, size.cx, size.cy);
hOld = (IntPtr)PlatformInvokeGDI32.SelectObject(Dest, HBitmap);
PlatformInvokeGDI32.BitBlt(Dest, 0, 0, size.cx, size.cy, Src, 0, 0, (PlatformInvokeGDI32.SRCCOPY|PlatformInvokeGDI32.CAPTUREBLT));
bmp = System.Drawing.Image.FromHbitmap(HBitmap);
PlatformInvokeGDI32.DeleteDC(Dest);
PlatformInvokeUSER32.ReleaseDC(IntPtr.Zero.GetDesktopWindow(), Src);
PlatformInvokeGDI32.DeleteObject(HBitmap);
This works properly on XP, but when it comes to Vista / 7, I need to disable Aero for this code to work, either it will just take a screenshot of what it's under the game as I said before.

Related

Taking a screenshot using Graphics.CopyFromScreen with 150% scaling

I'm trying to create a screenshot/bitmap of my screen. I wrote this function:
public static Bitmap CreateScreenshot(Rectangle bounds)
{
var bmpScreenshot = new Bitmap(bounds.Width, bounds.Height,
PixelFormat.Format32bppArgb);
var gfxScreenshot = Graphics.FromImage(bmpScreenshot);
gfxScreenshot.CopyFromScreen(bounds.X, bounds.Y,
0, 0,
new Size(bounds.Size.Width, bounds.Size.Height),
CopyPixelOperation.SourceCopy);
return bmpScreenshot;
}
This function is being called in my overlay form that should draw the bitmap onto itself. I'm currently using GDI+ for the whole process.
private void ScreenshotOverlay_Load(object sender, EventArgs e)
{
foreach (Screen screen in Screen.AllScreens)
Size += screen.Bounds.Size;
Location = Screen.PrimaryScreen.Bounds.Location;
_screenshot = BitmapHelper.CreateScreenshot(new Rectangle(new Point(0, 0), Size));
Invalidate(); // The screenshot/bitmap is drawn here
}
Yep, I dispose the bitmap later, so don't worry. ;)
On my laptop and desktop computer this works fine. I've tested this with different resolutions and the calculations are correct. I can see an image of the screen on the form.
The problem starts with the Surface 3. All elements are being scaled by a factor of 1.5 (150%). This consequently means that the DPI changes. If I try to take a screenshot there, it does only capture like the upper-left part of the screen but not the whole one.
I've made my way through Google and StackOverflow and tried out different things:
Get the DPI, divide it by 96 and multiply the size components (X and Y) of the screen with this factor.
Add an entry to application.manifest to make the application DPI-aware.
The first way did not bring the desired result. The second one did, but the whole application would have to be adjusted then and this is quite complicated in Windows Forms.
Now my question would be: Is there any way to capture a screenshot of the whole screen, even if it is has a scalation factor higher than 1 (higher DPI)?
There must be a way to do this in order to make it working everywhere.
But at this point I had no real search results that could help me.
Thanks in advance.
Try this, which is found within SharpAVI's library. It works well on devices regardless of resolution scale. And I have tested it on Surface 3 at 150%.
System.Windows.Media.Matrix toDevice;
using (var source = new HwndSource(new HwndSourceParameters()))
{
toDevice = source.CompositionTarget.TransformToDevice;
}
screenWidth = (int)Math.Round(SystemParameters.PrimaryScreenWidth * toDevice.M11);
screenHeight = (int)Math.Round(SystemParameters.PrimaryScreenHeight * toDevice.M22);
SharpAVI can be found here: https://github.com/baSSiLL/SharpAvi It is for videos but uses a similar copyFromScreen method when getting each frame:
graphics.CopyFromScreen(0, 0, 0, 0, new System.Drawing.Size(screenWidth, screenHeight));
Before taking your screen shot, you can make the process DPI aware:
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
private static Bitmap Screenshot()
{
SetProcessDPIAware();
var screen = System.Windows.Forms.Screen.PrimaryScreen;
var rect = screen.Bounds;
var size = rect.Size;
Bitmap bmpScreenshot = new Bitmap(size.Width, size.Height);
Graphics g = Graphics.FromImage(bmpScreenshot);
g.CopyFromScreen(0, 0, 0, 0, size);
return bmpScreenshot;
}

Detect game hack through screenshot analysis C#

I'm trying to write some code to detect a wallhack for a game.
Basically, some hacks exist which create a windows aero transparent window, and they draw the hack onto this external window, so it can't be detected by taking a screenshot of the game itself.
My approach at the moment is to -
1. take a screenshot of the game window.
2. take a screenshot of the windows desktop for the same coordinates.
3. perform image analysis to compare screenshot 1 to screenshot 2 to see if there is a difference.
My problem is that screenshot 1 and screenshot 2 are not performed simultaneously so new game frames can be drawn between the two screenshots, causing false positives when the images are compared.
I want to know if there is a way to coordinate the screenshots so they occur at exactly the same time ? or somehow stop the screen drawing any new frames until my screenshots are finished?
This is the code I use for taking screenshots.
Note, I have even tried to take the 2 screenshots in parallel by queuing two work items.
However, even this doesn't result in the screenshots happening at exactly the same time.
So I wonder if there is some way to stop any further updates to screen from the graphics card until my screenshots finish? Or any other way I can do this?
public void DoBitBlt(IntPtr dest, int width, int height, IntPtr src)
{
GDI32.BitBlt(dest, 0, 0, width, height, src, 0, 0, GDI32.SRCCOPY);
}
public struct Windows
{
public Bitmap window;
public Bitmap desktop;
}
public Windows CaptureWindows(IntPtr window, IntPtr desktop, User32.RECT coords)
{
Windows rslt = new Windows();
// get te hDC of the target window
IntPtr hdcSrcWindow = User32.GetWindowDC(window);
IntPtr hdcSrcDesktop = User32.GetWindowDC(desktop);
// get the size
int width = coords.right - coords.left;
int height = coords.bottom - coords.top;
// create a device context we can copy to
IntPtr hdcDestWindow = GDI32.CreateCompatibleDC(hdcSrcWindow);
IntPtr hdcDestDesktop = GDI32.CreateCompatibleDC(hdcSrcDesktop);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmapWindow = GDI32.CreateCompatibleBitmap(hdcSrcWindow, width, height);
IntPtr hBitmapDesktop = GDI32.CreateCompatibleBitmap(hdcSrcDesktop, width, height);
// select the bitmap object
IntPtr hOldWindow = GDI32.SelectObject(hdcDestWindow, hBitmapWindow);
IntPtr hOldDesktop = GDI32.SelectObject(hdcDestDesktop, hBitmapDesktop);
// bitblt over
var handle1 = new ManualResetEvent(false);
var handle2 = new ManualResetEvent(false);
Action actionWindow = () => { try { DoBitBlt(hdcDestWindow, width, height, hdcSrcWindow); } finally { handle1.Set(); } };
Action actionDesktop = () => { try { DoBitBlt(hdcDestDesktop, width, height, hdcSrcDesktop); } finally { handle2.Set(); } };
ThreadPool.QueueUserWorkItem(x => actionWindow());
ThreadPool.QueueUserWorkItem(x => actionDesktop());
WaitHandle.WaitAll(new WaitHandle[] { handle1, handle2 });
rslt.window = Bitmap.FromHbitmap(hBitmapWindow);
rslt.desktop = Bitmap.FromHbitmap(hBitmapDesktop);
// restore selection
GDI32.SelectObject(hdcDestWindow, hOldWindow);
GDI32.SelectObject(hdcDestDesktop, hOldDesktop);
// clean up
GDI32.DeleteDC(hdcDestWindow);
GDI32.DeleteDC(hdcDestDesktop);
User32.ReleaseDC(window, hdcSrcWindow);
User32.ReleaseDC(desktop, hdcSrcDesktop);
// free up the Bitmap object
GDI32.DeleteObject(hBitmapWindow);
GDI32.DeleteObject(hBitmapDesktop);
return rslt;
}
You are not going to be able to have both screenshots simultaneously, unless you resource to some graphic accelarators, meaning that that will not work in every computer...
About stopping rendering, as it is a game, I think this is not so good idea... you want your game to run smoothly.
Instead I would like to suggest to store recently rendered images of your game in memory, and when you take the screenshot compare it to them. If you can add some visual clue to decide which of the recent frames to compare to then it will work much better, because otherwise you will have to compare the screenshot to all of them and that will certainly eat some CPU/GPU time.
Are you using GDI to render? if so, what you want is to store the frames of your game in DIBs (Device Independent Bitmaps) to be able to compare them.
As for the clue to decide which image to use, I would go for some sort of time representation on screen, maybe a single pixel that changes color. If so, you will read the color of that pixel, use it to find the right frame, and them proced to compare the whole picture.

How to redraw only a region of a layered window?

I have a layered window which is normally drawn this way:
private void SelectBitmap(Bitmap bitmap)
{
IntPtr screenDc = GetDC(IntPtr.Zero);
IntPtr memDc = CreateCompatibleDC(screenDc);
IntPtr hBitmap = IntPtr.Zero;
IntPtr hOldBitmap = IntPtr.Zero;
try
{
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
hOldBitmap = SelectObject(memDc, hBitmap);
POINT sourceLocation = new POINT(0, 0);
BLENDFUNCTION blend = new BLENDFUNCTION();
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
SIZE newSize = new SIZE(bitmap.Width, bitmap.Height);
POINT newLocation = new POINT(Location.X, Location.Y);
UpdateLayeredWindow(Handle, screenDc,
ref newLocation, ref newSize,
memDc,
ref sourceLocation, 0,
ref blend,
ULW_ALPHA);
}
finally
{
ReleaseDC(IntPtr.Zero, screenDc);
if (hBitmap != IntPtr.Zero)
{
SelectObject(memDc, hOldBitmap);
DeleteObject(hBitmap);
}
DeleteDC(memDc);
}
}
However, this obviously redraw the whole window every time it's called. It's quite a performance drain on large window. (even on my top of the line PC, which make me wonder how people could handle that in Win2K)
If I read the Microsoft paper on the layered window, it says: UpdateLayeredWindow always updates the entire window. To update part of a window, use the traditional WM_PAINT and set the blend value using SetLayeredWindowAttributes.
I just can't understand the above. How is WM_PAINT supposed to access the layered window bitmap and redraw only part of it on the window? From what I understood, layered windows simply disable the WM_PAINT message and expect the user to draw the window by himself. There's obviously no way to bind the WM_PAINT to the custom drawing done.
Am I missing something very obvious?
After long profiling, I found out it wasn't really the layered window update that was bottleneck. Refreshing the whole screen, the SelectBitmap method above, on a 1920*1200 was taking about 6-8ms. Sure, not very amazing, but plenty enough to refresh at 30 FPS+.
In my case, the performance drains was coming from some thread asking for refresh almost a hundred time per redraw, making everything sluggish. The solution was to break down the refresh/redraw and separate them. One would update (union) a region and the other, when not drawing, would take that region, draw it and then empty it.

how to capture screen picture with normal aspect ratio in c#

i have wide screen monitor and when i capture screen picture via win API in c# the captured picture will be wide. but i want to take normal aspect ratio 1:1 picture.
i don't want take picture first, then resize it because items on image will be malformed.
how i can do that? is this possible?
i am using this code :
public static Bitmap GetDesktopImage()
{
//In size variable we shall keep the size of the screen.
SIZE size;
IntPtr hDC = PlatformInvokeUSER32.GetDC(PlatformInvokeUSER32.GetDesktopWindow());
IntPtr hMemDC = PlatformInvokeGDI32.CreateCompatibleDC(hDC);
do{
size.cx = PlatformInvokeUSER32.GetSystemMetrics(PlatformInvokeUSER32.SM_CXSCREEN);
size.cy = PlatformInvokeUSER32.GetSystemMetrics(PlatformInvokeUSER32.SM_CYSCREEN);
m_HBitmap = PlatformInvokeGDI32.CreateCompatibleBitmap(hDC, size.cx, size.cy);
} while (m_HBitmap == IntPtr.Zero);
if (m_HBitmap!=IntPtr.Zero)
{
IntPtr hOld = (IntPtr) PlatformInvokeGDI32.SelectObject(hMemDC, m_HBitmap);
PlatformInvokeGDI32.BitBlt(hMemDC,0, 0,size.cx,size.cy, hDC, 0, 0, PlatformInvokeGDI32.SRCCOPY);
PlatformInvokeGDI32.SelectObject(hMemDC, hOld);
PlatformInvokeGDI32.DeleteDC(hMemDC);
PlatformInvokeUSER32.ReleaseDC(PlatformInvokeUSER32.GetDesktopWindow(), hDC);
Bitmap res=System.Drawing.Image.FromHbitmap(m_HBitmap);
PlatformInvokeGDI32.DeleteObject(m_HBitmap);
return res;
}
return null;
}
you would need to crop part of the screen. you can't convert 16:9 to 4:3 without cropping.
-OR-
Change the screens resolution to 4:3 resolution,
Take the screen shot,
Change it back.
Call GetWindowRect on the desktop window handle to get the size. I don't think the GetSystemMetric call you use takes the menu bar and such into account.

Remove alpha from a BitmapSource

I use BitBlt() and CreateBitmapSourceFromHBitmap() to capture a window as a BitmapSource that I can display on an Image element in a WPF application. But for some reason, most of the application that it captures is transparent. Here is a source vs. capture image of what's happening:
(source: umbc.edu)
It's gray because the background of the window it's on is gray. Whatever background I give the window will show through.
How can I get the captured image to more accurately reflect the original?
The problem in your code could be due to the Win32 API you're using (CreateCompatibleDC, SelectObject, CreateBitmap...). I tried with a simpler code, using only GetDC and BitBlt, and it works fine for me. Here's my code:
public static Bitmap Capture(IntPtr hwnd)
{
IntPtr hDC = GetDC(hwnd);
if (hDC != IntPtr.Zero)
{
Rectangle rect = GetWindowRectangle(hwnd);
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
using (Graphics destGraphics = Graphics.FromImage(bmp))
{
BitBlt(
destGraphics.GetHdc(),
0,
0,
rect.Width,
rect.Height,
hDC,
0,
0,
TernaryRasterOperations.SRCCOPY);
}
return bmp;
}
return null;
}
I tried it in Windows Forms and WPF (with Imaging.CreateBitmapSourceFromHBitmap), it works fine in both cases for the same screenshot (SO page in Firefox).
HTH,

Categories

Resources