找回密码
 立即注册
收起左侧

第17篇 2D绘图(七)涂鸦板

52
回复
48964
查看
[复制链接]
累计签到:1568 天
连续签到:1 天
来源: 2013-5-2 21:37:41 显示全部楼层 |阅读模式
涂鸦板


版权声明

该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处!



导语

       通过前面几节的学习,大家应该已经对Qt2D绘图有了一定的认识,这一节我们将应用前面讲到的内容,编写一个简单的涂鸦板程序,这一节只是实现最基本的鼠标画线功能。


环境:Windows Xp + Qt 4.8.4+QtCreator 2.6.2



目录

一、实现涂鸦板
二、实现放大功能


正文


一、实现涂鸦板


1.新建Qt Gui应用,项目名称为pianter_3,基类这次还用QDialog,类名保持Dialog不变即可。

2.dialog.h文件中,先添加头文件包含:#include <QMouseEvent>

然后添加几个函数的声明:
protected:
    void paintEvent(QPaintEvent *);
    void mousePressEvent(QMouseEvent *);
    void mouseMoveEvent(QMouseEvent *);
    void mouseReleaseEvent(QMouseEvent *);


       第一个是绘制事件处理函数,后面分别是鼠标按下、移动和释放事件的处理函数。
下面再添加几个private私有变量声明:

QPixmap pix;
QPoint lastPoint;
QPoint endPoint;


因为在函数里声明的QPixmap类对象是临时变量,不能存储以前的值,为了实现保留上次的绘画结果,我们需要将其设为全局变量。后面两个QPoint变量存储鼠标指针的两个坐标值,我们需要用这两个坐标值完成绘图。


2.dialog.cpp文件中,先添加头文件包含:#include <QPainter>
然后在构造函数中添加如下初始代码:

resize(600, 500);    //窗口大小设置为600*500
pix = QPixmap(200, 200);
pix.fill(Qt::white);


下面添加几个函数的定义:

void Dialog::paintEvent(QPaintEvent *)
{   
      QPainter pp(&pix);    // 根据鼠标指针前后两个位置就行绘制直线   
      pp.drawLine(lastPoint, endPoint);    // 让前一个坐标值等于后一个坐标值,这样就能实现画出连续的线   
      lastPoint = endPoint;   
      QPainter painter(this);   
      painter.drawPixmap(0, 0, pix);
}

       这里使用了两个点来绘制线条,这两个点在下面的鼠标事件中获得。


void Dialog::mousePressEvent(QMouseEvent *event)
{   
               if(event->button()==Qt::LeftButton) //鼠标左键按下      
               lastPoint = event->pos();
}

       当鼠标左键按下时获得开始点。

void Dialog::mouseMoveEvent(QMouseEvent *event)
{   
         if(event->buttons()&Qt::LeftButton) //鼠标左键按下的同时移动鼠标   
        {        
                  endPoint = event->pos();        
                  update(); //进行绘制   
         }
}
当鼠标移动时获得结束点,并更新绘制。调用update()函数会执行paintEvent()函数进行重新绘制。

void Dialog::mouseReleaseEvent(QMouseEvent *event)
{   
            if(event->button() == Qt::LeftButton) //鼠标左键释放   
             {        
                      endPoint = event->pos();        
                      update();   
             }
}
       当鼠标按键释放时也进行重绘。
       现在运行程序,使用鼠标在白色画布上进行绘制,发现已经实现了简单的涂鸦板功能,效果如下图所示。



二、实现放大功能
       前面已经实现了简单的绘制功能,下面我们将实现放大功能,将画布放大后继续进行涂鸦。这里将使用两种方法来实现,也是对上一节坐标系统后面的问题的更进一步的应用实践。

1.添加放大按钮。到dialog.h文件中,先添加头文件:

#include <QPushButton>

然后添加下面private私有变量声明:

qreal scale;
QPushButton *button;

最后再添加一个私有槽声明:

private slots:
    void zoomIn();


2.dialog.cpp文件中,先在构造函数中添加如下代码:

