SRL Archive - General

[C#] Desktop Window Manager functions - grab the rendered image?


J J

2013-04-14 17:00:37

Hello,

I'm planning to learn more C# and make a neat program that can perform some nifty tasks. To start off I've been working on something to grab an image of a minimized window. I searched around and found that PrintWindow would be a pretty viable way. So after looking through source code I managed to get it working and it can take screenshots like this from processes.

Taking a screenshot of firefox while it's behind other windows:

Taking a screenshot of RuneScape @ firefox however..
So after some research PrintWindow does not work properly for everything. Apparantly Java is under that.

–

So I went back to something I originally found interesting, the Desktop Window Manager functions (http://msdn.microsoft.com/en-us/library/windows/desktop/aa969527%28v=vs.85%29.aspx) After going through the source of a really nice program (http://community.bartdesmet.net/blogs/bart/archive/2006/10/05/4495.aspx) and trial and error I managed to get it working.


So I thought it was rendering to the ImageBox but it's actually just rendering to the program.

After this wall of text here is the actual question.
I'm looking to get the thumbnail as a Bitmap, what would be the best way to do this? With the DWM functions you can only render it, is there anyway I can access this?
Or is there a way to modify PrintWindows to make it work?


TL;DR:
How to grab the rendered image using Desktop Window Manager functions?


PS:
Something cool I found is that it actually keeps updating the Automation Tool image while I'm typing this. I've added the .exe for anyone that wants to try it. You need to have firefox open then press the button. I will add the source later for those that are interest and/or don't trust random .exe files :P Oh, and you need Windows Vista or higher because that's when DWM got implemented.

Possible people that know anything
@Brandon; @masterBB; @Wizzup?;

Source:
Structs.cs where I define some structs to be used.
using System.Drawing;
using System.Runtime.InteropServices;

namespace AutomationTool
{
#region Interop structs

[StructLayout(LayoutKind.Sequential)]
internal struct DWM_THUMBNAIL_PROPERTIES
{
public int dwFlags;
public Rect rcDestination;
public Rect rcSource;
public byte opacity;
public bool fVisible;
public bool fSourceClientAreaOnly;
}

[StructLayout(LayoutKind.Sequential)]
internal struct Rect
{
internal Rect(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}

public int Left;
public int Top;
public int Right;
public int Bottom;
}

[StructLayout(LayoutKind.Sequential)]
internal struct PSIZE
{
public int x;
public int y;
}

#endregion
}

This is my test code after clicking on the button of the form
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace AutomationTool
{
public partial class Form1 : Form
{

#region DWM functions

[DllImport("dwmapi.dll")]
static extern int DwmRegisterThumbnail(IntPtr dest, IntPtr src, out IntPtr thumb);

[DllImport("dwmapi.dll")]
static extern int DwmUnregisterThumbnail(IntPtr thumb);

[DllImport("dwmapi.dll")]
static extern int DwmQueryThumbnailSourceSize(IntPtr thumb, out PSIZE size);

[DllImport("dwmapi.dll")]
static extern int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props);

#endregion

#region Constants

static readonly int DWM_TNP_VISIBLE = 0x8;
static readonly int DWM_TNP_OPACITY = 0x4;
static readonly int DWM_TNP_RECTDESTINATION = 0x1;

#endregion

private IntPtr thumb;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{

}


private void FindHandleButton_Click(object sender, EventArgs e)
{
Console.WriteLine("FindHandleButton has been clicked");
Process[] processList = Process.GetProcesses();
foreach (Process process in processList)
{
String processName = process.ProcessName;
int pid = process.Id;
Console.WriteLine("Process name: {0}, PID: {1}", processName, pid);

if (processName.Equals("firefox"))
{
Console.WriteLine("Found process!");


IntPtr hWnd = process.MainWindowHandle;
Console.WriteLine("Hwnd ptr: {0}", hWnd);

/*
Rect rc;
GetWindowRect(hWnd, out rc);

Console.WriteLine("Constructing bmp");
Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();

Console.WriteLine("Attempting to print window");
PrintWindow(hWnd, hdcBitmap, 0);

Console.WriteLine("Releasing stuff");
gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();

Console.WriteLine("Made BMP!");
bmp.Save("J:\\AutomationTool\\screenshot at " + DateTime.Now.ToString("HH-mm-ss tt") + ".png", ImageFormat.Png);
Console.WriteLine("Saved bmp!");
bmp.Dispose();

/*
Rectangle rc;
GetWindowRect(hwnd, out rc);
Console.WriteLine("RC: " + rc);

Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();

PrintWindow(hwnd, hdcBitmap, 0);

gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();

bmp.Save("J:\\AutomationTool");
Console.WriteLine("Saved image!");
*/

Console.WriteLine("Attempting to register thumbnail!");
int i = DwmRegisterThumbnail(this.Handle, hWnd, out thumb);
Console.WriteLine("Succesfully registered thumbnail!");


UpdateThumb();
Console.WriteLine("Succesfully updated thumb!");



break;
}



}

}

private void UpdateThumb()
{
if (thumb != IntPtr.Zero)
{
PSIZE size;
DwmQueryThumbnailSourceSize(thumb, out size);

DWM_THUMBNAIL_PROPERTIES props = new DWM_THUMBNAIL_PROPERTIES();

props.fVisible = true;
props.dwFlags = DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY;
props.opacity = 255;
props.rcDestination = new Rect(pictureBox.Left, pictureBox.Top, pictureBox.Right, pictureBox.Bottom);

/*
if (size.x < pictureBox.Width)
props.rcDestination.Right = props.rcDestination.Left + size.x;

if (size.y < pictureBox.Height)
props.rcDestination.Bottom = props.rcDestination.Top + size.y;
*/

DwmUpdateThumbnailProperties(thumb, ref props);
Console.WriteLine("Succesfully handled thumbnail stuff");

/*
Console.WriteLine("Making BMP!");
Bitmap bmp = new Bitmap(size.x, size.y, PixelFormat.Format32bppArgb);
Console.WriteLine("Made BMP; width = " + bmp.Width + ", height = " + bmp.Height);

//bmp.Save("J:\\AutomationTool\\screenshot at " + DateTime.Now.ToString("HH-mm-ss tt") + ".png", ImageFormat.Png);
bmp.Save(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\screenshot at " + DateTime.Now.ToString("HH-mm-ss tt") + ".png", ImageFormat.Png);

Console.WriteLine("Saved bmp!");
bmp.Dispose();
*/
}
}


}
}

rj

2013-04-14 17:08:10

Dang, it's pretty razor fast, not much ram used (7400k for me) what do you plan on using this for? Just messing around?

masterBB

2013-04-14 17:14:28

I am not a programmer. Simba uses:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd183370(v=vs.85).aspx

J J

2013-04-14 17:17:03

From: Officer Barbrady
Dang, it's pretty razor fast, not much ram used (7400k for me) what do you plan on using this for? Just messing around?

Well my ultimate goal is to create an application that can send mouse events and key events to programs. Select the program using a drop n drag tool like Simba has. Then using this it could get the image while the program is minimized.

So you could have firefox minimized with RuneScape and it could send events and still retrieve the images of RuneScape.

Pretty much SMART for anything.
But I've only been learning c# for a few days and just picked it up a bit 2 days ago so there is a lot to learn. But now that I have a nice goal to work towards to I'm definately going to learn more so I can finish it.

One way I could save the image is by taking a print screen of the program.. then cut it out by saving the region DWM renders to.. but that doesn't make much sense. But it would work, but you wouldn't be able to minimize the "AutomationTool" program.

EDIT: @masterBB; okay thought you might had something nifty like the algorithm in dungeoneering (a couple months ago) to split rooms.

I've seen BitBlt a few times but I think the window must be visible in order to use it? After a small google search it appears so but it should be able to capture hidden windows. But my goal is to capture minimized windows :P Not much information available to get something like that done. But it should be possible somehow.. perhaps intercept the rendering somehow with a hook like you hook OpenGL/DirectX on RuneScape.

masterBB

2013-04-14 17:27:28

I am a fan of math and interesting problems. My knowledge about apis, os's and libraries is practical zero though.

DannyRS

2013-04-14 17:59:32

Never tried bitmap image / memory stuff, but this is how I used to hook the windows mouse to click "Humanly", you might find it somewhat useful Idk, most not my code lol, from google'd questions


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace mousetest1
{
public partial class Form1 : Form
{
[DllImport("user32")]

public static extern int SetCursorPos(int x, int y);
private const int MOUSEEVENTF_LEFTDOWN = 0x0002; /* left button down */
private const int MOUSEEVENTF_LEFTUP = 0x0004; /* left button up */
private const int MOUSEEVENTF_RIGHTDOWN = 0x0008; /* right button down */
private const int MOUSEEVENTF_RIGHTUP = 0x0016; /* right button down */

#region Window styles
[Flags]
public enum ExtendedWindowStyles
{
// ...
WS_EX_TOOLWINDOW = 0x00000080,
// ...
}

public enum GetWindowLongFields
{
// ...
GWL_EXSTYLE = (-20),
// ...
}

[DllImport("user32.dll")]
public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

public static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
int error = 0;
IntPtr result = IntPtr.Zero;
// Win32 SetWindowLong doesn't clear error on success
SetLastError(0);

if (IntPtr.Size == 4)
{
// use SetWindowLong
Int32 tempResult = IntSetWindowLong(hWnd, nIndex, IntPtrToInt32(dwNewLong));
error = Marshal.GetLastWin32Error();
result = new IntPtr(tempResult);
}
else
{
// use SetWindowLongPtr
result = IntSetWindowLongPtr(hWnd, nIndex, dwNewLong);
error = Marshal.GetLastWin32Error();
}

if ((result == IntPtr.Zero) && (error != 0))
{
throw new System.ComponentModel.Win32Exception(error);
}

return result;
}

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
private static extern IntPtr IntSetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetWindowLong", SetLastError = true)]
private static extern Int32 IntSetWindowLong(IntPtr hWnd, int nIndex, Int32 dwNewLong);

private static int IntPtrToInt32(IntPtr intPtr)
{
return unchecked((int)intPtr.ToInt64());
}

[DllImport("kernel32.dll", EntryPoint = "SetLastError")]
public static extern void SetLastError(int dwErrorCode);
#endregion

Rectangle BoundRect;
Rectangle OldRect = Rectangle.Empty;

[DllImport("user32.dll", EntryPoint = "mouse_event")]
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

protected void Click()
{
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
}

private void EnableMouse()
{
Cursor.Clip = OldRect;
Cursor.Show();
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x201 || m.Msg == 0x202 || m.Msg == 0x203) return true;
if (m.Msg == 0x204 || m.Msg == 0x205 || m.Msg == 0x206) return true;
return false;
}
private void DisableMouse(int xpos, int ypos)
{
OldRect = Cursor.Clip;
// Arbitrary location.
BoundRect = new Rectangle(xpos, ypos, 1, 1);
Cursor.Clip = BoundRect;
Cursor.Hide();
}

bool move = false;
int direction = 1;
int count = 0;

public Form1()
{
InitializeComponent();
this.ShowInTaskbar = false;
this.WindowState = FormWindowState.Minimized;
DisableMouse(Cursor.Position.X, Cursor.Position.Y);
}

private void timer1_Tick(object sender, EventArgs e)
{
SetCursorPos(100, 1035); // obviously you need a MMove algo here
mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
//wait random
mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
}
}
}

