问题
??很早之前,在使用QGraphicsScene绘制一张横向大约7万个像素的图片时,图片绘制出来出了问题。绘制出来的结果是图片只显示了大约一半的内容,另外一半则全是空白的。 ??发生这么严重的问题,我当时第一反应谷歌了下,老外非常早就解决了我的疑惑:因为QPainter内部做了限制,最大尺寸是32767 。当时因为急着处理项目,只是记录了下,未再深究。 ??奈何最近家里疫情,心里闷得慌,正好翻到了以前的笔记,花了点时间找到了原因。下面记录了我整个查找的流程,觉得啰嗦的可以倒序看😭。
QPainter
??由于QGraphicsScene的render函数传入的第一个参数是QPainter,所以我们可以直接从QPainter入手。 ??QPainter要能成功绘制图片,必须在构造 时候或者在调用begin 函数时候,传入一个有效的绘制设备QPaintDevice 。先看QPainter的构造函数: ??不难发现如果一开始传入一个设备,那么会自动调用begin函数,所以我们把视线转到begin函数。
QPainter的绘制
??在继续深入之前,我们得明白画笔的绘制原理。Qt的QPainter要绘制在指定的QPaintDevice上,是得经过一层QPaintEngine 的,它提供了大量的绘制接口。 ??我的理解是笔必须得手拿着才能画东西在纸上,此时笔就是QPainter,手就是QPaintEngine,纸就是QPaintDevice。当然笔不一定要手拿着,机械臂也行,画的地方也不一定是纸,在墙上偷偷涂鸦也是很OK的。 ??可能我的比喻比较浮夸不贴切,所以还是贴下Qt官方的说明:
The QPaintEngine class provides an abstract definition of how QPainter draws to a given device on a given platform
QPainter的begin
函数原型
bool QPainter::begin(QPaintDevice *pd)
??begin的实现比较长,但我们已经知道了QPainter怎么绘制的,那么begin里必定有对engine的调用。
??上面的红箭头处,把设备的paintEngine赋值给painter的D指针,下面的箭头处,调用了QPaintEngine的begin。接着我们去看看QPaintEngine。
QPaintEngine
??我们转到QPaintEngine的begin定义,好吧,它是个纯虚函数。
virtual bool begin(QPaintDevice *pdev) = 0;
??我感觉一定遗漏了什么!是的,就是上图第一个红色箭头的 pd->paintEngine() 。 ??pd是QPaintDevice*,也就是说pd返回的paintEngine一定是QPaintEngine的派生类,我们只要知道pd是什么类型的QPaintDevice,就能知道它继承的PaintEngine叫什么名字。 ??这边我绘制的是QImage,所以我必须跑到QImage源码去瞅瞅。
QImage
我们来到QImage的实现的地方,直接搜索paintEngine函数。
QPaintEngine *QImage::paintEngine() const
{
if (!d)
return 0;
if (!d->paintEngine) {
QPaintDevice *paintDevice = const_cast<QImage *>(this);
QPlatformIntegration *platformIntegration = QGuiApplicationPrivate::platformIntegration();
if (platformIntegration)
d->paintEngine = platformIntegration->createImagePaintEngine(paintDevice);
if (!d->paintEngine)
d->paintEngine = new QRasterPaintEngine(paintDevice);
}
return d->paintEngine;
}
可以看到,当paintEngine为空时,会新建一个QRasterPaintEngine 类型的paintEngine。接着,我们去看看QRasterPaintEngine是如何实现begin的。
QRasterPaintEngine
在QRasterPaintEngine的begin中,它调用了一个systemStateChanged 函数,我们进入其中。 发现这个deviceRectUnclipped 变量定义了不裁剪的区域,这个区域的宽高中,有QT_RASTER_COORD_LIMIT 变量,转到定义。 我们发现它最大支持32767,但它不是老外说的在QPainter,而是在QPaintEngine的子类QRasterPaintEngine。
为何是32767
??但为什么只能是32767还是没解决,所以,还是得回到原点,也就是QGraphicsScene的render函数,我们要找到它绘制的函数,或许才能窥见原因。 ??在render的接近末尾处,调用了三个draw函数: ??我们直接进入drawBackground 一探究竟。 这里最关键的在我看来就一个fillRect函数,开始填充整个区域。这个函数来自于QRasterPaintEngine。 fillRect中调用了adjustSpanMethod 方法,此处省略一些过程,因为要说起来太多了。简而言之,在层层调用之后,来到了一个名为qt_intersect_spans 的函数: 这里都是裁剪相关的函数,我们可以看到,4个const short 类型锁定了区域。short的正范围是215-1,也就是32767。所以,至此知道了32767的由来。
其它
??可能有人会问,为何要用short而不是int来增大范围,这个查了半天也没查到原因,希望懂的人能留下你宝贵的评论 ??大图像无法绘制我这边采用了缩放的方法来解决,StackOverflow建议是可以通过拼接图像来完成,可以根据需求选择最合适的方法。
|