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);
}
}
}
}
}
}