Brandon

2013-04-14 20:01:06

To put it bluntly, it isn't possible to take a screenshot of minimized windows.

Well.. not without hacking the WindowProc or SubClassing/Hooking it. The reason is because Windows does NOT render anything for minimized windows. In other words, WM_PAINT message is never sent or received by the Window. You can Subclass it and Handle WM_PAINT yourself such that it will Paint to a DC you wish but at the same time, do all its original painting.


Windows TaskBar gets around this by saving a Cache'd thumbnail of the very last time the Application was on screen.

You can only get the screenshot of minimized windows that you own fairly easily. If it isn't a window you own, it can get dirty quick. Subclass is another option other than Injection.


As for what BB said, Bitblt is the fastest way to save Images. Any other way such as GetPixel is way too slow as it asks the Window for a DC every single time!


From: masterBB
I am not a programmer. Simba uses:

http://msdn.microsoft.com/en-us/library/windows/desktop/dd183370(v=vs.85).aspx



This is the root of all ImageProcessing on Windows.. Well.. almost.

Here's a list of things including the one mentioned above by BB that would most likely be used:

Bitblt is used to Copy the Image from a memoryDC to a DC on screen. A DC is a device context and it is used to hold images in memory or display them on screen. To obtain a DC, see the next function:

GetDC(HWND WindowHandle); Returns a DC of the specified window. PrintWindow does this stuff automatically for you by doing: SendMessage(WindowHandle, WM_PRINT, …, …); which tells the window to Print a copy of itself to the specified DC. You can then either save the DC to a file OR blit it to the screen.

