public void Start(int framesPerSecond) { string filename; if (recordingWindow != null) { string windowTitle = Regex.Replace(recordingWindow.Text, @"[^\x20\d\w]", ""); if (string.IsNullOrEmpty(windowTitle)) { windowTitle = "greenshot-recording"; } filename = Path.Combine(conf.OutputFilePath, windowTitle + ".avi"); } else { filename = Path.Combine(conf.OutputFilePath, "greenshot-recording.avi"); } if (File.Exists(filename)) { try { File.Delete(filename); } catch {} } LOG.InfoFormat("Capturing to {0}", filename); if (recordingWindow != null) { LOG.InfoFormat("Starting recording Window '{0}', {1}", recordingWindow.Text, recordingWindow.ClientRectangle); recordingSize = recordingWindow.ClientRectangle.Size; } else { LOG.InfoFormat("Starting recording rectangle {0}", recordingRectangle); recordingSize = recordingRectangle.Size; } if (recordingSize.Width % 8 > 0) { LOG.InfoFormat("Correcting width to be factor 8, {0} => {1}", recordingSize.Width, recordingSize.Width + (8 - (recordingSize.Width % 8))); recordingSize = new Size(recordingSize.Width + (8 - (recordingSize.Width % 8)), recordingSize.Height); } if (recordingSize.Height % 8 > 0) { LOG.InfoFormat("Correcting Height to be factor 8, {0} => {1}", recordingSize.Height, recordingSize.Height + (8 - (recordingSize.Height % 8))); recordingSize = new Size(recordingSize.Width, recordingSize.Height + (8 - (recordingSize.Height % 8))); } this.framesPerSecond = framesPerSecond; // "P/Invoke" Solution for capturing the screen hWndDesktop = User32.GetDesktopWindow(); // get te hDC of the target window hDCDesktop = User32.GetWindowDC(hWndDesktop); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a device context we can copy to hDCDest = GDI32.CreateCompatibleDC(hDCDesktop); // Check if the device context is there, if not throw an error with as much info as possible! if (hDCDest == IntPtr.Zero) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateCompatibleDC", recordingSize); // Cleanup User32.ReleaseDC(hWndDesktop, hDCDesktop); // throw exception throw exceptionToThrow; } // Create BitmapInfoHeader for CreateDIBSection BitmapInfoHeader bitmapInfoHeader = new BitmapInfoHeader(recordingSize.Width, recordingSize.Height, 32); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a bitmap we can copy it to, using GetDeviceCaps to get the width/height hDIBSection = GDI32.CreateDIBSection(hDCDesktop, ref bitmapInfoHeader, BitmapInfoHeader.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); if (hDIBSection == IntPtr.Zero) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", recordingSize); exceptionToThrow.Data.Add("hdcDest", hDCDest.ToInt32()); exceptionToThrow.Data.Add("hdcSrc", hDCDesktop.ToInt32()); // clean up GDI32.DeleteDC(hDCDest); User32.ReleaseDC(hWndDesktop, hDCDesktop); // Throw so people can report the problem throw exceptionToThrow; } // Create a GDI Bitmap so we can use GDI and GDI+ operations on the same memory GDIBitmap = new Bitmap(recordingSize.Width, recordingSize.Height, 32, PixelFormat.Format32bppArgb, bits0); // select the bitmap object and store the old handle hOldObject = GDI32.SelectObject(hDCDest, hDIBSection); stop = false; aviWriter = new AVIWriter(); // Comment the following 2 lines to make the user select it's own codec //aviWriter.Codec = "msvc"; //aviWriter.Quality = 99; aviWriter.FrameRate = framesPerSecond; aviWriter.Open(filename, recordingSize.Width, recordingSize.Height); // Start update check in the background backgroundTask = new Thread(new ThreadStart(CaptureFrame)); backgroundTask.IsBackground = true; backgroundTask.Start(); }
/// <summary> /// This method will use User32 code to capture the specified captureBounds from the screen /// </summary> /// <param name="captureBounds">Rectangle with the bounds to capture</param> /// <returns>Bitmap which is captured from the screen at the location specified by the captureBounds</returns> public static Bitmap CaptureRectangle(Rectangle captureBounds) { Bitmap returnBitmap = null; if (captureBounds.Height <= 0 || captureBounds.Width <= 0) { LOG.Warn("Nothing to capture, ignoring!"); return(null); } else { LOG.Debug("CaptureRectangle Called!"); } // .NET GDI+ Solution, according to some post this has a GDI+ leak... // See http://connect.microsoft.com/VisualStudio/feedback/details/344752/gdi-object-leak-when-calling-graphics-copyfromscreen // Bitmap capturedBitmap = new Bitmap(captureBounds.Width, captureBounds.Height); // using (Graphics graphics = Graphics.FromImage(capturedBitmap)) { // graphics.CopyFromScreen(captureBounds.Location, Point.Empty, captureBounds.Size, CopyPixelOperation.CaptureBlt); // } // capture.Image = capturedBitmap; // capture.Location = captureBounds.Location; // "P/Invoke" Solution for capturing the screen IntPtr hWndDesktop = User32.GetDesktopWindow(); // get te hDC of the target window IntPtr hDCDesktop = User32.GetWindowDC(hWndDesktop); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a device context we can copy to IntPtr hDCDest = GDI32.CreateCompatibleDC(hDCDesktop); // Check if the device context is there, if not throw an error with as much info as possible! if (hDCDest == IntPtr.Zero) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateCompatibleDC", captureBounds); // Cleanup User32.ReleaseDC(hWndDesktop, hDCDesktop); // throw exception throw exceptionToThrow; } // Create BitmapInfoHeader for CreateDIBSection BitmapInfoHeader bmi = new BitmapInfoHeader(captureBounds.Width, captureBounds.Height, 24); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a bitmap we can copy it to, using GetDeviceCaps to get the width/height IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. IntPtr hDIBSection = GDI32.CreateDIBSection(hDCDesktop, ref bmi, BitmapInfoHeader.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); if (hDIBSection == IntPtr.Zero) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", captureBounds); exceptionToThrow.Data.Add("hdcDest", hDCDest.ToInt32()); exceptionToThrow.Data.Add("hdcSrc", hDCDesktop.ToInt32()); // clean up GDI32.DeleteDC(hDCDest); User32.ReleaseDC(hWndDesktop, hDCDesktop); // Throw so people can report the problem throw exceptionToThrow; } else { // select the bitmap object and store the old handle IntPtr hOldObject = GDI32.SelectObject(hDCDest, hDIBSection); // bitblt over (make copy) GDI32.BitBlt(hDCDest, 0, 0, captureBounds.Width, captureBounds.Height, hDCDesktop, captureBounds.X, captureBounds.Y, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt); // restore selection (old handle) GDI32.SelectObject(hDCDest, hOldObject); // clean up GDI32.DeleteDC(hDCDest); User32.ReleaseDC(hWndDesktop, hDCDesktop); // get a .NET image object for it // A suggestion for the "A generic error occurred in GDI+." E_FAIL/0×80004005 error is to re-try... bool success = false; ExternalException exception = null; for (int i = 0; i < 3; i++) { try { // assign image to Capture, the image will be disposed there.. returnBitmap = Bitmap.FromHbitmap(hDIBSection); success = true; break; } catch (ExternalException ee) { LOG.Warn("Problem getting bitmap at try " + i + " : ", ee); exception = ee; } } if (!success) { LOG.Error("Still couldn't create Bitmap!"); throw exception; } // free up the Bitmap object GDI32.DeleteObject(hDIBSection); } return(returnBitmap); }
/// <summary> /// This method will use User32 code to capture the specified captureBounds from the screen /// </summary> /// <param name="captureBounds">Rectangle with the bounds to capture</param> /// <returns>Bitmap which is captured from the screen at the location specified by the captureBounds</returns> public static Bitmap CaptureRectangle(Rectangle captureBounds) { Bitmap returnBitmap = null; if (captureBounds.Height <= 0 || captureBounds.Width <= 0) { LOG.Warn("Nothing to capture, ignoring!"); return(null); } LOG.Debug("CaptureRectangle Called!"); // .NET GDI+ Solution, according to some post this has a GDI+ leak... // See http://connect.microsoft.com/VisualStudio/feedback/details/344752/gdi-object-leak-when-calling-graphics-copyfromscreen // Bitmap capturedBitmap = new Bitmap(captureBounds.Width, captureBounds.Height); // using (Graphics graphics = Graphics.FromImage(capturedBitmap)) { // graphics.CopyFromScreen(captureBounds.Location, Point.Empty, captureBounds.Size, CopyPixelOperation.CaptureBlt); // } // capture.Image = capturedBitmap; // capture.Location = captureBounds.Location; using (SafeWindowDCHandle desktopDCHandle = SafeWindowDCHandle.fromDesktop()) { if (desktopDCHandle.IsInvalid) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("desktopDCHandle", captureBounds); // throw exception throw exceptionToThrow; } // create a device context we can copy to using (SafeCompatibleDCHandle safeCompatibleDCHandle = GDI32.CreateCompatibleDC(desktopDCHandle)) { // Check if the device context is there, if not throw an error with as much info as possible! if (safeCompatibleDCHandle.IsInvalid) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateCompatibleDC", captureBounds); // throw exception throw exceptionToThrow; } // Create BITMAPINFOHEADER for CreateDIBSection BITMAPINFOHEADER bmi = new BITMAPINFOHEADER(captureBounds.Width, captureBounds.Height, 24); // Make sure the last error is set to 0 Win32.SetLastError(0); // create a bitmap we can copy it to, using GetDeviceCaps to get the width/height IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. using (SafeDibSectionHandle safeDibSectionHandle = GDI32.CreateDIBSection(desktopDCHandle, ref bmi, BITMAPINFOHEADER.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0)) { if (safeDibSectionHandle.IsInvalid) { // Get Exception before the error is lost Exception exceptionToThrow = CreateCaptureException("CreateDIBSection", captureBounds); exceptionToThrow.Data.Add("hdcDest", safeCompatibleDCHandle.DangerousGetHandle().ToInt32()); exceptionToThrow.Data.Add("hdcSrc", desktopDCHandle.DangerousGetHandle().ToInt32()); // Throw so people can report the problem throw exceptionToThrow; } // select the bitmap object and store the old handle using (safeCompatibleDCHandle.SelectObject(safeDibSectionHandle)) { // bitblt over (make copy) GDI32.BitBlt(safeCompatibleDCHandle, 0, 0, captureBounds.Width, captureBounds.Height, desktopDCHandle, captureBounds.X, captureBounds.Y, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt); } // get a .NET image object for it // A suggestion for the "A generic error occurred in GDI+." E_FAIL/0×80004005 error is to re-try... bool success = false; ExternalException exception = null; for (int i = 0; i < 3; i++) { try { // Collect all screens inside this capture List <Screen> screensInsideCapture = new List <Screen>(); foreach (Screen screen in Screen.AllScreens) { if (screen.Bounds.IntersectsWith(captureBounds)) { screensInsideCapture.Add(screen); } } // Check all all screens are of an equal size bool offscreenContent; using (Region captureRegion = new Region(captureBounds)) { // Exclude every visible part foreach (Screen screen in screensInsideCapture) { captureRegion.Exclude(screen.Bounds); } // If the region is not empty, we have "offscreenContent" using (Graphics screenGraphics = Graphics.FromHwnd(User32.GetDesktopWindow())) { offscreenContent = !captureRegion.IsEmpty(screenGraphics); } } // Check if we need to have a transparent background, needed for offscreen content if (offscreenContent) { using (Bitmap tmpBitmap = Image.FromHbitmap(safeDibSectionHandle.DangerousGetHandle())) { // Create a new bitmap which has a transparent background returnBitmap = ImageHelper.CreateEmpty(tmpBitmap.Width, tmpBitmap.Height, PixelFormat.Format32bppArgb, Color.Transparent, tmpBitmap.HorizontalResolution, tmpBitmap.VerticalResolution); // Content will be copied here using (Graphics graphics = Graphics.FromImage(returnBitmap)) { // For all screens copy the content to the new bitmap foreach (Screen screen in Screen.AllScreens) { Rectangle screenBounds = screen.Bounds; // Make sure the bounds are offsetted to the capture bounds screenBounds.Offset(-captureBounds.X, -captureBounds.Y); graphics.DrawImage(tmpBitmap, screenBounds, screenBounds.X, screenBounds.Y, screenBounds.Width, screenBounds.Height, GraphicsUnit.Pixel); } } } } else { // All screens, which are inside the capture, are of equal size // assign image to Capture, the image will be disposed there.. returnBitmap = Image.FromHbitmap(safeDibSectionHandle.DangerousGetHandle()); } // We got through the capture without exception success = true; break; } catch (ExternalException ee) { LOG.Warn("Problem getting bitmap at try " + i + " : ", ee); exception = ee; } } if (!success) { LOG.Error("Still couldn't create Bitmap!"); if (exception != null) { throw exception; } } } } } return(returnBitmap); }
/// <summary> /// FreeImage method /// </summary> /// <param name="bmp"></param> /* * public static void FI_ConvertSave(Bitmap bmp) * { * if (bmp != null) * { * using (FreeImageAPI.FreeImageBitmap fiBitmap = FreeImageAPI.FreeImageBitmap.FromHbitmap(bmp.GetHbitmap())) * { * if (fiBitmap.ColorDepth > 24) * { * fiBitmap.ConvertColorDepth(FreeImageAPI.FREE_IMAGE_COLOR_DEPTH.FICD_08_BPP); * } * for (int i = 16; i < 256; i++) * fiBitmap.Palette.Data[i] = new FreeImageAPI.RGBQUAD(Color.White); * * FreeImageAPI.Palette pl = new FreeImageAPI.Palette(256); * pl.CreateGrayscalePalette(); * //quantize using the NeuQuant neural-net quantization algorithm * fiBitmap.Quantize(FreeImageAPI.FREE_IMAGE_QUANTIZE.FIQ_NNQUANT, 16, pl); * * * * fiBitmap.Save("test_FreeImageOutput.png", FreeImageAPI.FREE_IMAGE_FORMAT.FIF_PNG, FreeImageAPI.FREE_IMAGE_SAVE_FLAGS.PNG_Z_BEST_COMPRESSION); * //bmp = fiBitmap.ToBitmap(); * //ms = new MemoryStream(); * //fiBitmap.Save(ms, FreeImageAPI.FREE_IMAGE_FORMAT.FIF_PNG, FreeImageAPI.FREE_IMAGE_SAVE_FLAGS.PNG_Z_DEFAULT_COMPRESSION); * } * bmp.Dispose(); * } * } */ // From wischik.com static Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp) { if (bpp != 1 && bpp != 8) { throw new System.ArgumentException("1 or 8", "bpp"); } // Plan: built into Windows GDI is the ability to convert // bitmaps from one format to another. Most of the time, this // job is actually done by the graphics hardware accelerator card // and so is extremely fast. The rest of the time, the job is done by // very fast native code. // We will call into this GDI functionality from C#. Our plan: // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed) // (2) Create a GDI monochrome hbitmap // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above) // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed) int w = b.Width; int h = b.Height; IntPtr hbm = b.GetHbitmap(); // this is step (1) // // Step (2): create the monochrome bitmap. // "BITMAPINFO" is an interop-struct which we define below. // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs GDI32.BITMAPINFOHEADER bmi = new GDI32.BITMAPINFOHEADER(); bmi.biSize = 40; // the size of the BITMAPHEADERINFO struct bmi.biWidth = w; bmi.biHeight = h; bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info. bmi.biBitCount = (ushort)bpp; // ie. 1bpp or 8bpp bmi.biCompression = (uint)GdiFlags.BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8); bmi.biXPelsPerMeter = 1000000; // not really important bmi.biYPelsPerMeter = 1000000; // not really important // Now for the colour table. uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp bmi.biClrUsed = ncols; bmi.biClrImportant = ncols; bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours if (bpp == 1) { bmi.cols[0] = GDI32.MAKERGB(0, 0, 0); bmi.cols[1] = GDI32.MAKERGB(255, 255, 255); } else { for (int i = 0; i < ncols; i++) { bmi.cols[i] = GDI32.MAKERGB(i, i, i); } } // For 8bpp we've created an palette with just greyscale colours. // You can set up any palette you want here. Here are some possibilities: // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i); bmi.biClrUsed = 216; bmi.biClrImportant = 216; int[] colv = new int[6] { 0, 51, 102, 153, 204, 255 }; for (int i = 0; i < 216; i++) { bmi.cols[i] = GDI32.MAKERGB(colv[i / 36], colv[(i / 6) % 6], colv[i % 6]); } // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255}; // for (int i=0; i<216; i++) bmi.cols[i]=GDI32.MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]); // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization // // Now create the indexed bitmap "hbm0" IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. IntPtr hbm0 = GDI32.CreateDIBSection(IntPtr.Zero, ref bmi, (uint)GdiFlags.DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); // // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC". IntPtr sdc = USER32.GetDC(IntPtr.Zero); // First we obtain the DC for the screen // Next, create a DC for the original hbitmap IntPtr hdc = GDI32.CreateCompatibleDC(sdc); GDI32.SelectObject(hdc, hbm); // and create a DC for the monochrome hbitmap IntPtr hdc0 = GDI32.CreateCompatibleDC(sdc); GDI32.SelectObject(hdc0, hbm0); // Now we can do the BitBlt: GDI32.BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, TernaryRasterOperations.SRCCOPY); // Step (4): convert this monochrome hbitmap back into a Bitmap: System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0); // // Finally some cleanup. GDI32.DeleteDC(hdc); GDI32.DeleteDC(hdc0); USER32.ReleaseDC(IntPtr.Zero, sdc); GDI32.DeleteObject(hbm); GDI32.DeleteObject(hbm0); // return(b0); }