Tuesday, August 22, 2006

Create a Thumbnail Image of web page

Tuesday, August 22, 2006
How do I create cached image of a webpage. I just wanted to create a thumbnail of a webpage based on URL. There is no way, you would find the right stuff to do so. I found Alan Dean's page on Generate an image of a web page. He has explained the technique very well. Described his search for such code. He wrote on his own. There are a very few people got it working. So here is the code, took me a lot of time to get it working. Read through the sample I added here very carefully before you copy the code as is. I tried my best to write all comments to make it easy readable.
Tried it in all possible ways to make it successful the way I wanted. This code works awesome but I wanted to use System.drawing very well to make it work. There is no way I could find to do this without using Win32 Api. Apart from that, I need to add the axWebBrowser control and it needs to be visible on form in order to find the image of the web page. Then how do I do it on the fly on my website. There must be a way to create a thumbnail from webpage with out adding ActiveX controls....
Good Luck!! Let me know if this works for you.

'*************************************************************************
'This is main form Class where you will add the basic functions.
'*************************************************************************


Imports
SHDocVw ' Import the shdocvw.dll from Widnows\System32 folder.

Imports mshtml  ' Import the mshtml.dll from Widnows\System32 folder.

Imports System.Runtime.InteropServices ' Need this as we added above two as Interop


Imports
System.Windows.Forms


Public
Class Form1


   
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


        'Add the axWebBrowser control from SHDocVw library to your form name it as axWebBrowser
        'Add the Event Handler as DocumentComplete, because we need to capture screen image when document is completely loaded.
        AddHandler axWebBrowser.DocumentComplete, AddressOf Me.OnDocumentComplete

        End Sub

      Private Sub OnDocumentComplete(ByVal sender As Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)

 
       
Dim document As IHTMLDocument2 = CType(Me.axWebBrowser.Document, IHTMLDocument2)

        If Not (document Is Nothing) Then

            Dim element As IHTMLElement = CType(document.body, IHTMLElement)

            If Not (element Is Nothing) Then

                Dim render As IHTMLElementRender = CType(element, IHTMLElementRender)

                If Not (render Is Nothing) Then

                    ' Using

                    Dim graphics As Graphics = Me.pictureBox.CreateGraphics

                    Try

                        Dim hdcDestination As IntPtr = graphics.GetHdc

                        render.DrawToDC(hdcDestination)

                        Dim hdcMemory As IntPtr = gdi32.CreateCompatibleDC(hdcDestination)

                        Dim bitmap As IntPtr = gdi32.CreateCompatibleBitmap(hdcDestination, Me.axWebBrowser.ClientRectangle.Width, Me.axWebBrowser.ClientRectangle.Height)

                        If Not (bitmap = IntPtr.Zero) Then

                            Dim hOld As IntPtr = CType(gdi32.SelectObject(hdcMemory, bitmap), IntPtr)

                            gdi32.BitBlt(hdcMemory, 0, 0, Me.axWebBrowser.ClientRectangle.Width, Me.axWebBrowser.ClientRectangle.Height, hdcDestination, 0, 0, CType(gdi32.TernaryRasterOperations.SRCCOPY, Integer))

                            gdi32.SelectObject(hdcMemory, hOld)

                            gdi32.DeleteDC(hdcMemory)

                            graphics.ReleaseHdc(hdcDestination)

                            'Add a PictureBox control to your form, named pictureBox. This way you can 
                            'see the image immediately after it is generated. You can save to FS using Bitmap.Save method.

                            Me.pictureBox.Image = Image.FromHbitmap(bitmap)


                       
End If


                   
Finally

                        'You must dispose Graphics Object for better use.

                        CType(graphics, IDisposable).Dispose()

                    End Try

                End If

            End If

        End If

    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        ' Add a button to you form named Button1. 
        ' This way we can control when we want to start Browsing.
        ' Select the URI to be browsed here.

        Me.axWebBrowser.Navigate(New Uri("http://www.khapre.org"))

 

    End Sub

End Class

'*************************************************************************
'This class for dummy for calling GDI Functions from Win32 Api.
'So that you can encapsulate whole GDI work outside. 
'*************************************************************************

Public Class gdi32

    ' I copied all the signatures in this class from http://www.pinvoke.net 

 

    Public Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As IntPtr) As Boolean

    Public Declare Function SelectObject Lib "gdi32.dll" (ByVal hdc As IntPtr, ByVal hgdiobj As IntPtr) As IntPtr

    Public Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hDC As IntPtr) As IntPtr

    Public Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hDC As IntPtr, ByVal nWidth As Integer, ByVal nHeight As Integer) As IntPtr

    Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As IntPtr, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hSrcDC As IntPtr, ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal dwRop As Integer) As Integer

    Enum TernaryRasterOperations As Integer

 

        SRCCOPY = 13369376      'dest = source

        SRCPAINT = 15597702     'dest = source OR dest

        SRCAND = 8913094        'dest = source AND dest

        SRCINVERT = 6684742     'dest = source XOR dest

        SRCERASE = 4457256      'dest = source AND (NOT dest )

        NOTSRCCOPY = 3342344    'dest = (NOT source)

        NOTSRCERASE = 1114278   'dest = (NOT src) AND (NOT dest)

        MERGECOPY = 12583114    'dest = (source AND pattern)

        MERGEPAINT = 12255782   'dest = (NOT source) OR dest

        PATCOPY = 15728673      'dest = pattern

        PATPAINT = 16452105     'dest = DPSnoo

        PATINVERT = 5898313     'dest = pattern XOR dest

        DSTINVERT = 5570569     'dest = (NOT dest)

        BLACKNESS = 66          'dest = BLACK

        WHITENESS = 16711778    'dest = WHITE

    End Enum

End Class


'Check out Alan Dean's explaination, because I don;t get it. He is smarter than me, so I just believed that it works.
<Guid("3050f669-98b5-11cf-bb82-00aa00bdce0b"), System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown), System.Runtime.InteropServices.ComVisible(True), System.Runtime.InteropServices.ComImport()> _

Interface IHTMLElementRender

    Sub DrawToDC(<System.Runtime.InteropServices.In()> ByVal hDC As IntPtr)

    Sub SetDocumentPrinter(<System.Runtime.InteropServices.In(), System.Runtime.InteropServices.MarshalAs(UnmanagedType.BStr)> ByVal bstrPrinterName As String, <System.Runtime.InteropServices.In()> ByVal hDC As IntPtr)

End Interface

 

 

 


2 comments:

Alan Dean said...

I have been moving my old entries across from dotnetjunkies to my new domain.

As part of that process, I have been reworking some of the content, where it was worth keeping. The "[How To] Generate an image of a web page" has been extensively rewritten and I have also made a Visual Studio 2005 solution available for download as well.

See http://thoughtpad.net/alan-dean/web-page-image-thumbnail.html

Thanks for the credit given :-)

Megha said...

Hi Vishal,

I want to create a windows service (batch process) which will take the newly added URLs from database and create thumbnails for the URLs. How can I change your code to make a Window's sevice.
Second challege is, if there is popup or confirmation box at the time of navigating to the URL using WebBrowser control, then OnDocumentCompleted event will not be complete and code will get hang at that point. Any solution for this problem?
-Megha