Bitblt only does Bitmaps. This can be a problem if you take screenshots of transparent things of course. StretchDIBits allows you to specify the SRC_AND parameter which will make it transparent but requires that you create an Image Mask.

This is where GDI+ comes in. For windows, GDI+ is the next best thing for ImageProcessing other than writing it all manually.
I usually write the code in C++ for this into a DLL and load that DLL with C#. Makes life pretty easy but since this is strictly C#..


Control.DrawToBitmap(…..); will let you draw the specified control to a Bitmap. So if your DWM functions are rendering to a control, you can ask that control for the image rendered.


Brace yourself because this will get pretty dirty quick:

This code will capture any window as a Bitmap. As I have said before, PrintWindow is a GDI function and it uses Bitblt in combination with SendMessage(Handle, WM_PRINT…) to grab a Window DC. Thus this will not take care of transparency! The code however, uses the PrintWindow Function in combination with the FindWindow function and GetDC functions in order to get the Screenshot that includes the Java Applet.

If you could explain in more detail what exactly you're trying to do, then I'd be more than glad to donate some code or a detailed explanation of how to accomplish it. As for your DWM stuff, what is it supposed to do? I do not understand. AFAIK, you're rendering it just fine :S


[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr WindowHandle);

[DllImport("user32.dll")]
private static extern void ReleaseDC(IntPtr WindowHandle, IntPtr DC);

