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

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

准备条件:

安装QT5 QtCreator

简介:

最新预览:

Github最新源码

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

效果预览

主界面

C++ QT5 画一颗分形树,自己的樱花树_第1张图片

导出图片

C++ QT5 画一颗分形树,自己的樱花树_第2张图片

创建项目:

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

配置项目属性:

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

CONFIG +=c++11

界面搭建:

展开刚才创建的项目–>展开界面文件–>双击打开widget.ui
C++ QT5 画一颗分形树,自己的樱花树_第3张图片
上图为布局中的ObjectName值,注意,这里的ObjectName是和代码对应的,相当于控件的ID
C++ QT5 画一颗分形树,自己的樱花树_第4张图片
(布局方式:选中一个控件容器(比如一个widget),在选择布局方式即可)
总体两个widget垂直布局,上面一个widget作为控制栏,最高高度为60,下面一个widget用来绘图。
上面一个widget进行水平布局,其中放几个spinbox和pushbutton.

源码:

widget.h

包含头文件

#include//Qt绘图类
#include//Qt绘图事件类
#include//C/C++数学函数库,用来计算角度
#include//C/C++事件库,用来生成随机数
#include//Qt绘图设备类
#include//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//文件打开或者保存对话框
#include//提示框

构造函数中添加:

	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);
}

你可能感兴趣的:(C++,QT开发)