/Platters/classes/screenshot.cs

http://skimpt.googlecode.com/ · C# · 333 lines · 70 code · 64 blank · 199 comment · 1 complexity · 83824b796547d36303695807c6cb9c97 MD5 · raw file

  1. #region "License Agreement"
  2. /* Skimpt, an open source screenshot utility.
  3. Copyright (C) <year> <name of author>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. this program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  14. #endregion
  15. using System;
  16. using System.Runtime.InteropServices;
  17. using System.Drawing;
  18. using System.Drawing.Imaging;
  19. using System.Diagnostics;
  20. using System.Windows.Forms;
  21. /* Please note a C++ copy is commented out at the bottom of this class */
  22. /// <summary>
  23. /// This is a static class which exposes two methods
  24. /// It allows us to take a screen capture.
  25. /// It is sealed so it can not be inherited.
  26. /// </summary>
  27. public sealed class ScreenCapture
  28. {
  29. /// Intro to DCs:
  30. /// If you are familiar, skip ahead. If not, read on.
  31. /// Device contexts are just generic ways to represent devices,
  32. /// or things you can draw on or interact with graphically.
  33. /// This is hugely over simplified, and I'm going to get flamed, but for the sake of brevity,
  34. /// let's just go with that for now. You can get device context's, or dc's as
  35. /// they are commonly referred to, from images, windows, monitors, and even printers.
  36. /// Every window has them, and you can use them to draw with. Everything you see on your screen is being
  37. /// drawn upon a device context. The desktop, every window, your taskbar, and anything you see.
  38. /// You can draw upon them, or copy what they have drawn upon them. If you can get a hold of them,
  39. /// you can pretty much draw or steal whatever you want graphically speaking.
  40. /// Working with device contexts is fast, and both GDI and GDI+ are based on them.
  41. /// in the case of capturing the screen, we know that somewhere Windows is drawing upon a device context, so that we can see it.
  42. /// In fact, there's one for each monitor you have attached to your system, and that desktop that you are seeing on it,
  43. /// is being drawn on that monitor's device context.
  44. /// All we have to do is grab a hold of that device context, create another one of our own,
  45. /// and copy the screen's device context image data to our own, and we've got a screen capture
  46. public static Bitmap GetDesktopWindowCaptureAsBitmap()
  47. {
  48. //SEE HOW THIS FUNCTION WORKS AT THE END OF THIS FUNCTION.
  49. Rectangle rcScreen = Rectangle.Empty;
  50. Screen[] screens = Screen.AllScreens;
  51. // Create a rectangle encompassing all screens...
  52. foreach (Screen screen in Screen.AllScreens)
  53. rcScreen = Rectangle.Union(rcScreen, screen.Bounds);
  54. // Create a composite bitmap of the size of all screens...
  55. Bitmap finalBitmap = new Bitmap(rcScreen.Width, rcScreen.Height);
  56. // Get a graphics object for the composite bitmap and initialize it...
  57. Graphics g = Graphics.FromImage(finalBitmap);
  58. g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
  59. g.FillRectangle(
  60. SystemBrushes.Desktop,
  61. 0,
  62. 0,
  63. rcScreen.Width - rcScreen.X,
  64. rcScreen.Height - rcScreen.Y);
  65. // Get an HDC for the composite area...
  66. IntPtr hdcDestination = g.GetHdc();
  67. // Now, loop through screens,
  68. // Blting each to the composite HDC created above...
  69. foreach (Screen screen in Screen.AllScreens)
  70. {
  71. // Create DC for each source monitor...
  72. IntPtr hdcSource = Win32.CreateDC(
  73. IntPtr.Zero,
  74. screen.DeviceName,
  75. IntPtr.Zero,
  76. IntPtr.Zero);
  77. // Blt the source directly to the composite destination...
  78. int xDest = screen.Bounds.X - rcScreen.X;
  79. int yDest = screen.Bounds.Y - rcScreen.Y;
  80. bool success = Win32.StretchBlt(
  81. hdcDestination,
  82. xDest,
  83. yDest,
  84. screen.Bounds.Width,
  85. screen.Bounds.Height,
  86. hdcSource,
  87. 0,
  88. 0,
  89. screen.Bounds.Width,
  90. screen.Bounds.Height,
  91. (int)Win32.TernaryRasterOperations.SRCCOPY);
  92. // System.Diagnostics.Trace.WriteLine(screen.Bounds);
  93. if (!success)
  94. {
  95. System.ComponentModel.Win32Exception win32Exception =
  96. new System.ComponentModel.Win32Exception();
  97. System.Diagnostics.Trace.WriteLine(win32Exception);
  98. }
  99. // Cleanup source HDC...
  100. Win32.DeleteDC(hdcSource);
  101. }
  102. // Cleanup destination HDC and Graphics...
  103. g.ReleaseHdc(hdcDestination);
  104. g.Dispose();
  105. // Return composite bitmap which will become our Form's PictureBox's image...
  106. return finalBitmap;
  107. ///Looking at the code, the first thing you'll see is that I'm using a mixture of GDI and GDI+.
  108. ///This is due largely to the fact that there is a bug present in GDI+ and the BtBlt API.
  109. ///This issue only manifests itself on systems with multiple monitors, and if I remember correctly,
  110. ///the system had to have a NVida display adapter on the non-primary monitor, and of course, our old friend Windows 98 running as the OS.
  111. ///What happens is the primary monitor captures fine, the secondary (or any non-primary) monitor stands a chance of returning garbage for an image.
  112. ///It looks like cable channel with no signal.
  113. ///Instead of relying on purely managed code, do copy the images,
  114. ///or backing up to the BtBlt API, we instead fall back to it's somewhat slower cousin, StretchBlt.
  115. ///first up we just grab all of the monitors using the Screen class' AllScreens property.
  116. ///This does two things for us. First it allows us to figure out how big the entire desktop is,
  117. ///and create an image just big enough to hold all of the screens inside.
  118. ///And secondly, it allows us to figure out just where each monitor is positioned in relation to the other.
  119. ///Remember, with multiple monitor support you can "arrange" your monitors in different ways,
  120. ///and with different resolutions, so don't think in terms of a pure rectangle when you think of
  121. ///how your monitors are positioned.
  122. ///Once we have those screens, it's a trivial matter to calculate the size of the entire bitmap
  123. ///by using the Rectangle.Union method to build up the size of the overall image.
  124. ///After we've figured out the size of the final image, we'll grab a Graphics object from the image.
  125. ///The GDI+ Graphics object is just the .NET wrapper around a device context.
  126. ///Using that graphics context, we can draw on the bitmap with the graphics object.
  127. ///Next, we'll enumerate through each monitor, and draw what that monitor has on it's device context,
  128. ///upon the image we just created that will hold the final screen shot.
  129. ///Well draw it using it's coordinates so that in case the monitors have different resolutions or positioning
  130. ///we'll be able to see them as the Display Control Panel applet sees them.
  131. ///Go check it out if you have multiple monitors, and you didn't know you could move them.
  132. ///Chances are there if you have multiple monitors, you know this already, but if not so harm no foul.
  133. ///Open the settings tab and drag one of the monitors around and
  134. ///you'll see you can reposition it in relation to the other monitors.
  135. ///For each monitor, we'll simply use the StretchBlt API to copy that monitor's device context contents,
  136. ///to the bitmap that will serve as the screen capture of the desktop.
  137. ///Notice that I'm creating a device context each time, this gives us access to that monitor's device context so
  138. ///that we can copy from it. Keep in mind that if we create it, we must destroy it,
  139. ///so we delete the device context when we are finished with it. If you don't, you'll have a memory leak,
  140. ///so keep a watchful eye on your dc's and make sure to release or destroy them.
  141. ///A simple rule is, if you "acquire" it, you're required to "release" it.
  142. ///And if you "create" it, then you must "destroy" it.
  143. ///I quote those because if you look at the GDI APIs, with that in mind you'll find the necessary APIs to do exactly what you want.
  144. ///Finally, after copying the contents of each device context to that bitmap we created,
  145. ///we'll release the Graphics object we acquired from the bitmap, and dispose it.
  146. ///That's the proper way to clean up a graphics object, if you've acquired a device context from it.
  147. }
  148. public static Bitmap CaptureDeskTopRectangle(Rectangle CapRect, int CapRectWidth, int CapRectHeight)
  149. {
  150. //This function just calls the GetDesktop, and just crops it.
  151. Bitmap bmpImage = new Bitmap(GetDesktopWindowCaptureAsBitmap());
  152. Bitmap bmpCrop = new Bitmap(CapRectWidth, CapRectHeight, bmpImage.PixelFormat);
  153. Rectangle recCrop = new Rectangle(CapRect.X, CapRect.Y, CapRectWidth, CapRectHeight);
  154. Graphics gphCrop = Graphics.FromImage(bmpCrop);
  155. Rectangle recDest = new Rectangle(0, 0, CapRectWidth, CapRectHeight);
  156. gphCrop.DrawImage(bmpImage, recDest, recCrop.X, recCrop.Y, recCrop.Width, recCrop.Height, GraphicsUnit.Pixel);
  157. return bmpCrop;
  158. }
  159. }
  160. //
  161. // Credits: Mark (Code6) Belles C# version
  162. // Me :+)) for the porting code
  163. // Thanks to EVA for g->ReleaseHDC or you may have a black screen
  164. // and MSDN http://msdn2.microsoft.com/en-us/library/ms533843(VS.85).aspx
  165. // Enjoy: Feel free to use it....
  166. //
  167. //#include "stdafx.h"
  168. //#include <windows.h>
  169. //#include <gdiplus.h>
  170. //#pragma comment(lib, "gdiplus.lib")
  171. //#define SAFE_DELETE(ptrToDelete) if (ptrToDelete != NULL) { delete ptrToDelete; ptrToDelete = NULL; }
  172. //ULONG_PTR gdiplusToken;
  173. //int m_nScreenCount;
  174. //RECT m_ScreensRect[8];
  175. //TCHAR m_ScreensName[8][CCHDEVICENAME];
  176. //BOOL CALLBACK MyInfoEnumProcs( HMONITOR hMonitor, HDC hdcMonitor,
  177. // LPRECT lprcMonitor, LPARAM dwData )
  178. //{
  179. // MONITORINFOEX monitorInfo;
  180. // memcpy(&m_ScreensRect[m_nScreenCount], lprcMonitor, sizeof(RECT));
  181. // memset(&monitorInfo, 0x0, sizeof(MONITORINFOEX));
  182. // monitorInfo.cbSize = sizeof(MONITORINFOEX);
  183. // GetMonitorInfo(hMonitor, &monitorInfo);
  184. // strcpy_s((char *) &m_ScreensName[m_nScreenCount], CCHDEVICENAME, monitorInfo.szDevice);
  185. // m_nScreenCount++;
  186. // return TRUE;
  187. //}
  188. //int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
  189. //{
  190. // UINT num = 0; // number of image encoders
  191. // UINT size = 0; // size of the image encoder array
  192. // // in bytes
  193. // Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
  194. // Gdiplus::GetImageEncodersSize(&num, &size);
  195. // if(size == 0)
  196. // return -1;
  197. // pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
  198. // if(pImageCodecInfo == NULL)
  199. // return -1;
  200. // GetImageEncoders(num, size, pImageCodecInfo);
  201. // for(UINT j = 0; j < num; ++j)
  202. // {
  203. // if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
  204. // {
  205. // *pClsid = pImageCodecInfo[j].Clsid;
  206. // free(pImageCodecInfo);
  207. // return j; // Success
  208. // }
  209. // }
  210. // free(pImageCodecInfo);
  211. // return -1;
  212. //}
  213. //int _tmain(int argc, _TCHAR* argv[])
  214. //{
  215. // Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  216. // gdiplusToken = NULL;
  217. // if (GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Gdiplus::Ok)
  218. // {
  219. // gdiplusToken = NULL;
  220. // }
  221. // memset(&m_ScreensRect, 0x0, sizeof(m_ScreensRect));
  222. // memset(&m_ScreensName, 0x0, sizeof(m_ScreensName));
  223. // EnumDisplayMonitors(NULL, NULL, MyInfoEnumProcs, 0);
  224. // HDC hdcDestination = NULL;
  225. // RECT rcScreen;
  226. // memset(&rcScreen, 0x0, sizeof(rcScreen));
  227. // for(int screenCount=0; screenCount < m_nScreenCount; screenCount++)
  228. // {
  229. // UnionRect(&rcScreen, &rcScreen, &m_ScreensRect[screenCount]);
  230. // }
  231. // // Create a composite bitmap of the size of all screens...
  232. // Gdiplus::Bitmap* finalBitmap = new Gdiplus::Bitmap(rcScreen.right, rcScreen.bottom);
  233. // // Get a graphics object for the composite bitmap and initialize it...
  234. // Gdiplus::Graphics * g = Gdiplus::Graphics::FromImage(finalBitmap);
  235. // g->SetCompositingQuality(Gdiplus::CompositingQualityHighSpeed);
  236. // Gdiplus::SolidBrush solidBrush(Gdiplus::Color(0,0,0));
  237. // g->FillRectangle(&solidBrush, 0, 0, rcScreen.right, rcScreen.bottom);
  238. // // Get an HDC for the composite area...
  239. // hdcDestination = g->GetHDC();
  240. // // Now, loop through screens, BitBlting each to the composite HDC created above...
  241. // for(int screenCount=0; screenCount < m_nScreenCount; screenCount++)
  242. // {
  243. // // Create DC for each source monitor...
  244. // HDC hdcSource = CreateDC(NULL, m_ScreensName[screenCount], NULL, NULL);
  245. // // Blt the source directly to the composite destination...
  246. // int xDest = m_ScreensRect[screenCount].left - rcScreen.left;
  247. // int yDest = m_ScreensRect[screenCount].top - rcScreen.top;
  248. // StretchBlt(hdcDestination, xDest, yDest, m_ScreensRect[screenCount].right, m_ScreensRect[screenCount].bottom,
  249. // hdcSource, 0, 0, m_ScreensRect[screenCount].right, m_ScreensRect[screenCount].bottom, SRCCOPY | CAPTUREBLT);
  250. // // Cleanup source HDC...
  251. // DeleteDC(hdcSource);
  252. // }
  253. // g->ReleaseHDC(hdcDestination);
  254. // CLSID clsidJPEG;
  255. // GetEncoderClsid(L"image/jpeg", &clsidJPEG);
  256. // finalBitmap->Save(L"c:\\screenshot.jpg", &clsidJPEG);
  257. // GetEncoderClsid(L"image/gif", &clsidJPEG);
  258. // finalBitmap->Save(L"c:\\screenshot.gif", &clsidJPEG);
  259. // GetEncoderClsid(L"image/bmp", &clsidJPEG);
  260. // finalBitmap->Save(L"c:\\screenshot.bmp", &clsidJPEG);
  261. // // Cleanup destination HDC and Graphics...
  262. // DeleteDC(hdcDestination);
  263. // SAFE_DELETE(g);
  264. // SAFE_DELETE(finalBitmap);
  265. // return 0;
  266. //}