zoukankan      html  css  js  c++  java
  • 刚刚做完的一个屏幕截图程序,分享一下

                                        刚刚做完的一个屏幕截图程序,分享一下

                                                      周银辉

    好长时间没有更新博客了啊,把绝大多数时间花在了那个开源的WPFToolkit 和 《深入理解操作系统》上, 然后见缝插针地做了这个截图程序。

    你可以在这里下载DEMO程序试试效果(我感觉还行~) 

    拖拽的效果和Windows7自带的snipping tool 差不多,拖拽区域之外是半透明遮罩,拖拽区域之内被镂空的,但其拖拽完成后并不立即截图,你可拖拽手柄来重新调节截图区域,然后双击截图区域,完成截图

     

    1, 选用C++,WinForm还是WPF来完成该程序

    选用C++来做的话,我们可以很方便地用bitblt函数来进行屏幕图像的拷贝,似乎大多数截图程序都是这么干的

    选用WinForm的话,可以采用Graphics对象的CopyFromScreen函数来屏幕图像的拷贝,这也很方便,不过其相对于WPF更大的好处在于,GDI+是实时绘图的,在你绘制上图中的那个蓝色框时不会有明显的滞后感,特别是对应高分辨率多显示器这样的环境下,WPF的OnRender函数的滞后感是很严重的

    选用WPF嘛,从程序本身看,没什么好处,并且WPF貌似没有截图的API吧~~ 况且还有上面所说的滞后感呢。

    但我还是选择了WPF,原因是,需要和其他WPF应用集成,我希望是清一色的WPF。洁癖??不是啦,主要的原因还是想偷懒,因为以前做过一个ImageEditor控件,其中的拖拽控件(就是上图的蓝色框)是可以重用的,至于刚才所说的弊端嘛,有办法可以绕过去。 

    2, 基本思路

    截图前,先拷贝整个屏幕图像到一个Image中,我们称之为ScreenSnapshot, 然后用户通过鼠标操作,确定一个矩形区域Rect,将Rect传递给函数BitmapSource Clip(Rect r) , Clip函数在ScreenSnapshot上截取Rect对于的那一部分图像,并返回。 

    3, 如何截取屏幕图像

    WPF没有内置的函数,但可以借用WinForm的Graphics来完成,其将图像截取并保存在一个System.Drawing.Bitmap上,然后我们使用一个辅助函数将System.Drawing.Bitmap转化为WPF版本的System.Media.Imaging.BitmapSource对象就可以了

            public static Bitmap GetScreenSnapshot()
            {
                Rectangle rc 
    = SystemInformation.VirtualScreen;
                var bitmap 
    = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb);

                
    using (Graphics g = Graphics.FromImage(bitmap))
                {
                    g.CopyFromScreen(rc.X, rc.Y, 
    00, rc.Size, CopyPixelOperation.SourceCopy);
                }

                
    return bitmap;
            }

            
    public static BitmapSource ToBitmapSource(this Bitmap bmp)
            {
                BitmapSource returnSource;

                
    try
                {
                    returnSource 
    = Imaging.CreateBitmapSourceFromHBitmap(bmp.GetHbitmap(),IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                }
                
    catch
                {
                    returnSource 
    = null;
                }

                
    return returnSource;

            }

    4,如何绘制选择框

    所谓选择框,就是用户拖拽鼠标时显示的那个框选线框,专业一点的术语叫rubber band

    我最开始的做法是在OnRender函数中drawingContext.DrawingRectangle(....) ,对于单屏显示器而言,效果还不错,但在双屏(1920x1200x2)上效果很差,明显的滞后感,另外一位帅哥在“OnRender is not as cheap as OnPaint”中也说到了该问题,总结了下面一段话,让人看了很伤心:

    It is ok to use OnRender for things that won’t change, but what you render changes on MouseOver or SizeChanged, you’ll may be causing a layout perf problem for your app.
    The way to avoid the OnRender tax is to predefine your UI in a template, then for the bits that change, use triggers within the template to change it.

    不过,这个问题可以轻松绕过去:不就想画一个框吗?给你一个框(System.Windows.Shapes.Rectangle)便是。也就是这个框不是靠我们先前的DrawingContext绘制出来的,而是作为一个子控件添加进去的 

    至于何时去刷新选择框的大小和位置嘛,你可以再用户鼠标拖拽时立即更新,也可以定时更新,我选择的后者,当然,不是自己写定时器,而是才用了CompositionTarget.Rendering事件, 这是一个帧回调,这里的帧就是FPS(frame per second)中的F,所以,这个刷得到多快,就取决于你计算机的FPS了

    5, 如何实现遮罩和镂空效果

    遮罩很容易实现:在你要遮盖的东西上放置一个半透明控件(比如canvas)就可以了,不过,不要使用让被遮盖物体半透明的方式来实现,比如窗口半透明了,窗口上的子控件也会半透明显示,这不是我们所需要的,我们那个选择框就不是半透的。

    镂空嘛,以前会有很学院派的想法,然需要镂空的区域使用OpacityMask或者Xor画刷刷一下不就OK了么?No,太学院派了,费力不讨好。OpacityMask效率很低啦。

    实际情况是这样的,如下图, 用四个面板(图中的绿色,橙色,淡蓝,紫色)排列在一起,中间留个洞就可以了,那四个面板共同构成我们的半透明遮罩。

     

     当用户拖拽鼠标时,我们重新排列那四个遮罩面板来改变中间镂空区域的大小和位置,用户就会感觉真的像是在屏幕上画了洞。

    6, 如何根据用户拖拽区域截图

    这就非常简单了,同样利用Graphics对象在先前的ScreenSnapshot上截取一部分就可以了

            internal BitmapSource CopyFromScreenSnapshot(Rect region)
            {
                var sourceRect 
    = region.ToRectangle();
                var destRect 
    = new Rectangle(00, sourceRect.Width, sourceRect.Height);

                
    if (screenSnapshot != null)
                {
                    var bitmap 
    = new Bitmap(sourceRect.Width, sourceRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                    
    using (Graphics g = Graphics.FromImage(bitmap))
                    {
                        g.DrawImage(screenSnapshot, destRect, sourceRect, GraphicsUnit.Pixel);
                    }

                    
    return bitmap.ToBitmapSource();
                }

                
    return null;
            }

    7, 其他的

    省着点用CompositionTarget.Rendering事件,也就是说,让其回调函数的效率尽量的高,因为其会被不间断地频繁调用。 

  • 相关阅读:
    jQuery 教程
    单例设计模式getInstance()
    JS和JQuery总结
    Anchor 对象
    static关键字
    HTML、html
    HTML DOM
    HTML DOM 实例Document 对象
    水池进水与放水问题:有一个水池,水池的容量是固定 的500L,一边为进水口,一边为出水口.........(多线程应用)
    迷你DVD管理器(Java版)
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1804762.html
Copyright © 2011-2022 走看看