一、抗锯齿渲染

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绘图部分窗口、视口的研究

Qt 2D绘图之二:抗锯齿渲染和坐标系统的更多相关文章

  1. [Qt2D绘图]-02坐标系统&&抗锯齿渲染

    本节的内容可以在帮助中通过Coordinate System关键字查看. 或者入门可以看<Qt Creator 快速入门>这本书.强烈推荐入门使用.下面的内容为本书的阅读笔记,喜欢的可以买 ...

  2. CSS3中字体平滑处理和抗锯齿渲染

    在围观Drupal官方主题的时候,发现了一个有意思的非标准CSS选择器-webkit-font-smoothing,于是上手把玩了一番.如何使用css3字体平滑显示呢 要知道,W3C对CSS中字体的抗 ...

  3. CSS3属性-webkit-font-smoothing字体抗锯齿渲染

    对字体进行抗锯齿渲染可以使字体看起来会更清晰舒服.在图标字体成为一种趋势的今天,抗锯齿渲染使用也越来越多. font-smoothing是非标准的CSS定义.它被列入标准规范的草案中,后由于某些原因从 ...

  4. Qt 2D绘图之五:图形视图框架的结构和坐标系统

    一.图形视图框架的结构 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们.但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动.检测它们的碰撞和叠加:或者我们想让自己绘制的图 ...

  5. Qt 2D绘图之三:绘制文字、路径、图像、复合模式

    一.绘制文字 除了绘制图形以外,还可以使用QPainter::darwText()函数来绘制文字,也可以使用QPainter::setFont()设置文字所使用的字体,使用QPainter::font ...

  6. qt 2D绘图技巧

    2D绘图 Qt4中的2D绘图部分称为Arthur绘图系统.它由3个类支撑整个框架,QPainter,QPainterDevice和QPainterEngine.QPainter用来执行具体的绘图相关操 ...

  7. Qt 2D绘图之一:基本图形绘制和渐变填充

    Qt中提供了强大的2D绘图系统,可以使用相同的API在屏幕和绘图设备上进行绘制,它主要基于QPainter.QPaintDevice和QPaintEngine这三个类.它们三者的关系如下图所示: QP ...

  8. 使用Win2D在UWP程序中2D绘图(二)

    绘制API 首先还是看一下前文的的示例: args.DrawingSession.DrawEllipse(155, 115, 80, 30, Colors.Black, 3);    args.Dra ...

  9. Qt 2D绘图之六:图形视图框架的事件处理与传播

    一.简介 图形视图框架中的事件都是首先由视图进行接收,然后传递给场景,再由场景传递给相应的图形项.而对于键盘事件,它会传递给获得焦点的图形项,可以使用QGraphicsScene类的setFocusI ...

随机推荐

  1. java中String.valueOf(obj)、(String)obj与obj.toString()有什么区别

    方法1:采用 Object.toString()方法 在这种使用方法中,因为java.lang.Object类里已有public方法.toString(),所以对任何严格意义上的java对象都可以调用 ...

  2. POJ2478 Farey Sequence —— 欧拉函数

    题目链接:https://vjudge.net/problem/POJ-2478 Farey Sequence Time Limit: 1000MS   Memory Limit: 65536K To ...

  3. jquery之extend

    jquery的extend方法的用法1. [代码][JavaScript]代码     01<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01// ...

  4. 数学题--On Sum of Fractions

    题目链接 题目意思: 定义v(n)是不超过n的最大素数, u(n)是大于n的最小素数. 以分数形式"p/q"输出 sigma(i = 2 to n) (1 / (v(i)*u(i) ...

  5. python装饰器精髓代码

    #!/usr/bin/env python #-*- coding:utf-8 -*- import time def foo(func): def inner(): print('fs...') f ...

  6. exBSGS算法

    BSGS,全称\(Baby Step Giant Step\),是用于求解离散对数的一种算法. 就是用来求\(A^x \equiv B (mod\ p)\) 的x这么一种算法-- 理论知识是:在[0, ...

  7. bzoj 3489 A simple rmq problem——主席树套线段树

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3489 题解:http://www.itdaan.com/blog/2017/11/24/9b ...

  8. python读文件和写文件

    f=open('D:\\wangdongjie\\files\\webservice\\baidu\\3.txt','r+') f.write('中国电视台1][][23qwe12f我是一个小小的石头 ...

  9. android调试之adb

    ADB 其实大部分的PC开发机与Android设备的操作都是通过adb(android debug bridge)技术完成的,这是一个C/S架构的命令行工具,主要由三个部分组成 运行在PC开发机上的命 ...

  10. Flutter实战视频-移动电商-58.购物车_删除商品功能制作

    58.购物车_删除商品功能制作 主要做购物车后面的删除按钮 删除的方法写在provide里面 provide/cart.dart文件 传入goodsId,循环对比,找到后进行移除 //删除单个购物车商 ...