C++ QT5 画一颗分形树,自己的樱花树
程序员文章站
2022-03-27 10:34:16
...
C++ QT5 画一颗分形树,自己的樱花树
准备条件:
安装QT5 QtCreator
简介:
教你从零画一棵自己的树,从项目创建到界面搭建,再到源码讲解。
效果预览
主界面
导出图片
创建项目:
启动QT creator–>New Project–>Application–>Qt Widgets Application–>choose->填写项目名称(无中文)和项目路径(无中文)–>下一步–>下一步–>更改基类为QWidget–>下一步–>完成。
配置项目属性:
由于会使用到C++11标准的Lambda表达式。
展开刚才创建的项目–>双击打开*.pro文件–>在最后添加一行:
CONFIG +=c++11
界面搭建:
展开刚才创建的项目–>展开界面文件–>双击打开widget.ui
上图为布局中的ObjectName值,注意,这里的ObjectName是和代码对应的,相当于控件的ID
(布局方式:选中一个控件容器(比如一个widget),在选择布局方式即可)
总体两个widget垂直布局,上面一个widget作为控制栏,最高高度为60,下面一个widget用来绘图。
上面一个widget进行水平布局,其中放几个spinbox和pushbutton.
源码:
widget.h
包含头文件
#include<QPainter>//Qt绘图类
#include<QPaintEvent>//Qt绘图事件类
#include<math.h>//C/C++数学函数库,用来计算角度
#include<time.h>//C/C++事件库,用来生成随机数
#include<QPaintDevice>//Qt绘图设备类
#include<QPixmap>//Qt图片类,用来加载、保存图片等
添加成员变量和函数声明
protected:
QPixmap map;//保存一张图片
void PaintNewCheeryTree();//绘制一棵新的树到map中
void DrawCherryTree();//将map中的树绘制到界面上
bool eventFilter(QObject *, QEvent *);//重写事件过滤器,接管界面重绘
void CherryTree(QPainter * painter,QPoint pstart,QPoint pend,int width,int level);//绘制一棵树的递归入口
void CherryTreeSec(QPainter * painter,QPoint pstart,int length,double direct,int width,int level);//绘制树的递归过程
widget.cpp
包含头文件:
#include<QFileDialog>//文件打开或者保存对话框
#include<QMessageBox>//提示框
构造函数中添加:
this->setWindowTitle("CherryTree dev Ugex");//设置窗口标题,喜欢叫什么就什么
ui->widgetPaintArea->installEventFilter(this);//给绘图区域安装事件过滤器,接管paint事件
//设置默认的递归层次15,树干宽度8,树干的最大占比1/4
ui->spinBoxLevel->setValue(15);
ui->spinBoxMainWidth->setValue(8);
ui->spinBoxMainRate->setValue(4);
//创建重绘按钮响应,连接信号和槽(信号:ui->pushButtonRePain的QPushButton::clicked)(槽:是一个匿名函数(Lambda表达式[=](){}))
connect(ui->pushButtonRePaint,&QPushButton::clicked,this,[=](){
PaintNewCheeryTree();
ui->widgetPaintArea->update();//更新绘图区域
});
connect(ui->pushButtonSave,&QPushButton::clicked,this,[=]{
QString filename=QFileDialog::getSaveFileName(this,"请输入保存图片的文件名");
if(filename.isEmpty())
{
QMessageBox::warning(this,"Warnning","not select file,operate cancel.");
return;
}
map.save(filename,"PNG");
});
其他成员函数实现:
void Widget::PaintNewCheeryTree(){
int wid=ui->widgetPaintArea->width();
int hei=ui->widgetPaintArea->height();
map=QPixmap(wid,hei);//创建一个和绘图区域一样大小的图片
map.fill();//将图片填充为白色(这里是默认值)
QPainter painter(&map);//将图片设置为绘图设备
int len=hei*1/ui->spinBoxMainRate->value();//或者最底部树干的长度(高度:站了绘图设备高度的1/value(),通过spinbox控件的value()方法获得显示的值)
//设置一条直线的起点和终点
QPoint pstart(wid/2,hei);
QPoint pend(wid/2,hei-len);
CherryTree(&painter,pstart,pend,ui->spinBoxMainWidth->value(),ui->spinBoxLevel->value());
}
void Widget::DrawCherryTree()
{
QPainter rpainter(ui->widgetPaintArea);//将显示的绘图区域设置为绘图设备
rpainter.drawPixmap(0,0,map);//将原来绘制在图片中的图像绘制到界面上的绘图设备
}
bool Widget::eventFilter(QObject * obj, QEvent * e)
{
//判断是否是界面上的绘图设备第一次显示show
if(ui->widgetPaintArea==obj && e->type()==QEvent::Show)
{
PaintNewCheeryTree();
}
//判断是否是绘图设备需要重绘
if(ui->widgetPaintArea==obj && e->type()==QEvent::Paint)
{
DrawCherryTree();
return true;//标识这个事件处理完毕,系统不要在下发事件了,此事件被拦截过滤了
}
return QWidget::eventFilter(obj,e);//其他事件,使用默认处理即可
}
//参数说明:
/*
painter:绘图类(画家)指针
pstart:绘图开始点
pend:绘图结束点
width:主干宽度
level:总共的递归层次,如果此参数为0就不再递归
*/
void Widget::CherryTree(QPainter * painter,QPoint pstart,QPoint pend,int width,int level)
{
srand((unsigned int)time(NULL));//设置随机数种子
QPen pen(QBrush(QColor(rand()%150,rand()%150,rand()%150)),width);//产生一个对应宽度和颜色的画笔
painter->setPen(pen);//把画笔应用到画家上
QLine line(pstart,pend);//利用开始点和结束点创建一条直线
double direct=atan2(line.dy()*1.0,line.dx()*1.0);//计算直线的方向atan2(dy,dx),这个方向是弧度制的
painter->drawLine(line);//绘制这条直线
//进行递归调用
CherryTreeSec(painter,pend,sqrt(pow(line.dx(),2.0)+pow(line.dy(),2.0)),direct,width-1,level-1);
}
/*
参数说明:
painter:画家指针
pstart:开始绘制点的坐标
length:上一个树枝的长度
direct:上一个树枝的方向
width:新树枝的宽度
level:当前剩余要绘制的层次
*/
void Widget::CherryTreeSec(QPainter * painter,QPoint pstart,int length,double direct,int width,int level)
{
//如果已经到了最后一层,那么绘制结束,就在树枝顶端绘制一些图形
if(level==0)
{
if(rand()%100<5){//有5%的几率绘制金色的圆圈,否则绘制红色的圆圈
painter->setPen(QColor(rand()%55+200,rand()%150+100,0));
painter->drawEllipse(pstart,rand()%4+2,rand()%4+2);
}else
{
painter->setPen(QColor(rand()%55+200,rand()%100,rand()%155+100));
painter->drawEllipse(pstart,rand()%3+1,rand()%3+1);
}
return;
}
if(width<=0)//如果树枝的宽度小于等于0时,绘制宽度为1,保持可见
width=1;
//创建一个绘制树枝的画笔,根据传入进来的参数
QPen pen(QBrush(QColor(rand()%60,rand()%120,rand()%60)),width);
painter->setPen(pen);
//绘制左边的树枝
int llen=length*(rand()%5+5)/10;//计算出一个左边树枝的长度
double lfactor=(rand()%7+1)/10.0;//计算出一个逆时针偏转的角度,相对上一级树枝角度
double ldirect=direct+lfactor;//得到左边树枝的生长方向
QPoint plend(pstart.x()+llen*cos(ldirect),pstart.y()+llen*sin(ldirect));//计算出新长出来的左边树枝的终点
painter->drawLine(pstart,plend);//从起点绘制到左树枝终点的直线(新树枝)
CherryTreeSec(painter,plend,llen,ldirect,width-1,level-1);//递归绘制左树枝的树枝们
painter->setPen(pen);//由于绘制可能会遇到刚绘制玩一棵树枝的情况,树枝顶端的颜色和树干颜色不应该一样,因此,重新设置树干颜色
//同理,绘制右边树枝
int rlen=length*(rand()%5+5)/10;
double rfactor=(rand()%7+1)/10.0;
double rdirect=direct-rfactor;
QPoint prend(pstart.x()+rlen*cos(rdirect),pstart.y()+rlen*sin(rdirect));
painter->drawLine(pstart,prend);
CherryTreeSec(painter,prend,rlen,rdirect,width-1,level-1);
}