剛剛做完的一個螢幕截圖程式,分享一下
周銀輝
拖拽的效果和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事件,也就是說,讓其回調函數的效率盡量的高,因為其會被不間斷地頻繁調用。