Click here to Skip to main content
13,407,374 members (52,213 online)
Click here to Skip to main content
Add your own
alternative version


73 bookmarked
Posted 17 Nov 2006

How To Get A Website Thumbnail in a C# Application Without Creating A Form (console)

, 5 Jul 2008
Rate this:
Please Sign up or sign in to vote.
The article describes how to get a thumbnail of a Website in .NET Framework 2.0+ without launching a fully interactive WinForms application.


I've updated the code and the binary with the great improvements that Piers Lawson suggested in the comments. The app should no longer have problems taking snapshots of some images with JavaScript or just plain random problems. It is also slightly optimized with suggestions from Frank Herget. It looks like he's based a very nice service around it on his site - check it out!

Thanks again for your great support!


The article describes a console-like application that loads a Web page, makes a screenshot of it and saves it as a JPG file.

Our beloved sys admin - (we all bow to him and worship his skills) has recently asked if it's possible to write a .NET application to make a thumbnail of a Website. The task is pretty trivial with Windows Forms actually. But with him being the Linux guy and all... I decided to pick up the more challenging part of it being the console app. An interesting use case anyway.

In WinForms, all you really need to do is drop a WebBrowser from your Toolbox on your form and once it's loaded the page call:

Bitmap bitmap = new Bitmap(width, height);
    new Rectangle(webBrowser1.Location.X, webBrowser1.Location.Y, 
        webBrowser1.Width, webBrowser1.Height));

Obvious enough. When it gets tricky is when you want to do it in a console application in a way that can take a shot of multitude of Websites provided in a batch file. There is a dirty way of instantiating a whole form, making it show (or not), doing the work and then exiting the WinForms app. This might probably be enough for a quick solution, but I wanted a clean piece of code, so I would actually NOT take pride in something in that tone.

How is it done then...

So we instantiate the Web control in our class constructor...

public WebPageBitmap(string url, int width, int height, bool scrollBarsEnabled)
    this.url = url;
    this.width = width;
    this.height = height;
    webBrowser = new WebBrowser();
    webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(documentCompletedEventHandler);
    webBrowser.Size = new Size(width, height);
    webBrowser.ScrollBarsEnabled = scrollBarsEnabled;

Easy so far and pretty similar to what the regular app would do anyway. The documentCompletedEventHandler is a delegate to tell that it has loaded. (I initially wanted to use that for drawing the bitmap but deferred that to the point where the bitmap is actually fetched after I added the resizing part.) Now comes the interesting case.

The Neat Part

Since the call is asynchronous, a simple webBrowser.Navigate(URL); just won't cut it. We are in a single thread and the browser does not create a separate thread for that. This makes sense by the canonical windows rule: Only the thread that creates a control, accesses the control. We need to somehow allow the control to take the flow of the thread and do its work. Navigate only tells it that it should perform the action and immediately exits. The developer's responsibility then is to know when the control is ready for consumption. Which is the case when the webBrowser.ReadyState progresses to (or returns to) the state of WebBrowserReadyState.Complete.

The Solution

To pass the flow to the app controls, you need to perform Application.DoEvents(); which was a bit of a wild guess when I used it. Surprise, surprise, it works just like it did in other Windows frameworks that I used before.

public void Fetch()
        while (webBrowser.ReadyState != WebBrowserReadyState.Complete)

The effect is a tiny and neat (I hope) app that pulls a Web page from the net and makes a screenshot off of it (with possible rescaling).

You can get the source code or get the app directly. App usage:

GetSiteThumbnail.exe thumbnail.jpg 
  [browser_width(defaults to 800) browser_height (defaults to 600) ] 
  [thumbnail_width thumbnail_height]

GetSiteThumbnail.exe cognifide.jpg 1280 1024 640 480


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Web Developer
Poland Poland
Adam Najmanowicz is a Senior Developer at Cognifide Poland.

His previous experience includes Delphi, .Net as well as Java Development.

Currently working on ASP.NET + MSSql Server(Oracle) solutions for enterprises.

Also developing desktop applications for Stardock Systems.

You may also be interested in...


Comments and Discussions

GeneralAxWebBrowser Vs. WebBrowser (.NET2) events Pin
Assaf Koren23-Nov-06 9:43
memberAssaf Koren23-Nov-06 9:43 
GeneralRe: AxWebBrowser Vs. WebBrowser (.NET2) events Pin
AdamNajmanowicz28-Nov-06 9:23
memberAdamNajmanowicz28-Nov-06 9:23 
GeneralGetting it to work in an ASP.NET application [modified] Pin
dB.20-Nov-06 21:51
memberdB.20-Nov-06 21:51 
GeneralRe: Getting it to work in an ASP.NET application Pin
ligaz25-Nov-06 5:06
memberligaz25-Nov-06 5:06 
GeneralRe: Getting it to work in an ASP.NET application Pin
dB.25-Nov-06 6:11
memberdB.25-Nov-06 6:11 
GeneralRe: Getting it to work in an ASP.NET application Pin
AdamNajmanowicz28-Nov-06 9:19
memberAdamNajmanowicz28-Nov-06 9:19 
GeneralRe: Getting it to work in an ASP.NET application Pin
chr872-Mar-07 3:19
memberchr872-Mar-07 3:19 
GeneralRe: Getting it to work in an ASP.NET application Pin
Chris Vann23-Mar-08 11:46
memberChris Vann23-Mar-08 11:46 
After some troubleshooting and tweaking, I've come up with a relatively simple, self-contained class to get a bitmap of a web site. Since it seems to be such a difficult and confusing task at times, I'll offer my code for your use:

