zoukankan      html  css  js  c++  java
  • IE浏览器整页截屏程序

    最近项目中涉及到浏览器整页截屏的功能,有点复杂,研究了一天,终于在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)未考虑在多线程环境下的应用。

    作者:返回主页 linux运维-loring
    出处:http://www.cnblogs.com/zlf344242525/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果文中有什么错误,欢迎指出。以免更多的人被误导。
  • 相关阅读:
    如何使用SAP Intelligent Robotic Process Automation自动操作Excel
    OpenSAML 使用引导 IV: 安全特性
    Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务
    微服务架构集大成者—Spring Cloud (转载)
    Spring Cloud Eureka 服务注册列表显示 IP 配置问题
    使用 Notification API 开启浏览器桌面提醒
    SignalR 中使用 MessagePack 序列化提高 WebSocket 通信性能
    配置 Nginx 的目录浏览功能
    关于 Nginx 配置 WebSocket 400 问题
    Migrate from ASP.NET Core 2.0 to 2.1
  • 原文地址:https://www.cnblogs.com/zlf344242525/p/2631905.html
Copyright © 2011-2022 走看看