Community discussion forum
Generate an Image of a Web Page
-
This thread is for discussions of Generate an Image of a Web Page.
-
Advertisement
Simply the fastest line-level profiler for .NET ever
“The low overhead means it has minimal impact on the execution of my program”
Mark Everest, Development Team Leader, Renault F1 Team Ltd.Try out the new ANTS Profiler 4 for yourself. Download your 14-day trial now
-
- When i tryed this, i got the following results (after some struglings with interops and the like):
a) With the web browser in the viewable part of the form i get the image of the site with scrollbars
b) With the web browser outside of the viewable part of the form i get an empty image
2. The image copyed is limited to the size of the web browser in the first place;
3. What i tryed to archieve would be to point to a site, grab it's content, render it in a "out-of-screen" object and copy it's "image" to a graphic, at the size that the content requires... Is there a way of doing it? - When i tryed this, i got the following results (after some struglings with interops and the like):
-
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 ClassRegion " 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 SubEnd 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 AxWebBrowser1DocumentComplete(ByVal sender As System.Object, ByVal e As AxSHDocVw.DWebBrowserEvents2DocumentCompleteEvent) 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 -
Code:
render = (IHTMLElementRender)element;
this statement throw an exception: Specified cast is not valid.
How it happen? Did you met this problem? -
My code is written by C# but it's exactly as yours. I got the same result as you described. Have you resolved this problem? I am really want to know. Thanks very much.
-
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.ThreadingPublic 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 IHTMLElementRenderSub 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 InterfacePrivate Sub mapcopy()
'This sub copies a web page as image to picturebox as bitmap. It uses msHTML.dll, GDI32.dll, and axSHDocVw.dllDim 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 thebitmap 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 SubEnd 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 -
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 } } } } -
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
-
Update if you use a .NET 2.0 WebBrowser component instead of activex.
.NET 2.0 has a WebBrowser component that is a wrapper around the ActiveX control. It has the advantage of a DrawToBitmap method. Below is an event handler that for the Webbrowser.DocumentComplete event.
private void OnDocumentComplete(object sender, EventArgs e)
{
WebBrowser browser = (WebBrowser) sender;
browser.DrawToBitmap
(((Bitmap)pictureBox1.Image),
new Rectangle(0,0, pictureBox1.Width, pictureBox1.Height));
pictureBox1.Refresh();
/* http://www.developerfusion.co.uk/show/4712/ */
} -
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 -
zippy1981 wrote the following post at 03-07-2007 7:23 PM:
".NET 2.0 has a WebBrowser component that is a wrapper around the ActiveX control. It has the advantage of a DrawToBitmap method."
If only our life was that simple! Sadly, this is a bum steer.
See the MSDN docs:
http://msdn2.microsoft.com/en-us/library/system.windows.forms.webbrowser_members.aspx
they clearly state that "This method is not supported by this control." (I suspect that it was supported during beta, but then dropped)
However, I have good news ... see my previous forum post: http://www.developerfusion.co.uk/forums/thread/152827/#152827
-
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 -
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
-
[quote user="alan.dean"]
zippy1981 wrote the following post at 03-07-2007 7:23 PM:
".NET 2.0 has a WebBrowser component that is a wrapper around the ActiveX control. It has the advantage of a DrawToBitmap method."
If only our life was that simple! Sadly, this is a bum steer.
See the MSDN docs:
http://msdn2.microsoft.com/en-us/library/system.windows.forms.webbrowser_members.aspx
they clearly state that "This method is not supported by this control." (I suspect that it was supported during beta, but then dropped)
However, I have good news ... see my previous forum post: http://www.developerfusion.co.uk/forums/thread/152827/#152827
[/quote]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)?
-
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?
-
Actually I have tried to specifically dispose all objects where and whenever possible (even though they are destroyed anyway as soon as the scope they are in is completed) but it makes no difference. The WebBrowser object is created within code and it works fine for me:
WebBrowser x = new WebBrowser();
x.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser_DocumentCompleted);
x.Navigate("http://www.somesite.com");Anyways, I have concluded my quest for solution with the conclusion that multiple WebBrowser objects running simultaneously simply create some problems in the underlying framework (IE) that, and at some point (too many simultaneous WebBrowser objects/requests as it's possible in web-server environment), can result in unexpected results. And, that this issue can't be solved from within the calling code.
-
I managed to get the WebBrowser to work off screen too, I'm not sure what fixed it though. I totally buy that multiple WebBrowser controls will cause problems too, makes sense.
However, calling Dispose() explicitly is actually very important. Simply going out of scope isn't good enough, calling dispose will clear up any unmanaged resources and simply going out of scope will cause the GC to only clean up managed resources. It is VERY important to always call dispose on an object implementing IDisposable. Wrap it in a using(IDisposable d = ...) { ... } to make your life easier (so dispose will still get called if there is an exception).
-
As I said, I tried disposing all possible objects - but with no difference to the outcome. Disposing gets rid of the no-longer-used objects, but in my case the problem lies in objects working at the same time in different threads - that somehow results with a conflict in the underlying framework level (odd as it may be).
-
That's unfortunate but doesn't surprise me. I suppose the logical conclusion would be to have a single WebBrowser control that does all of the rendering then queue the requests and asynchronously render the images and pass them back to the requestors. A lot more work but it's worth it if it fixes bugs that take down the system.
-
Have used this Code for a long time with multiple weBrowser controls without problems but recently I've noticed that FLASH, SIlverlight and other stuff are not being thumbnailed. While most of the standard HTML shows up the newer stuff shows as black spots in the case of FLASH and Styler-sheets with xml, Sliverlight just ends up showing the background color. Standard HTML works fine!
Does the DOM/IHTMLELEMENT not carry these nodes or items? Are they carried somewhere else?
ANY HELP GREATLY APPRECIATED!MY VERSION of CODE BELOW VS2005/Net 2.0 - VB.net
Public Enum TernaryRasterOperations
SRCCOPY = &HCC0020 ' dest = source
SRCPAINT = &HEE0086 ' dest = source OR dest
SRCAND = &H8800C6 ' dest = source AND dest
SRCINVERT = &H660046 ' dest = source XOR dest
SRCERASE = &H440328 ' dest = source AND (NOT dest )
NOTSRCCOPY = &H330008 ' dest = (NOT source)
NOTSRCERASE = &H1100A6 ' dest = (NOT src) AND (NOT dest)
MERGECOPY = &HC000CA ' dest = (source AND pattern)
MERGEPAINT = &HBB0226 ' dest = (NOT source) OR dest
PATCOPY = &HF00021 ' dest = pattern
PATPAINT = &HFB0A09 ' dest = DPSnoo
PATINVERT = &H5A0049 ' dest = pattern XOR dest
DSTINVERT = &H550009 ' dest = (NOT dest)
BLACKNESS = &H42 ' dest = BLACK
WHITENESS = &HFF0062 ' dest = WHITE
End Enum<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 InterfacePublic Sub Thumb()
'Console.WriteLine("Public Class BrowserThumbnail-Public Sub Thumb|E-- ")
Try
Dim document As mshtml.IHTMLDocument2 = DirectCast(Myform2.Ax15Form2Browser.Document.DomDocument, mshtml.IHTMLDocument2)
Dim element As mshtml.IHTMLElement2 = DirectCast(document.body, mshtml.IHTMLElement2)
Dim render As IHTMLElementRender = DirectCast(element, IHTMLElementRender)
Dim GraphicsObject As Graphics = Myform2.thumbScr15.CreateGraphics
Dim PointerToGraphics As IntPtr = GraphicsObject.GetHdc
Dim PointerToDC As IntPtr = CreateCompatibleDC(PointerToDC)
Dim PointerToBitmap As IntPtr = CreateCompatibleBitmap(PointerToGraphics, ThumbCopyFromWT, ThumbCopyFromHT)
SelectObject(PointerToDC, PointerToBitmap)
render.DrawToDC(PointerToDC)
GraphicsObject.ReleaseHdc(PointerToGraphics)
Dim dummyCallBack As System.Drawing.Image.GetThumbnailImageAbort
dummyCallBack = New System.Drawing.Image.GetThumbnailImageAbort(AddressOf ThumbnailCallback)
Dim fullSizeImg As System.Drawing.Image
fullSizeImg = System.Drawing.Image.FromHbitmap(PointerToBitmap)
Dim URLfullSizeImg As System.Drawing.Image
URLfullSizeImg = System.Drawing.Image.FromHbitmap(PointerToBitmap)
Myform2.thumbScr15.Image = fullSizeImg.GetThumbnailImage(ThumbWidth, ThumbSizeHT, dummyCallBack, IntPtr.Zero)
URLHistThumbImage15 = URLfullSizeImg.GetThumbnailImage(URLHistImageWD, URLHistImageHT, dummyCallBack, IntPtr.Zero)
'Cleanup
CType(GraphicsObject, IDisposable).Dispose()
DeleteDC(PointerToDC)
DeleteObject(PointerToBitmap)
T15Done = True
Catch ex As System.NullReferenceException
Console.WriteLine("--- NullReferenceException ---" & " " & _
vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
' Code reacting to NullReferenceException
Catch ex As Exception
' Code reacting to any exception
Console.WriteLine(vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
End Try -
Greg,
Unfortunately, the code is unable to capture Flash or Silverlight and I am not aware of any way around this.
Sorry, Alan
-
Hi Alan,
I’ve looked around a bit and no one has a solution. I certainly can do a screen capture and thumbnail it that way; unfortunately this is a display and capture mechanism and I lose the benefit of thumb-nailing multiple browsers under the cover.
Do you think it’s possible (under the cover) to navigate to a site, then somehow device context it upon browser completion and thumbnail it from a DC?
Well… maybe not. If I find a way to do this I will certainly post it here.
Thanks Alan,
Greg
-
Hi Alan,
Found a timing bug in my project (browser completion was occurring before FLASH was completed) so FLASH now does work, but SilverLight and xsl/parser/xml HTML generated images do not.
It was suggested via MSDN that IHTMLElementRender has problems (misses’ stuff) that could be resolved by using IViewObject’s Draw Function. What I need is a VB.net example of the parameters required by IViewObject:Draw to draw [BELOW--- (DIM) Drawrender to a DC (device Context)]. I been searching all over to find an example but have had no luck… Do you have a VB.net example?
Thanks Alan, Greg
Imports Microsoft.VisualStudio.OLE.Interop
<Guid("0000010D-0000-0000-C000-000000000046")> <InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown), ComVisible(True), ComImport()> _
Interface IViewObjectSub Draw(ByVal dwDrawAspect As UInteger, _
ByVal lindex As Integer, _
ByVal pvAspect As UInteger, _
ByVal ptd() As Microsoft.VisualStudio.OLE.Interop.DVTARGETDEVICE, _
ByVal hdcTargetDev As UInteger, ByVal hdcDraw As UInteger, _
ByVal lprcBounds() As Microsoft.VisualStudio.OLE.Interop.RECTL, _
ByVal lprcWBounds() As Microsoft.VisualStudio.OLE.Interop.RECTL, _
ByVal pContinue As Microsoft.VisualStudio.OLE.Interop.IContinue)End Interface
Try
Dim document As mshtml.IHTMLDocument2 = DirectCast(Me.WebBrowser1.Document.DomDocument, mshtml.IHTMLDocument2)
Dim element As mshtml.IHTMLElement2 = DirectCast(document.body, mshtml.IHTMLElement2)'Dim render As IHTMLElementRender = DirectCast(element, IHTMLElementRender)
Dim Drawrender As IViewObject = DirectCast(element, IViewObject)Dim GraphicsObject As Graphics = Me.PictureBox1.CreateGraphics
Dim PointerToGraphics As IntPtr = GraphicsObject.GetHdc
Dim PointerToDC As IntPtr = CreateCompatibleDC(IntPtr.Zero)
Dim PointerToBitmap As IntPtr = CreateCompatibleBitmap(PointerToGraphics, Me.WebBrowser1.Width, Me.WebBrowser1.Height)SelectObject(PointerToDC, PointerToBitmap)
DrawRender.Draw(?????...
'render.DrawToDC(PointerToDC) -
I'd love to know the solution, as I'm in the same boat. So if you do figure it out, please post back here to this thread!
The code I came up with is below. However, it faults inside of the Draw call. I'm really not sure if passing an "array of one RECTL" is the right way to handle this interface, which wants a pointer to a RECTL when you're writing it natively.
public Image GetHtmlThumbnail(Size size){
Image imgThumb = new Bitmap(size.Width, size.Height, PixelFormat.Format24bppRgb); Rectangle rectDest = new Rectangle(new Point(0, 0), size);_browser.Navigate("about:blank;"); while (_browser.ReadyState != WebBrowserReadyState.Complete)Application.DoEvents();_browser.DocumentText = aobjData[
DataFormats.Html] as string;Size sizeDocument = Utilities.GetHTMLDocumentSize(_browser.Document); IViewObjectEx viewObjectEx = _browser.Document.DomDocument as IViewObjectEx;IOleObject oleObject = viewObjectEx as IOleObject; double xScale = (double)size.Width / (double)sizeDocument.Width;int iPadding = (int)(xScale * System.Windows.Forms.SystemInformation.VerticalScrollBarWidth); SIZEL sizel;sizel.cx = Utilities.PixelsToHIMETRICX(sizeDocument.Width);sizel.cy = Utilities.PixelsToHIMETRICY(sizeDocument.Height);
// Save away the old size so we can restore it SIZEL [] sizelOld = new SIZEL[1];oleObject.GetExtent(1, sizelOld);
// DVASPECT_CONTENT == 1
using (Graphics g = Graphics.FromImage(imgThumb)){
RECTL [] rectl = new RECTL[1];rectl[0].top = 0;
rectl[0].left = 0;
rectl[0].right = size.Width + iPadding;
// Extra padding so scrollbar renders outside thumbnailrectl[0].bottom = size.Height;
IntPtr hdc = g.GetHdc();viewObjectEx.Draw(1, -1, 0, null, 0, (uint) hdc, rectl, null, null);g.ReleaseHdc(hdc);
}
oleObject.SetExtent(1, sizelOld);
return null;}
-
Hi Dave I’m working on it.
I have a variation of the code that gets the WebBrowser control from its rendered address passed to the context of the rendered desktop window. This means It works only if the WebBrowser control is displayed on the desktop in a form (Code Below) and it does not matter where the webBrowser control is on the desktop or what is in it XSL/XML, Silverlight whatever. I am trying to figure out how the rendered desktop window gets passed the rendered HTML.
From code below:
BitBlt(AddrCreateCompatibleDC, 0, 0, WinWidth, WinHeight, GetWindowDC(Me.WebBrowser1.Handle), 0, 0, SRCCOPY)StretchBlt(AddrOfPB1, 0, 0, 100, 100, AddrCreateCompatibleDC, 0, 0, Me.WebBrowser1.Width, Me.WebBrowser1.Height, TernaryRasterOperations.SRCCOPY)
Of course this is not what I want and for whatever reason I cannot figure out where the rendered HTML is being stored. So now I am using WinDBG to go through the assembly to find out what address (buffer) all HTML for display are being rendered to before being posted to the screens context! Once I figure this out I’ll write a callable assembler or C++ routine from any .Net language.
I tried to go another route to see what the value of Webbrowse1.handle would be outside of NET so I Marshaled it from the outside back to NET – got the same result. I’ve included the code if you want to play around with it. To use the mshtml._RemotableHandle, a BitBlt call which I have commented out getRHandle(Me.WebBrowser1.Handle) You’ll need the following sub to grap it from outside of NET
-------
Public remotableHandle As mshtml._RemotableHandle'This essentially stores the IntPtr into unmanaged memory and then marshalls it again into a new structure, in this case a _RemotableHandle. Can be used as follows:
Public Function getRHandle(ByVal windowHandle As IntPtr)
'Allocate unmanaged memory to store this IntPtr0
Dim address As IntPtr = Marshal.AllocHGlobal(IntPtr.Size)'Write the IntPtr into unmanaged memory
Marshal.WriteIntPtr(address, windowHandle)'Return the RemotableHandle by marshalling it from unmanaged memory
Return Marshal.PtrToStructure(address, GetType(mshtml._RemotableHandle))
End Function--------------
Private Structure RECT
Dim Left As Integer
Dim Top As Integer
Dim Right As Integer
Dim Bottom As Integer
End StructureTry
'Handle for the desktop window
Dim AddrDesktopWindow As IntPtr
If AddrDesktopWindow = 0 Then AddrDesktopWindow = GetDesktopWindow'getWindow Size
Dim rcWindow As RECT
GetWindowRect(AddrDesktopWindow, rcWindow)
Dim WinHeight As Integer = rcWindow.Right - rcWindow.Left
Dim WinWidth As Integer = rcWindow.Bottom - rcWindow.TopDim PB1 As Graphics = Me.PictureBox1.CreateGraphics
Dim AddrOfPB1 As IntPtr = PB1.GetHdcDim AddrOfPictureBox1 As IntPtr = Me.PictureBox1.Handle
'create a compatible DC
Dim AddrCreateCompatibleDC As IntPtr = CreateCompatibleDC(IntPtr.Zero)'create a memory bitmap in the DC just created, the size of the window we're capturing
Dim AddrCreateCompatibleBitmap As IntPtr = CreateCompatibleBitmap(AddrOfPB1, WinWidth, WinHeight)'Prepare DC as a Bitmap using selectObject to configure the DC
SelectObject(AddrCreateCompatibleDC, AddrCreateCompatibleBitmap)'copy the screen image into the DC
'BitBlt(AddrCreateCompatibleDC, 0, 0, WinWidth, WinHeight, GetWindowDC(AddrDesktopWindow), 0, 0, SRCCOPY)
BitBlt(AddrCreateCompatibleDC, 0, 0, WinWidth, WinHeight, GetWindowDC(Me.WebBrowser1.Handle), 0, 0, SRCCOPY)
'BitBlt(AddrCreateCompatibleDC, 0, 0, WinWidth, WinHeight, getRHandle(Me.WebBrowser1.Handle), 0, 0, SRCCOPY)
'Clone image of PictureBox1 to Picturebox2 when the IMAGE is in the Device Context
'#### BitBlt(AddrOfPB1, 0, 0, WinWidth, WinHeight, AddrCreateCompatibleDC, 0, 0, SRCCOPY)
StretchBlt(AddrOfPB1, 0, 0, 100, 100, AddrCreateCompatibleDC, 0, 0, Me.WebBrowser1.Width, Me.WebBrowser1.Height, TernaryRasterOperations.SRCCOPY)PB1.ReleaseHdc(AddrOfPB1)
CType(PB1, IDisposable).Dispose()
DeleteDC(AddrCreateCompatibleDC)
DeleteObject(AddrCreateCompatibleBitmap)Catch ex As System.NullReferenceException
MsgBox("--- NullReferenceException ---" & " " & _
vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
' Code reacting to NullReferenceException
Catch ex As Exception
' Code reacting to any exception
MsgBox(vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
End Try
End Sub -
Thanks! For my appliction the web browser control needs to be offscreen (not on a form), so it won't work for me. I did find one thing interesting, though, which is the the interface definition for IViewObjectEx::Draw that ships in microsoft.visualstudio.ole.interop is wrong. It's missing the last parameter (the DWORD value passed to the continuation function) from the definition of Draw. So when you call it, its going to pop extra bytes off the stack before returning, and will crash. Which it did, so I had to create my own interface definition (with the same guid) and that got it working. That interface is below, in case it helps anyone.
I'm still having a problem that its not drawing anything into my DC, and that's where I'm at right now, debugging the Draw() implementation in mshtml to figure out why...
[
Guid("3AF24292-0C96-11CE-A0CF-00AA00600AB8")]
[ComConversionLoss]
[InterfaceType(1)]
public interface IViewObjectExDave
{
void Draw(uint dwDrawAspect, int lindex, uint pvAspect, DVTARGETDEVICE[] ptd, uint hdcTargetDev, uint hdcDraw, RECTL[] lprcBounds, RECTL[] lprcWBounds, IContinue pContinue, uint x);
// etc, rest of methods
}
-
OK, I FINALLY GOT IT TO WORK…..OFFSCREEN with SilverLight and XSL/XML EXCEPT SHOCKWAVE FLASH!At least I have a way to call Alan's routine when I detect a non-Flash HTML screen and now use IViewObject Draw when I have a Silverlight or xml/xsl css gened screen. AGAIN all this works off Screen!
Unfortunately as far as I can tell IViewObject is incompatible with trying to DirectCast it down to the element as in Alan's code so, while I now have a dual solution (with some upfront HTML parsing) I will keep looking into a single step solution.
After looking at WinDbg and screwing around with memory addresses. I finally got it to write off screen using IViewObject Draw function to a DC and then to a thumbnail. What a pain this was!
I had to use StretchBlt to grab it from the DC to shrink it down to a 100 by 100 thumbnail. I do not like the way StretchBlt renders it. The thumbnail is grainy and not as clear as Alan’s original FromBitmap code. BUT for NOW ENOUGH IS ENOUGH! I’ll screw with that later… ( I now have replace StretchBlt see my next reply)
The Following CODE will write the rendered HTML screen from its memory location (not the form on the Desktop) and post it to a DEVICE COINTEXT. It works WITH SILVERLIGHT and XSL/XML. From the DC I use StretchBlt (will change that) to shrink it to the Picturebox 100 by 100 thumb.
<ComImport(), _
InterfaceType(ComInterfaceType.InterfaceIsIUnknown), _
Guid("00000127-0000-0000-C000-000000000046")> _
Public Interface IViewObject
Function Draw(ByVal dwDrawAspect As Int32, _
ByVal lindex As Int32, _
ByVal pvAspect As IntPtr, _
ByVal ptd As IntPtr, _
ByVal hicTargetDev As Int32, _
ByVal hdcDraw As IntPtr, _
ByVal prcBounds As IntPtr, _
ByVal prcWBounds As RectangleF, _
ByVal fnContinue As IntPtr, _
ByVal dwContinue As Int32) As Int32
End InterfacePrivate Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Try
Dim ViewObject As IViewObject = DirectCast(Me.WebBrowser1.Document.DomDocument, IViewObject)
Dim PB1 As Graphics = Me.PictureBox1.CreateGraphics
Dim AddrOfPB1 As IntPtr = PB1.GetHdc'create a compatible DC
Dim AddrCreateCompatibleDC As IntPtr = CreateCompatibleDC(IntPtr.Zero)'create a memory bitmap in the DC just created, the size of the window we're capturing
Dim AddrCreateCompatibleBitmap As IntPtr = CreateCompatibleBitmap(AddrOfPB1, Me.WebBrowser1.Width, Me.WebBrowser1.Height)'Prepare DC as a Bitmap using selectObject to configure the DC
SelectObject(AddrCreateCompatibleDC, AddrCreateCompatibleBitmap)'Draw the Rendered DomDocument to the Device Context
ViewObject.Draw(1, 1, Nothing, Nothing, 0, AddrCreateCompatibleDC, Nothing, Nothing, Nothing, 0)'Use StretchBlt to shrink it down to the Theubnail
StretchBlt(AddrOfPB1, 0, 0, 100, 100, AddrCreateCompatibleDC, 0, 0, Me.WebBrowser1.Width, Me.WebBrowser1.Height, TernaryRasterOperations.SRCCOPY)
'Dim fullSizeImg As System.Drawing.Image
'fullSizeImg = System.Drawing.Image.FromHbitmap(AddrCreateCompatibleBitmap)PB1.ReleaseHdc(AddrOfPB1)
CType(PB1, IDisposable).Dispose()
DeleteDC(AddrCreateCompatibleDC)
DeleteObject(AddrCreateCompatibleBitmap)Catch ex As System.NullReferenceException
MsgBox("--- NullReferenceException ---" & " " & _
vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
' Code reacting to NullReferenceException
Catch ex As Exception
' Code reacting to any exception
MsgBox(vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
End Try
End Sub -
Ok just replace StretchBlt with. Not sure why this was not working before? other than I was up very late and just couldn't see straight..
Dim fullSizeImg As System.Drawing.ImagefullSizeImg = System.Drawing.Image.FromHbitmap(AddrCreateCompatibleBitmap)
Me.PictureBox1.Image = fullSizeImg.GetThumbnailImage(100, 100, Nothing, IntPtr.Zero) -
Ok, this is the final version – and final comments.
IViewObjectDraw is now my standard. When the HTML text is converted into a rendered image sitting in memory and available to be moved to the desktop form’s screen buffer for display, … for whatever reason IVewObject.Draw will do a better job of picking up all of the pieces at whatever addresses they are stored at giving me an off-screen image (using the screen’s device context) to thumbnail from.
I now use the original IHTMLElement.DrawToDc to pickup the only item (so far) the “Adobe Shockwave embedded player” the rest can be handled by IViewObject.Draw – flash, xsl/css, silverlight, etc. It’s now a simple matter of searching the HTML document text looking for Adobe Shockwave and calling the original IHTMLElement.DrawToDc.
It is kind of a pain to do it this way because I must pre-check for Shcokwave, but the overhead with some reduced instruction tricks is reasonable… Also IViewObject code seems faster
Greg
Public Sub IviewDrawStyleThumb1()
Try
Dim ViewObject As IViewObject = DirectCast(Myform2.Ax1Form2Browser.Document.DomDocument, IViewObject)Dim PB1 As Graphics = Myform2.thumbScr1.CreateGraphics
Dim AddrOfPB1 As IntPtr = PB1.GetHdc'create a compatible DC
Dim AddrCreateCompatibleDC As IntPtr = CreateCompatibleDC(IntPtr.Zero)'create a memory bitmap in the DC just created, the size of the window we're capturing
Dim AddrCreateCompatibleBitmap As IntPtr = CreateCompatibleBitmap(AddrOfPB1, ThumbCopyFromWT, ThumbCopyFromHT)'Prepare DC as a Bitmap using selectObject to configure the DC
SelectObject(AddrCreateCompatibleDC, AddrCreateCompatibleBitmap)'Draw the Rendered DomDocument to the Device Context
ViewObject.Draw(1, 1, Nothing, Nothing, 0, AddrCreateCompatibleDC, Nothing, Nothing, Nothing, 0)Dim fullSizeImg1 As System.Drawing.Image
fullSizeImg1 = System.Drawing.Image.FromHbitmap(AddrCreateCompatibleBitmap)
Myform2.thumbScr1.Image = fullSizeImg1.GetThumbnailImage(ThumbWidth, ThumbSizeHT, Nothing, IntPtr.Zero)Dim URLfullSizeImg As System.Drawing.Image
URLfullSizeImg = System.Drawing.Image.FromHbitmap(AddrCreateCompatibleBitmap)
URLHistThumbImage1 = URLfullSizeImg.GetThumbnailImage(URLHistImageWD, URLHistImageHT, Nothing, IntPtr.Zero)PB1.ReleaseHdc(AddrOfPB1)
CType(PB1, IDisposable).Dispose()
DeleteDC(AddrCreateCompatibleDC)
DeleteObject(AddrCreateCompatibleBitmap)T1Done = True
Catch ex As System.NullReferenceException
MsgBox("--- NullReferenceException ---" & " " & _
vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
' Code reacting to NullReferenceException
Catch ex As Exception
' Code reacting to any exception
MsgBox(vbCrLf & "Message --- " & ex.Message & " " & _
vbCrLf & "Source --- " & ex.Source & " " & _
vbCrLf & "StackTrace --- " & ex.StackTrace & " " & _
vbCrLf & "TargetSite --- " & ex.TargetSite.ToString)
End TryEnd Sub
Post a reply
Related discussion
-
Problem after strong naming an assembly
by rinkurathor1 (0 replies)
-
VB.net class to connect to sql database
by senol01 (2 replies)
-
ASP.NET Patterns every developer should know
by konikula (3 replies)
-
String was not recognized as a valid DateTime.
by buvanasubi (22 replies)
-
help me to get simple requirement
by Slicksim (1 replies)
Related articles
Quick links
Recent activity
- jack semwal replied to i have struck with my proj...
- Larry Bargers replied to Error 4 Make sure that the ...
- Ashok Singh replied to How to receive data in web ...
- Cheryl Tan replied to File Upload.. how?
- Bhim Singh replied to Problem after strong naming...
- Jack Russel replied to how do you hide all in VB6
Enter your message below
Sign in or Join us (it's free).