C#中的绘图

参考:http://www.cnblogs.com/zihuatanejo/articles/1392689.htmlphp

GDI+:Graphics Device Interface Plus也就是图形设备接口,提供了各类丰富的图形图像处理功能;在C#.NET中,使用GDI+处理二维(2D)的图形和图像,使用DirectX处理三维(3D)的图形图像,图形图像处理用到的主要命名空间是System . Drawing:提供了对GDI+基本图形功能的访问,主要有Graphics类、Bitmap类、从Brush类继承的类、Font类、Icon类、Image类、Pen类、Color类等.

  大概了解了什么是GDI+后,咱们来看一下绘图要用到的主要工具,要画图,确定要画板吧,在C#中画板能够经过Graphics这个类来建立,有了画板,总得弄个笔什么之类的吧,否则怎么画呀,难不成咱们用手指画.笔又能够分好多种类,好比铅笔,画刷等.它们的区别主要是铅笔能够用来画线条,而画刷呢,嘿嘿,本身考虑下.在c#中咱们能够用Pen,Brush类来实现相似功能.颜料则天然是用Color类了.html

有了工具,咱们就能够开始动手了!(所需命名空间:using System.Drawing;)

  实现效果:在空白窗体中画基本图形

  准备一个画板:

  建立一个画板主要有3种方式:

  A: 在窗体或控件的Paint事件中直接引用Graphics对象

  B: 利用窗体或某个控件的CreateGraphics方法

  C: 从继承自图像的任何对象建立Graphics对象

  此次咱们就先以A为例:

private void Form1_Paint(object sender, PaintEventArgs e)
    {
      Graphics g = e.Graphics; //建立画板,这里的画板是由Form提供的.
    }

  而后,咱们要只笔:

private void Form1_Paint(object sender, PaintEventArgs e)
    {
      Graphics g = e.Graphics; //建立画板,这里的画板是由Form提供的.
      Pen p = new Pen(Color.Blue, 2);//定义了一个蓝色,宽度为2的画笔
    }

  接下来咱们就能够来画画了.

private void Form1_Paint(object sender, PaintEventArgs e)
    {
      Graphics g = e.Graphics; //建立画板,这里的画板是由Form提供的.
      Pen p = new Pen(Color.Blue, 2);//定义了一个蓝色,宽度为的画笔编程

        g.DrawLine(p, 10, 10, 100, 100);//在画板上画直线,起始坐标为(10,10),终点坐标为(100,100)
      g.DrawRectangle(p, 10, 10, 100, 100);//在画板上画矩形,起始坐标为(10,10),宽为,高为
      g.DrawEllipse(p, 10, 10, 100, 100);//在画板上画椭圆,起始坐标为(10,10),外接矩形的宽为,高为100
    }c#

 

 

1.首先咱们来看下上一片中咱们使用过的Pen.

  Pen的属性主要有: Color(颜色),DashCap(短划线终点形状),DashStyle(虚线样式),EndCap(线尾形状), StartCap(线头形状),Width(粗细)等.咱们能够用Pen 来画虚线,带箭头的直线等

Pen p = new Pen(Color.Blue, 5);//设置笔的粗细为,颜色为蓝色
Graphics g = this.CreateGraphics();
//画虚线
p.DashStyle = DashStyle.Dot;//定义虚线的样式为点
g.DrawLine(p, 10, 10, 200, 10);
//自定义虚线
p.DashPattern = new float[] { 2, 1 };//设置短划线和空白部分的数组
g.DrawLine(p, 10, 20, 20, 200, 20);windows

//画箭头,只对不封闭曲线有用
p.DashStyle = DashStyle.Solid;//实线
p.EndCap = LineCap.ArrowAnchor;//定义线尾的样式为箭头
g.DrawLine(p, 10, 30, 200, 30);
g.Dispose();
p.Dispose();
设计模式

 

2.接下来咱们来看下Brush的使用数组

  做用:咱们能够用画刷填充各类图形形状,如矩形、椭圆、扇形、多边形和封闭路径等,主要有几种不一样类型的画刷:ide

  ?     SolidBrush:画刷最简单的形式,用纯色进行绘制工具

  ?     HatchBrush:相似于 SolidBrush,可是能够利用该类从大量预设的图案中选择绘制时要使用的图案,而不是纯色布局

  ?     TextureBrush:使用纹理(如图像)进行绘制

  ?     LinearGradientBrush:使用沿渐变混合的两种颜色进行绘制

  ?     PathGradientBrush :基于编程者定义的惟一路径,使用复杂的混合色渐变进行绘制

  咱们这里只是简单介绍使用其中的几种:

Graphics g = this.CreateGraphics();
Rectangle rect = new Rectangle(10, 10, 50, 50);//定义矩形,参数为起点横纵坐标以及其长和宽
//单色填充
SolidBrush b1 = new SolidBrush(Color.Blue);//定义单色画刷     
g.FillRectangle(b1, rect);//填充这个矩形
//字符串
g.DrawString("字符串", new Font("宋体", 10), b1, new PointF(90, 10));
//用图片填充
TextureBrush b2 = new TextureBrush(Image.FromFile(@"e:picture1.jpg"));
rect.Location = new Point(10, 70);//

rect.Width = 200;//更改这个矩形的宽来
rect.Height = 200;//更改这个矩形的高
g.FillRectangle(b2, rect);
//用渐变色填充
rect.Location = new Point(10, 290);
LinearGradientBrush b3 = new LinearGradientBrush(rect, Color.Yellow , Color.Black , LinearGradientMode.Horizontal);
g.FillRectangle(b3, rect);

 

 

3.坐标轴变换

 

  在winform中的坐标轴和咱们平时接触的平面直角坐标轴不一样,winform中的坐标轴方向彻底相反:窗体的左上角为原点(0,0),水平向左则X增大,垂直下向则Y增大

接下来,咱们来实际操做下,经过旋转坐标轴的方向来画出不一样角度的图案,或经过更改坐标原点的位置来平衡坐标轴的位置.

Graphics g = this.CreateGraphics();
//单色填充
//SolidBrush b1 = new SolidBrush(Color.Blue);//定义单色画刷     
Pen p = new Pen(Color.Blue,1);
//转变坐标轴角度
for (int i = 0; i < 90; i++)
{
  g.RotateTransform(i);//每旋转一度就画一条线
  g.DrawLine(p, 0, 0, 100, 0);
  g.ResetTransform();//恢复坐标轴坐标
}
//平移坐标轴
g.TranslateTransform(100, 100);
g.DrawLine(p, 0, 0, 100, 0);
g.ResetTransform();
//先平移到指定坐标,而后进行度旋转
g.TranslateTransform(100,200);
for (int i = 0; i < 8; i++)
{
g.RotateTransform(45);
g.DrawLine(p, 0, 0, 100, 0);
}
g.Dispose();

 

 

4.最后咱们来看下Graphics这个画板上咱们还能够画什么

 

  其实咱们上面用到的都是在画一些简单的图形,直线,矩形,扇形,圆孤等,咱们还能够用它来绘制图片,这能够用它的DrawImage方法.这里我不详细讲解,你们有兴趣能够本身去MSDN了解下.咱们后面会讲到的截图就会用到这个方法.

 

 感谢你们的支持,这几天从早忙到晚,一个字累呀!!!如今挺困的,可是又不习惯这么早睡觉,哎~~仍是利用这个时间继续来写第三篇吧.

 

  前两篇已经基本向你们介绍了绘图的基本知识.那么,我就用咱们上两篇所学的,作几个例子.

 

  咱们先来作一个简单的----仿QQ截图关于这个的例子其实网上已经有这方面的资料了,可是为了文章的完整性,仍是以为有必要讲解.

 

