C#实现屏幕截图

时间:2022-06-27 15:24:07

背景

在很多聊天工具中都有一个屏幕截图功能,可以方便的截取屏幕部分区域,以图片的形式传输给对方,使得沟通更加直观。其实不仅在聊天时需要截图,在其他时候我们也需要截图工具,但一般我们都使采用Print Screen按键来截取整个屏幕,然后使用MSPaint再选取部分区域。其实使用C#也很容易实现截图功能。

原理

首先我们也需要对整个屏幕拍一个快照,然后在这个图片上选择一个区域,再截取这部分区域图片即可。另外截取部分图片我们可以通过Graphic.DrawImage方法实现,而屏幕快照也可以使用Graphic.CopyFromScreen方法获取。那么问题就在于与用户的交互了,即如何在屏幕上画一个矩形区域,拖动矩形区域,修改矩形区域的尺寸。

这里我们的实现方式是,创建一个窗体,使其全屏显示,背景为屏幕快照,那么问题就成了在窗体上画矩形了。如果有这方面经验,则问题就方便解决了。

C#实现

这里的截图功能包括:Ctrl+Alt+A快捷键,截图保存,区域拖放,区域尺寸修改等功能。

ScreenOverlay.cs为上面说的窗体,其背景为屏幕快照,代码如下:
public sealed class ScreenOverlay : Form {
public SelectionRectangle Selection;
private ContextMenuStrip selectedMenu;
private IContainer components = null;

protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}

public ScreenOverlay() {
this.components = new System.ComponentModel.Container();
this.selectedMenu = new System.Windows.Forms.ContextMenuStrip(this.components);

this.selectedMenu.SuspendLayout();
this.SuspendLayout();

this.selectedMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
new ToolStripMenuItem("Save as"){ Tag = "save" },
new ToolStripMenuItem("Copy to Clipboard"){Tag = "copy"},
new ToolStripSeparator(),
new ToolStripMenuItem("Reset"){ Tag = "reset"},
new ToolStripMenuItem("Exit"){ Tag = "exit"}
});

this.selectedMenu.Size = new System.Drawing.Size(161, 98);
this.selectedMenu.Closed += new ToolStripDropDownClosedEventHandler

(this.selectedMenu_Closed);
this.selectedMenu.ItemClicked += new ToolStripItemClickedEventHandler

(this.selectedMenu_ItemClicked);

this.ShowInTaskbar = false;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.TopMost = true;

this.DoubleClick += new System.EventHandler(this.ScreenOverlay_DoubleClick);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ScreenOverlay_KeyDown);
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ScreenOverlay_MouseDown);
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ScreenOverlay_MouseMove);
this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.ScreenOverlay_MouseUp);
this.selectedMenu.ResumeLayout(false);
this.ResumeLayout(false);

Rectangle rect = Screen.PrimaryScreen.Bounds;
Image bitmap = new Bitmap(rect.Width, rect.Height);
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(rect.Location, new Point(0, 0), rect.Size);
g.Dispose();

this.Location = rect.Location;
this.BackgroundImage = bitmap;
this.Size = rect.Size;

this.Selection = new SelectionRectangle(bitmap, rect);
}

private void ScreenOverlay_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
if (this.Selection.Status == SelectStatus.Unknow) {
this.Selection.StartTo(e.Location);
this.Selection.Status = SelectStatus.Selecting;
} else if (this.Selection.Status == SelectStatus.None) {
direction = this.Selection.MouseOn(e.Location);
if (direction != DirectionType.Out) {
this.Cursor = GetCursor(direction);
this.Selection.Status = SelectStatus.Sizing;
start = e.Location;
} else if (this.Selection.Contains(e.Location)) {
this.Cursor = Cursors.SizeAll;
this.Selection.Status = SelectStatus.Moving;
start = e.Location;
}
this.Selection.Draw(this.CreateGraphics());
}
}
}

