当前位置: 首页 > news >正文

基于C#窗体+GDI+绘图实现分形树

实验:分形树

一.  引言

实验目的:继续学习C#窗体应用程序的图形化界面设计以及GDI+绘图的一些基本指示,通过制作各种类型的分形树增强对于递归的理解,在创造分形图形的过程中感受编程的快乐

Tutorial任务

1.制作不同类型的分形图形(本次演示的是两种不同类型的分形树)

2.设计一个美观合理的UI界面

3.巩固关于打开文件和保存文件的操作

二.实验环境

Windows系统下的visual studio 2017

C#窗体应用程序

三.实验流程

1.实验原理介绍

首先,我们先介绍一下分形图形的绘制过程(由于分形图形的种类有很多种,但大多数是采用了递归的思想进行绘制,所以本文以较为常见和相对来讲难度不那么大的分形树进行讲解,感兴趣的读者可以尝试去制作更为复杂的分形图形)

首先,我们先来看一段分形树绘制的核心代码

private void paintFractalTree(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line)
{float yf = yo - (sin(Converttohudu(dangle)) * length);//sin函数将角度转换成弧度float xf = xo - (cos(Converttohudu(dangle)) * length);line.DrawLine(pen, xo, yo, xf, yf);paintFractalTree(pen2, xf, yf, depth - 1, dangle + angle, length, angle, line);paintFractalTree(pen3, xf, yf, depth - 1, dangle - angle, length, angle, line);//画左分支
}
在上述的代码中我们已经看到了递归的影子,接下来会对递归流程进行详细介绍:

百度百科当中关于分形这个几何学术语的解释如下:

分形,具有以非整数维形式充填空间的形态特征。通常被定义为“一个粗糙或零碎的几何形状,可以分成数个部分,且每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。
分形使人们觉悟到科学与艺术的融合,数学与艺术审美上的统一,使从前枯燥的数学不再仅仅是抽象的哲理,而是具体的感受;不再仅仅是揭示一类存在,而是一种艺术创作,分形搭起了科学与艺术的桥梁。

通过"每一部分都(至少近似地)是整体缩小后的形状”,即具有自相似的性质。这一点也能够看出做出分形图案的主要思想就是递归.

我们先考虑当分形树的分形元是下述图案的情况:

这个分形元在C#中是怎么绘制出来的呢?

1.首先,我们设A点坐标为(x,y),B点坐标为(x0,y0)

2.接下来绘制AB这条直线,也就是树的主干部分,即(x,y)--(x0,y0)直线,同时注意到,AB这条线段与横轴的夹角为90度,那么我们就以这个90度作为基础,结合用户输入的angle角度的可调整值,来绘制接下来的分形树分支

3.假设C点坐标为(x1,y1),则由三角函数的性质,结合上图中的α角我们可以看出C点的横坐标为x0-L'×cos(90°-α)(注意,这里面的L'指的是枝干的长度,为了让我们的分形树绘制的更加美观,这里面取枝干长度L'=2L/3,α就是用户输入的角度值)

4.类比3的思路,C点的y坐标y1为y0-L'×sin(90°-α)(值得注意的是,绘图板的左上角为(0,0),所以在向上绘制的时候相应的y坐标就会减少)

5.结合刚才的思想,我们很容易根据图中的提示绘制出右侧的分支,D点的横坐标为x0-L'×cos(90°+α),D点的纵坐标为y0-L'×sin(90°+α),如上图所示

6.连接BC,BD,分形元绘制完成

2.实验过程

这个时候我们回过头来看刚才的那段代码,就显得清楚多了