接下来看看这是如何作到的. 

 

  思路:聊天窗体上有一个截图按钮,点击按钮后,程序将整个屏幕画在一个新的全屏窗体上,而后显示这个窗体.由于是全屏的窗体,而且隐藏了菜单栏、工具栏等,因此在咱们看来就好像是一个桌面的截图,而后在这个新窗体上画矩形,最后保存矩形中的内容并显示在原来的聊天窗体中.

 

  步骤:

 

  A.新建一个窗体.命名为Catch.而后设置这个窗体的FormBorderStyle为None,WindowState为Maximized.

 

  B.咱们对代码进行编辑:

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace Client
{
  public partial class Catch : Form
  {
    public Catch()
    {
      InitializeComponent();
    }

用户变量#region 用户变量

    private Point DownPoint = Point.Empty;//记录鼠标按下坐标,用来肯定绘图起点
    private bool CatchFinished = false;//用来表示是否截图完成
    private bool CatchStart = false;//表示截图开始
    private Bitmap originBmp;//用来保存原始图像
    private Rectangle CatchRect;//用来保存截图的矩形
    #endregion
    //窗体初始化操做
    private void Catch_Load(object sender, EventArgs e)
    {
      this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
      this.UpdateStyles();
      //以上两句是为了设置控件样式为双缓冲,这能够有效减小图片闪烁的问题,关于这个你们能够本身去搜索下
      originBmp = new Bitmap(this.BackgroundImage);//BackgroundImage为全屏图片,咱们另用变量来保存全屏图片
    }
    //鼠标右键点击结束截图
    private void Catch_MouseClick(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Right)
      {
        this.DialogResult = DialogResult.OK;
        this.Close();
      }
    }

//鼠标左键按下时动做
    private void Catch_MouseDown(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Left)
      {
        if (!CatchStart)
        {//若是捕捉没有开始
          CatchStart = true;
          DownPoint = new Point(e.X, e.Y);//保存鼠标按下坐标
        }
      }
    }
    private void Catch_MouseMove(object sender, MouseEventArgs e)
    {
      if (CatchStart)
      {//若是捕捉开始
        Bitmap destBmp = (Bitmap)originBmp.Clone();//新建一个图片对象,并让它与原始图片相同
        Point newPoint = new Point(DownPoint.X, DownPoint.Y);//获取鼠标的坐标
        Graphics g = Graphics.FromImage(destBmp);//在刚才新建的图片上新建一个画板
        Pen p = new Pen(Color.Blue,1);
        int width = Math.Abs(e.X - DownPoint.X), height = Math.Abs(e.Y - DownPoint.Y);//获取矩形的长和宽
        if (e.X < DownPoint.X)
        {
          newPoint.X = e.X;
        }
        if (e.Y < DownPoint.Y)
        {
          newPoint.Y = e.Y;
        }

CatchRect = new Rectangle(newPoint,new Size(width,height));//保存矩形
        g.DrawRectangle(p,CatchRect);//将矩形画在这个画板上
        g.Dispose();//释放目前的这个画板
        p.Dispose();
        Graphics g1 = this.CreateGraphics();//从新新建一个Graphics类
        //若是以前那个画板不释放,而直接g=this.CreateGraphics()这样的话没法释放掉第一次建立的g,由于只是把地址转到新的g了.如同string同样
        g1 = this.CreateGraphics();//在整个全屏窗体上新建画板

        g1.DrawImage(destBmp,new Point(0,0));//将刚才所画的图片画到这个窗体上
        //这个也能够属于二次缓冲技术,若是直接将矩形画在窗体上,会形成图片抖动而且会有无数个矩形.
        g1.Dispose();
        destBmp.Dispose();//要及时释放,否则内存将会被大量消耗
      }
    }
    private void Catch_MouseUp(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Left)
      {
        if (CatchStart)
        {
          CatchStart = false;
          CatchFinished = true;
        }
      }
    }
    //鼠标双击事件,若是鼠标位于矩形内,则将矩形内的图片保存到剪贴板中

