65.9K
CodeProject 正在变化。 阅读更多。
Home

图形领域要做的事情

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.24/5 (12投票s)

2010年6月2日

CPOL

8分钟阅读

viewsIcon

36995

通过 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);
    }
}
}

结果

1.JPG

对于大多数 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);
     }
}

结果

2.JPG

通用路径

我们的下一个示例演示了通用路径的使用。通用路径是由直线和复杂曲线构成的形状。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 );
        } 
    } 
}

结果

3.JPG

下一个示例 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 像素宽的绿色线条。最后,我们使用 DashCapDashStyle 枚举(命名空间 System.Drawing.Drawing2D)来指定虚线的设置。然后,我们设置 coloredPenDashCap 属性(不要与 DashCap 枚举混淆)为一个 DashCap 枚举的成员。DashCap 枚举指定虚线开头和结尾的样式。在本例中,我们希望虚线的两端都是圆形的,因此我们使用 DashCap.Round。然后,我们将 coloredPenDashStyle 属性(不要与 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 );
    } 
}

结果

4.JPG

图像处理

图像的处理包括基于某些数学运算修改其像素。每个像素都使用三个整数值进行编码:一个用于红色,一个用于绿色,一个用于蓝色。值的范围取决于每像素的位数。截至本文撰写之时,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));
        }
    }
}

这是初始图像:

6.JPG

这是处理后的图像。只看图像,颜色就会反转。

7.JPG

缓冲图形

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());
        }
    }
}

结果

8.JPG

Graphics 属性可用于绘制到图形缓冲区。此属性提供对 Graphics 对象的访问,该对象用于绘制为该 BufferedGraphics 对象分配的图形缓冲区。不带参数的 Render 方法将图形缓冲区的 contents 绘制到分配缓冲区时指定的表面。Render 方法的其他重载允许您指定一个 Graphics 对象或一个指向设备上下文的 IntPtr 对象(或更确切地说,引用一个设备上下文),以将图形缓冲区的 contents 绘制到该设备上下文。

参考文献

  • MSDN Library - Graphics 部分。
© . All rights reserved.