天天看點

剛剛做完的一個螢幕截圖程式,分享一下

剛剛做完的一個螢幕截圖程式,分享一下

                                                  周銀輝

拖拽的效果和Windows7自帶的snipping tool 差不多,拖拽區域之外是半透明遮罩,拖拽區域之内被镂空的,但其拖拽完成後并不立即截圖,你可拖拽搖桿來重新調節截圖區域,然後輕按兩下截圖區域,完成截圖

1, 選用C++,WinForm還是WPF來完成該程式

選用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, 0, 0, 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

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繪制出來的,而是作為一個子控件添加進去的 

5, 如何實作遮罩和镂空效果

遮罩很容易實作:在你要遮蓋的東西上放置一個半透明控件(比如canvas)就可以了,不過,不要使用讓被遮蓋物體半透明的方式來實作,比如視窗半透明了,視窗上的子控件也會半透明顯示,這不是我們所需要的,我們那個選擇框就不是半透的。

镂空嘛,以前會有很學院派的想法,然需要镂空的區域使用OpacityMask或者Xor畫刷刷一下不就OK了麼?No,太學院派了,費力不讨好。OpacityMask效率很低啦。

實際情況是這樣的,如下圖, 用四個面闆(圖中的綠色,橙色,淡藍,紫色)排列在一起,中間留個洞就可以了,那四個面闆共同構成我們的半透明遮罩。

 當使用者拖拽滑鼠時,我們重新排列那四個遮罩面闆來改變中間镂空區域的大小和位置,使用者就會感覺真的像是在螢幕上畫了洞。

6, 如何根據使用者拖拽區域截圖

這就非常簡單了,同樣利用Graphics對象在先前的ScreenSnapshot上截取一部分就可以了

        internal BitmapSource CopyFromScreenSnapshot(Rect region)

            var sourceRect = region.ToRectangle();

            var destRect = new Rectangle(0, 0, 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事件,也就是說,讓其回調函數的效率盡量的高,因為其會被不間斷地頻繁調用。