private void Catch_MouseDoubleClick(object sender, MouseEventArgs e)
    {
      if (e.Button == MouseButtons.Left&&CatchFinished)
      {
        if (CatchRect.Contains(new Point(e.X, e.Y)))
        {
          Bitmap CatchedBmp = new Bitmap(CatchRect.Width, CatchRect.Height);//新建一个于矩形等大的空白图片
          Graphics g = Graphics.FromImage(CatchedBmp);
      

          g.DrawImage(originBmp, new Rectangle(0, 0, CatchRect.Width, CatchRect.Height), CatchRect, GraphicsUnit.Pixel);
          //把orginBmp中的指定部分按照指定大小画在画板上
          Clipboard.SetImage(CatchedBmp);//将图片保存到剪贴板
          g.Dispose();
          CatchFinished = false;
          this.BackgroundImage = originBmp;
          CatchedBmp.Dispose();
          this.DialogResult = DialogResult.OK;

          this.Close();
        }
      }
    }
  }
}

C.建立了Catch窗体后,咱们在截图按钮(位于聊天窗体上)上加入如下事件:

    private void bCatch_Click(object sender, EventArgs e)
    {if (bCatch_HideCurrent.Checked)
      {
        this.Hide();//隐藏当前窗体
        Thread.Sleep(50);//让线程睡眠一段时间,窗体消失须要一点时间
        Catch CatchForm = new Catch();

        Bitmap CatchBmp = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);//新建一个和屏幕大小相同的图片    
        Graphics g = Graphics.FromImage(CatchBmp);
        g.CopyFromScreen(new Point(0, 0), new Point(0, 0), new Size(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height));//保存全屏图片
        CatchForm.BackgroundImage = CatchBmp;//将Catch窗体的背景设为全屏时的图片
        if (CatchForm.ShowDialog() == DialogResult.OK)
        {//若是Catch窗体结束,就将剪贴板中的图片放到信息发送框中
          IDataObject iData = Clipboard.GetDataObject();
          DataFormats.Format myFormat = DataFormats.GetFormat(DataFormats.Bitmap);
          if (iData.GetDataPresent(DataFormats.Bitmap))
          {

            richtextbox1.Paste(myFormat);
            Clipboard.Clear();//清除剪贴板中的对象
          }
          this.Show();//从新显示窗体
        }
      }
    }

这样咱们的截图功能便完成了.

  我想对于初学者来讲如何消去第一次绘制的图片是个比较困难的问题.若是没有采起措施,你会发现只要你鼠标移动,就会画一个矩形,这样便会出现N多的矩形,而咱们只是要最后的那一个.

  通常解决这种问题的方法有两种:

  1.就是在绘制第二个图形时,咱们先用与底色相同的颜色将上次绘制的图形从新绘制一下.但这每每须要底色为纯色时使用.

  2.咱们并不直接将图形画在画板上,咱们用一个图片A来保存原画板上的图片.而后再新建一个与图片A相同的图片B,将咱们要绘制的图形画在该图片B上,而后再将该图片B画在画板上.这样图片A并无被改变.因而第二次画的时候咱们仍是一样新建一个与图片A相同的图片进行绘制.那么上一次的图形就不会被保留下来.问题也就解决了.

  下一次,向你们介绍如何作一个仿windows画板的程序.

  前几篇我已经向你们介绍了如何使用GDI+来绘图,并作了一个截图的实例,这篇我向你们介绍下如何来作一个相似windows画图的工具.

主要实现功能:画直线,矩形,橡皮,圆形,切换颜色,打开图片,保存图片,清除图片,手动调节画布大小;软件刚启动时,为一张空白画布,咱们能够直接在画布上绘图,也能够经过菜单中的“打开”,导入一张图片,而后咱们就能够在这张图片上进行绘制。

  平台:VS2005 WINFORM

  因为代码过多,在这里只简要介绍下制做步骤,提供你们工程下载.

  1.对整个界面进行布局.

  2.实现绘图工具的功能

  3.实现颜色拾取的功能,这里咱们直接拿上次写的自定义控件来用.

  4.实现菜单功能

  5.实现手动调节画布大小的功能

  6.测试

 