private void paintFractalTree(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line)//dangle是目前的角度,也就是刚才说的(90+angle)或者(90-angle)传入作为dangle(其实就是需要计算正余弦的角度)
{float yf = yo - (sin(Converttohudu(dangle)) * length);//sin函数将角度转换成弧度,算出新的点所应该在的位置的纵坐标float xf = xo - (cos(Converttohudu(dangle)) * length);//算出新的点所应该在的位置的横坐标line.DrawLine(pen, xo, yo, xf, yf);//将原来点和新的点之间连一条线paintFractalTree(pen2, xf, yf, depth - 1, dangle + angle, length, angle, line);//递归画右分支paintFractalTree(pen3, xf, yf, depth - 1, dangle - angle, length, angle, line);//画左分支
}
//对于递归部分而言,需要注意再下一次递归的时候初始角度相当于不再是90度了,而是90-a(如果是左分支的话),或者是(90+a)(如果是右分支的话),以后每次递归都需要更新前面的角度值(也就是每次传入参数的dangle),通过设置递归深度可以绘制出如下的美丽图形:

注:读者可以在上述基础上做出创新,比如给这颗分形树绘制不同的颜色,让不同的分支长度不同等等,在这里我绘制了一种随机颜色的分形树,效果和代码部分如下:

private void paintFractalTree(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line){if (depth > -1){length = length * 2 / 3;float yf = yo - (sin(Converttohudu(dangle)) * length);//转换成弧度float xf = xo - (cos(Converttohudu(dangle)) * length);line.DrawLine(pen, xo, yo, xf, yf);Random rnd = new Random();// 随机色生成,可以画出彩色的树Color myColor1 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen2 = new Pen(myColor1);paintFractalTree(pen2, xf, yf, depth - 1, dangle + angle, length, angle, line);//画右分支Color myColor2 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen3 = new Pen(myColor2);paintFractalTree(pen3, xf, yf, depth - 1, dangle - angle, length, angle, line);//画左分支}}

几个较为美观的色彩图如下图所示:

通过不同的angle角度的设置,可以产生许多好看的图形,以上为30度和60度以及90度设置下的图形,读者可以自行尝试其他方案,绘制更好看的分形树

3.实验创新

绘制出了上述的最简单的分形树之后,我们也许会思考其他分形元是不是会画出其他神奇的分形图案?答案是肯定的,接下来我会以我绘制的第二种分形树为例,向读者展示绘制不同种分形树的一般方法:

首先来看第二种分形树的效果图:

细心的读者可能已经发现了,实际上绘制一颗分形树的要点就在于一定要掌握好递归的思想,我们简单看一下上图就不难分析出分形元是如下这种图形了:

再分解一下其实就是图中的黑色部分了,也就是我们首先要绘制出黑色的部分,具体的原理读者可以自行尝试一下,在这里直接给出核心代码:

private void paintFractalTree2(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line){double attach = 50;if (depth > -1){float changelength = length * 2 / 3;float x1 = xo - (cos(Converttohudu(dangle))) * length;float y1 = yo- (sin(Converttohudu(dangle)))*length;float x2 = x1 - (cos(Converttohudu(dangle))) * changelength;float y2 = y1 - (sin(Converttohudu(dangle))) * changelength;float x2r = x1 - (cos(Converttohudu(dangle+angle))) * changelength;float y2r = y1 - (sin(Converttohudu(dangle+angle))) * changelength;float x2l = x1 - (cos(Converttohudu(dangle -angle))) * changelength;float y2l = y1 - (sin(Converttohudu(dangle - angle))) * changelength;line.DrawLine(pen, xo, yo, x1, y1);//line.DrawLine(pen, x1, y1, x2, y2);line.DrawLine(pen, x1, y1, x2r, y2r);//line.DrawLine(pen, x1, y1, x2l, y2l);Random rnd = new Random();// 随机色生成Color myColor1 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen2 = new Pen(myColor1);paintFractalTree2(pen2, x2r, y2r, depth - 1, dangle + angle*3/2 + angle, length*2/3, angle, line);//画右分支,这里让角度发生了变化,起到一个图形"折起来"的效果paintFractalTree2(pen2, x2r, y2r, depth - 1, dangle + angle * 3 / 2 - angle, length*2/3, angle, line);//画左分支//paintFractalTree2(pen2, x2l, y2l, depth - 1, dangle - angle * 3 / 2 - angle, length*2/3, 
//这句话加上去又是一种新的图案了,可以试试?Color myColor2 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen3 = new Pen(myColor2);}}

到这里,我们的两种分形树就绘制完成了,可以结合之前的画图软件的操作将我们喜欢的分形树图案保存起来,也可以打开之前绘制好的分形树图案,绘制的窗体界面如下:

总的代码如下图所示(窗体设计部分已省去)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.IO;namespace FractalTree
{public partial class Form1 : Form{bool click1 = false;bool click2 = false;//文件名public string curFileName= @"E:\期末考试复习\C#\分形树v3.0\FractalTree\FractalTree\bin\Debug\picture.jpg";//图像对象private System.Drawing.Bitmap curBitmap;//public Bitmap bmp = new Bitmap(769, 650);public Form1(){InitializeComponent();this.MaximizeBox = false;this.Size = new Size(769, 650);this.CenterToScreen();this.FormBorderStyle = FormBorderStyle.FixedSingle;picturebox.BorderStyle = BorderStyle.Fixed3D;this.Text = "分形树";curBitmap = (Bitmap)Image.FromFile(curFileName);//Graphics g = Graphics.FromImage(bmp);//picturebox.Image = ;//curBitmap = (Bitmap)bmp;}public void pictureBox_Paint(object sender, PaintEventArgs e){//Graphics g = this.CreateGraphics();//picturebox.Refresh();if (curBitmap != null){//使用DrawImage的方法绘制图像Graphics g = e.Graphics;//curBitmap.Width, curBitmap.Height图像的宽度和高度g.DrawImage(curBitmap, 0, 0, curBitmap.Width, curBitmap.Height);}if (click1){//picturebox.Refresh();//Graphics g = e.Graphics;//if(curFileName!=null)curBitmap = (Bitmap)Image.FromFile(curFileName);//else//  curBitmap = bmp;this.picturebox.Image = curBitmap;Graphics g = Graphics.FromImage(this.picturebox.Image);Pen pen = new Pen(Color.Black);float length = Convert.ToSingle(tb1.Text) * 10;double angle = Convert.ToDouble(nud3.Value);int depth = Convert.ToInt32(nud1.Value);float xo = (picturebox.Width / 2) - 2;float yo = picturebox.Height - Convert.ToSingle(5.4);  paintFractalTree(pen, xo, yo, depth, 90, length, angle, g);//那个90是确定这颗树往哪个方向开始长的pen.Dispose();click1 = false;}else if(click2){//picturebox.Refresh();if (curFileName != null)curBitmap = (Bitmap)Image.FromFile(curFileName);//else//  curBitmap = bmp;this.picturebox.Image = curBitmap;Graphics g = Graphics.FromImage(this.picturebox.Image);//Graphics g = e.Graphics;Pen pen = new Pen(Color.Black);float length = Convert.ToSingle(tb1.Text) * 10;double angle = Convert.ToDouble(nud3.Value);int depth = Convert.ToInt32(nud1.Value);float xo = (picturebox.Width / 2) - 2;float yo = picturebox.Height - Convert.ToSingle(5.4);paintFractalTree2(pen, xo, yo, depth, 90, length*1/2, angle, g);//那个90是确定这颗树往哪个方向开始长的pen.Dispose();click2 = false;}}private void paintFractalTree(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line){if (depth > -1){length = length * 2 / 3;float yf = yo - (sin(Converttohudu(dangle)) * length);//转换成弧度float xf = xo - (cos(Converttohudu(dangle)) * length);line.DrawLine(pen, xo, yo, xf, yf);Random rnd = new Random();// 随机色生成Color myColor1 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen2 = new Pen(myColor1);paintFractalTree(pen2, xf, yf, depth - 1, dangle + angle, length, angle, line);//画右分支Color myColor2 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen3 = new Pen(myColor2);paintFractalTree(pen3, xf, yf, depth - 1, dangle - angle, length, angle, line);//画左分支}}private void paintFractalTree2(Pen pen, float xo, float yo, int depth, double dangle, float length, double angle, Graphics line){double attach = 50;if (depth > -1){float changelength = length * 2 / 3;float x1 = xo - (cos(Converttohudu(dangle))) * length;float y1 = yo- (sin(Converttohudu(dangle)))*length;float x2 = x1 - (cos(Converttohudu(dangle))) * changelength;float y2 = y1 - (sin(Converttohudu(dangle))) * changelength;float x2r = x1 - (cos(Converttohudu(dangle+angle))) * changelength;float y2r = y1 - (sin(Converttohudu(dangle+angle))) * changelength;float x2l = x1 - (cos(Converttohudu(dangle -angle))) * changelength;float y2l = y1 - (sin(Converttohudu(dangle - angle))) * changelength;line.DrawLine(pen, xo, yo, x1, y1);//line.DrawLine(pen, x1, y1, x2, y2);line.DrawLine(pen, x1, y1, x2r, y2r);//line.DrawLine(pen, x1, y1, x2l, y2l);Random rnd = new Random();// 随机色生成Color myColor1 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen2 = new Pen(myColor1);paintFractalTree2(pen2, x2r, y2r, depth - 1, dangle + angle*3/2 + angle, length*2/3, angle, line);//画右分支paintFractalTree2(pen2, x2r, y2r, depth - 1, dangle + angle * 3 / 2 - angle, length*2/3, angle, line);//画右分支//paintFractalTree2(pen2, x2l, y2l, depth - 1, dangle - angle * 3 / 2 - angle, length*2/3, angle, line);//画右分支//paintFractalTree2(pen2, x2l, y2l, depth - 1, dangle - angle * 3 / 2 + angle, length * 2 / 3, angle, line);//画右分支Color myColor2 = Color.FromArgb(rnd.Next(0, 255), /*红色*/rnd.Next(0, 255), /*绿色*/rnd.Next(0, 255)  /*蓝色*/);Pen pen3 = new Pen(myColor2);}}private float cos(double angle){return (float)Math.Cos(angle);}//求cos的值private float sin(double angle){return (float)Math.Sin(angle);}//求sin的值private double Converttohudu(double angle){return (Math.PI * angle) / 180;}//将角度转换成弧度private void textBox1_KeyPress(object sender, KeyPressEventArgs e){if (!Char.IsDigit(e.KeyChar) && !Char.IsControl(e.KeyChar) && !e.KeyChar.Equals(',')){e.Handled = true;//可以从文本框中获取键盘读入的数据}}private void drawbt_Click(object sender, EventArgs e){click1 = true;picturebox.Refresh();}private void nud3_ValueChanged(object sender, EventArgs e){}private void nud1_ValueChanged(object sender, EventArgs e){}private void Form1_Load(object sender, EventArgs e){}private void 打开ToolStripMenuItem_Click(object sender, EventArgs e){OpenFileDialog opnDlg = new OpenFileDialog();opnDlg.Filter = "所有图像文件 | *.bmp; *.pcx; *.png; *.jpg; *.gif;" +"*.tif; *.ico; *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf|" +"位图( *.bmp; *.jpg; *.png;...) | *.bmp; *.pcx; *.png; *.jpg; *.gif; *.tif; *.ico|" +"矢量图( *.wmf; *.eps; *.emf;...) | *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf";opnDlg.Title = "打开图像文件";opnDlg.ShowHelp = true;if (opnDlg.ShowDialog() == DialogResult.OK){curFileName = opnDlg.FileName;try{curBitmap = (Bitmap)Image.FromFile(curFileName);}catch (Exception exp){MessageBox.Show(exp.Message);}}//对窗体进行重新绘制,这将强制执行paint事件处理程序Invalidate();picturebox.Refresh();}//private void Form1_Paint(object sender, PaintEventArgs e)//{//    //使用窗体的Paint事件的PaintEventArgs属性来获取一个与窗体相关联的Graphic对象。//    Graphics g = e.Graphics;//    if (curBitmap != null)//    {//        //使用DrawImage的方法绘制图像//        //160,20 :显示在主窗体内,图像左上角的坐标//        //curBitmap.Width, curBitmap.Height图像的宽度和高度//        g.DrawImage(curBitmap, 160, 20, curBitmap.Width, curBitmap.Height);//    }//}public void 保存ToolStripMenuItem_Click(object sender, EventArgs e ){ }private void button1_Click(object sender, EventArgs e){click2 = true;picturebox.Refresh();}private void 保存ToolStripMenuItem_Click_1(object sender, EventArgs e){if (curBitmap == null){return;}Graphics g = Graphics.FromImage(curBitmap);SaveFileDialog saveDlg = new SaveFileDialog();saveDlg.Title = "保存为";saveDlg.OverwritePrompt = true;saveDlg.Filter ="BMP文件 (*.bmp) | *.bmp|" +"Gif文件 (*.gif) | *.gif|" +"JPEG文件 (*.jpg) | *.jpg|" +"PNG文件 (*.png) | *.png";saveDlg.ShowHelp = true;if (saveDlg.ShowDialog() == DialogResult.OK){string fileName = saveDlg.FileName;string strFilExtn = fileName.Remove(0, fileName.Length - 3);switch (strFilExtn){case "bmp":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Bmp);break;case "jpg":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);break;case "gif":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Gif);break;case "tif":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Tiff);break;case "png":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);break;default:break;}}}private void 另存为ToolStripMenuItem_Click(object sender, EventArgs e){if (curBitmap == null){return;}Graphics g = Graphics.FromImage(curBitmap);SaveFileDialog saveDlg = new SaveFileDialog();saveDlg.Title = "保存为";saveDlg.OverwritePrompt = true;saveDlg.Filter ="BMP文件 (*.bmp) | *.bmp|" +"Gif文件 (*.gif) | *.gif|" +"JPEG文件 (*.jpg) | *.jpg|" +"PNG文件 (*.png) | *.png";saveDlg.ShowHelp = true;if (saveDlg.ShowDialog() == DialogResult.OK){string fileName = saveDlg.FileName;string strFilExtn = fileName.Remove(0, fileName.Length - 3);switch (strFilExtn){case "bmp":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Bmp);break;case "jpg":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Jpeg);break;case "gif":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Gif);break;case "tif":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Tiff);break;case "png":curBitmap.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);break;default:break;}}}}}

至此,我们的分形树以及相关代码实现就完成了。

相关文章:

  • 华锐视点历经十八年沉淀所形成的产品特性
  • Electron-vite中ELECTRON_RENDERER_URL环境变量如何被设置的
  • java 加入本地lib jar处理方案
  • 如何创建并使用极狐GitLab 议题模板?
  • HarmonyOS运动开发:如何监听用户运动步数数据
  • 基于Lucene的多场景检索系统开发指南
  • docker 通过定时任务恢复MySQL数据库
  • P1494 [国家集训队] 小 Z 的袜子 Solution
  • Java 基础--运算符全解析
  • MySQL 连接池 (Pool) 常用方法详解
  • HTML应用指南:利用POST请求获取全国达美乐门店位置信息
  • 【网络编程】UDP协议 和 Socket编程
  • Seaborn一个用于统计图形绘制的高级API
  • 基于C++数据结构双向循环链表实现的贪吃蛇
  • AgeTravel | 银发文娱旅游一周新鲜事
  • 使用高德MCP+AI编程工具打造一个旅游小助手
  • 线程同步与互斥核心要点整理
  • 精益数据分析(30/126):电商商业模式的深度剖析与关键指标解读
  • linux安装ragflow
  • 《从线性到二维:CSS Grid与Flex的布局范式革命与差异解析》
  • 国泰海通合并后首份业绩报告出炉:一季度净利润增逾391%
  • 五一小长假,带着小狗去上海音乐厅
  • 宋徽宗《芙蓉锦鸡图》亮相,故宫首展历代动物绘画
  • 运动健康|不同能力跑者,跑步前后营养补给差别这么大?
  • 辽宁辽阳火灾事故饭店经营者已被控制,善后处置全面展开
  • 西班牙遭遇史上最严重停电,已进入国家紧急状态