//设置初始放大倍数为1,即不放大
scale =1;
//新建按钮对象
button = new QPushButton(this);
//设置按钮显示文本
button->setText(tr("zoomIn"));
//设置按钮放置位置
button->move(500, 450);
//对按钮的单击事件和其槽函数进行关联
connect(button, SIGNAL(clicked()), this, SLOT(zoomIn()));


       这里使用代码创建了一个按钮对象,并将其单击信号关联到了放大槽上,也就是说按下这个按钮,就会执行zoomIn()槽。


3.下面添加zoomIn()的定义:
void Dialog::zoomIn()
{
    scale *=2;
    update();
}
    这里我们让每按下这个按钮,放大值都扩大两倍。后面调用update()函数来更新显示。

4. 通过上一节的学习,我们应该已经知道想让画布的内容放大有两个办法,一个是直接放大画布的坐标系统,一个是放大窗口的坐标系统。下面我们先来放大窗口的坐标系统。更改paintEvent()函数如下:

void Dialog::paintEvent(QPaintEvent *)
{
    QPainter pp(&pix);
    pp.drawLine(lastPoint, endPoint);   
    lastPoint = endPoint;   
    QPainter painter(this);
    //进行放大操作
    painter.scale(scale, scale);
    painter.drawPixmap(0, 0, pix);
}
    现在运行程序,先在白色画布上任意绘制一个图形,效果如下图所示。


    然后按下zoomIn按钮,效果如下图所示。



       现在再用鼠标进行绘制,发现图形已经不能和鼠标轨迹重合了,效果如下图所示。


有了前面一节的知识,就不难理解出现这个问题的原因了。窗口的坐标扩大了,但是画布的坐标并没有扩大,而我们画图用的坐标值是鼠标指针的,鼠标指针又是获取的窗口的坐标值。现在窗口和画布的同一点的坐标并不相等,所以就出现了这样的问题。
其实解决办法很简单,窗口放大了多少倍,就将获得的鼠标指针的坐标值缩小多少倍就行了。我们将paintEvent()函数更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter pp(&pix);
    pp.drawLine(lastPoint/scale, endPoint/scale);
    lastPoint = endPoint;
    QPainter painter(this);
    painter.scale(scale, scale);
    painter.drawPixmap(0, 0, pix);
}
       运行程序,效果如下图所示。可以看到,已经能够在放大以后继续绘图了。


这种用改变窗口坐标大小来改变画布面积的方法,实际上是有损图片质量的。就像将一张位图放大一样,越放大越不清晰。原因就是,它的像素的个数没有变,如果将可视面积放大,那么单位面积里的像素个数就变少了,所以画质就差了。


5.方法二。扩大画布坐标系统。先将paintEvent()更改如下:
void Dialog::paintEvent(QPaintEvent *)
{
    QPainter pp(&pix);
    pp.scale(scale, scale);
    pp.drawLine(lastPoint,endPoint);
    lastPoint = endPoint;
    QPainter painter(this);
    painter.drawPixmap(0, 0, pix);
}
       这时运行程序,先进行绘制,然后点击zoomIn按钮,发现以前的内容并没有放大,而当我们再次绘画时,发现鼠标指针和绘制的线条又不重合了。效果如下图所示。


       这并不是我们想要的结果,为了实现按下放大按钮,画布和图形都进行放大,我们可以使用缓冲画布(就是一个辅助画布)来实现。将paintEvent()函数内容更改如下。

void Dialog::paintEvent(QPaintEvent *)
{
    if(scale!=1) //如果进行放大操作
    {
       //临时画布,大小变化了scale倍
       QPixmap copyPix(pix.size()*scale);
       QPainter pter(&copyPix);
       pter.scale(scale, scale);
       //将以前画布上的内容复制到现在的画布上
       pter.drawPixmap(0, 0, pix);
       //将放大后的内容再复制回原来的画布上
       pix = copyPix;
       //让scale重新置1
       scale =1;
    }
    QPainter pp(&pix);
    pp.scale(scale,scale);
    pp.drawLine(lastPoint/scale,endPoint/scale);
    lastPoint = endPoint;
    QPainter painter(this);
    painter.drawPixmap(0,0,pix);
}
       现在运行程序,效果如下图所示。



