今天我所要分享的内容为如何在Qt中编写鼠标绘图的程序。
绘制的图形可以有直线、矩形、椭圆、多边形、自由曲线。
当然还在其中添加了一个扫描线算法,对自己绘制的多边形进行填充,不过还有一两点特殊情况没有考虑清楚,导致填充不完整。不过其他画图的程序都是没有问题的。Qt中有自带的填充算法,所以我用QPainterPath绘制自由曲线时,自动填充没有问题。
主程序如下:
#include "minidraw.h"
#include <QtWidgets/QApplication>
#include <QLabel>
#include "viewwidget.h"
int main(int argc, char *argv[])
{
_CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);//检查内存泄漏
QApplication a(argc, argv);
MiniDraw mini_draw_action_;
mini_draw_action_.show();
mini_draw_action_.setMinimumSize(600, 400);
return a.exec();
}
Qt界面按钮设置的H文件和C文件:
H文件如下:
#ifndef MINIDRAW_H
#define MINIDRAW_H
#include <QtWidgets/QMainWindow>
#include "ui_minidraw.h"
#include "viewwidget.h"
class MiniDraw : public QMainWindow
{
Q_OBJECT
public:
MiniDraw(QWidget *parent = 0);
~MiniDraw();
void Init();
void set_figure_type_to_line();
void set_figure_type_to_ellipse();
void set_figure_type_to_rectangle();
void set_figure_type_to_polygon();
void set_figure_type_to_freehand();
void set_figure_type_to_delete();
void set_figure_type_to_color();
void set_figure_type_to_freehan1();
void color_filling();
public:
Ui::MiniDrawClass ui;
viewwidget* view_widget_; //声明viewwidget指针
QAction* mini_draw_action_; //声明动作
QMenu* main_menu_; //声明菜单
QToolBar* main_toolbar_; //声明工具栏
void CreateButtons(); //声明函数
};
#endif // MINIDRAW_H
C文件如下:
#include "minidraw.h"
#include <QMessageBox>
#include <QColorDialog>
#include "viewwidget.h"
#include "figures.h"
MiniDraw::MiniDraw(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
CreateButtons();
Init();
}
MiniDraw::~MiniDraw()
{
}
void MiniDraw::CreateButtons()
{
main_menu_ = menuBar()->addMenu(tr("&Figure Tool")); //创建菜单,标签为Figure Tool
main_toolbar_ = addToolBar(tr("&Figure Tool")); //创建工具栏,标签为Figure Tool
mini_draw_action_ = new QAction(tr("&Line"), this); //&的作用:设置此动作的快捷键为标签字符串的首字母,此例为’L’
main_menu_->addAction(mini_draw_action_); //将动作Line加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Line加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered,this, &MiniDraw::set_figure_type_to_line);
mini_draw_action_ = new QAction(tr("&Ellipse"), this); //&的作用:设置此动作的快捷键为标签字符串的首字母,此例为’E’
main_menu_->addAction(mini_draw_action_); //将动作Ellipse加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Ellipse加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_ellipse);
mini_draw_action_ = new QAction(tr("&Rectangle"), this); //&的作用:设置此动作的快捷键为标签字符串的首字母,此例为’R’
main_menu_->addAction(mini_draw_action_); //将动作Rectangle加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Rectangle加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_rectangle);
mini_draw_action_ = new QAction(tr("&Polygon"), this); //&的作用:设置此动作的快捷键为标签字符串的首字母,此例为’P’
main_menu_->addAction(mini_draw_action_); //将动作Polygon加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Polygon加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_polygon);
mini_draw_action_ = new QAction(tr("&Freehand"), this);
main_menu_->addAction(mini_draw_action_); //将动作Freehand加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Freehand加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_freehand);
mini_draw_action_ = new QAction(tr("&Freehand1"), this);
main_menu_->addAction(mini_draw_action_); //将动作Freehand1加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Freehand1加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_freehan1);
mini_draw_action_ = new QAction(tr("&Delete"), this);
main_menu_->addAction(mini_draw_action_); //将动作Delete加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作Delete加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_delete);
mini_draw_action_ = new QAction(tr("&Color"), this);
main_menu_->addAction(mini_draw_action_); //将动作设置颜色加载到Main菜单中
main_toolbar_->addAction(mini_draw_action_); //将动作设置颜色加载到Main工具栏中
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::set_figure_type_to_color);
mini_draw_action_ = new QAction(tr("&Color_Fill"), this);
main_menu_->addAction(mini_draw_action_); //将动作颜色填充加载到主菜单中
main_toolbar_->addAction(mini_draw_action_);
connect(mini_draw_action_, &QAction::triggered, this, &MiniDraw::color_filling);
}
void MiniDraw::Init()
{
view_widget_ = new viewwidget();
setCentralWidget(view_widget_);
//实例代码中将绘图控件的实例化放在Init函数中
//Init函数将在MiniDraw的构造函数中被调用
//实例化viewwidgett控件窗口
//将viewwidget控件设置为主窗口的中心位置
}
void MiniDraw::set_figure_type_to_line()
{
view_widget_->figure_type_ = viewwidget::kLine;
}
void MiniDraw::set_figure_type_to_ellipse()
{
view_widget_->figure_type_ = viewwidget::kEllipse;
}
void MiniDraw::set_figure_type_to_rectangle()
{
view_widget_->figure_type_ = viewwidget::kRectangle;
}
void MiniDraw::set_figure_type_to_polygon()
{
view_widget_->figure_type_ = viewwidget::kPolygon;
}
void MiniDraw::set_figure_type_to_freehand()
{
view_widget_->figure_type_ = viewwidget::kFreehand;
}
void MiniDraw::set_figure_type_to_freehan1()
{
view_widget_->figure_type_ = viewwidget::kFreehand1;
}
void MiniDraw::set_figure_type_to_delete()
{
if (!view_widget_->figure_array_.empty())
{
view_widget_->figure_array_.pop_back();
}
}
void MiniDraw::set_figure_type_to_color()
{
view_widget_->color = QColorDialog::getColor(Qt::white, this);
}
void MiniDraw::color_filling()
{
view_widget_->figure_type_ = viewwidget::kColorFill;
}
具体的鼠标响应函数的声明和定义:
H文件如下:
#ifndef VIEWWIDGET_H
#define VIEWWIDGET_H
#include <QWidget>
#include <QPainterPath>
#include "ui_viewwidget.h"
#include <QMouseEvent>
#include <vector>
#include <iostream>
#include<QColor>
#include "figures.h"
class viewwidget : public QWidget
{
Q_OBJECT
public:
viewwidget(QWidget *parent = 0); //绘制函数声明,Qt所有的绘制都只能在此函数中完成
~viewwidget();
QColor color;
enum CFigureType{ kLine = 0, kRectangle = 1, kEllipse = 2, kPolygon = 3,kFreehand=4,kColorFill=5,kFreehand1};
std::vector <CFigures*>figure_array_;
CFigureType figure_type_;
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent(QMouseEvent *event); //鼠标击发响应函数(左右键,单双击)
void mouseMoveEvent(QMouseEvent *event); //鼠标移动响应函数
void mouseReleaseEvent(QMouseEvent *event); //鼠标释放响应函数(左右键,单双击)
void colorFilling(std::vector<QPoint> ThePolygon,QPainter &paint);
int calculate_x_intersect(QPoint point_a, QPoint point_b, int y);
public:
Ui::viewwidget ui;
bool draw_status_;
//当前绘制状态,true为绘制当前鼠标拖动的图元,false为不绘制
QPoint start_point_;
//当前图元的起始点
QPoint end_point_;
//当前图元的终止点
QPoint current_point_;
std::vector<QPoint>points; //保存鼠标经过的点
QPainterPath *path;
};
#endif // VIEWWIDGET_H
C文件如下:
#include "viewwidget.h"
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QPoint>
#include <QScreen>
#include <QPixmap>
#include <QImage>
#include <iostream>
#include <QColor>
#include <vector>
#include <QColorDialog>
#include <QMessageBox>
#include <algorithm>
#include "figure.h"
viewwidget::viewwidget(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
draw_status_ = false; //设置初始绘制状态为 – 不绘制
this->path = NULL;
}
viewwidget::~viewwidget()
{
for (size_t i = 0; i < figure_array_.size(); i++)
{
if (figure_array_[i])
{
delete figure_array_[i];
figure_array_[i] = NULL;
}
}
}
//鼠标击发响应函数函数体
void viewwidget::mousePressEvent(QMouseEvent *event)
{
if (Qt::LeftButton == event->button()) //判断是否是鼠标左击
{
draw_status_ = true; //设置绘制状态为 – 绘制
start_point_ = end_point_ = event->pos(); //将图元初始点设置为当前鼠标击发点
if (figure_type_==kPolygon)
{
points.push_back(start_point_);
}
if (figure_type_==kFreehand)
{
path = new QPainterPath;
path->moveTo(event->pos());
}
if (figure_type_==kFreehand1)
{
points.push_back(start_point_);
}
}
}
//鼠标移动响应函数函数体
void viewwidget::mouseMoveEvent(QMouseEvent *event)
{
if (draw_status_) //判断当前绘制状态
{
end_point_ = event->pos(); //若为真,则设置图元终止点为鼠标当前位置
}
if (figure_type_ == kFreehand)
{
path->lineTo(event->pos());
}
if (figure_type_==kFreehand1)
{
points.push_back(start_point_);
}
}
//鼠标释放响应函数函数体
void viewwidget::mouseReleaseEvent(QMouseEvent *event)
{
CFigures* current_figure_ = NULL;
switch(figure_type_)
{
case kLine:
current_figure_ = new Line(start_point_.rx(), start_point_.ry(), end_point_.rx(), end_point_.ry());
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
draw_status_ = false; //设置绘制状态为 – 不绘制
break;
case kEllipse:
current_figure_ = new Ellipse(start_point_.rx(), start_point_.ry(), end_point_.rx(), end_point_.ry());
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
draw_status_ = false; //设置绘制状态为 – 不绘制
break;
case kPolygon:
if (Qt::RightButton==event->button())
{
current_figure_ = new Polygon(points);
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
//points.clear(); //清除这些点,若不用进行颜色填充则不用注释
//draw_status_ = false; //设置绘制状态为 – 不绘制
}
break;
case kRectangle:
current_figure_ = new Rectangle(start_point_.rx(), start_point_.ry(), end_point_.rx(), end_point_.ry());
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
draw_status_ = false; //设置绘制状态为 – 不绘制
break; //必不可少
case kFreehand:
current_figure_ = new Freehand(*path);
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
draw_status_ = false;
delete path;
path = NULL;
break;
case kFreehand1:
if (Qt::RightButton == event->button())
{
current_figure_ = new Polygon(points);
current_figure_->color3 = color;
figure_array_.push_back(current_figure_);
//points.clear(); //清除这些点,若不用进行颜色填充则不用注释
//draw_status_ = false; //设置绘制状态为 – 不绘制
}
break;
case kColorFill:
if (Qt::RightButton == event->button())
{
points.clear();
}
break;
default:
break;
}
current_figure_ = NULL;
}
//鼠标绘制函数函数体
void viewwidget::paintEvent(QPaintEvent *)
{
QPainter painter(this); //定义painter在this指向的控件(此例为ViewWidget)中绘图
for (size_t i = 0; i < figure_array_.size(); i++)
{
painter.setPen(figure_array_[i]->color3);
figure_array_[i]->Draw(painter);
}
if (draw_status_)
{
switch (figure_type_)
{
case kLine:
painter.setPen(color);
painter.drawLine(start_point_, end_point_);
break;
case kRectangle:
painter.setPen(color);
painter.drawRect(start_point_.rx(), start_point_.ry(), end_point_.rx() - start_point_.rx(), end_point_.ry() - start_point_.ry());
break;
case kEllipse:
painter.setPen(color);
painter.drawEllipse(start_point_.rx(), start_point_.ry(), end_point_.rx() - start_point_.rx(), end_point_.ry() - start_point_.ry());
break;
case kPolygon:
{
painter.setPen(color);
painter.drawLine(start_point_, end_point_);
if (points.size() == 0)
{
break;
}
QPoint pre = points[0];
for (int i = 1; i < points.size(); i++)
{
painter.drawLine(pre, points[i]);
pre = points[i];
}
}
break;
case kFreehand:
painter.setPen(color);
if (path!=NULL)
{
painter.drawPath(*path);
}
break;
case kFreehand1:
{
painter.setPen(color);
if (points.size() == 0)
{
break;
}
QPoint pre = points[0];
for (int i = 1; i < points.size(); i++)
{
painter.drawLine(pre, points[i]);
pre = points[i];
}
}
break;
case kColorFill:
{
if (points.size() == 0)
{
break;
}
QPen pen = painter.pen();
painter.setPen(color);
pen.setColor(Qt::red);
colorFilling(points, painter);
}
break;
default:
break;
}
}
update(); //更新窗口
}
void viewwidget::colorFilling(std::vector<QPoint> ThePolygon, QPainter &paint)
{
int minY, maxY;
minY = maxY = (*ThePolygon.begin()).ry();
for (int i = 0; i < ThePolygon.size(); i++)
{
if (ThePolygon[i].ry()<minY)
{
minY = ThePolygon[i].ry();
}
if (ThePolygon[i].ry()>maxY)
{
maxY = ThePolygon[i].ry();
}
}
std::vector<bool> isUpDown(ThePolygon.size(), false);
for (int i = 0; i < ThePolygon.size(); i++)
{
QPoint p = ThePolygon[i];
QPoint p_pre = ThePolygon[(i == 0) ? ThePolygon.size() - 1 : i - 1];
QPoint p_next = ThePolygon[(i == ThePolygon.size() - 1) ? 0 : i + 1];
int tp = (p.ry() - p_pre.ry())*(p.ry() - p_next.ry());
if (tp < 0)
{
isUpDown[i] = true;
}
}
for (int h = minY; h <= maxY; h++)
{
std::vector<int> intersects;
std::vector<bool> isVertsUsed(ThePolygon.size(), false);
for (int j = 0; j < ThePolygon.size(); j++)
{
QPoint start_pt = ThePolygon[j];
int end_idx = (j == ThePolygon.size() - 1) ? 0 : j + 1;
QPoint end_pt = ThePolygon[end_idx];
int res_x = calculate_x_intersect(start_pt, end_pt, h);
if (res_x == -1)
{
continue;
}
if (res_x==-2&&start_pt.ry() == end_pt.ry())
{
QPoint p_pre_pre = ThePolygon[(j == 0) ? ThePolygon.size() - 1 : j - 1];
QPoint p_next_next = ThePolygon[(j == ThePolygon.size() - 1) ? 0 : j + 1];
if ((start_pt.ry() - p_pre_pre.ry())*(start_pt.ry()-p_next_next.ry())<0)
{
intersects.push_back(start_pt.rx());
}
continue;
}
if (h == start_pt.ry())
{
if (isUpDown[j])
{
if (!isVertsUsed[j])
{
isVertsUsed[j] = true;
intersects.push_back(start_pt.rx());
}
else
{
continue;
}
}
else
{
intersects.push_back(start_pt.rx());
}
continue;
}
if (h == end_pt.ry())
{
if (isUpDown[end_idx])
{
if (!isVertsUsed[end_idx])
{
isVertsUsed[end_idx] = true;
intersects.push_back(end_pt.rx());
}
else
{
continue;
}
}
else
{
intersects.push_back(end_pt.rx());
}
continue;
}
intersects.push_back(res_x);
}
if (intersects.size() % 2 == 1)
{
intersects.push_back(intersects[intersects.size()-1]);
}
std::sort(intersects.begin(), intersects.end());
if (intersects.size() < 2)
{
continue;
}
for (int i = 0; i < intersects.size(); i = i + 2)
{
QPoint p(intersects[i], h);
QPoint q(intersects[i + 1], h);
paint.drawLine(p, q);
}
intersects.clear();
}
}
int viewwidget::calculate_x_intersect(QPoint point_a, QPoint point_b, int y)
{
float slop = 0; //多边形的任意边的斜率
float intercept = 0; //多边形的任意边的直线方程中的截距
int x_inter = 0; //交点坐标x
float y_1;
if ((point_a.ry() < y&&point_b.ry() > y || point_a.ry() > y&&point_b.ry() < y) && (point_a.rx() == point_b.rx()))
{
x_inter = point_a.rx(); //与斜率不存在的线段相交
}
else if (point_a.ry() == point_b.ry())
{
//x_inter = point_a.rx();
x_inter = -2; //斜率为0,返回-2
}
else if (point_a.ry() == y&&point_b.ry() != y)
{
x_inter = point_a.rx(); //与线段的端点相交
}
else if (point_b.ry() == y&&point_a.ry() != y)
{
x_inter = point_b.rx(); //与线段的端点相交
}
else if (point_a.ry() > y&&point_b.ry() > y || point_a.ry() < y&&point_b.ry() < y)
{
x_inter = -1; //没有交点,返回-1
}
else if ((point_a.ry() < y&&point_b.ry() > y || point_a.ry() > y&&point_b.ry() < y) && (point_a.ry() != point_b.ry()) && (point_a.rx() != point_b.rx()))
{
slop = float((point_b.rx() - point_a.rx())) / float((point_b.ry() - point_a.ry())); //计算直线方程的参数
intercept = float(point_a.rx()) - slop*float(point_a.ry());
x_inter = int(slop*float(y) + intercept+0.5); //交点坐标x
}
return x_inter;
}
#ifndef _FIGURES_H_
#define _FIGURES_H_
#include <QPainter>
#include <vector>
#include <QPainterPath>
#include <QColor>
#include <algorithm>
#include <iostream>
class CFigures
{
public:
CFigures();
virtual ~CFigures();
QColor color3;
virtual void Draw(QPainter &paint);
};
class Line :public CFigures
{
public:
Line(void);
Line(int start_point_x, int start_point_y, int end_point_x, int end_point_y)
{
start_point_x_ = start_point_x;
start_point_y_ = start_point_y;
end_point_x_ = end_point_x;
end_point_y_ = end_point_y;
}
~Line()
{
;
}
public:
void Draw(QPainter &paint)
{
paint.drawLine(start_point_x_, start_point_y_, end_point_x_, end_point_y_);
}
private:
int start_point_x_, start_point_y_, end_point_x_, end_point_y_;
};
class Rectangle:public CFigures
{
public:
Rectangle(void);
Rectangle(int start_point_x, int start_point_y, int end_point_x, int end_point_y)
{
start_point_x_ = start_point_x;
start_point_y_ = start_point_y;
end_point_x_ = end_point_x;
end_point_y_ = end_point_y;
}
~Rectangle()
{
;
}
public:
void Draw(QPainter &paint)
{
paint.drawRect(start_point_x_, start_point_y_, end_point_x_ - start_point_x_, end_point_y_ - start_point_y_);
}
private:
int start_point_x_, start_point_y_, end_point_x_, end_point_y_;
};
class Ellipse : public CFigures
{
public:
Ellipse(void);
Ellipse(int start_point_x, int start_point_y, int end_point_x, int end_point_y)
{
start_point_x_ = start_point_x;
start_point_y_ = start_point_y;
end_point_x_ = end_point_x;
end_point_y_ = end_point_y;
}
~Ellipse()
{
;
}
public:
void Draw(QPainter &paint)
{
paint.drawEllipse(start_point_x_, start_point_y_, end_point_x_ - start_point_x_, end_point_y_ -start_point_y_);
}
private:
int start_point_x_, start_point_y_, end_point_x_, end_point_y_;
};
class Polygon : public CFigures
{
public:
Polygon(void);
Polygon(std::vector<QPoint>points)
{
point = points;
}
~Polygon()
{
;
}
public:
void Draw(QPainter &paint)
{
paint.drawPolygon(&point.at(0), point.size());
}
private:
std::vector<QPoint>point;
};
class Freehand : public CFigures
{
public:
Freehand(void);
Freehand(QPainterPath paths)
{
path = paths;
}
~Freehand()
{
;
}
public:
void Draw(QPainter &paint)
{
QLinearGradient myGradient;
//QPen myPen;
myGradient.setColorAt(0, Qt::black);
paint.setBrush(myGradient);
//paint.setPen(myPen);//以上几句话对颜色填充有作用,注释掉就不填充颜色
paint.drawPath(path);
}
private:
QPainterPath path;
};
#endif
C文件如下:
#include "figures.h"
#include "viewwidget.h"
#include <QColorDialog>
CFigures::CFigures()
{
;
}
CFigures::~CFigures()
{
;
}
void CFigures::Draw(QPainter &paint)
{
;
}
这次分享的代码中,其实还有很多不完善的地方。改了很久,还是有些小漏洞没改完。
以下是简单的绘图结果截屏:
以下这张图是Qt中自带的path绘制自由曲线以及自带的颜色填充结果:
以下这张图是简单的自由曲线:
以下这张图是对自己绘制的多边形进行颜色填充,图形不负责,所以填充效果不错:
以下这张图就比较差劲了,扫描线程序没有写好。就是对于一些特殊情况没有考虑到。