[DllImport("user32.dll")]
private static extern IntPtr GetWindowRect(IntPtr WindowHandle, ref Rect rect);

[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);

[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}

public static Bitmap Capture(IntPtr handle)
{
Rect rect = new Rect();
GetWindowRect(handle, ref rect);
Bitmap Bmp = new Bitmap(rect.Right - rect.Left, rect.Bottom - rect.Top);

Graphics memoryGraphics = Graphics.FromImage(Bmp);
IntPtr dc = memoryGraphics.GetHdc();
bool success = PrintWindow(handle, dc, 0);
memoryGraphics.ReleaseHdc(dc);

return Bmp;
}

private void Form1_Load(object sender, EventArgs e)
{
IntPtr WindowHandle = FindWindow("Chrome_WidgetWin_1", "RuneScape - MMORPG - The No.1 Free Online Multiplayer Game - Google Chrome");
Bitmap BMP = Capture(WindowHandle);
BMP.Save("C:/Users/Brandon/Desktop/Foo.bmp");
BMP.Dispose();
}

The above code is fine if it's just for RS as there is no Transparency on the Runescape Image Canvas

I'm sorry if the picture is large guys.. Its a demonstration so it's only right I post the exact results.

If you want something stronger than that (faster/more control) then the following is in C# AND C++.. Ported from my Imaging class:

Only wrote half of it so far.. I did all the imports for you. Wouldn't be so hard to finish it. Below it is the corresponding C++ code..

The C# Code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Imaging
{
class PBitmap
{
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public int Left, Top,Right, Bottom;
}

private enum ObjectType
{
OBJ_PEN = 1,
OBJ_BRUSH = 2,
OBJ_DC = 3,
OBJ_METADC = 4,
OBJ_PAL = 5,
OBJ_FONT = 6,
OBJ_BITMAP = 7,
OBJ_REGION = 8,
OBJ_METAFILE = 9,
OBJ_MEMDC = 10,
OBJ_EXTPEN = 11,
OBJ_ENHMETADC = 12,
OBJ_ENHMETAFILE = 13
}

[StructLayout(LayoutKind.Sequential)]
public struct BITMAP
{
public int bmType;
public int bmWidth;
public int bmHeight;
public int bmWidthBytes;
public ushort bmPlanes;
public ushort bmBitsPixel;
public IntPtr bmBits;
};

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

[DllImport("user32.dll")]
static extern IntPtr GetDC(IntPtr WindowHandle);

[DllImport("user32.dll")]
static extern void ReleaseDC(IntPtr WindowHandle, IntPtr DC);

[DllImport("user32.dll")]
static extern IntPtr GetWindowRect(IntPtr WindowHandle, ref Rect rect);

[DllImport("gdi32.dll")]
private static extern IntPtr GetCurrentObject(IntPtr DC, uint uObjectType);

[DllImport("gdi32.dll")]
static extern int GetObject(IntPtr hObject, int nCount, ref BITMAP lpObject);

[DllImport("gdi32.dll", SetLastError = true)]
static extern IntPtr CreateCompatibleDC(IntPtr DC);

[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

[DllImport("gdi32.dll")]
static extern bool DeleteObject(IntPtr hObject);

[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);

public PBitmap(IntPtr WindowHandle, int X, int Y, int Width, int Height)
{
IntPtr DC = GetDC(WindowHandle);
IntPtr hBmp = GetCurrentObject(DC, (uint)ObjectType.OBJ_BITMAP);

Rect WindowRect = new Rect();
BITMAP bitmap = new BITMAP();

GetObject(IntPtr.Zero, Marshal.SizeOf(bitmap), ref bitmap);
GetWindowRect(WindowHandle, ref WindowRect);

IntPtr MemDC = GetDC(IntPtr.Zero);
IntPtr SDC = CreateCompatibleDC(MemDC);
IntPtr hSBmp = CreateCompatibleBitmap(MemDC, Width, Height);
DeleteObject(SelectObject(SDC, hSBmp));


}

~PBitmap()
{
//Can use a destructor for cleaning up IF needed..
}
}
}