结语

      本节讲到的涂鸦板,只是为了实践前面的知识,起到抛砖引玉的作用。大家可以根据自己的理解继续探究下去。在下一节,我们将讲解怎样在涂鸦板上绘制出矩形、椭圆等图形。

本程序中存在一些问题,如果大家想进一步学习研究,可以参考下载页面的涂鸦板开源软件。





涉及到的源码:



上一篇:    第16篇 2D绘图(六)坐标系统

下一篇:    第18篇 2D绘图(八)双缓冲绘图

返回:系列教程目录    



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
参与人数 1人气 +2 收起 理由
coyoteie + 2

查看全部评分总评分 : 人气 +2

回复

使用道具 举报

尚未签到

2013-5-27 10:37:17 显示全部楼层
程序里边  QPainter pter(©Pix);后边那个符号是什么符号啊
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-5-27 22:28:36 显示全部楼层
VMoon 发表于 2013-5-27 10:37
程序里边  QPainter pter(©Pix);后边那个符号是什么符号啊

取地址。函数原型是QPainter ( QPaintDevice * device )
回复 支持 反对

使用道具 举报

尚未签到

2013-5-28 09:43:56 显示全部楼层
yafeilinux 发表于 2013-5-27 22:28
取地址。函数原型是QPainter ( QPaintDevice * device )

恩 谢谢了, 问一下楼主有没有绘制实时数据曲线的例程啊,帮我发一份可以吗,tech_vmoon@163.com
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-5-28 19:42:01 显示全部楼层
VMoon 发表于 2013-5-28 09:43
恩 谢谢了, 问一下楼主有没有绘制实时数据曲线的例程啊,帮我发一份可以吗, ...

这个暂时还没有,可以搜索一下qwt
回复 支持 反对

使用道具 举报

累计签到:84 天
连续签到:1 天
2013-6-12 15:46:01 显示全部楼层
QPainter pter(©Pix); 这个报错啊。
回复 支持 反对

使用道具 举报

累计签到:84 天
连续签到:1 天
2013-6-12 15:50:31 显示全部楼层
  QPainter pter(&-copy-Pix); 去掉“-” ,原来是这个。
回复 支持 反对

使用道具 举报

累计签到:75 天
连续签到:1 天
2013-7-24 16:31:51 显示全部楼层
void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt:eftButton) //鼠标左键按下
        lastPoint=endPoint =event->pos();                             
   
}
楼主好,是不是需要把astPoint=endPoint =event->pos();  ,要不程序开始画从 0,0 开始

点评

而且把mouseReleaseEvent函数去掉都不会有任何影响  发表于 2014-8-9 21:11
对啊 为什么会从零开始,是定义的哪一步造成的?  发表于 2014-8-9 21:07
不加放大功能时是对的,加了放大就必须像楼上这样做了,,奇怪。。。  发表于 2014-6-2 14:05
Qt5.3MinGW测试, 不用修改,程序不会从0,0开始画, 楼主的方法是没问题的。。。  发表于 2014-6-2 13:22
赞同8楼,Qt5.1亲测,此法可以解决!  发表于 2014-4-28 19:03
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-7-24 16:57:15 显示全部楼层
noway 发表于 2013-7-24 16:31
void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt:eftButton) //鼠标左 ...

不需要,你可以先下载源码测试下效果。
回复 支持 反对

使用道具 举报

累计签到:75 天
连续签到:1 天
2013-7-24 20:48:35 显示全部楼层
yafeilinux 发表于 2013-7-24 16:57
不需要,你可以先下载源码测试下效果。

我测试过了
应该就是版本问题 ,我的QT 版本是
Qt Creator 2.7.0
基于 Qt 5.0.2 (32 bit)

回复 支持 反对

使用道具 举报