using System;<br />
using System.Drawing;<br />
using System.Drawing.Drawing2D;<br />
using System.IO;<br />
using System.Net;<br />
using System.Windows.Forms;<br />
using System.Threading;<br />
using System.Runtime.InteropServices;<br />
using System.Text.RegularExpressions;<br />
<br />
namespace Sample<br />
{<br />
    public class Thumbnailer<br />
    {<br />
        const int TIMEOUT = 30000;<br />
        const int START_WIDTH = 800;<br />
        const int START_HEIGHT = 600;<br />
<br />
        Bitmap bmp = null;<br />
        ManualResetEvent mre;<br />
<br />
        public Bitmap GetThumbnailFromWeb(string url, int width, int height)<br />
        {<br />
            mre = new ManualResetEvent(false);<br />
<br />
            Thread t = new Thread(new ParameterizedThreadStart(BitmapThreadWorker));<br />
            t.SetApartmentState(ApartmentState.STA);<br />
            t.Start(url);<br />
            mre.WaitOne();<br />
<br />
            if (!t.Join(TIMEOUT))<br />
                t.Abort();<br />
<br />
            if (bmp == null)<br />
                bmp = new Bitmap(START_WIDTH, START_HEIGHT);<br />
<br />
            return Resize(bmp, width, height);<br />
        }<br />
<br />
        public Bitmap GetThumbnailFromFile(string path, int width, int height)<br />
        {<br />
            Bitmap input = (Bitmap)Bitmap.FromFile(path);<br />
            return Resize(input, width, height);<br />
        }<br />
<br />
        public Bitmap GetThumbnailFromBitmap(Bitmap input, int width, int height)<br />
        {<br />
            return Resize(bmp, width, height);<br />
        }<br />
<br />
        public Bitmap Resize(Bitmap input, int width, int height)<br />
        {<br />
            Bitmap output = new Bitmap(width, height);<br />
            Rectangle r = new Rectangle(0, 0, width, height);<br />
            Graphics g = Graphics.FromImage(output);<br />
            g.CompositingQuality = CompositingQuality.HighQuality;<br />
            g.SmoothingMode = SmoothingMode.HighQuality;<br />
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;<br />
            g.DrawImage(input, r);<br />
            input.Dispose();<br />
            return output;<br />
        }<br />
<br />
        public void BitmapThreadWorker(object url)<br />
        {<br />
            DateTime started = DateTime.Now;<br />
            WebBrowser browser = new WebBrowser();<br />
            browser.ScrollBarsEnabled = false;<br />
            browser.ClientSize = new Size(START_WIDTH, START_HEIGHT);<br />
            browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(browser_DocumentCompleted);<br />
<br />
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create((string)url);<br />
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();<br />
<br />
            using (StreamReader sr = new StreamReader(response.GetResponseStream()))<br />
            {<br />
                string content = sr.ReadToEnd();<br />
                content = Regex.Replace(content, @"(<head>)", string.Format(@"$1<base href="" {0}="">", url));<br />
                content = Regex.Replace(content, @"<\s*(script|object|embed|noscript)[>]*>(.*?)<\s*/$1\s*>", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);<br />
                browser.DocumentText = content;<br />
            }            <br />
<br />
            while (bmp == null)<br />
            {<br />
                Thread.Sleep(1000);<br />
                Application.DoEvents();<br />
                TimeSpan elapsed = DateTime.Now.Subtract(started);<br />
                if (elapsed.TotalMilliseconds > TIMEOUT)<br />
                {<br />
                    browser.Dispose();<br />
                    mre.Set();<br />
                    break;<br />
                }<br />
            }<br />
        }<br />
<br />
        private void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)<br />
        {<br />
            WebBrowser browser = (WebBrowser)sender;<br />
            browser.Document.Window.Error += new HtmlElementErrorEventHandler(Window_Error);<br />
<br />
            bmp = new Bitmap(browser.Width, browser.Height);<br />
            browser.DrawToBitmap(bmp, browser.Bounds);<br />
            browser.Dispose();<br />
            mre.Set();<br />
        }<br />
<br />
        void Window_Error(object sender, HtmlElementErrorEventArgs e)<br />
        {<br />
            e.Handled = true;<br />
        }<br />
    }<br />
}<br />

Ask not what you can do with your programming language; ask what your programming language can do for you.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.180221.1 | Last Updated 5 Jul 2008
Article Copyright 2006 by AdamNajmanowicz
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid