实验要求
目的:熟悉图形化界面程序,开发有趣应用。
2.2 参考分形的概念,绘制分形树或者其他分形图形。要求可以对图形进行保存和打开等操作。
需求分析
抛开分形树不看,这次的实验就是简单的图片浏览器,并且无需编辑功能,只需实现读写即可。这部分内容我们其实已经在实验#2实现过,本质上就是调用C#的自带控件。所以本次实验的重点即为 分形树 的绘制。
分形树
分形(Fractal),具有以非整数维形式充填空间的形态特征。通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
而分形树,其基本单元就是一棵“Y”型的“树”,再将这棵树的两个分叉分别作为另外两棵树的根,以此不断扩张。换句话说,类似于一棵上下颠倒的 满二叉树 。
结合与其结构类似的满二叉树,可以想到,我们应该用 递归 来实现分形树。
代码实现
定义变量
1 2 3 4 5 6 7
| Graphics graphics; Bitmap paintImg; int pWidth; int pHeight; Pen pen; float penWidth = 5.0f; float drawLength = 100.0f;
|
graphics
与 paintImg
变量用来绘制分形树并将其显示在屏幕上。而 penWidth
与 drawLength
变量则是用于限定整棵分形树的根的宽度与长度的。
初始化画布
1 2 3 4 5 6 7 8 9 10 11 12
| pWidth = pictureBox.Width; pHeight = pictureBox.Height;
paintImg = new Bitmap(pWidth, pHeight); graphics = Graphics.FromImage(paintImg); graphics.Clear(Color.Yellow); pictureBox.Image = paintImg;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
由于要在程序中实时显示图片,所以我们需要一个 PictureBox
控件来存放当前图片。然后用该控件的大小创建一个空的 Bitmap
,来作为绘制分形树的图片,并将其置于画布上,然后将背景设为一个好看的颜色。最终将其显示在PictureBox
上。由于画布的默认线条较为离散,我们需要一行 SmoothingMode
的设置对其进行抗锯齿化。
绘制分形树
1
| private void Draw_FractalTree(Point start, int fractalTimes,float width,float length,double angle,int colorA) { }
|
前文已经提到过,绘制分形树的函数部分是一个递归函数,所以我们先将其结构写出来。我们来分析一下分形树的每部分基础结构所需要的参数:
- 起点
start
用于确定当前子树的根的起点坐标,我们从这个点开始进行绘制。
fractalTimes
是作为递归次数的标记,由于递归函数总是需要一个终点的,我们就设置一个递归次数标记,当其值为 0 时即结束绘制。
width
, length
与 angle
则分别对应当前子树的笔刷宽度,根长度与偏转角。为了让我们的树更加美观,这三个参数在每次绘制中都应该做少许改变。
colorA
则是作为笔刷颜色的透明度参数,这个参数只是用来使得树看起来更美观,不是必要存在。
首先先为递归设置好退出条件,使绘制部分拥有出口:if (fractalTimes == 0) return;
。
接下来就可以设置笔刷的基本参数然后绘制线条了:
1 2 3 4 5
| pen = new Pen(Color.FromArgb(colorA, 0, 0, 0), width); pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; Point end = new Point(start.X + (int)(length * Math.Sin(angle)), start.Y - (int)(length * Math.Cos(angle))); graphics.DrawLine(pen, start, end);
|
以上内容绘制了二叉中的其中一叉,绘制完后即可从该线段的终点为下一棵子树的起点继续进行绘制,并且对参数做一些细微改变:
1
| Draw_FractalTree(end, fractalTimes - 1, width * 0.8f, length * 0.8f, angle + Math.PI / 8, colorA - 20);
|
同理,我们可以画出另外一叉:
1 2 3
| end = new Point(start.X + (int)(length * Math.Sin(angle)), start.Y - (int)(length * Math.Cos(angle))); graphics.DrawLine(pen, start, end); Draw_FractalTree(end, fractalTimes - 1, width * 0.8f, length * 0.8f, angle - Math.PI / 8, colorA - 20);
|
最后我们在主体函数中调用该函数,并设置好基础参数,就可以运行了:
1
| Draw_FractalTree(new Point(pWidth / 2, pHeight), 12, penWidth, drawLength, 0, 255);
|
打开/保存文件
打开图像可以直接调用C#的控件 OpenFileDialog
,同样,保存图像可以调用 SaveFileDialog
。由于我们处理的是图像文件,所以把 DefaultExt
(默认扩展名)一项设置为 PNG
;把 Filter
(文件筛选器)设置为 PNG(\*.png)|\*.png|JPEG(\*.jpg;\*.jpeg)|\*.jpg
。
接着可以创建一个 MenuStrip
套入这两个功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void 打开OToolStripMenuItem1_Click(object sender, EventArgs e) { if (openFileDialog.ShowDialog() == DialogResult.OK) { paintImg = (Bitmap)Image.FromFile(openFileDialog.FileName); pictureBox.Image = paintImg; } }
private void 保存SToolStripMenuItem_Click(object sender, EventArgs e) { if (saveFileDialog.ShowDialog() == DialogResult.OK) { paintImg.Save(saveFileDialog.FileName, ImageFormat.Png); } }
|
完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Windows.Forms;
namespace FractalTree { public partial class MainForm : Form { Graphics graphics; Bitmap paintImg; int pWidth; int pHeight; Pen pen; float penWidth = 5.0f; float drawLength = 100.0f;
public MainForm() { InitializeComponent(); }
private void MainForm_Load(object sender, EventArgs e) { pWidth = pictureBox.Width; pHeight = pictureBox.Height;
paintImg = new Bitmap(pWidth, pHeight); graphics = Graphics.FromImage(paintImg); graphics.Clear(Color.Yellow); pictureBox.Image = paintImg;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Draw_FractalTree(new Point(pWidth / 2, pHeight), 12, penWidth, drawLength, 0, 255); }
private void Draw_FractalTree(Point start, int fractalTimes,float width,float length,double angle,int colorA) { if (fractalTimes == 0) return; pen = new Pen(Color.FromArgb(colorA, 0, 0, 0), width); pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; Point end = new Point(start.X + (int)(length * Math.Sin(angle)), start.Y - (int)(length * Math.Cos(angle))); graphics.DrawLine(pen, start, end); Draw_FractalTree(end, fractalTimes - 1, width * 0.8f, length * 0.8f, angle + Math.PI / 8, colorA - 20); end = new Point(start.X + (int)(length * Math.Sin(angle)), start.Y - (int)(length * Math.Cos(angle))); graphics.DrawLine(pen, start, end); Draw_FractalTree(end, fractalTimes - 1, width * 0.8f, length * 0.8f, angle - Math.PI / 8, colorA - 20); }
private void 打开OToolStripMenuItem1_Click(object sender, EventArgs e) { if (openFileDialog.ShowDialog() == DialogResult.OK) { paintImg = (Bitmap)Image.FromFile(openFileDialog.FileName); pictureBox.Image = paintImg; } }
private void 保存SToolStripMenuItem_Click(object sender, EventArgs e) { if (saveFileDialog.ShowDialog() == DialogResult.OK) { paintImg.Save(saveFileDialog.FileName, ImageFormat.Png); } }
} }
|