Introduction
In this article, I will show you how to capture an HTML document as an image using a WebBrowser
object and the IViewObject.Draw
method, which according to MSDN draws a representation of an object onto the specified device context. Before we get started, I just want to mention that the obtained results were identical to those obtained using commercial libraries, so I hope this will be useful to someone.
The IViewObject interface
The very first thing that we must do is to define the IViewObject
interface.
[ComVisible(true), ComImport()]
[GuidAttribute("0000010d-0000-0000-C000-000000000046")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IViewObject
{
[return: MarshalAs(UnmanagedType.I4)]
[PreserveSig]
int Draw(
[MarshalAs(UnmanagedType.U4)] UInt32 dwDrawAspect,
int lindex,
IntPtr pvAspect,
[In] IntPtr ptd,
IntPtr hdcTargetDev,
IntPtr hdcDraw,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcBounds,
[MarshalAs(UnmanagedType.Struct)] ref Rectangle lprcWBounds,
IntPtr pfnContinue,
[MarshalAs(UnmanagedType.U4)] UInt32 dwContinue);
[PreserveSig]
int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect,[In] IntPtr ptd,
IntPtr hicTargetDev, [Out] IntPtr ppColorSet);
[PreserveSig]
int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect,
int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
[PreserveSig]
int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
void SetAdvise([In, MarshalAs(UnmanagedType.U4)] int aspects,
[In, MarshalAs(UnmanagedType.U4)] int advf,
[In, MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects,
[In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf,
[In, Out, MarshalAs(UnmanagedType.LPArray)] IAdviseSink[] pAdvSink);
}
Below is a summary description of the parameters that the Draw
method takes (this is the only method we will use):
UInt32 dwDrawAspect
- specifies the aspect to be drawn. Valid values are taken from the DVASPECT
and DVASPECT2
enumerations. In this example, I'm using DVASPECT.CONTENT
so the value passed is 1.
int lindex
- portion of the object that is of interest for the draw operation. Currently, only -1 is supported.
IntPtr pvAspect
- pointer to the additional information.
IntPtr ptd
- describes the device for which the object is to be rendered. We will render for the default target device, so the value passed will be IntPtr.Zero
.
IntPtr hdcTargetDev
- information context for the target device indicated by the ptd
parameter.
IntPtr hdcDraw
- device context on which to draw.
ref Rectangle lprcBounds
- the size of the captured image.
ref Rectangle lprcWBounds
- the region of the WebBrowser
object that we want to be captured.
IntPtr pfnContinue
- pointer to a callback function (not used here).
UInt32 dwContinue
- value to pass as a parameter to the function (not used here).
The HtmlCapture class
Now that we have defined our IViewObject
interface, it is time to move on and create a class that will be used to capture a web page as an image.
public class HtmlCapture
{
private WebBrowser web;
private Timer tready;
private Rectangle screen;
private Size? imgsize=null;
public delegate void HtmlCaptureEvent(object sender,
Uri url, Bitmap image);
public event HtmlCaptureEvent HtmlImageCapture;
public HtmlCapture()
{
web = new WebBrowser();
tready = new Timer();
tready.Interval = 2000;
screen = Screen.PrimaryScreen.Bounds;
web.Width = screen.Width;
web.Height = screen.Height;
web.ScriptErrorsSuppressed = true;
web.ScrollBarsEnabled = false;
web.Navigating +=
new WebBrowserNavigatingEventHandler(web_Navigating);
web.DocumentCompleted += new
WebBrowserDocumentCompletedEventHandler(web_DocumentCompleted);
tready.Tick += new EventHandler(tready_Tick);
}
#region Public methods
public void Create(string url)
{
imgsize = null;
web.Navigate(url);
}
public void Create(string url,Size imgsz)
{
this.imgsize = imgsz;
web.Navigate(url);
}
#endregion
#region Events
void web_DocumentCompleted(object sender,
WebBrowserDocumentCompletedEventArgs e)
{
tready.Start();
}
void web_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
tready.Stop();
}
void tready_Tick(object sender, EventArgs e)
{
tready.Stop();
}
#endregion
}
As you can see, I'm using a Timer
object to determine if the HTML document is fully loaded and can be captured. The reason I'm doing this is because an HTML document can trigger the DocumentCompleted
event multiple times. After the document is fully loaded, the tready_Tick
method is called.
void tready_Tick(object sender, EventArgs e)
{
tready.Stop();
Rectangle body = web.Document.Body.ScrollRectangle;
Rectangle docRectangle = new Rectangle()
{
Location=new Point(0,0),
Size=new Size(body.Width > screen.Width ? body.Width : screen.Width,
body.Height > screen.Height ? body.Height : screen.Height)
};
web.Width = docRectangle.Width;
web.Height = docRectangle.Height;
Rectangle imgRectangle;
if (imgsize == null)
imgRectangle = docRectangle;
else
imgRectangle = new Rectangle()
{
Location=new Point(0,0),
Size =imgsize.Value
};
Bitmap bitmap = new Bitmap(imgRectangle.Width,imgRectangle.Height);
IViewObject ivo = web.Document.DomDocument as IViewObject;
using (Graphics g = Graphics.FromImage(bitmap))
{
IntPtr hdc = g.GetHdc();
ivo.Draw(1, -1, IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero, hdc, ref imgRectangle,
ref docRectangle, IntPtr.Zero, 0);
g.ReleaseHdc(hdc);
}
HtmlImageCapture(this, web.Url, bitmap);
}
Using the code
HtmlCapture
has an overloaded method named Create
. If you use the Create(string url)
method, the size of the image will be the same as the size of the HTML document. If you want to create a thumbnail image of the HTML document, use Create(string url,Size imgsz)
.
private void button2_Click(object sender, EventArgs e)
{
HtmlCapture hc = new HtmlCapture();
hc.HtmlImageCapture +=
new HtmlCapture.HtmlCaptureEvent(hc_HtmlImageCapture);
hc.Create("http://www.codeproject.com");
hc.Create("http://www.codeproject.com",new Size(200,300));
}
void hc_HtmlImageCapture(object sender, Uri url, Bitmap image)
{
image.Save("C:/"+ url.Authority+ ".bmp");
}