图形领域要做的事情






4.24/5 (12投票s)
通过 C# 进行基础和高级图形处理。
通过 C# 进行基础和高级图形处理
C# 和 .NET Framework 最被低估的方面之一就是其高级图形处理能力。本文将重点介绍这些能力,但首先会从一些基础知识开始,以便按顺序呈现内容。对于 .NET 图形领域的高级学生来说,可以跳过本文。但是,以我有限的知识,我将尝试(通过示例)解释如何掌握这些图形处理能力。
首先,众所周知,要在窗体上绘制,我们首先通过调用 System.Windows.Forms.Control.CreateGraphics
方法创建一个 Graphics
对象。然后,我们创建一个 Pen
对象,并调用 Graphics
类的一个成员,使用 Pen
在控件上进行绘制。默认情况下,笔绘制实线。要绘制虚线,请将 Pen.DashStyle
属性设置为以下值之一:
DashStyle.Dash
DashStyle.DashDot
DashStyle.DashDotDot
DashStyle.Dot
DashStyle.Solid
以下是设置这些属性的代码示例:
namespace PenApp
{
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(4, 13);
this.ClientSize = new System.Drawing.Size(320, 273);
this.Text = "Pens...";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.Red, 7);
p.DashStyle = DashStyle.Dot;
g.DrawLine(p, 50, 25, 400, 25);
p.DashStyle = DashStyle.Dash;
g.DrawLine(p, 50, 50, 400, 50);
p.DashStyle = DashStyle.DashDot;
g.DrawLine(p, 50, 75, 400, 75);
p.DashStyle = DashStyle.DashDotDot;
g.DrawLine(p, 50, 100, 400, 100);
p.DashStyle = DashStyle.Solid;
g.DrawLine(p, 50, 125, 400, 125);
}
}
}
结果
对于大多数 Draw
方法,Graphics
类还具有 Fill
方法,用于绘制形状并填充其内容。这些方法的工作方式与 Draw
方法完全相同,只是它们需要一个 Brush
类的实例,而不是 Pen
类。Brush
类是 abstract
的,因此您必须实例化其中一个子类:
System.Drawing.Drawing2D.HatchBrush
System.Drawing.Drawing2D.LinearGradientBrush
System.Drawing.Drawing2D.PathGradientBrush.
System.Drawing.SolidBrush
System.Drawing.TextureBrush
下面的这个非常基础的代码示例绘制了一个实心的、紫红色的五边形:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint +=
new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Brush b = new SolidBrush(Color.Maroon);
Point [] points = new Point[]
{new Point(10, 10),
new Point(10, 100),
new Point(50, 65),
new Point(100, 100),
new Point(85, 40)};
g.FillPolygon(b, points);
}
}
结果
通用路径
我们的下一个示例演示了通用路径的使用。通用路径是由直线和复杂曲线构成的形状。GraphicsPath
类(命名空间 System.Drawing.Drawing2D
)的对象表示通用路径。GraphicsPath
类提供了功能,可以从基于矢量的基本图形对象创建复杂形状。GraphicsPath
对象由简单形状定义的图形组成。添加到路径的每个矢量图形对象(如线或弧)的起始点都通过一条直线连接到前一个对象的终点。调用 CloseFigure
方法时,会用一条直线将最后一个矢量图形对象的终点连接到当前图形的初始起点,然后开始一个新的图形。StartFigure
方法在不关闭前一个图形的情况下,开始路径中的新图形。
因此,下面的程序绘制了五角星形状的通用路径。我们定义了两个 int
数组,分别表示星形点的 x 和 y 坐标,然后定义一个 GraphicsPath
对象 star。然后,一个循环创建连接星形点的线并将这些线添加到 star 中。我们使用 GraphicsPath
方法 AddLine
将线段追加到形状。AddLine
的参数指定线段终点的坐标;每次调用 AddLine
都会从前一个点添加一条线段到当前点。我们使用 GraphicsPath
方法 CloseFigure
来完成形状。
之后,我们设置 Graphics
对象的原点。TranslateTransform
方法的参数表示原点应平移到坐标 (150, 150)。第几行中的循环绘制了 18 次星形,围绕原点旋转。调用 Graphics
方法 RotateTransform
以移动到窗体上的下一个位置;参数指定旋转角度(以度为单位)。然后,Graphics
方法 FillPath
使用创建的 Brush
绘制填充的星形。应用程序使用 Random
方法 Next
随机确定 SolidBrush
的颜色。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class DrawStarsForm : Form
{
public DrawStarsForm()
{
InitializeComponent();
} // end constructor
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.SuspendLayout();
//
// DrawStarsForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(313, 302);
this.Name = "DrawStarsForm";
this.Text = "Drawing Stars";
this.Paint += new System.Windows.Forms.PaintEventHandler(
this.DrawStarsForm_Paint);
this.ResumeLayout(false);
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new DrawStarsForm());
}
private void DrawStarsForm_Paint(object sender, PaintEventArgs e)
{
Graphics graphicsObject = e.Graphics;
Random random = new Random();
SolidBrush brush = new SolidBrush( Color.DarkMagenta );
int[] xPoints = { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 };
int[] yPoints = { 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 };
GraphicsPath star = new GraphicsPath();
for ( int i = 0; i <= 8; i += 2 )
star.AddLine( xPoints[ i ], yPoints[ i ],
xPoints[ i + 1 ], yPoints[ i + 1 ] );
star.CloseFigure();
// translate the origin to (150, 150)
graphicsObject.TranslateTransform( 150, 150 );
// rotate the origin and draw stars in random colors
for ( int i = 1; i <= 18; i++ )
{
graphicsObject.RotateTransform( 20 );
brush.Color = Color.FromArgb(
random.Next( 200, 255 ), random.Next( 255 ),
random.Next( 255 ), random.Next( 255 ) );
graphicsObject.FillPath( brush, star );
}
}
}
结果
下一个示例 sort of 集成了迄今为止讨论的一些概念。在 C# 中,所有线性渐变都沿着确定渐变端点的线定义。可以通过起始点和结束点或矩形的对角线来指定此线。第一个参数 Rectangle drawArea1
表示线性渐变的端点:左上角是起始点,右下角是结束点。第二个和第三个参数指定渐变将使用的颜色。在这种情况下,椭圆的颜色将从 Color.Blue
逐渐变为 Color.Yellow
。最后一个参数(LinearGradientMode
枚举的一个类型)指定线性渐变的方向。在本例中,我们使用 LinearGradientMode.ForwardDiagonal
,它创建从左上角到右下角的渐变。然后,我们使用 Graphics
方法 FillEllipse
绘制一个带有 linearBrush
的椭圆;颜色如上所述,从蓝色逐渐变为黄色。我们创建了一个 Pen
对象 thickRedPen
。我们将 Color.Red
和一个 int
参数 10 传递给 thickRedPen
的构造函数,表示我们希望 thickRedPen
绘制 10 像素宽的红色线条。
然后,我们创建一个新的 Bitmap
图像,该图像最初是空的。Bitmap
类可以生成彩色和灰度图像;这个特定的 Bitmap
宽度为 10 像素,高度为 10 像素。FromImage
方法是 Graphics
类的静态成员,它检索与 Image
关联的 Graphics
对象,该对象可用于在图像上绘制。第 52-65 行在 Bitmap
上绘制了一个由黑色、蓝色、红色和黄色矩形以及线条组成的图案。TextureBrush
是一种画笔,它用图像而不是纯色填充形状的内部。在第 71 行,TextureBrush
对象 textureBrush
使用我们的 Bitmap
填充了一个矩形。使用的 TextureBrush
构造函数接受一个定义其纹理的图像作为参数。
接下来,我们绘制一个带有粗白线的扇形弧。然后,我们将 coloredPen
的颜色设置为 White
,并将其宽度修改为 6 像素。然后,我们通过指定 Pen
、边界矩形的 x 坐标、y 坐标、宽度和高度以及开始和扫掠角度来在窗体上绘制扇形。
进一步,我们绘制一条 5 像素宽的绿色线条。最后,我们使用 DashCap
和 DashStyle
枚举(命名空间 System.Drawing.Drawing2D
)来指定虚线的设置。然后,我们设置 coloredPen
的 DashCap
属性(不要与 DashCap
枚举混淆)为一个 DashCap
枚举的成员。DashCap
枚举指定虚线开头和结尾的样式。在本例中,我们希望虚线的两端都是圆形的,因此我们使用 DashCap.Round
。然后,我们将 coloredPen
的 DashStyle
属性(不要与 DashStyle
枚举混淆)设置为 DashStyle.Dash
,表示我们希望我们的线条完全由短划线组成。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public partial class DrawShapesForm : Form
{
public DrawShapesForm()
{
InitializeComponent();
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.SuspendLayout();
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(449, 188);
this.Name = "DrawShapesForm";
this.Text = "Drawing Shapes";
this.Paint += new System.Windows.Forms.PaintEventHandler(
this.DrawShapesForm_Paint);
this.ResumeLayout(false);
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new DrawShapesForm());
}
private void DrawShapesForm_Paint( object sender, PaintEventArgs e )
{
Graphics graphicsObject = e.Graphics;
Rectangle drawArea1 = new Rectangle( 5, 35, 30, 100 );
LinearGradientBrush linearBrush =
new LinearGradientBrush( drawArea1, Color.Blue,
Color.Yellow, LinearGradientMode.ForwardDiagonal );
graphicsObject.FillEllipse( linearBrush, 5, 30, 65, 100 );
Pen thickRedPen = new Pen( Color.Red, 10 );
Rectangle drawArea2 = new Rectangle( 80, 30, 65, 100 );
graphicsObject.DrawRectangle( thickRedPen, drawArea2 );
Bitmap textureBitmap = new Bitmap( 10, 10 );
Graphics graphicsObject2 =
Graphics.FromImage( textureBitmap );
SolidBrush solidColorBrush =
new SolidBrush( Color.Red );
Pen coloredPen = new Pen( solidColorBrush );
solidColorBrush.Color = Color.Yellow;
graphicsObject2.FillRectangle( solidColorBrush, 0, 0, 10, 10 );
coloredPen.Color = Color.Black;
graphicsObject2.DrawRectangle( coloredPen, 1, 1, 6, 6 );
solidColorBrush.Color = Color.Blue;
graphicsObject2.FillRectangle( solidColorBrush, 1, 1, 3, 3 );
solidColorBrush.Color = Color.Red;
graphicsObject2.FillRectangle( solidColorBrush, 4, 4, 3, 3 );
TextureBrush texturedBrush =
new TextureBrush( textureBitmap );
graphicsObject.FillRectangle( texturedBrush, 155, 30, 75, 100 );
coloredPen.Color = Color.White;
coloredPen.Width = 6;
graphicsObject.DrawPie( coloredPen, 240, 30, 75, 100, 0, 270 );
coloredPen.Color = Color.Green;
coloredPen.Width = 5;
graphicsObject.DrawLine( coloredPen, 395, 30, 320, 150 );
coloredPen.Color = Color.Yellow;
coloredPen.DashCap = DashCap.Round;
coloredPen.DashStyle = DashStyle.Dash;
graphicsObject.DrawLine( coloredPen, 320, 30, 395, 150 );
}
}
结果
图像处理
图像的处理包括基于某些数学运算修改其像素。每个像素都使用三个整数值进行编码:一个用于红色,一个用于绿色,一个用于蓝色。值的范围取决于每像素的位数。截至本文撰写之时,24 位每像素可获得最佳质量,每个分量的值在 0 到 255 之间,这意味着超过 1600 万种颜色。下面的示例通过涉及颜色反转来展示图像处理。例如,假设每像素的位数是 24。颜色的反转包括为图像的每个像素的三个分量分配 255 的补码。运行代码,观察图像,然后颜色就会反转。
using System;
using System.Threading;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Data;
public class MainForm : Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize= new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 272);
this.Text = "Picture.jpg";
this.Paint += new System.Windows.Forms.PaintEventHandler(
this.MainForm_Paint);
}
[STAThread]
static void Main() {
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, PaintEventArgs e)
{
using ( Graphics g = CreateGraphics() ) {
Bitmap m_Bmp = new Bitmap(@"C:\garden.jpg");
g.DrawImage( m_Bmp, new Point(5,5) );
Thread.Sleep(1000);
int width = m_Bmp.Width;
int height = m_Bmp.Height;
Color cSrc, cDest;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++) {
cSrc = m_Bmp.GetPixel(x, y);
cDest = Color.FromArgb( 255-cSrc.R, 255-cSrc.G, 255-cSrc.B);
m_Bmp.SetPixel(x, y, cDest);
}
g.DrawImage(m_Bmp, new Point (5, 5));
}
}
}
这是初始图像:
这是处理后的图像。只看图像,颜色就会反转。
缓冲图形
BufferedGraphics
类允许您为图形实现自定义双缓冲。它提供了图形缓冲区的包装器,以及用于写入缓冲区并将其内容呈现到输出设备的 方法。使用双缓冲的图形可以减少或消除因重绘显示表面而引起的闪烁。当您使用双缓冲时,更新后的图形会首先绘制到内存中的缓冲区,然后该缓冲区的 contents 会被快速写入到显示表面的全部或部分。这种相对短暂地覆盖显示图形的操作通常会减少或消除图形更新时有时会发生的闪烁。BufferedGraphics
类没有公共构造函数,必须由应用程序域的 BufferedGraphicsContext
使用其 Allocate
方法创建。您可以从静态 BufferedGraphicsManager.Current
属性检索当前应用程序域的 BufferedGraphicsContext
。
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace BufferingExample
{
public class BufferingExample : Form
{
private BufferedGraphicsContext context;
private BufferedGraphics grafx;
private byte bufferingMode;
private string[] bufferingModeStrings =
{
"Draw to Form without OptimizedDoubleBufferring control style",
"Draw to Form using OptimizedDoubleBuffering control style",
"Draw to HDC for form" };
private System.Windows.Forms.Timer timer1;
private byte count;
public BufferingExample() : base()
{
// Configure the Form for this example.
this.Text = "User double buffering";
this.MouseDown += new MouseEventHandler(this.MouseDownHandler);
this.Resize += new EventHandler(this.OnResize);
this.SetStyle( ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true );
// Configure a timer to draw graphics updates.
timer1 = new System.Windows.Forms.Timer();
timer1.Interval = 200;
timer1.Tick += new EventHandler(this.OnTimer);
bufferingMode = 2;
count = 0;
context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size(this.Width+1, this.Height+1);
grafx = context.Allocate(this.CreateGraphics(),
new Rectangle( 0, 0, this.Width, this.Height ));
DrawToBuffer(grafx.Graphics);
}
private void MouseDownHandler(object sender, MouseEventArgs e)
{
if( e.Button == MouseButtons.Right )
{
if( ++bufferingMode > 2 )
bufferingMode = 0;
if( bufferingMode == 1 )
this.SetStyle( ControlStyles.OptimizedDoubleBuffer, true );
// If the current buffering mode uses
// the OptimizedDoubleBuffering ControlStyle,
// enabke the control style.
if( bufferingMode == 2 )
this.SetStyle( ControlStyles.OptimizedDoubleBuffer, false );
// Cause the background to be cleared and redraw.
count = 6;
DrawToBuffer(grafx.Graphics);
this.Refresh();
}
else
{
// Toggle whether the redraw timer is active.
if( timer1.Enabled )
timer1.Stop();
else
timer1.Start();
}
}
private void OnTimer(object sender, EventArgs e)
{
// Draw randomly positioned ellipses to the buffer.
DrawToBuffer(grafx.Graphics);
// If in bufferingMode 2, draw to the form's HDC.
if( bufferingMode == 2 )
// Render the graphics buffer to the form's HDC.
grafx.Render(Graphics.FromHwnd(this.Handle));
// If in bufferingMode 0 or 1, draw in the paint method.
else
this.Refresh();
}
private void OnResize(object sender, EventArgs e)
{
// Re-create the graphics buffer for a new window size.
context.MaximumBuffer = new Size(this.Width+1, this.Height+1);
if( grafx != null )
{
grafx.Dispose();
grafx = null;
}
grafx = context.Allocate(this.CreateGraphics(),
new Rectangle( 0, 0, this.Width, this.Height ));
// Cause the background to be cleared and redraw.
count = 6;
DrawToBuffer(grafx.Graphics);
this.Refresh();
}
private void DrawToBuffer(Graphics g)
{
// Clear the graphics buffer every five updates.
if( ++count > 5 )
{
count = 0;
grafx.Graphics.FillRectangle(Brushes.Black, 0, 0,
this.Width, this.Height);
}
// Draw randomly positioned and colored ellipses.
Random rnd = new Random();
for( int i=0; i<20; i++ )
{
int px = rnd.Next(20,this.Width-40);
int py = rnd.Next(20,this.Height-40);
g.DrawEllipse(new Pen(Color.FromArgb(rnd.Next(0, 255), rnd.Next(0,255),
rnd.Next(0,255)), 1), px, py,
px+rnd.Next(0, this.Width-px-20),
py+rnd.Next(0, this.Height-py-20));
}
// Draw information strings.
g.DrawString("Buffering Mode: "+bufferingModeStrings[bufferingMode],
new Font("Arial", 8), Brushes.White, 10, 10);
g.DrawString("Right-click to cycle buffering mode",
new Font("Arial", 8), Brushes.White, 10, 22);
g.DrawString("Left-click to toggle timed display refresh",
new Font("Arial", 8), Brushes.White, 10, 34);
}
protected override void OnPaint(PaintEventArgs e)
{
grafx.Render(e.Graphics);
}
[STAThread]
public static void Main(string[] args)
{
Application.Run(new BufferingExample());
}
}
}
结果
Graphics
属性可用于绘制到图形缓冲区。此属性提供对 Graphics
对象的访问,该对象用于绘制为该 BufferedGraphics
对象分配的图形缓冲区。不带参数的 Render
方法将图形缓冲区的 contents 绘制到分配缓冲区时指定的表面。Render
方法的其他重载允许您指定一个 Graphics
对象或一个指向设备上下文的 IntPtr
对象(或更确切地说,引用一个设备上下文),以将图形缓冲区的 contents 绘制到该设备上下文。
参考文献
- MSDN Library - Graphics 部分。