累计签到:29 天
连续签到:1 天
2013-7-26 12:52:11 显示全部楼层
VMoon 发表于 2013-5-27 10:37
程序里边  QPainter pter(©Pix);后边那个符号是什么符号啊

那个函数的形参需要一个指针,所以你传实参的时候要将地址传进去,所以是&取地址。
C++的基本语法啊。
回复 支持 反对

使用道具 举报

累计签到:3 天
连续签到:1 天
2013-8-17 18:47:25 显示全部楼层
楼主你好,鼠标移动事件中,这两个是一样功能的吧? if(event->buttons()&Qt::eftButton)和 if(Qt::eftButton)
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-8-18 08:56:53 显示全部楼层
agehacat 发表于 2013-8-17 18:47
楼主你好,鼠标移动事件中,这两个是一样功能的吧? if(event->buttons()&Qt::eftButton)和 if(Qt::eftButton) ...

不一样,你可以试一下。
回复 支持 反对

使用道具 举报

累计签到:3 天
连续签到:1 天
2013-8-19 20:19:31 显示全部楼层
yafeilinux 发表于 2013-8-18 08:56
不一样,你可以试一下。

if(event->buttons()&Qt::eftButton) 其实我不是很懂这句话的意思,您可以解释一下吗?
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-8-19 22:56:25 显示全部楼层
agehacat 发表于 2013-8-19 20:19
if(event->buttons()&Qt::eftButton) 其实我不是很懂这句话的意思,您可以解释一下吗? ...

你可能不理解为什么不直接判断是否是左键按下,那是因为:

Qt::MouseButtons QMouseEvent::buttons () const
Returns the button state when the event was generated. The button state is a combination of Qt::LeftButton, Qt::RightButton, Qt::MidButton using the OR operator. For mouse move events, this is all buttons that are pressed down.

我们要先用这个函数判断是否移动了鼠标,如果移动了鼠标,并且左键按下了,才绘制。

event->buttons()&Qt::eftButton表示按位与,它们表示的值都是十六进制数,这个可以查看下帮助文档。
回复 支持 反对

使用道具 举报

累计签到:4 天
连续签到:1 天
2013-9-11 11:21:13 显示全部楼层
本帖最后由 luogenewuser 于 2013-9-11 11:22 编辑

最后面,使用缓冲画布时有小错误。paintEvent()函数中的QPainter pter(&pix);应该改正为 QPainter pter(&copyPix);下面提供的源码是正确的,但是教程中写错了。
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2013-9-11 11:42:46 显示全部楼层
luogenewuser 发表于 2013-9-11 11:21
最后面,使用缓冲画布时有小错误。paintEvent()函数中的QPainter pter(&pix);应该改正为 QPainter pter(&co ...

嗯。不是教程中写错了,是编辑器的问题,编辑器自动将&copy这个字符更换为了那个图标。

已经修正了。
回复 支持 反对

使用道具 举报

累计签到:15 天
连续签到:1 天
2013-11-30 10:26:27 显示全部楼层
noway 发表于 2013-7-24 16:31
void Dialog::mousePressEvent(QMouseEvent *event)
{
    if(event->button()==Qt:eftButton) //鼠标左 ...

确实是版本问题。  我5.1 也会从0,0开始。加了就不会了;
回复 支持 反对

使用道具 举报

累计签到:5 天
连续签到:1 天
2014-1-27 13:29:47 显示全部楼层
自己写了下,示例代码也运行了,但是在画线时碰到问题:时不时出现上一笔的最后一点和下一笔的开始点相连,这个是什么问题?
回复 支持 反对

使用道具 举报

累计签到:1568 天
连续签到:1 天
2014-1-27 22:27:13 显示全部楼层
cleverxuy 发表于 2014-1-27 13:29
自己写了下,示例代码也运行了,但是在画线时碰到问题:时不时出现上一笔的最后一点和下一笔的开始点相连, ...

嗯,是代码问题,这个其实可以自己想办法解决。也可以看看下载页面http://www.qter.org/?page_id=161中的涂鸦板程序和里面的教程。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了