背景
软件的一个功能是:
- 打开图片
- 在图片上绘制序号,序号的样式是圆圈内包含数字
- 将带有序号的图片打印出来
实现思路也很简单,在屏幕上显示时重写paintEvent
函数,利用QPainter
完成图片和序号的绘制。打印时只需要将QPainter
对应的QPaintDevice
切换成QPrinter
就可以了。
具体来说就是利用drawEllipse
绘制圆圈,利用drawText
绘制数字,利用QFont
的setPointSizeF
设置数字大小,以适配圆圈大小。
问题
这个功能的逻辑不算复杂,在开发时没有什么问题,能够正常显示和打印。
但是在测试阶段发现如下问题:
- 屏幕上显示的序号看上去很正常,但打印出的序号数字明显变小了。
- 换了一台机器运行,序号中的数字变得很大,导致数字只能部分显示。
这两个问题都是字体相对于圆圈大小的问题。
原因
Qt提供了两种方法设置字体大小:
setPixelSize
Sets the font size to pixelSize pixels, with a maxiumum size of an unsigned 16-bit integer.Using this function makes the font device dependent. Use setPointSize() or setPointSizeF() to set the size of the font in a device independent manner.
setPointSize
/setPointSizeF
Sets the point size to pointSize. The point size must be greater than zero.
按照官方文档的说法,通过setPointSizeF
设置字体大小,可以做到与设备无关。但上面遇到的问题明显是和QPainter
对应的设备有关。
对于第一个问题 ,屏幕绘制与打印唯一的区别就是QPainter
的QPaintDevice
不同,所以基本可以确定问题出在QPaintDevice
上。
第二个问题 基本可以确认是硬件上的原因,进一步推定是屏幕的原因。
一番测试后,基本确定是由于QPaintDevice
的DPI不同造成的。
尝试给出最小复现代码:
- 自定义
QPaintDevice
,实现不同DPI的QPaintDevice
- 利用
QPainter
在自定义QPaintDevice
上绘制序号
对比不同DPI对绘制效果的影响。
cpp
const int customDPI = 48 * 2;
class CustomPaintDevice : public QPaintDevice {
public:
CustomPaintDevice(int width, int height) : image(width, height, QImage::Format_ARGB32_Premultiplied) {
image.fill(Qt::white);
}
QImage getImage() const { return image; }
protected:
int metric(PaintDeviceMetric metric) const override {
switch (metric) {
case PdmWidth:
return image.width();
case PdmHeight:
return image.height();
case PdmDpiX:
case PdmDpiY:
return customDPI;
default:
return 0;
}
}
QPaintEngine* paintEngine() const override {
return image.paintEngine();
}
private:
QImage image;
};
cpp
class TestDeviceDPI : public QWidget
{
Q_OBJECT
public:
explicit TestDeviceDPI(QWidget *parent = nullptr) : QWidget{parent} {}
protected:
void paintEvent(QPaintEvent *e) {
int diameter = 50; // diameter of circle
QPoint pos(100,100);
QPainter customDevicePainter;
CustomPaintDevice *customDevice = new CustomPaintDevice(500,500); // Define dimensions
customDevicePainter.begin(customDevice);
QFont font;
font.setPointSizeF(diameter / 2.0);
font.setBold(true);
customDevicePainter.setFont(font);
customDevicePainter.drawText(QRectF(pos.x() - diameter / 2.0, pos.y() - diameter / 2.0, diameter, diameter),
Qt::AlignmentFlag::AlignCenter, QString::number(10));
customDevicePainter.drawEllipse(QRectF(pos.x() - diameter / 2.0,
pos.y() - diameter / 2.0,
diameter, diameter));
customDevicePainter.end();
QPainter widgetPainter(this);
QImage renderedImage = customDevice->getImage();
widgetPainter.drawImage(0, 0, renderedImage);
QWidget::paintEvent(e);
}
};
在保持diameter
不变的情况下,分别设置customDPI
为48、96、192效果如下图:
规律非常明显,DPI越大,绘制字体的效果越大,但圆圈大小保持不变。
不知道是不是哪里使用出了问题,至少目前看来QPaintDevice
的DPI会影响drawText
的字体大小,但不会影响drawEllipse
。
解决
方案1
尝试使用setPixelSize
设置字体大小,发现不会出现上面说的问题,这和官方文档的说法正好相反 ,我都有些怀疑是不是我英语不好理解错了......
但官方文档在setPixelSize下的说法:
Using this function makes the font device dependent. Use setPointSize() or setPointSizeF() to set the size of the font in a device independent manner.
分明就是setPixelSize
与设备相关,setPointSize
与设备无关......
方案2
另一个方案就是在设置字体大小时将DPI这个因素考虑进去即可:
cpp
customDevicePainter.begin(customDevice);
QFont font;
float baseDpi = 96; // Typical DPI for a QWidget
float deviceDpi = p->device()->logicalDpiY();
font.setPointSizeF((diameter / 2.0) / (deviceDpi / baseDpi));
font.setBold(true);
customDevicePainter.setFont(font);
customDevicePainter.drawText(QRectF(pos.x() - diameter / 2.0, pos.y() - diameter / 2.0, diameter, diameter),
Qt::AlignmentFlag::AlignCenter, QString::number(10));
customDevicePainter.drawEllipse(QRectF(pos.x() - diameter / 2.0,
pos.y() - diameter / 2.0,
diameter, diameter));
customDevicePainter.end();
虽然找到了解决方案,但没能完全明白问题所在。
各位大神有清楚的请多指教。