欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ QT5 画一颗分形树,自己的樱花树

程序员文章站 2022-03-27 10:34:16
...

C++ QT5 画一颗分形树,自己的樱花树

准备条件:

安装QT5 QtCreator

简介:

教你从零画一棵自己的树,从项目创建到界面搭建,再到源码讲解。

效果预览

主界面

C++ QT5 画一颗分形树,自己的樱花树

导出图片

C++ QT5 画一颗分形树,自己的樱花树

创建项目:

启动QT creator–>New Project–>Application–>Qt Widgets Application–>choose->填写项目名称(无中文)和项目路径(无中文)–>下一步–>下一步–>更改基类为QWidget–>下一步–>完成。

配置项目属性:

由于会使用到C++11标准的Lambda表达式。
展开刚才创建的项目–>双击打开*.pro文件–>在最后添加一行:

CONFIG +=c++11

界面搭建:

展开刚才创建的项目–>展开界面文件–>双击打开widget.ui
C++ QT5 画一颗分形树,自己的樱花树
上图为布局中的ObjectName值,注意,这里的ObjectName是和代码对应的,相当于控件的ID
C++ QT5 画一颗分形树,自己的樱花树
(布局方式:选中一个控件容器(比如一个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);
}