Codesnipp.it Social Code Sharing

Uwe Keim

Capture a screenshot from a browser in WinForms

by Uwe Keim on Feb 16, 2012

namespace ZetaProducer.DesktopDesigner.Code { using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Windows.Forms; public class WebBrowserScreenshotCapture { private class FocuslessForm : Form{} public void CaptureBrowserScreenshot( Uri url, FileInfo saveAsFilePath, Size scaleSmallerToMaxBounds) { // Ensure always a square, no matter what resolution. var min = Math.Min( Screen.PrimaryScreen.WorkingArea.Width, Screen.PrimaryScreen.WorkingArea.Height); var sb = Math.Max( SystemInformation.VerticalScrollBarWidth, SystemInformation.HorizontalScrollBarHeight); var size = new Size(min, min); using (var form = new FocuslessForm { Width = size.Width + sb, Height = size.Height + sb, Padding = new Padding(0), Margin = new Padding(0), FormBorderStyle = FormBorderStyle.None, Opacity = 0, TabStop = false, ShowInTaskbar = false }) { var webBrowser1 = new WebBrowser { Padding = new Padding(0), Margin = new Padding(0), Dock = DockStyle.Fill, Url = url, TabStop = false }; form.Controls.Add(webBrowser1); var finished = false; webBrowser1.DocumentCompleted += delegate { finished = true; }; form.Show(); while (!finished) { Application.DoEvents(); } // -- CaptureBrowserScreenshot(webBrowser1, saveAsFilePath, scaleSmallerToMaxBounds); form.Close(); } } public void CaptureBrowserScreenshot( WebBrowser webBrowser, FileInfo saveAsFilePath, Size scaleSmallerToMaxBounds) { using (var screenshot = new Bitmap( webBrowser.ClientSize.Width, webBrowser.ClientSize.Height)) { getImage(webBrowser.ActiveXInstance, screenshot, Color.White); using (var effectiveImage = new Bitmap( webBrowser.ClientSize.Width - SystemInformation.VerticalScrollBarWidth, webBrowser.ClientSize.Height - SystemInformation.HorizontalScrollBarHeight)) { using (var graphics = Graphics.FromImage(effectiveImage)) { graphics.DrawImageUnscaled(screenshot, 0, 0); } using (var croppedImage = cropImage(effectiveImage, true)) { if (scaleSmallerToMaxBounds == Size.Empty || !needReduceSizeProportionally( croppedImage, scaleSmallerToMaxBounds.Width, scaleSmallerToMaxBounds.Height)) { croppedImage.Save( saveAsFilePath.FullName, getImageFormatFromFileExtension( saveAsFilePath.Extension)); } else { using (var bmp = reduceSizeProportionally (croppedImage, scaleSmallerToMaxBounds.Width, scaleSmallerToMaxBounds.Height)) { bmp.Save( saveAsFilePath.FullName, getImageFormatFromFileExtension( saveAsFilePath.Extension)); } } } } } } /// <summary> /// http://stackoverflow.com/questions/7083853/simple-algorithm-to-crop-empty-borders-from-an-image-by-code /// </summary> private static Image cropImage(Bitmap image, bool square) { var cropRect = measureImageCrop(image, square); // NO "using" here, because the image is returned (and // would be disposed by "using")! var effectiveImage = new Bitmap( cropRect.Width, cropRect.Height); using (var graphics = Graphics.FromImage(effectiveImage)) { graphics.DrawImage(image, 0, 0, cropRect, GraphicsUnit.Pixel); } return effectiveImage; } private static Rectangle measureImageCrop(Bitmap image, bool square) { // GDI+ still lies to us - the return format is BGR, NOT RGB. var bmData = image.LockBits( new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); var stride = bmData.Stride; var scan0 = bmData.Scan0; var cutLeft = 0; var cutRight = 0; var cutTop = 0; var cutBottom = 0; const int step = 1; unsafe { var p = (byte*)(void*)scan0; var refBlue = p[0]; var refGreen = p[1]; var refRed = p[2]; // -- // Top. var wantBreak = false; for (var y = 0; y < image.Height; y += step) { p = ((byte*)(void*)scan0) + stride * y; for (var x = 0; x < image.Width; x += step) { var blue = p[0]; var green = p[1]; var red = p[2]; if (blue != refBlue || green != refGreen || red != refRed) { wantBreak = true; break; } p += 3 * step; } if (wantBreak) { break; } cutTop += step; } // -- // Bottom. wantBreak = false; for (var y = image.Height - 1; y >= 0; y -= step) { p = ((byte*)(void*)scan0) + stride * y; for (var x = 0; x < image.Width; x += step) { var blue = p[0]; var green = p[1]; var red = p[2]; if (blue != refBlue || green != refGreen || red != refRed) { wantBreak = true; break; } p += 3 * step; } if (wantBreak) { break; } cutBottom += step; } // -- // Left. p = (byte*)(void*)scan0; wantBreak = false; for (var x = 0; x < image.Width; x += step) { for (var y = 0; y < image.Height; y += step) { var blue = p[0]; var green = p[1]; var red = p[2]; if (blue != refBlue || green != refGreen || red != refRed) { wantBreak = true; break; } p += stride * step; } if (wantBreak) { break; } cutLeft += step; p -= stride * image.Height; p += 3 * step; } // -- // Right. p = (byte*)(void*)scan0; p += (image.Width - 1) * 3; wantBreak = false; for (var x = image.Width - 1; x >= 0; x -= step) { for (var y = 0; y < image.Height; y += step) { var blue = p[0]; var green = p[1]; var red = p[2]; if (blue != refBlue || green != refGreen || red != refRed) { wantBreak = true; break; } p += stride * step; } if (wantBreak) { break; } cutRight += step; p -= stride * image.Height; p -= 3 * step; } } // -- image.UnlockBits(bmData); if (square) { var vert = cutLeft + cutRight; var horz = cutTop + cutBottom; if (vert > horz) { var delta = vert - horz; cutLeft -= delta / 2; cutRight -= delta / 2; } else { var delta = horz - vert; //cutTop -= delta / 2; //cutBottom -= delta / 2; cutBottom -= delta; } /*var min = cutLeft; min = Math.Min(min, cutRight); min = Math.Min(min, cutTop); min = Math.Min(min, cutBottom); cutLeft = min; cutRight = min; cutTop = min; cutBottom = min;*/ } return new Rectangle( cutLeft, cutTop, image.Width - cutRight - cutLeft, image.Height - cutBottom - cutTop); } private static Image reduceSizeProportionally( Image image, int maxWidth, int maxHeight) { if (needReduceSizeProportionally(image, maxWidth, maxHeight)) { // The factors in x and y. var facX = maxWidth / ((double)image.Width); var facY = maxHeight / ((double)image.Height); // Select factor. var fac = facX < facY ? facX : facY; return pixelScaleImage( image, (int)(image.Width * fac), (int)(image.Height * fac), InterpolationMode.Bicubic); } else { return image; } } private static Image pixelScaleImage( Image image, int width, int height, InterpolationMode ipm) { // NO "using" here, because the image is returned (and // would be disposed by "using")! var dst = new Bitmap(image, width, height); using (var g = Graphics.FromImage(dst)) { // White background. g.FillRectangle(Brushes.White, 0, 0, width, height); g.InterpolationMode = ipm; g.DrawImage( image, new Rectangle( 0, 0, width, height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel); return dst; } } private static bool needReduceSizeProportionally( Image image, int maxWidth, int maxHeight) { if (image == null) { return false; } else { if (image.Width <= maxWidth && image.Height <= maxHeight) { // Nothing to do. return false; } else { return true; } } } private static ImageFormat getImageFormatFromFileExtension( string extension) { extension = extension.Trim('.').ToLowerInvariant(); ImageFormat format; switch (extension) { case @"bmp": format = ImageFormat.Bmp; break; case @"png": format = ImageFormat.Png; break; case @"gif": format = ImageFormat.Gif; break; case @"jpg": case @"jpeg": format = ImageFormat.Jpeg; break; case @"tif": case @"tiff": format = ImageFormat.Tiff; break; default: Trace.WriteLine( string.Format( @"Unknown file format extension '{0}'. Using PNG instead.", extension)); format = ImageFormat.Png; break; } return format; } [ComImport] [Guid(@"0000010D-0000-0000-C000-000000000046")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IViewObject { void Draw([MarshalAs(UnmanagedType.U4)] uint dwAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw, [MarshalAs(UnmanagedType.Struct)] ref RECT lprcBounds, [In] IntPtr lprcWBounds, IntPtr pfnContinue, [MarshalAs(UnmanagedType.U4)] uint dwContinue); } [StructLayout(LayoutKind.Sequential, Pack = 4)] // ReSharper disable InconsistentNaming // ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable MemberCanBePrivate.Local private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } // ReSharper restore MemberCanBePrivate.Local // ReSharper restore FieldCanBeMadeReadOnly.Local // ReSharper restore InconsistentNaming private static void getImage(object obj, Image destination, Color backgroundColor) { using (var graphics = Graphics.FromImage(destination)) { var deviceContextHandle = IntPtr.Zero; var rectangle = new RECT { Right = destination.Width, Bottom = destination.Height }; graphics.Clear(backgroundColor); try { deviceContextHandle = graphics.GetHdc(); var viewObject = (IViewObject)obj; viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, deviceContextHandle, ref rectangle, IntPtr.Zero, IntPtr.Zero, 0); } finally { if (deviceContextHandle != IntPtr.Zero) { graphics.ReleaseHdc(deviceContextHandle); } } } } } }

Can't see the comments? Please login first :)