The Corresponding C++ Code:

PBitmap(HWND Window, Box Area, std::uint32_t BitsPerPixel)
{
Pixels.clear();
if ((BitsPerPixel != 24) && (BitsPerPixel != 32)) {throw std::invalid_argument("Invalid File Format. Required: 24 or 32 Bit Image.");}
std::memset(&Info, 0, sizeof(BITMAPINFO));
HDC DC = GetDC(Window);
BITMAP Bmp;
HBITMAP hBmp = (HBITMAP)GetCurrentObject(DC, OBJ_BITMAP);

if (GetObject(hBmp, sizeof(BITMAP), &Bmp) == 0)
tcout<<ErrorMessage(ERROR_INVALID_WINDOW_HANDLE);

Box BMBox;
GetClientRect(Window, reinterpret_cast<LPRECT>(&BMBox));
width = (Area.Width() == 0) ? BMBox.Width() : Area.Width();
height = (Area.Height() == 0) ? BMBox.Height() : Area.Height();

HDC MemDC = GetDC(nullptr);
HDC SDC = CreateCompatibleDC(MemDC);
HBITMAP hSBmp = CreateCompatibleBitmap(MemDC, width, height);
DeleteObject(SelectObject(SDC, hSBmp));

BitBlt(SDC, 0, 0, width, height, DC, Area.X1, Area.Y1, SRCCOPY);
size = ((width * BitsPerPixel + 31) / 32) * 4 * height;

Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
Info.bmiHeader.biWidth = width;
Info.bmiHeader.biHeight = height;
Info.bmiHeader.biPlanes = 1;
Info.bmiHeader.biBitCount = BitsPerPixel;
Info.bmiHeader.biCompression = BI_RGB;
Info.bmiHeader.biSizeImage = size;
bFileHeader.bfType = 0x4D42;
bFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(Info.bmiHeader);
bFileHeader.bfSize = bFileHeader.bfOffBits + size;
channels = (BitsPerPixel > 24 ? 4 : 3);
colortype = (BitsPerPixel > 24 ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB);

std::vector<std::uint8_t> ImageData(size);
GetDIBits(SDC, hSBmp, 0, height, ImageData.data(), &Info, DIB_RGB_COLORS);

DeleteDC(SDC);
DeleteObject(hSBmp);
ReleaseDC(nullptr, MemDC);
ReleaseDC(Window, DC);

height = std::abs(height);
std::uint8_t* BuffPos = ImageData.data();
Pixels.resize(width * height);

for (std::size_t I = 0; I < height; ++I)
{
for (std::size_t J = 0; J < width; ++J)
{
Pixels[(height - 1 - I) * width + J].B = *(BuffPos++);
Pixels[(height - 1 - I) * width + J].G = *(BuffPos++);
Pixels[(height - 1 - I) * width + J].R = *(BuffPos++);
Pixels[(height - 1 - I) * width + J].A = (Info.bmiHeader.biBitCount > 24 ? *(BuffPos++) : 0xFF);
}
if(Info.bmiHeader.biBitCount == 24)
BuffPos += (-width * 3) & 3;
}
}

The reason the above is more powerful is because where you see the SRC_COPY parameter for Bitblt, you can SRC_AND instead for an okay transparency. It also gives you an array of all pixels in case you need to do some Searching algorithms. Also takes care of Padding.


For capturing transparent Images, using GDI+ would be fairly easy but Invoking it through C# is a pain in the ass but the code is MUCH shorter!

In C++:

#ifndef GDIIMAGE_HPP_INCLUDED
#define GDIIMAGE_HPP_INCLUDED

#include "Unicode.hpp"
#include <windows.h>
#include <gdiplus.h>
#include "Types.hpp"

