首页 Qt 学习之路 2 Qt 学习之路 2(26):反走样

Qt 学习之路 2(26):反走样

9 2.2K

我们在光栅图形显示器上绘制非水平、非垂直的直线或多边形边界时,或多或少会呈现锯齿状外观。这是因为直线和多边形的边界是连续的,而光栅则是由离散的点组成。在光栅显示设备上表现直线、多边形等,必须在离散位置采样。由于采样不充分重建后造成的信息失真,就叫走样;用于减少或消除这种效果的技术,就称为反走样。

反走样是图形学中的重要概念,用以防止通常所说的“锯齿”现象的出现。很多系统的绘图 API 里面都内置了有关反走样的算法,不过由于性能问题,默认一般是关闭的,Qt 也不例外。下面我们来看看代码:

void paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
    painter.setBrush(Qt::yellow);
    painter.drawEllipse(50, 150, 200, 150);

    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));
    painter.setBrush(Qt::yellow);
    painter.drawEllipse(300, 150, 200, 150);
}

看看运行后的结果:

Qt 反走样示例

注意看左侧椭圆与右侧椭圆在边界处的区别。左侧没有使用反锯齿,明细显示出锯齿的样子;右侧则增加了反锯齿代码。

在这段代码中,我们创建了一个黑色 5 像素宽的画笔,使用了点线的样式,圆形笔帽:

painter.setPen(QPen(Qt::black, 5, Qt::DashDotLine, Qt::RoundCap));

然后我们使用一个黄色的画刷填充,绘制一个椭圆。

第二个椭圆的绘制与第一个十分相似,唯一的区别是多了一句

painter.setRenderHint(QPainter::Antialiasing, true);

显然,我们通过这条语句,将Antialiasing属性(也就是反走样)设置为 true。经过这句设置,我们就打开了QPainter的反走样功能。还记得我们曾经说过,QPainter是一个状态机,因此,只要这里我们打开了它,之后所有的代码都会是反走样绘制的了。由于反走样需要比较复杂的算法,在一些对图像质量要求不是很高的应用中,是不需要进行反走样的。为了提高效率,一般的图形绘制系统,如 Java2D、OpenGL 之类都是默认不进行反走样的。

虽然反走样比不反走样的图像质量高很多,但是,没有反走样的图形绘制还是有很大用处的。首先,就像前面说的一样,在一些对图像质量要求不高的环境下,或者说性能受限的环境下,比如嵌入式和手机环境,一般是不进行反走样的。另外,在一些必须精确操作像素的应用中,也是不能进行反走样的。这是由于反走样技术本身的限制的。请看下面的图片:

Photoshop 走样与反走样对比

这是使用 Photoshop 的铅笔和画笔工具绘制的 1 像素的点,放大 3200% 的视图。在一定程度上,我们可以认为,Photoshop 的铅笔工具是不进行反走样,而画笔是要进行反走样的。在放大的情况下就会知道,有反走样的情况下是不能进行精确到 1 像素的操作的。因为反走样很难让你控制到 1 个像素。这不是 Photoshop 画笔工具的缺陷,而是反走样算法的问题。反走样之所以看起来比较模糊,就是因为它需要以一种近似色来替换原始的像素色,这样一来就会显得模糊而圆滑。

9 评论

庸俗 2013年12月18日 - 17:45

Widget是怎么画出来的?他也有自己的QPainter来画的吗,如果有,可不可以获取到,设置他的属性为反走样。

回复
豆子 2013年12月18日 - 17:48

你认为哪里需要自己再设置反走样?你的需求是什么?

回复
庸俗 2013年12月19日 - 10:07

LibOpenglRender::AndroVM_FrameBuffer_setupSubWindow(w.GetSDLWidgetId(), InitWidth, InitHeight, 0.0),我是通过widget来显示一个FrameBuffer,但是放大后,拉伸严重。我想通过设置反走样能让锯齿效果不那么明显。我在显示FrameBuffer的widget的paintEvent里尝试了如下代码
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
QPixmap pixmap = QPixmap::grabWindow(winId());
painter.setRenderHint(QPainter::Antialiasing);
QRect target(0, 0, m_iScreenWidth, m_iScreenHeight);
QRect source(0, 0, m_iScreenWidth, m_iScreenHeight);
painter.drawPixmap(target, pixmap, source);
未能达到效果。

回复
庸俗 2013年12月19日 - 11:00

查了文档发现QPixmap::grabWindow是废弃的,我把他替换成了QWidget的grab();
QPixmap pixmap = QPixmap::grabWindow(winId());->QPixmap pixmap = grab();运行会崩溃

回复
庸俗 2013年12月19日 - 11:01

提示错我QWidget::repaint: Recursive repaint detected

回复
歪歪 2014年7月25日 - 15:23

运行后提示error: C2355: “this”: 只能在非静态成员函数或非静态数据成员初始值设定项的内部引用,不知道是哪里出了问题呢

回复
豆子 2014年7月25日 - 23:35

如果是直接复制的文中的代码,因为 paintEvent() 没有类限定符,所以成了全局函数,就不能使用 this。解决方法是给 paintEvent() 函数增加类限定符,作为成员函数。

回复
菜瓜 2015年11月9日 - 17:07

把Paintevent加到MainWindow类里面成为它的成员函数,运行通过,但是没有图出来,我觉得是因为这个函数实际上没有被调用(我的main程序依然是Qtcreator自己生成的那个,没有改写),应该怎么调用?

回复
豆子 2015年11月9日 - 21:01

注意下函数名是不是写对了,可以在 paintEvent() 添加断点试试看

回复

回复 庸俗 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2