We're building a brand new version of the site, and we'd love to hear your ideas
Members
Technology Zones
IBM Learning Center
Articles
Hosted By
Info
|
[4712] Generate an Image of a Web Page
Last post 07-11-2008 4:19 PM by gregcost. 28 replies.
-
01-01-1999 12:00 AM
|
|
Advertisement
|
|
-
-
-
lferro


- Joined on 11-30-2005

- Points 15
|
This is the code i'm using for the test (in a standard form/windows desktop project):
Imports System.Runtime.InteropServices
Imports mshtml
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System
<Guid("3050f669-98b5-11cf-bb82-00aa00bdce0b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), ComVisible(True), ComImport()> _
Interface IHTMLElementRender
Sub DrawToDC(<[In]()> _
ByVal hDC As IntPtr)
Sub SetDocumentPrinter(<[In](), MarshalAs(UnmanagedType.BStr)> _
ByVal bstrPrinterName As String, <[In]()> _
ByVal hDC As IntPtr)
End Interface
Public Class Form1
Inherits System.Windows.Forms.Form
Private Class GDI32
Public Const SRCCOPY As Integer = 13369376
<DllImport("gdi32.dll")> _
Public Shared Function BitBlt(ByVal hObject As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hObjectSource As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As Integer) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function CreateCompatibleBitmap(ByVal hDC As IntPtr, ByVal nWidth As Integer, ByVal nHeight As Integer) As IntPtr
End Function
<DllImport("gdi32.dll")> _
Public Shared Function CreateCompatibleDC(ByVal hDC As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll")> _
Public Shared Function DeleteDC(ByVal hDC As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function SelectObject(ByVal hDC As IntPtr, ByVal hObject As IntPtr) As IntPtr
End Function
End Class
#Region " Windows Form Designer generated code "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
'Add any initialization after the InitializeComponent() call
End Sub
'Form overrides dispose to clean up the component list.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
Friend WithEvents PictureBox1 As System.Windows.Forms.PictureBox
Friend WithEvents AxWebBrowser1 As AxSHDocVw.AxWebBrowser
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(Form1))
Me.PictureBox1 = New System.Windows.Forms.PictureBox
Me.AxWebBrowser1 = New AxSHDocVw.AxWebBrowser
CType(Me.AxWebBrowser1, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
'
'PictureBox1
'
Me.PictureBox1.Location = New System.Drawing.Point(8, 8)
Me.PictureBox1.Name = "PictureBox1"
Me.PictureBox1.Size = New System.Drawing.Size(488, 312)
Me.PictureBox1.TabIndex = 1
Me.PictureBox1.TabStop = False
'
'AxWebBrowser1
'
Me.AxWebBrowser1.Enabled = True
Me.AxWebBrowser1.Location = New System.Drawing.Point(8, 336)
Me.AxWebBrowser1.OcxState = CType(resources.GetObject("AxWebBrowser1.OcxState"), System.Windows.Forms.AxHost.State)
Me.AxWebBrowser1.Size = New System.Drawing.Size(488, 208)
Me.AxWebBrowser1.TabIndex = 2
'
'Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(504, 549)
Me.Controls.Add(Me.AxWebBrowser1)
Me.Controls.Add(Me.PictureBox1)
Me.Name = "Form1"
Me.Text = "Form1"
CType(Me.AxWebBrowser1, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)
End Sub
#End Region
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AxWebBrowser1.Navigate("http://www.teladigital.pt")
End Sub
Private Sub AxWebBrowser1_DocumentComplete(ByVal sender As System.Object, ByVal e As AxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent) Handles AxWebBrowser1.DocumentComplete
Dim document As IHTMLDocument2 = CType(Me.AxWebBrowser1.Document, IHTMLDocument2)
Dim graphics1 As Graphics = Me.PictureBox1.CreateGraphics
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
Try
Dim hdcDestination As IntPtr = graphics1.GetHdc
render.DrawToD
|
|
-
-
ppstay


- Joined on 06-23-2005

- Points 10
|
I got a casting exception
Code:
render = (IHTMLElementRender)element;
this statement throw an exception: Specified cast is not valid.
How it happen? Did you met this problem?
|
|
-
-
-
jgroleau


- Joined on 03-28-2006
- United States

- Points 5
|
Re: [4712] Generate an Image of a Web Page
Has anyone been able to get the code in this article to work in VB Express. I have tried the following VB code to no avail. I think I have followed the article code pretty close. I have added references to msHTML,GDI32, and ax shdocVw so I do not think that is the problem. The render.DrawTo DC() does not seem to work. I think it may have something to do with the way I have set up the IHTMLElementRender interface. I may be missing a subtle step in setting up the interface. I have tried it both inside and outside the form1 class but the results do not change. The picturebox does not change at all. The code not shown navigates to a webpage and waits for completion, then a call to mapcopy() to generate an image of the HTML in a picturebox, and then a call to me.Invalidate() to cause the picturebox to to be refreshed. Can anyone point me in the right direction or tell me a better way to accomplish the same thing I would appreciate it.
Option Explicit On Imports System.Runtime.InteropServices Imports mshtml Imports System.Xml Imports System.IO Imports System.Net Imports System Imports System.Text Imports System.Text.RegularExpressions Imports System.Threading
Public Class Form1 <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
Private Sub mapcopy() 'This sub copies a web page as image to picturebox as bitmap. It uses msHTML.dll, GDI32.dll, and axSHDocVw.dll
Dim document As IHTMLDocument2 = CType(Me.AxWebBrowser1.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 Drawing.Graphics = Me.PictureBox2.CreateGraphics Try Dim PointerToGraphics As IntPtr = graphics.GetHdc 'Set the graphics pointer render.DrawToDC(PointerToGraphics) 'draw to the picturebox2 Dim PointerToDC As IntPtr = gdi32.CreateCompatibleDC(PointerToGraphics) 'Set the DC pointer Dim PointerToBitmap As IntPtr = gdi32.CreateCompatibleBitmap(PointerToGraphics, Me.AxWebBrowser1.ClientRectangle.Width, Me.WebBrowser1.ClientRectangle.Height) 'Set the
bitmap pointer
If Not (PointerToBitmap = IntPtr.Zero) Then Dim hOld As IntPtr = CType(gdi32.SelectObject(PointerToDC, PointerToBitmap), IntPtr)
gdi32.BitBlt(PointerToDC, 0, 0, Me.AxWebBrowser1.ClientRectangle.Width, Me.AxWebBrowser1.ClientRectangle.Height, PointerToGraphics, 0, 0,
CType(gdi32.TernaryRasterOperations.SRCCOPY, Integer)) gdi32.SelectObject(PointerToDC, hOld) gdi32.DeleteDC(PointerToDC) graphics.ReleaseHdc(PointerToGraphics) Me.PictureBox2.Image = Drawing.Image.FromHbitmap(PointerToBitmap) 'Copy Bitmap to Picturebox2 Me.PictureBox2.Image.Save("c:\temp\map.bmp") End If Finally CType(graphics, IDisposable).Dispose() End Try End If End If End If End Sub
End Class
Public Class gdi32 'This class is used by Sub Mapcopy 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 'This Enum is used by Mapcopy sub 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
|
|
-
-
Arthurs


- Joined on 09-18-2006

- Points 10
|
Re: [4712] Generate an Image of a Web Page
I was also doing some poking around with this, only using the wrapped web browser object that is now part of the .Net Form Controls that is part of .Net 2.0
What I had was essentially a standard .Net webform, with a WebBrowser control on it (called webBrowserNavigate)....
slight modification of the code leads to:
HtmlDocument document = this.webBrowserNavigate.Document;
if (document != null)
{
HtmlElement element = document.Body;
if (element != null)
{
IHTMLElementRender render= (IHTMLElementRender)element.DomElement;
if (render != null)
{
using (Bitmap B = new Bitmap(
this.webBrowserNavigate.ClientRectangle.Width,
this.webBrowserNavigate.ClientRectangle.Height))
{
//Code as normal from here
}
}
}
}
|
|
-
-
azure_ss


- Joined on 10-24-2006

- Points 5
|
Re: [4712] Generate an Image of a Web Page
Arthur,
How can I add this webbrowsernavigate control to the toolbox? I can only find microsoft web browser under com componets and webbrowser under .net framework comopents(in System.windows.forms namespace). I am using visual studio 2005. thanks
|
|
-
-
-
alan.dean


- Joined on 04-28-2005

- Points 25
|
Re: [4712] Generate an Image of a Web Page
I have been moving my old entries across from dotnetjunkies to my new domain (this article on developfusion was culled from a blog entry). As part of that process, I have been reworking some of the content, where it was worth keeping. The "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
|
|
-
-
-
-
levaniko


- Joined on 07-03-2007
- Georgia

- Points 10
|
Re: [4712] Generate an Image of a Web Page
levaniko wrote:Here's my thoughts about modifications of code for achieving better results:
First possible solution: The main problem still exists. when you're trying to get content of rendered page, you use BitBlt function which clips content and if your source window is not shown on desktop then you will not get image. I'll explain how I think this problem should be solved.
Question: where BitBlt function gets information about clipping? Answer: from Graphics object, which contains property with name "Clip". I'm currently investigating how to set correct clipping...
Second possible solution: Solution goes around same function. In order to avoid clipping, we should avoid BitBlt and replace it with GetDIBBits or something like that. in this case I think we will get full image without clipping...
I'm currently working on implementing both of solutions but if anyone has solution already, please let me know...
Best Regards, Levan Jgharkava
Here's solution: Everything related to clipping comes from System.Drawing.Graphics object and we need to turn off clipping in Graphics object. To do this we should not create Graphics object from our picturebox and not from any window. I noticed Graphics object can be created from Image object which is base class for Bitmap object. So we can create Graphics object and write directly to Bitmap which has no clipping initially. in order to create bitmap, compatible to contents of webbrowser we can write code like this: bmp = new Bitmap(1, 1, webBrowser.CreateGraphics()); PixelFormat pfmt = bmp.PixelFormat; bmp = new Bitmap(webBrowser.ClientRectangle.Width, webBrowser.ClientRectangle.Height, pfmt); and code for creating Graphics object is following: Graphics graphics = Graphics.FromImage(bmp); So, after this, clipping issues are fixed and we have bitmap of size of webbrowser control window. But... here's another problem: DrawToDC method of IHTMLElementRender interface is not rendering correctly all pages, especially with dynamic content and styling. for example, if we try to render http://www microsoft.com then it gives just some gradient on top and empty space after that. YouTube.com is not rendered at all! http://video.google.com has problems with rendering some background spaces e.t.c... so next goal is to use something else than DrawToDC. I did some investigation on drawing related functions in MSHTML object and found some functions which I'm trying right now. Any help will be appreciated... Best Regards, Levan Jgharkava
|
|
-
-
smarc


- Joined on 01-25-2008
- Estonia

- Points 30
|
Re: [4712] Generate an Image of a Web Page
alan.dean:
WebBrowser component itself doesn't have DrawToBitmap method, but it is derived from base-class Control that has this method - witch allows to cast the object into Control and use the method on that:
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { WebBrowser wbCurrent = (WebBrowser)sender; wbCurrent.Width = wbCurrent.Document.Body.ScrollRectangle.Width; wbCurrent.Height = wbCurrent.Document.Body.ScrollRectangle.Height; Bitmap myBitmap = new Bitmap(wbCurrent.Width, wbCurrent.Height); ((Control)(wbCurrent)).DrawToBitmap(myBitmap, new Rectangle(0, 0, wbCurrent.Width, wbCurrent.Height)); MemoryStream rawImage = new MemoryStream(); myBitmap.Save(rawImage, ImageFormat.Png); pbViewer.Image = Image.FromStream(rawImage); //put image to PictureBox in the form } Not sure if this solution raises some additional problems or not, but it seemed to work quite fine. I also set WebBrowser initial Height and Width to 1 - to allow content to be exactly as large it must be. However, I have another question regarding it - I needed to use this solution (or ability to create images of specified web-pages) in web-server environment. The problem is however, that WebBrowser control can only work in Single-threaded apartment, whereas aspx calls run in Multi-threaded apartment - I managed to bypass this by encapsulating the task into dll where I create separate thread and set it's apartment-state to Single-threaded apartment. The solution actually works, but it has some undetectable bugs - like crashing the IIS application-thread from time-to-time (and IIS has default setting of disabling the application in an event of specified number of fatal crashes), and loosing the ability to navigate to given address after a while. For example after some amount of hits to one address - probably from aspx files running on different application-pool threads - this address becomes unreachable. The application-pool recycling resolves that problem, but it doesn't explain why this is happening in the first place. Also, the overall memory usage of IIS rises drastically over time and as more calls are coming against the component, the slower it gets. What I want to find out, is, if these problems are caused by the usage of WebBrowser control itself (that shouldn't work in MTA anyway) or because of the way I get the image (casting to Control) or because of the way I bypass the STA limitation (creating new STA thread from MTA thread)?
|
|
-
-
justncase80


- Joined on 03-11-2008
- United States

- Points 25
|
Re: [4712] Generate an Image of a Web Page
Well if the code you have above is exactly what you have in your application then you probably need to dispose of some resources! Which is easy and will probably help a lot try this: using(Bitmap myBitmap = new Bitmap(wbCurrent.Width, wbCurrent.Height) { //... using(MemoryStream rawImage = new MemoryStream()) { //.... } } The question is how are you creating your WebBrowser and calling navigate? WHen I do it I get no exceptions, no errors and no calls to my event handlers. I will actually get Navigating to fire but never DocumentCompelted or ProgressChanged or anything else. Any hints?
|
|
|
Search
Code Samples
New Members
|