class GdiImage
{
private:
bool Initialized;
Gdiplus::Image* Img = nullptr;

static int Instances;
static ULONG_PTR GDIToken;
static Gdiplus::GdiplusStartupInput GDIStartInput;

public:
GdiImage(const tstring &ImageFile);
GdiImage(const GdiImage &G) = delete;
GdiImage(GdiImage&& G) = delete;
~GdiImage();

//operator Gdiplus::Image*() const;

GdiImage& operator = (const GdiImage& G) = delete;
GdiImage& operator = (GdiImage&& G) = delete;

void DrawTo(HWND Window, Point Location = Point(0, 0));
};

#endif // GDIIMAGE_HPP_INCLUDED

The actual implementation. As you can see, it's fairly short! But like I said, you'd have to do all the stupid Marshalling and DLLImports for GDI32.dll.


#include "GdiImage.hpp"

int GdiImage::Instances = 0;
ULONG_PTR GdiImage::GDIToken = 0;
Gdiplus::GdiplusStartupInput GdiImage::GDIStartInput = nullptr;

GdiImage::~GdiImage() {delete Img; if (--Instances <= 0) {Gdiplus::GdiplusShutdown(GdiImage::GDIToken);}}

GdiImage::GdiImage(const tstring &ImageFile) : Initialized(true)
{
++Instances;
if (!GdiImage::GDIToken) {Gdiplus::GdiplusStartup(&GdiImage::GDIToken, &GdiImage::GDIStartInput, nullptr);}
Img = Gdiplus::Image::FromFile(ImageFile.c_str());
}

//GdiImage::operator Gdiplus::Image*() const {return this->Img;}

void GdiImage::DrawTo(HWND Window, Point Location)
{
HDC DC = GetDC(Window);
Gdiplus::Graphics G(DC);
G.DrawImage(Img, Location.X, Location.Y);
ReleaseDC(Window, DC);
}


These are your options. Hooking is your very last option if you insist on Capturing Minimized Windows.

rj

2013-04-14 20:08:10