private Point start;
private DirectionType direction;
private Cursor GetCursor(DirectionType type) {
switch (type) {
case DirectionType.E:
case DirectionType.W:
return Cursors.SizeWE;
case DirectionType.N:
case DirectionType.S:
return Cursors.SizeNS;
case DirectionType.NE:
case DirectionType.SW:
return Cursors.SizeNESW;
case DirectionType.ES:
case DirectionType.WN:
return Cursors.SizeNWSE;
default:
return Cursors.Default;
}
}

private void ScreenOverlay_MouseMove(object sender, MouseEventArgs e) {
if (this.Selection.Status == SelectStatus.Selecting) {
this.Selection.EndTo(e.Location);
this.Selection.Draw(this.CreateGraphics());
} else if (this.Selection.Status == SelectStatus.Sizing) {
Point offset = new Point(e.Location.X - start.X, e.Location.Y - start.Y);
this.Selection.ChangeSize(offset, direction);
this.Selection.Draw(this.CreateGraphics());
start = e.Location;
} else if (this.Selection.Status == SelectStatus.Moving) {
Point offset = new Point(e.Location.X - start.X, e.Location.Y - start.Y);
this.Selection.MoveTo(offset);
this.Selection.Draw(this.CreateGraphics());
start = e.Location;
}
}

private void ScreenOverlay_MouseUp(object sender, MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
if (this.Selection.Status == SelectStatus.Selecting) {
this.Selection.EndSelect();
}

if (this.Selection.Status != SelectStatus.Unknow && this.Selection.Status !=

SelectStatus.None) {
this.Selection.Status = SelectStatus.None;
this.Selection.Draw(this.CreateGraphics());
this.Cursor = Cursors.Default;
}
} else if (e.Button == System.Windows.Forms.MouseButtons.Right) {
if (this.Selection.Contains(e.Location)) {
selectedMenu.Show(e.Location);
}
}
}

private void ScreenOverlay_DoubleClick(object sender, EventArgs e) {
this.Selection.Status = SelectStatus.Unknow;
Close();
}

private void ScreenOverlay_KeyDown(object sender, KeyEventArgs e) {
if (!e.Control && !e.Alt && !e.Shift && e.KeyCode == Keys.Escape) {
Close();
}
}

private void selectedMenu_Closed(object sender, ToolStripDropDownClosedEventArgs e) {
this.Selection.Draw(this.CreateGraphics());
}

private void selectedMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) {
switch (e.ClickedItem.Tag.ToString()) {
case "save":
SaveFileDialog sfd = new SaveFileDialog();
sfd.DefaultExt = ".png";
sfd.FileName = "screenshot";
sfd.Filter = "PNG(*.png)|*.png|JPEG(*.jpg)|*.jpg|GIF(*.gif)|*.gif|BMP(*.bmp)|*.bmp";
sfd.FilterIndex = 0;
sfd.InitialDirectory = Environment.CurrentDirectory;
sfd.OverwritePrompt = true;
sfd.RestoreDirectory = true;
sfd.Title = "Save As...";
selectedMenu.Close();

DialogResult dr = sfd.ShowDialog();
if (dr == System.Windows.Forms.DialogResult.OK) {
this.Selection.SaveTo(sfd.FileName);
}
Close();
break;
case "copy":
this.Selection.SaveToClipboard();
Close();
break;
case "reset":
this.Selection.Status = SelectStatus.Unknow;
this.Selection.Draw(this.CreateGraphics());
break;
case "exit":
Close();
break;
}
}


}

其中用到的辅助类,DirectionType.cs, SelectStatus.cs, SelectionRectangle.cs如下:
public enum DirectionType {
Out = -1,
/// <summary>
/// North
/// </summary>
N,
/// <summary>
/// NorthEast
/// </summary>
NE,
/// <summary>
/// East
/// </summary>
E,
/// <summary>
/// East South
/// </summary>
ES,
/// <summary>
/// South
/// </summary>
S,
/// <summary>
/// South West
/// </summary>
SW,
/// <summary>
/// West
/// </summary>
W,
/// <summary>
/// West North
/// </summary>
WN
}