实现绘图工具的功能

  为了让代码藕合度小点,稍许用了些设计模式,由于不是很会,因此代码仍是有点乱乱的,绘图工具的这些功能块所有写在了DrawTools这个类里.那么在主窗体中,只须要调用这个类来完成绘制就好了,而不须要过多的涉及到具体的绘图代码。绘图工具这个类提供的主要工具就是:铅笔、橡皮、直线、矩形、圆形、实心矩形、实心圆形。关于这些功能块的代码,并不难,只要你们对认真看过前几篇内容,那应该都看得懂。

  这里有几点要注意:

 

  1.如何防止记录没必要要的绘图过程当中的痕迹?

  这个问题在第三篇中有提到过,你们不妨先去看看那一篇。为了让代码看起来可读性高点,我设置了两个Image变量,finishingImg用来保存绘图过程当中的痕迹,orginalImg用来保存已完成的绘图过程和初始时的背景图片。

  2.这个类如何与主窗体进行通讯?

  固然若是直接将这些功能块写在主窗体中天然没有这个问题。可是那样代码会显得很混杂,若是只是工具代码出现问题就须要改整个项目。我在这里经过定义方法和属性,让主窗体经过给属性赋值将画板画布以及颜色什么的信息传给这个工具类,而后经过调用相应的工具方法来使用这些工具。

  3.关键属性

  要想让这些工具能正常使用,必须传递给他如下几样东西:目标画板(也就是picturebox),绘图颜色,原始画布。

  实现菜单功能

这里就须要咱们对文件的操做有一点了解,你们能够去查一下相关资料。

  难点主要就是“打开”这个菜单项的实现

  咱们要实现将打开后的图片在修改后从新保存就必须让文件在打开后就能关闭,不然就会由于文件打开而没法覆盖原文件。就会致使编译时弹出“GDI  通常性错误”。因此根据网上其它朋友的作法就是先将打开的图片经过GDI+将图片画到另外一个画布上,而后及时关闭打开的图片和用来绘制该图片的画板。详见http://www.wanxin.org/redirect.php?tid=3&goto=lastpost

private void openPic_Click(object sender, EventArgs e)

这里就须要咱们对文件的操做有一点了解,你们能够去查一下相关资料。

  难点主要就是“打开”这个菜单项的实现

  咱们要实现将打开后的图片在修改后从新保存就必须让文件在打开后就能关闭,不然就会由于文件打开而没法覆盖原文件。就会致使编译时弹出“GDI  通常性错误”。因此根据网上其它朋友的作法就是先将打开的图片经过GDI+将图片画到另外一个画布上,而后及时关闭打开的图片和用来绘制该图片的画板。详见http://www.wanxin.org/redirect.php?tid=3&goto=lastpost

private void openPic_Click(object sender, EventArgs e)

{
      OpenFileDialog ofd = new OpenFileDialog();//实例化文件打开对话框
      ofd.Filter = "JPG|*.jpg|Bmp|*.bmp|全部文件|*.*";//设置对话框打开文件的括展名
      if (ofd.ShowDialog() == DialogResult.OK)
      {
        Bitmap bmpformfile = new Bitmap(ofd.FileName);//获取打开的文件
        panel2.AutoScrollPosition = new Point(0,0);//将滚动条复位
        pbImg.Size = bmpformfile.Size;//调整绘图区大小为图片大小
        reSize.Location = new Point(bmpformfile.Width, bmpformfile.Height);//reSize为我用来实现手动调节画布大小用的
        //由于咱们初始时的空白画布大小有限,"打开"操做可能引发画板大小改变,因此要将画板从新传入工具类

        dt.DrawTools_Graphics = pbImg.CreateGraphics();
        Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height);
        Graphics g = Graphics.FromImage(bmp);
        g.FillRectangle(new SolidBrush(pbImg.BackColor), new Rectangle(0, 0, pbImg.Width, pbImg.Height));//不使用这句话,那么这个bmp的背景就是透明的
        g.DrawImage(bmpformfile, 0, 0,bmpformfile.Width,bmpformfile.Height);//将图片画到画板上
        g.Dispose();//释放画板所占资源
        //不直接使用pbImg.Image = Image.FormFile(ofd.FileName)是由于这样会让图片一直处于打开状态,也就没法保存修改后的图片
        bmpformfile.Dispose();//释放图片所占资源
        g = pbImg.CreateGraphics();
        g.DrawImage(bmp, 0, 0);

        g.Dispose();
        dt.OrginalImg = bmp;
        bmp.Dispose();
        sFileName = ofd.FileName;//储存打开的图片文件的详细路径,用来稍后能覆盖这个文件
        ofd.Dispose();
      }
    }