@Brandon (didn't want to quote a big post)

Do you have a massive library of c++ programs for like… everything?

Brandon

2013-04-14 20:13:38

From: Officer Barbrady
@Brandon (didn't want to quote a big post)

Do you have a massive library of c++ programs for like… everything?


To put it honestly, yes I do. Took me a while to write.
Not only C++, but Java, C#, and ASM. Especially ASM as it takes too long to write functions from scratch every time I need something.

I don't know if you remember but last year, before writing the OpenGL hooks, I wrote a C++ version of Simba's library after seeing Yakman's Scar++. It has all the functions Simba has as well as loading Smart and finding Images. Only problem was CTS3 which I didn't finish.

Meh I tend to write Libraries rather than programs. Then I test the libraries using programs :) I usually write a tutorial or something for others looking for the same functions.


EDIT (At OP):

Imports.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Imaging
{
static class Imports
{
public enum ObjectType : uint
{
OBJ_PEN = 1,
OBJ_BRUSH = 2,
OBJ_DC = 3,
OBJ_METADC = 4,
OBJ_PAL = 5,
OBJ_FONT = 6,
OBJ_BITMAP = 7,
OBJ_REGION = 8,
OBJ_METAFILE = 9,
OBJ_MEMDC = 10,
OBJ_EXTPEN = 11,
OBJ_ENHMETADC = 12,
OBJ_ENHMETAFILE = 13
}

public enum TernaryRasterOperations : uint
{
SRCCOPY = 0x00CC0020,
SRCPAINT = 0x00EE0086,
SRCAND = 0x008800C6,
SRCINVERT = 0x00660046,
SRCERASE = 0x00440328,
NOTSRCCOPY = 0x00330008,
NOTSRCERASE = 0x001100A6,
MERGECOPY = 0x00C000CA,
MERGEPAINT = 0x00BB0226,
PATCOPY = 0x00F00021,
PATPAINT = 0x00FB0A09,
PATINVERT = 0x005A0049,
DSTINVERT = 0x00550009,
BLACKNESS = 0x00000042,
WHITENESS = 0x00FF0062,
CAPTUREBLT = 0x40000000
}

[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int Left, Top, Right, Bottom;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BITMAPFILEHEADER
{
public ushort bfType;
public uint bfSize;
public ushort bfReserved1;
public ushort bfReserved2;
public uint bfOffBits;
}

[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFOHEADER
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;

public void Init()
{
biSize = (uint)Marshal.SizeOf(this);
}
}

[StructLayout(LayoutKind.Sequential)]
public struct BITMAPINFO
{
public uint biSize;
public int biWidth;
public int biHeight;
public ushort biPlanes;
public ushort biBitCount;
public uint biCompression;
public uint biSizeImage;
public int biXPelsPerMeter;
public int biYPelsPerMeter;
public uint biClrUsed;
public uint biClrImportant;

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
public uint[] cols;
}

[StructLayout(LayoutKind.Sequential)]
public struct BITMAP
{
public int bmType;
public int bmWidth;
public int bmHeight;
public int bmWidthBytes;
public ushort bmPlanes;
public ushort bmBitsPixel;
public IntPtr bmBits;
};

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr WindowHandle);

[DllImport("user32.dll")]
public static extern void ReleaseDC(IntPtr WindowHandle, IntPtr DC);

[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr WindowHandle, ref Rect rect);

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr DC, uint nFlags);

[DllImport("user32.dll")]
public static extern int SetForegroundWindow(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);

[DllImport("user32.dll")]
public static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);

[DllImport("gdi32.dll")]
public static extern IntPtr GetCurrentObject(IntPtr DC, ObjectType uObjectType);

[DllImport("gdi32.dll")]
public static extern int GetObject(IntPtr hObject, int nCount, ref BITMAP lpObject);

[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr DC);

[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern bool DeleteDC(IntPtr DC);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr DC, int nWidth, int nHeight);

[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr DC, IntPtr hgdiobj);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

[DllImport("gdi32.dll")]
static extern bool FillRgn(IntPtr DC, IntPtr hrgn, IntPtr hbr);

[DllImport("gdi32.dll")]
public static extern IntPtr CreateSolidBrush(uint crColor);

[DllImport("gdi32.dll")]
public static extern int GetDIBits(IntPtr DC, IntPtr hbmp, uint uStartScan, uint cScanLines, [Out] byte[] lpvBits, ref BITMAPINFO lpbmi, uint uUsage);

[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool BitBlt(IntPtr DC, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr SrcDC, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
}
}

PBitmap.cs (The C# version of the C++ code above)

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace Imaging
{
class PBitmap
{
private Bitmap Img = null;
private Imports.BITMAPINFO Info;
private Imports.BITMAPFILEHEADER bFileHeader;

public PBitmap(IntPtr Window, int X, int Y, uint BitsPerPixel = 24)
{
IntPtr DC = Imports.GetDC(Window);
Imports.BITMAP Bmp = new Imports.BITMAP();
IntPtr hBmp = Imports.GetCurrentObject(DC, Imports.ObjectType.OBJ_BITMAP);

if (Imports.GetObject(hBmp, Marshal.SizeOf(Bmp), ref Bmp) == 0)
throw new Exception("ERROR_INVALID_WINDOW_HANDLE.");

Imports.Rect BMBox = new Imports.Rect();
Imports.GetWindowRect(Window, ref BMBox); //GetClientRect
int width = BMBox.Right - BMBox.Left;
int height = BMBox.Bottom - BMBox.Top;

IntPtr MemDC = Imports.GetDC(IntPtr.Zero);
IntPtr SDC = Imports.CreateCompatibleDC(MemDC);
IntPtr hSBmp = Imports.CreateCompatibleBitmap(MemDC, width, height);
Imports.DeleteObject(Imports.SelectObject(SDC, hSBmp));

Imports.BitBlt(SDC, 0, 0, width, height, DC, X, Y, Imports.TernaryRasterOperations.SRCCOPY);
long size = ((width * BitsPerPixel + 31) / 32) * 4 * height;

Info = new Imports.BITMAPINFO();
bFileHeader = new Imports.BITMAPFILEHEADER();

Info.biSize = (uint)Marshal.SizeOf(Info);
Info.biWidth = width;
Info.biHeight = height;
Info.biPlanes = 1;
Info.biBitCount = (ushort)BitsPerPixel;
Info.biCompression = 0;
Info.biSizeImage = (uint)size;
bFileHeader.bfType = 0x4D42;
bFileHeader.bfOffBits = (uint)(Marshal.SizeOf(bFileHeader) + Marshal.SizeOf(Info));
bFileHeader.bfSize = (uint)(bFileHeader.bfOffBits + size);

byte[] ImageData = new byte[size];
Imports.GetDIBits(SDC, hSBmp, 0, (uint)height, ImageData, ref Info, 0);

var bw = new BinaryWriter(File.Open("C:/Users/Brandon/Desktop/Foo.bmp",FileMode.OpenOrCreate));

byte[] FileHeaderBuffer = new byte[Marshal.SizeOf(bFileHeader)];
GCHandle h = GCHandle.Alloc(FileHeaderBuffer, GCHandleType.Pinned);
Marshal.StructureToPtr(bFileHeader, h.AddrOfPinnedObject(), false);
h.Free();

byte[] InfoHeaderBuffer = new byte[Marshal.SizeOf(Info)];
h = GCHandle.Alloc(InfoHeaderBuffer, GCHandleType.Pinned);
Marshal.StructureToPtr(Info, h.AddrOfPinnedObject(), false);
h.Free();


Imports.BITMAPINFOHEADER Dummy = new Imports.BITMAPINFOHEADER();
bw.Write(FileHeaderBuffer);
bw.Write(InfoHeaderBuffer, 0, Marshal.SizeOf(Dummy));
bw.Write(ImageData);

bw.Flush();
bw.Close();
bw.Dispose();

Imports.DeleteDC(SDC);
Imports.DeleteObject(hSBmp);
Imports.ReleaseDC(IntPtr.Zero, MemDC);
Imports.ReleaseDC(Window, DC);
}

public PBitmap(IntPtr WindowHandle, int X, int Y, int Width, int Height)
{
Imports.Rect rect = new Imports.Rect();
Imports.GetWindowRect(WindowHandle, ref rect);
Img = new Bitmap(rect.Right - rect.Left, rect.Bottom - rect.Top, PixelFormat.Format32bppArgb);

Graphics memoryGraphics = Graphics.FromImage(Img);
IntPtr DC = memoryGraphics.GetHdc();
Imports.PrintWindow(WindowHandle, DC, 0);
memoryGraphics.ReleaseHdc(DC);

/*IntPtr hRgn = Imports.CreateRectRgn(0, 0, 0, 0);
Imports.GetWindowRgn(WindowHandle, hRgn);
System.Drawing.Region region = System.Drawing.Region.FromHrgn(hRgn);

if (!region.IsEmpty(memoryGraphics))
{
memoryGraphics.ExcludeClip(region);
memoryGraphics.Clear(System.Drawing.Color.Transparent);

}*/

memoryGraphics.Dispose();
}

public void Save(string Path)
{
if (Img != null)
{
Img.Save(Path, ImageFormat.Bmp);
}
}

public void Save(string Path, ImageFormat Format)
{
if (Img != null)
{
Img.Save(Path, Format);
}
}

public void Dispose()
{
if (Img != null)
{
Img.Dispose();
Img = null;
}
}

~PBitmap()
{
Dispose();
}
}
}

CynicRus

2013-04-15 05:49:29




Lol, here's a variation on Delphi. I was interested to deal with DWMApi.

J J

2013-04-15 08:26:11

Thanks a lot for the posts @Brandon;. Really interesting and I understand image capturing better already :)

Windows TaskBar gets around this by saving a Cache'd thumbnail of the very last time the Application was on screen.

Well, start a video on YouTube or Windows Media Player and minimize it. If you hover over the icon it will display the video realtime. Wait I actually just tried it and it doesn't.. it does though when you have it open or hidden but minimized it does indeed just show a cached thumbnail.

So my whole idea of using DWM to render a minimized window does not work properly. I just tested it, when I minimized the video it does indeed also display a cached image. No idea why I didn't realize this yesterday. Think I was just testing it on a static page and though it worked properly -.-

Anyways, I will look more into Bitblt and the other variations you have posted.

If you could explain in more detail what exactly you're trying to do, then I'd be more than glad to donate some code or a detailed explanation of how to accomplish it. As for your DWM stuff, what is it supposed to do? I do not understand. AFAIK, you're rendering it just fine :S

My main reason for posting this was how to intercept the image being rendered by DWM. It renders straight to my program and I was wondering if you can intercept the pixels. However, now that I have realized it does not render minimized windows realtime I'm most likely better off with one of the methods you have posted.

I'm going to add the source code so far to the first post.

Wizzup?

2013-05-30 07:38:04

It is possible to grab images of windows that are minimised, but it's hard and requires a lot of grabbing hooks. And they won't be truly minimised. Truly minimised means that they truly do not re-draw their screen. So you'll always need some kind of compositor. I'm hoping to do this on Linux when Simba works well on Linux with Lape.

dwimage

2013-06-06 22:57:35

if you want to try something in bitmap image c#, here is some samples you may want to refer to. bitmap is a common used formats.