public enum SelectStatus {
Unknow,
None,
Selecting,
Selected,
Sizing,
Moving
}

public sealed class SelectionRectangle {
public SelectionRectangle(Image backgroundImage, Rectangle screen) {
StartPoint = new Point();
EndPoint = new Point();
BackgroundImage = backgroundImage;
Screen = screen;
Status = SelectStatus.Unknow;
}

public SelectStatus Status { get; set; }
public Rectangle Screen { get; private set; }
public Point StartPoint { get; set; }
public Point EndPoint { get; set; }

public Rectangle SeletionArea {
get {
return new Rectangle(Math.Min(StartPoint.X, EndPoint.X),
Math.Min(StartPoint.Y, EndPoint.Y),
Math.Abs(StartPoint.X - EndPoint.X),
Math.Abs(StartPoint.Y - EndPoint.Y));
}
}

public Image BackgroundImage { get; private set; }
private const int RANGE = 4;

/// <summary>
/// Check the relative position of mouse point with selection area.
/// For changing mouse style and selection area size.
/// </summary>
/// <param name="point">The mouse point</param>
/// <returns></returns>
public DirectionType MouseOn(Point point) {
int x = point.X, y = point.Y;

if (Math.Abs(x - SeletionArea.X) < RANGE) {
if (Math.Abs(y - SeletionArea.Top) < RANGE) {
return DirectionType.WN;
} else if (Math.Abs(y - SeletionArea.Bottom) < RANGE) {
return DirectionType.SW;
} else {
return DirectionType.W;
}
} else if (Math.Abs(x - SeletionArea.Right) < RANGE) {
if (Math.Abs(y - SeletionArea.Top) < RANGE) {
return DirectionType.NE;
} else if (Math.Abs(y - SeletionArea.Bottom) < RANGE) {
return DirectionType.ES;
} else {
return DirectionType.E;
}
} else if (Math.Abs(y - SeletionArea.Top) < RANGE) {
return DirectionType.N;
} else if (Math.Abs(y - SeletionArea.Bottom) < RANGE) {
return DirectionType.S;
}

return DirectionType.Out;
}

public bool Contains(Point point) {
return this.SeletionArea.Contains(point);
}

public void Draw(Graphics g) {
Pen pen = new Pen(Color.FromArgb(150, Color.Red), 3f);
if (this.Status == SelectStatus.Selecting) {
pen = new Pen(Color.FromArgb(150, Color.Red), 3f) { DashStyle = DashStyle.Dash };
} else if (this.Status == SelectStatus.Selected || this.Status == SelectStatus.Moving ||

this.Status == SelectStatus.Sizing) {
pen = new Pen(Color.FromArgb(200, Color.Red), 3f);
}

Rectangle selected = this.SeletionArea;
BufferedGraphicsContext cxt = BufferedGraphicsManager.Current;
BufferedGraphics bg = cxt.Allocate(g, this.Screen);
cxt.MaximumBuffer = new Size(this.Screen.Width + 1, this.Screen.Height + 1);

bg.Graphics.DrawImage(this.BackgroundImage, new Point(0, 0));

if (this.Status != SelectStatus.Unknow) {
bg.Graphics.DrawRectangle(pen, selected);
}

bg.Render(g);
bg.Dispose();
g.Dispose();
}

public void MoveTo(Point offset) {
this.StartPoint = new Point(this.StartPoint.X + offset.X, this.StartPoint.Y + offset.Y);
this.EndPoint = new Point(this.EndPoint.X + offset.X, this.EndPoint.Y + offset.Y);
}

public void ChangeSize(Point offset, DirectionType direction) {
switch (direction) {
case DirectionType.E:
this.EndPoint = new Point(this.EndPoint.X + offset.X, this.EndPoint.Y);
break;
case DirectionType.ES:
this.EndPoint = new Point(this.EndPoint.X + offset.X, this.EndPoint.Y + offset.Y);
break;
case DirectionType.N:
this.StartPoint = new Point(this.StartPoint.X, this.StartPoint.Y + offset.Y);
break;
case DirectionType.NE:
this.StartPoint = new Point(this.StartPoint.X, this.StartPoint.Y + offset.Y);
this.EndPoint = new Point(this.EndPoint.X + offset.X, this.EndPoint.Y);
break;
case DirectionType.S:
this.EndPoint = new Point(this.EndPoint.X, this.EndPoint.Y + offset.Y);
break;
case DirectionType.SW:
this.StartPoint = new Point(this.StartPoint.X + offset.X, this.StartPoint.Y);
this.EndPoint = new Point(this.EndPoint.X, this.EndPoint.Y + offset.Y);
break;
case DirectionType.W:
this.StartPoint = new Point(this.StartPoint.X + offset.X, this.StartPoint.Y);
break;
case DirectionType.WN:
this.StartPoint = new Point(this.StartPoint.X + offset.X, this.StartPoint.Y +

offset.Y);
break;
}
}

public void StartTo(Point point) {
StartPoint = point;
}

public void EndTo(Point local) {
EndPoint = local;
}

public void EndSelect() {
int x1 = this.StartPoint.X, y1 = this.StartPoint.Y,
x2 = this.EndPoint.X, y2 = this.EndPoint.Y;

this.StartPoint = new Point(Math.Min(x1, x2), Math.Min(y1, y2));
this.EndPoint = new Point(Math.Max(x1, x2), Math.Max(y1, y2));
}

private Image GetImage() {
Rectangle rect = this.SeletionArea;
Image img = new Bitmap(rect.Width, rect.Height);

Graphics g = Graphics.FromImage(img);
g.DrawImage(this.BackgroundImage, new Rectangle(0, 0, img.Width, img.Height), rect,

GraphicsUnit.Pixel);
g.Dispose();

return img;
}

/// <summary>
/// Save image to clipboard
/// </summary>
public void SaveToClipboard() {
Clipboard.SetImage(GetImage());
}

/// <summary>
/// Save image to disk.
/// </summary>
/// <param name="filename"></param>
public void SaveTo(string filename) {
Image img = GetImage();
img.Save(filename);
}
}

