最近项目中涉及到浏览器整页截屏的功能,有点复杂,研究了一天,终于在IE浏览器下实现,至于其他浏览器,以后再研究。
所谓整页截屏,就是说把整个页面全部截进去,包括通过滚动才能看到的部分。
在网上搜了一下,大家用的都是同一种办法:通过滚动页面,截取每一屏的图片,然后再合并成一张整的图片。
方法是好的,悲催的是,没有一个代码是能正常运行的,相信很多人都有同感!没办法,自己动手,丰衣足食。
我需要用.NET来实现。分析一下,主要有以下几个技术点:
1、如何取得浏览器对象。首先要确定IE版本,我用的是IE8浏览器,对象结构和IE6、IE7有点区别。这个可以通过Win32API中的FindWindow函数来实现。
2、对指定控件截屏。这个可以通过Win32API中的PrintWindow函数来实现,这个函数有一个优点:即使浏览器被其它窗口挡住,也可以正常截屏。但是,如果浏览器窗口最小化了,那就漆黑一片了。。。
3、合并图片。这个用GDI+可以很方便地实现。在内存中创建一个大的画布,然后将图片从上至下依次画上去即可。
开始动手。
首先,创建一个Console应用程序(用Form程序也没关系)。
(1)添加对System.Drawing和System.Windows.Forms的引用。
(2)添加对两个COM组件的引用:SHDocVw、MSHTML,并设置属性“嵌入互操作类型”为False(这个很重要,否则无法接下来的程序无法编译通过)。
(3)将程序入口Main方法标记为[STAThread](这个也很重要,否则接下来的程序会运行失败)。
然后,加入Win32API类,该类对几个重要的API进行了封装,接下来我们会用到这些API。代码如下:
using System; using System.Runtime.InteropServices; namespace IECapture { /// <summary> /// Win32API封装类。 /// </summary> internal static class Win32API { //User32 [DllImport("User32.dll")] public static extern int FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle); [DllImport("user32.dll")] public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect); [DllImport("user32.dll")] public static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("User32.dll")] public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, int nFlags); //gdi32 [DllImport("gdi32.dll")] public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); [DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); public struct RECT { public int left; public int top; public int right; public int bottom; } } }
最后,加入主程序。代码逻辑如下:
(1)获取当前IE浏览器对象。
(2)获取浏览器核心控件的矩形区域,计算整个页面一共有多少屏。
(3)通过滚动窗口的方式,对每一屏的页面进行截屏。
(4)将所有图片合并为一张整的图片。
主程序的源代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Drawing; using System.Windows.Forms; namespace IECapture { class Program { //必须指定COM线程模型为单线程 [STAThread] static void Main(string[] args) { //获取浏览器对象 SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindowsClass(); var webBrowser = shellWindows.Cast<SHDocVw.WebBrowser>().FirstOrDefault(c => c.Name == "Windows Internet Explorer"); if (webBrowser == null) { Console.WriteLine("当前未打开任何IE浏览器"); Console.ReadLine(); return; } //查找浏览器核心控件 IntPtr childHandle1 = Win32API.FindWindowEx(new IntPtr(webBrowser.HWND), IntPtr.Zero, "Frame Tab", IntPtr.Zero); IntPtr childHandle2 = Win32API.FindWindowEx(childHandle1, IntPtr.Zero, "TabWindowClass", IntPtr.Zero); IntPtr childHandle3 = Win32API.FindWindowEx(childHandle2, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero); IntPtr childHandle4 = Win32API.FindWindowEx(childHandle3, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero); if (childHandle4 == IntPtr.Zero) { Console.WriteLine("当前未打开任何IE浏览器"); Console.ReadLine(); return; } //获取浏览器核心控件的矩形区域 Win32API.RECT rc = new Win32API.RECT(); Win32API.GetWindowRect(childHandle4, ref rc); int pageHeight = rc.bottom - rc.top; //获取HTML文档对象 mshtml.IHTMLDocument2 htmlDoc = (mshtml.IHTMLDocument2)webBrowser.Document; //计算总共有多少页,以及最后一页的高度 int pageCount = htmlDoc.body.offsetHeight / pageHeight; int lastPageHeight = htmlDoc.body.offsetHeight % pageHeight; if (lastPageHeight > 0) pageCount++; int scrollBarWidth = pageCount > 1 ? SystemInformation.VerticalScrollBarWidth : 0; //图片列表,用于放置每一屏的截图 List<Image> pageImages = new List<Image>(); //一页一页地滚动截图 htmlDoc.parentWindow.scrollTo(0, 0); for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { using (Image image = CaptureWindow(childHandle4)) { //去掉边框,以及垂直滚动条的宽度 Rectangle innerRect = new Rectangle(2, 2, image.Width - scrollBarWidth - 4, image.Height - 4); if (pageCount > 1 && pageIndex == pageCount - 1 && lastPageHeight > 0) innerRect = new Rectangle(2, pageHeight - lastPageHeight + 2, image.Width - scrollBarWidth - 4, lastPageHeight - 4); pageImages.Add(GetImageByRect(image, innerRect)); } htmlDoc.parentWindow.scrollBy(0, pageHeight); } //拼接所有图片 Image fullImage = MergeImages(pageImages); if (fullImage == null) { Console.WriteLine("截屏失败,未获得任何图片"); Console.ReadLine(); return; } //将截屏图片保存到指定目录 try { string fileName = @"c:\IE整屏截图.png"; fullImage.Save(fileName); Console.WriteLine("截屏成功,图片位置:" + fileName); } finally { fullImage.Dispose(); } Console.ReadLine(); } /// <summary> /// 合并图片。 /// </summary> /// <param name="imageList">图片列表。</param> /// <returns>合并后的图片。</returns> static Image MergeImages(List<Image> imageList) { if (imageList == null || imageList.Count == 0) return null; if (imageList.Count == 1) return imageList[0]; int pageWidth = imageList[0].Width; int totalPageHeight = imageList.Sum(c => c.Height); Bitmap fullImage = new Bitmap(pageWidth, totalPageHeight); using (Graphics g = Graphics.FromImage(fullImage)) { int y = 0; for (int i = 0; i < imageList.Count; i++) { g.DrawImageUnscaled(imageList[i], 0, y, imageList[i].Width, imageList[i].Height); y += imageList[i].Height; imageList[i].Dispose();//释放图片资源 } } return fullImage; } /// <summary> /// 获取图片的指定区域。 /// </summary> /// <param name="image">原始图片。</param> /// <param name="rect">目标区域。</param> /// <returns></returns> static Image GetImageByRect(Image image, Rectangle rect) { if (image == null || rect.IsEmpty) return image; Bitmap bitmap = new Bitmap(rect.Width, rect.Height); using (Graphics g = Graphics.FromImage(bitmap)) { g.DrawImage(image, 0, 0, rect, GraphicsUnit.Pixel); } return bitmap; } /// <summary> /// 为指定窗口或控件截屏。 /// </summary> /// <param name="hWnd">句柄。</param> /// <returns>截屏图片。</returns> static Image CaptureWindow(IntPtr hWnd) { IntPtr hscrdc = Win32API.GetWindowDC(hWnd); if (hscrdc == IntPtr.Zero) return null; Win32API.RECT windowRect = new Win32API.RECT(); Win32API.GetWindowRect(hWnd, ref windowRect); int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; IntPtr hbitmap = Win32API.CreateCompatibleBitmap(hscrdc, width, height); IntPtr hmemdc = Win32API.CreateCompatibleDC(hscrdc); Win32API.SelectObject(hmemdc, hbitmap); Win32API.PrintWindow(hWnd, hmemdc, 0); Image bmp = Image.FromHbitmap(hbitmap); Win32API.DeleteDC(hscrdc); Win32API.DeleteDC(hmemdc); return bmp; } } }
【总结】
要想写一个好的整页截屏程序,还是很困难的。就拿本文的程序来说,就存在以下几点不足之处:
(1)仅在IE8浏览器上测试通过,无法在FireFox、Chrome上运行。即使同是IE家族的IE6、IE7、IE9,我也不敢保证能正常运行,各位同学可以测试一下,并尝试修改,欢迎交流。
(2)如果有浮动DIV随着页面一起滚动,在每一屏上都会被截屏。
(3)未考虑水平滚动条的影响。
(4)未考虑在多线程环境下的应用。
最后附上一个cnblogs.com首页的截屏,看看效果:)