/// <summary> /// Place the surface as Format17 bitmap on the clipboard /// </summary> /// <param name="clipboardAccessToken">IClipboardAccessToken</param> /// <param name="surface">ISurface</param> public static void SetAsFormat17(this IClipboardAccessToken clipboardAccessToken, ISurface surface) { // Create the stream for the clipboard using (var dibV5Stream = new MemoryStream()) { var outputSettings = new SurfaceOutputSettings(OutputFormats.bmp, 100, false); bool dispose = ImageOutput.CreateBitmapFromSurface(surface, outputSettings, out var bitmapToSave); // Create the BITMAPINFOHEADER var header = BitmapInfoHeader.Create(bitmapToSave.Width, bitmapToSave.Height, 32); // Make sure we have BI_BITFIELDS, this seems to be normal for Format17? header.Compression = BitmapCompressionMethods.BI_BITFIELDS; var headerBytes = BinaryStructHelper.ToByteArray(header); // Write the BITMAPINFOHEADER to the stream dibV5Stream.Write(headerBytes, 0, headerBytes.Length); // As we have specified BI_COMPRESSION.BI_BITFIELDS, the BitfieldColorMask needs to be added var colorMask = BitfieldColorMask.Create(); // Create the byte[] from the struct var colorMaskBytes = BinaryStructHelper.ToByteArray(colorMask); Array.Reverse(colorMaskBytes); // Write to the stream dibV5Stream.Write(colorMaskBytes, 0, colorMaskBytes.Length); // Create the raw bytes for the pixels only var bitmapBytes = BitmapToByteArray(bitmapToSave); // Write to the stream dibV5Stream.Write(bitmapBytes, 0, bitmapBytes.Length); // Reset the stream to the beginning so it can be written dibV5Stream.Seek(0, SeekOrigin.Begin); // Set the DIBv5 to the clipboard DataObject clipboardAccessToken.SetAsStream("Format17", dibV5Stream); if (dispose) { bitmapToSave.Dispose(); } } }
/// <summary> /// This method will use User32 code to capture the specified captureBounds from the screen /// </summary> /// <param name="captureBounds">NativeRect 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(NativeRect captureBounds) { Bitmap returnBitmap = null; if (captureBounds.Height <= 0 || captureBounds.Width <= 0) { Log.Warn().WriteLine("Nothing to capture, ignoring!"); return(null); } Log.Debug().WriteLine("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, NativePoint.Empty, captureBounds.Size, CopyPixelOperation.CaptureBlt); // } // capture.Image = capturedBitmap; // capture.Location = captureBounds.Location; using (var desktopDcHandle = SafeWindowDcHandle.FromDesktop()) { if (desktopDcHandle.IsInvalid) { // Get Exception before the error is lost var exceptionToThrow = CreateCaptureException("desktopDCHandle", captureBounds); // throw exception throw exceptionToThrow; } // create a device context we can copy to using (var safeCompatibleDcHandle = Gdi32Api.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 var exceptionToThrow = CreateCaptureException("CreateCompatibleDC", captureBounds); // throw exception throw exceptionToThrow; } // Create BITMAPINFOHEADER for CreateDIBSection var bmi = BitmapInfoHeader.Create(captureBounds.Width, captureBounds.Height, 24); // TODO: Enable when the function is available again // 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. // TODO: Change the usage to an enum? using (var safeDibSectionHandle = Gdi32Api.CreateDIBSection(desktopDcHandle, ref bmi, 0, out bits0, IntPtr.Zero, 0)) { if (safeDibSectionHandle.IsInvalid) { // Get Exception before the error is lost var 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) // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags Gdi32Api.BitBlt(safeCompatibleDcHandle, 0, 0, captureBounds.Width, captureBounds.Height, desktopDcHandle, captureBounds.X, captureBounds.Y, RasterOperations.SourceCopy | RasterOperations.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... var success = false; ExternalException exception = null; for (var i = 0; i < 3; i++) { try { // Collect all screens inside this capture var screensInsideCapture = new List <Screen>(); foreach (var screen in Screen.AllScreens) { if (screen.Bounds.IntersectsWith(captureBounds)) { screensInsideCapture.Add(screen); } } // Check all all screens are of an equal size bool offscreenContent; using (var captureRegion = new Region(captureBounds)) { // Exclude every visible part foreach (var screen in screensInsideCapture) { captureRegion.Exclude(screen.Bounds); } // If the region is not empty, we have "offscreenContent" using (var screenGraphics = Graphics.FromHwnd(User32Api.GetDesktopWindow())) { offscreenContent = !captureRegion.IsEmpty(screenGraphics); } } // Check if we need to have a transparent background, needed for offscreen content if (offscreenContent) { using (var tmpBitmap = Image.FromHbitmap(safeDibSectionHandle.DangerousGetHandle())) { // Create a new bitmap which has a transparent background returnBitmap = BitmapFactory.CreateEmpty(tmpBitmap.Width, tmpBitmap.Height, PixelFormat.Format32bppArgb, Color.Transparent, tmpBitmap.HorizontalResolution, tmpBitmap.VerticalResolution); // Content will be copied here using (var graphics = Graphics.FromImage(returnBitmap)) { // For all screens copy the content to the new bitmap foreach (var screen in Screen.AllScreens) { // Make sure the bounds are offsetted to the capture bounds var screenBounds = screen.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().WriteLine(ee, "Problem getting bitmap at try " + i + " : "); exception = ee; } } if (!success) { Log.Error().WriteLine(null, "Still couldn't create Bitmap!"); if (exception != null) { throw exception; } } } } } return(returnBitmap); }
/// <summary> /// Set an Image to the clipboard /// This method will place images to the clipboard depending on the ClipboardFormats setting. /// e.g. Bitmap which works with pretty much everything and type Dib for e.g. OpenOffice /// because OpenOffice has a bug http://qa.openoffice.org/issues/show_bug.cgi?id=85661 /// The Dib (Device Indenpendend Bitmap) in 32bpp actually won't work with Powerpoint 2003! /// When pasting a Dib in PP 2003 the Bitmap is somehow shifted left! /// For this problem the user should not use the direct paste (=Dib), but select Bitmap /// </summary> public static void SetClipboardData(ISurface surface) { var dataObject = new DataObject(); // This will work for Office and most other applications //ido.SetData(DataFormats.Bitmap, true, image); MemoryStream dibStream = null; MemoryStream dibV5Stream = null; MemoryStream pngStream = null; Bitmap bitmapToSave = null; var disposeImage = false; try { var outputSettings = new SurfaceOutputSettings(OutputFormats.png, 100, false); // Create the image which is going to be saved so we don't create it multiple times disposeImage = ImageOutput.CreateBitmapFromSurface(surface, outputSettings, out bitmapToSave); try { // Create PNG stream if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.PNG)) { pngStream = new MemoryStream(); // PNG works for e.g. Powerpoint var pngOutputSettings = new SurfaceOutputSettings(OutputFormats.png, 100, false); ImageOutput.SaveToStream(bitmapToSave, null, pngStream, pngOutputSettings); pngStream.Seek(0, SeekOrigin.Begin); // Set the PNG stream dataObject.SetData(FORMAT_PNG, false, pngStream); } } catch (Exception pngEx) { Log.Error().WriteLine(pngEx, "Error creating PNG for the Clipboard."); } try { if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.DIB)) { using (var tmpBmpStream = new MemoryStream()) { // Save image as BMP var bmpOutputSettings = new SurfaceOutputSettings(OutputFormats.bmp, 100, false); ImageOutput.SaveToStream(bitmapToSave, null, tmpBmpStream, bmpOutputSettings); dibStream = new MemoryStream(); // Copy the source, but skip the "BITMAPFILEHEADER" which has a size of 14 dibStream.Write(tmpBmpStream.GetBuffer(), BITMAPFILEHEADER_LENGTH, (int)tmpBmpStream.Length - BITMAPFILEHEADER_LENGTH); } // Set the DIB to the clipboard DataObject dataObject.SetData(DataFormats.Dib, true, dibStream); } } catch (Exception dibEx) { Log.Error().WriteLine(dibEx, "Error creating DIB for the Clipboard."); } // CF_DibV5 try { if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.DIBV5)) { // Create the stream for the clipboard dibV5Stream = new MemoryStream(); // Create the BITMAPINFOHEADER var header = BitmapInfoHeader.Create(bitmapToSave.Width, bitmapToSave.Height, 32); // Make sure we have BI_BITFIELDS, this seems to be normal for Format17? header.Compression = BitmapCompressionMethods.BI_BITFIELDS; var headerBytes = BinaryStructHelper.ToByteArray(header); // Write the BITMAPINFOHEADER to the stream dibV5Stream.Write(headerBytes, 0, headerBytes.Length); // As we have specified BI_COMPRESSION.BI_BITFIELDS, the BitfieldColorMask needs to be added var colorMask = BitfieldColorMask.Create(); // Create the byte[] from the struct var colorMaskBytes = BinaryStructHelper.ToByteArray(colorMask); Array.Reverse(colorMaskBytes); // Write to the stream dibV5Stream.Write(colorMaskBytes, 0, colorMaskBytes.Length); // Create the raw bytes for the pixels only var bitmapBytes = BitmapToByteArray(bitmapToSave); // Write to the stream dibV5Stream.Write(bitmapBytes, 0, bitmapBytes.Length); // Set the DIBv5 to the clipboard DataObject dataObject.SetData(FORMAT_17, true, dibV5Stream); } } catch (Exception dibEx) { Log.Error().WriteLine(dibEx, "Error creating DIB for the Clipboard."); } // Set the HTML if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.HTML)) { var tmpFile = ImageOutput.SaveToTmpFile(surface, new SurfaceOutputSettings(OutputFormats.png, 100, false), null); var html = GetHtmlString(surface, tmpFile); dataObject.SetText(html, TextDataFormat.Html); } else if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.HTMLDATAURL)) { string html; using (var tmpPngStream = new MemoryStream()) { var pngOutputSettings = new SurfaceOutputSettings(OutputFormats.png, 100, false) { // Do not allow to reduce the colors, some applications dislike 256 color images // reported with bug #3594681 DisableReduceColors = true }; // Check if we can use the previously used image if (bitmapToSave.PixelFormat != PixelFormat.Format8bppIndexed) { ImageOutput.SaveToStream(bitmapToSave, surface, tmpPngStream, pngOutputSettings); } else { ImageOutput.SaveToStream(surface, tmpPngStream, pngOutputSettings); } html = GetHtmlDataUrlString(surface, tmpPngStream); } dataObject.SetText(html, TextDataFormat.Html); } } finally { // we need to use the SetDataOject before the streams are closed otherwise the buffer will be gone! // Check if Bitmap is wanted if (CoreConfig.ClipboardFormats.Contains(ClipboardFormats.BITMAP)) { dataObject.SetImage(bitmapToSave); // Place the DataObject to the clipboard SetDataObject(dataObject, true); } else { // Place the DataObject to the clipboard SetDataObject(dataObject, true); } pngStream?.Dispose(); dibStream?.Dispose(); dibV5Stream?.Dispose(); // cleanup if needed if (disposeImage) { bitmapToSave?.Dispose(); } } }