• Qt 2D绘图之二:抗锯齿渲染和坐标系统


    一、抗锯齿渲染

    1.1 逻辑绘图

    图形基元的大小(宽度和高度)始终与其数学模型相对应,下图示意了忽略其渲染时使用的画笔的宽度的样子。


    1.2 物理绘图(默认情况)

    在默认的情况下,绘制会产生锯齿,并且使用这样的规则进行绘制: 当使用宽度为一个像素的画笔进行渲染时,像素会在数学定义的点的右边和下边进行渲染,如下图1所示。当使用一个拥有偶数像素的画笔进行渲染时,像素会在数学定义的点的周围对称渲染;而当使用一个拥有奇数像素的面笔进行渲染时,首先按照偶数对称绘制,最后一个像素会被渲染到数学定义的点的右边和下边,如下图2所示。

    所以看起来图像不是很平滑,像是有锯齿,所以为了消锯齿,就要用到抗锯齿绘图。


    1.3 抗锯齿绘图

    抗锯齿( Anti-aliased)又称为反锯齿或者反走样,就是对图像的边缘进行平滑处理,使其看起来更加柔和流畅的一种技术。QPaint er 进行绘制时可以使用QPainter ::RenderHint 渲染提示来指定是否要使用抗锯齿功能,RenderHint 取值分为以下三种。


    如果在绘制时使用了抗锯齿渲染提示,即使用 QPainter:: setRenderHint(RenderHint hint, bool on = true) 函数,将参数 hint 设置为了 QPainter:: Antialiasing。那么像素就会在数学定义的点的两侧对称的进行渲染,如下图所示。


    示例程序为:

    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); //抗锯齿和使用平滑转换算法
    

    二、坐标系统

    2.1 坐标系统简介

    Qt的坐标系统是由QPainter类控制的,而QPainter是在绘图设备上绘制的。一个绘图设备的默认坐标系统中原点(0, 0)在其左上角,x坐标向右增长,y坐标向下增长。在基于像素的设备上,默认的单位是一个像素,而在打印机上默认的单位是一个点(1/72英寸)。

    下面仍然在上一节的程序中进行代码演示,更改paintEvent()的内容如下:

    void Widget::paintEvent(QPaintEvent *)
    {
        QPainter painter(this);
        painter.setBrush(Qt::red);
        painter.drawRect(0, 0, 100, 100);
        painter.setBrush(Qt::yellow);
        painter.drawRect(-50, -50, 100, 100);
    }
    

    我们先在原点(0,0)绘制了一个长宽都是100像素的红色矩形,又在(-50,-50)点绘制了一个同样大小的黄色矩形。可以看到,我们只能看到黄色矩形的四分之一部分。运行程序,效果如下图所示。


    2.2 坐标系统变换

    默认情况下,QPainter在指定设备的坐标系统上进行绘制,在进行绘图时,使用QPainter::translate()函数平移坐标系统;可以使用QPainter::scale()函数缩放坐标系统;使用QPainter::rotate()函数顺时针旋转坐标系统;还可以使用QPainter::shear()围绕原点来扭曲坐标系统。如下图所示。

    我们可以使用前面提到的那些便捷函数进行坐标系统变换,也可以通过QTransform类实现。


    (1)平移变换

    将paintEvent()函数内容更改如下:

    void Widget::paintEvent(QPaintEvent *)
    {
        //平移坐标系统
        QPainter painter(this);
        painter.setBrush(Qt::yellow);
        painter.drawRect(0, 0, 50, 50);
        //将坐标系原点向右、向下平移100像素点,即使原点坐标变为(100,100)
        painter.translate(100, 100);
        painter.setBrush(Qt::red);
        painter.drawRect(0, 0, 50, 50);
        //将坐标系原点向左、向上平移100像素点,即重新使原点坐标变为(0,0)
        painter.translate(-100, -100);
        painter.drawLine(0, 0, 20, 20);
    }
    

    这里先在原点(0, 0)绘制了一个宽、高均为50的正方形,然后使用translate()函数将坐标系统进行了平移,使(100, 100)点成为了新原点,所以我们再次进行绘制的时候,虽然drawRect()中的逻辑坐标还是(0, 0)点,但实际显示出来的却是在(100, 100)点的红色正方形。可以再次使用translate()函数进行反向平移,使原点重新回到窗口左上角。运行程序,效果如下图所示。


    (2)缩放变换

    将paintEvent()函数中的内容更改如下:

    void Widget::paintEvent(QPaintEvent *)
    {
        //缩放坐标系统
        QPainter painter(this);
        painter.setBrush(Qt::yellow);
        painter.drawRect(0, 0, 100, 100);
        //将坐标系统的横、纵坐标都放大两倍
        painter.scale(2, 2);
        painter.setBrush(Qt::red);
        painter.drawRect(50, 50, 50, 50);
    }
    

    可以看到,当我们使用scale()函数将坐标系统的横、纵坐标都放大两倍以后,逻辑上的(50,50)点变成了窗口上的(100, 100)点,而逻辑上的长度50,绘制到窗口上的长度却是100。运行程序,效果如下图所示。


    (3)旋转变换

    将paintEvent()函数更改如下:

    void Widget::paintEvent(QPaintEvent *)
    {
        //旋转坐标系统
        QPainter painter(this);
        painter.drawLine(0, 0, 100, 0);
        //以原点为中心,顺时针旋转30度
        painter.rotate(30);
        painter.drawLine(0, 0, 100, 0);
        painter.translate(100, 100);
        painter.rotate(30);
        painter.drawLine(0, 0, 100, 0);
    }
    

    这里先绘制了一条水平的直线,然后将坐标系统旋转了30度,又绘制了一条直线。可以看到,默认是以原点(0, 0)为中心旋转的。如果想改变旋转中心,可以使用translate()函数,比如这里将中心移动到了(100, 100)点,然后旋转了30度,又绘制了一条直线。运行程序,效果如下图所示。


    (4)扭曲变换

    将paintEvent()函数更改如下:

    void Widget::paintEvent(QPaintEvent *)
    {
        //扭曲坐标系统
        QPainter painter(this);
        painter.setBrush(Qt::yellow);
        painter.drawRect(0, 0, 50, 50);
        //纵向扭曲变形
        painter.shear(0, 1);
        painter.setBrush(Qt::red);
        painter.drawRect(50, 0, 50, 50);
    }
    

    shear()有两个参数,第一个是对横向进行扭曲,第二个是对纵向进行扭曲,而取值就是扭曲的程度。比如程序中对纵向扭曲值为1,那么就是红色正方形左边的边下移一个单位,右边的边下移两个单位,值为1就表明右边的边比左边的边多下移一个单位。大家可以更改取值,测试效果。运行程序,效果如下图所示。


    2.3 ”窗口-视口”转换

    在使用QPainter进行绘制时,会使用逻辑坐标进行绘制,然后再转换为绘图设备的物理坐标。逻辑坐标到物理坐标的映射由QPainter的worldTransform()函数和QPainter的viewport()以及window()函数进行处理。其中视口(viewport)表示物理坐标下指定的一个任意矩形,而窗口(window,与以前讲的窗口部件的概念不同)表示逻辑坐标下的相同的矩形。默认的,逻辑坐标和物理坐标是重合的,它们都相当于绘图设备上的矩形。

    使用”窗口-视口”转换可以使逻辑坐标系统适合应用的要求,这个机制也可以用来让绘图代码独立于绘图设备。 下面来看一个例子。

    创建一个Widget窗口应用(其默认宽400像素,高300像素,左上角为原点)。首先正常绘制一个正方形:

    void Widget::paintEvent(QPaintEvent *)
    {
        QPainter p(this);
        p.setPen(Qt::blue);
        p.drawRect(0, 0, 100, 100);
    }
    

    这个是没有问题的,因为现在的绘图设备就是Widget,其左上角就是原点(0, 0)点。效果如下图所示。


    现在我们使用setWindow来设置逻辑坐标矩形:

    void Widget::paintEvent(QPaintEvent *)
    {
        QPainter p(this);
        p.setPen(Qt::blue);
        p.drawRect(0, 0, 100, 100);
    
        p.setWindow(-50, -50, 100, 100);
        p.setPen(Qt::red);
        p.drawRect(0, 0, 100, 100);
    }
    

    这时,效果如下图所示。


    现在来说p.setWindow(-50, -50, 100, 100)的作用,它将逻辑坐标矩形(后面提到的术语window窗口)与我们现在的设备物理坐标矩形(后面提到的术语viewport视口)进行了线性映射,这里所说的设备物理坐标矩形就是我们可见的Widget的坐标,就是左上角为(0, 0)点,宽400,高300这样的矩形,线性映射的示意图如下:

    也就是说,调用p.setWindow(-50, -50, 100, 100)之后,再次使用p进行绘制,那么坐标原点就不再是Widget的左上角了,而是到了其中心,以前绘制的宽100、高100的正方形,现在也会按比例变为宽400, 高300,也就是我们看到的这个红色矩形。


    再来修改代码:

    void Widget::paintEvent(QPaintEvent *)
    
    {
        QPainter p(this);
        p.setPen(Qt::blue);
        p.drawRect(0, 0, 100, 100);
    
        p.setWindow(-50, -50, 100, 100);
        p.setPen(Qt::red);
        p.drawRect(0, 0, 20, 20);
    }
    

    运行效果如下图所示:


    我们将绘制的红色矩形变小,可以明显看到,本应该是个正方形,现在却变成了长方形。就是因为上面说的比例变换造成的,那么怎么才能让它显示应有的形状呢,我们来设置视口:

    void Widget::paintEvent(QPaintEvent *)
    {
        QPainter p(this);
        p.setPen(Qt::blue);
        p.drawRect(0, 0, 100, 100);
    
        int side = qMin(width(), height());
        p.setViewport((width() - side)/2, (height() - side) /2, side, side);
        p.setWindow(-50, -50, 100, 100);
    
        p.setPen(Qt::red);
        p.drawRect(0, 0, 20, 20);
    }
    

    现在使用setViewport设置视口为一个正方形,就是Widget可是区域上最大的正方形,这样逻辑坐标和物理坐标进行比例变换的时候,红色矩形的宽和高就不会因为缩放的比例不同而发生变形了,如下图所示。


    问题提出:那么为什么要修改这个逻辑坐标矩形?

    这是为了便于我们绘图,因为我们一般绘图时只是想在标准的坐标系中应该绘制成什么样子,不会考虑不同绘图设备的具体坐标系(比如有的设备坐标原点在其左上角,有的在中心等等),也不会考虑窗口的大小不同而使用不同的代码(比如我们只想在一个宽100、高100的绘图区域的中心绘制一个高20、宽20的正方形,到底实际绘图设备的单位是像素、还是英寸、还是厘米,我们不用考虑)。


    引申术语:窗口、视口

    这里说的我们想象中的宽100、高100、原点在中心的绘图区域,就是逻辑坐标下的矩形,也就是使用setWindow设置的所谓的窗口;而实际的绘图设备,比如这里的Widget部件,其可视化的区域上设置的一个矩形被称为视口(英文为viewport),默认就是可视化区域的大小,但是可以通过setViewport来设置。窗口与视口相对应,可以进行线性变换,这样,我们就可以通过先设置视口,再设置对应的窗口的方法,来确保我们的代码在标准的想象中的坐标系中绘制的图形,可以准确地显示在不同的绘图设备界面上。


    参考:

    67 2D绘图(反走样绘图 / 抗锯齿渲染)

    Qt 2D绘图部分窗口、视口的研究


  • 相关阅读:
    Dom解析
    几道算法水题
    Bom和Dom编程以及js中prototype的详解
    sqlserver练习
    java框架BeanUtils及路径问题练习
    Java的IO以及线程练习
    在数据库查询时解决大量in 关键字的方法
    SaltStack--配置管理
    SaltStack--远程执行
    SaltStack--快速入门
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/11058857.html
Copyright © 2020-2023  润新知