截图应用

ScreenOverlay窗体是为截图和在屏幕上选择区域而实现的,那么我们需要调用这个窗体来实现截图功能。下面我们实现一个简单的截图应用,该应用只有一个窗体Form1,窗体上只有一个按钮。点击该按钮即进行截图。并且该应用支持Ctrl+Alt+A快捷键进行截图。Form.cs部分代码:

public partial class Form1 : Form {
[Flags()]
public enum KeyModifiers {
None = 0,
Alt = 1,
Ctrl = 2,
Shift = 4,
WindowsKey = 8
}

private const int ScreenPrintKeyshortID = 90012;

[DllImport("user32")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers control, Keys vk);

[DllImport("user32")]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

public Form1() {
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e) {
ScreenOverlay so = new ScreenOverlay();
so.Show(this);
}

private void Form1_Load(object sender, EventArgs e) {
RegisterHotKey(this.Handle, ScreenPrintKeyshortID, KeyModifiers.Ctrl | KeyModifiers.Alt,

Keys.A);
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
UnregisterHotKey(this.Handle, ScreenPrintKeyshortID);
}

protected override void WndProc(ref Message m) {
switch (m.Msg) {
case 0x0312:
if (m.WParam.ToInt32() == ScreenPrintKeyshortID) {
ScreenOverlay so = new ScreenOverlay();
so.Show(this);
}
break;
}
base.WndProc(ref m);
}
}


当运行该应用程序时,你可以点击按钮来进行截图,也可以使用快捷键进行截图。两者不同之处在于后者可以隐藏当前应用程序窗体的情况下进行截图。

效果

C#实现屏幕截图