清除图像其实就是用白色填充整个画布,其它的都比较简单,这就不具体讲了。

  实现手动调节画布大小

  网上有人说使用API,可是我的以为仍是使用其它控件帮忙比较简单,至少咱们还看得懂。

  思路:放置一个picturebox1(尺寸为5*5),将它固定在主画板的右下角,而后改变鼠标进入时的Cursor为箭头形状,设置鼠标按下移动时的事件,让该picturebox1 跟随鼠标移动。当鼠标松开时,将主画板的右下角坐标调整为picturebox1的坐标。

  下面来看下代码:

  其中的reSize就是咱们用来帮忙的picturebox控件

private bool bReSize = false;//是否改变画布大小
    private void reSize_MouseDown(object sender, MouseEventArgs e)
    {
      bReSize = true;//当鼠标按下时,说明要开始调节大小
    }
    private void reSize_MouseMove(object sender, MouseEventArgs e)
    {

     if (bReSize)
      {
        reSize.Location = new Point(reSize.Location.X + e.X, reSize.Location.Y + e.Y);
      }
    }
    private void reSize_MouseUp(object sender, MouseEventArgs e)
    {
      bReSize = false;//大小改变结束
      //调节大小可能形成画板大小超过屏幕区域,因此事先要设置autoScroll为true.
      //可是滚动条的出现反而增长了咱们的难度,由于滚动条上下移动并不会自动帮咱们调整图片的坐标。

      if (bReSize)
      {
        reSize.Location = new Point(reSize.Location.X + e.X, reSize.Location.Y + e.Y);
      }
    }
    private void reSize_MouseUp(object sender, MouseEventArgs e)
    {
      bReSize = false;//大小改变结束
      //调节大小可能形成画板大小超过屏幕区域,因此事先要设置autoScroll为true.
      //可是滚动条的出现反而增长了咱们的难度,由于滚动条上下移动并不会自动帮咱们调整图片的坐标。

      //这是由于GDI绘图的坐标系不仅一个,好像有三个,没有仔细了解,一个是屏幕坐标,一个是客户区坐标,还个是文档坐标。
      //滚动条的上下移动改变的是文档的坐标,可是客户区坐标不变,而location属性就属于客户区坐标,因此咱们直接计算会出现错误
      //这时咱们就须要知道文档坐标与客户区坐标的偏移量,这就是AutoScrollPostion能够提供的
      pbImg.Size = new Size(reSize.Location.X - (this.panel2.AutoScrollPosition.X), reSize.Location.Y - (this.panel2.AutoScrollPosition.Y));
      dt.DrawTools_Graphics = pbImg.CreateGraphics();//由于画板的大小被改变因此必须从新赋值

      //另外画布也被改变因此也要从新赋值
      Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height);
      Graphics g = Graphics.FromImage(bmp);
      g.FillRectangle(new SolidBrush(Color.White), 0, 0, pbImg.Width, pbImg.Height);
      g.DrawImage(dt.OrginalImg, 0, 0);
      g.Dispose();
      g = pbImg.CreateGraphics();
      g.DrawImage(bmp, 0, 0);
      g.Dispose();
      dt.OrginalImg = bmp;
      bmp.Dispose();
    }

此时就能够经过拖动那个小方块来调节图片大小了。

相关文章
相关标签/搜索