【Qt】drawText字体大小问题探究

背景

软件的一个功能是:

  1. 打开图片
  2. 在图片上绘制序号,序号的样式是圆圈内包含数字
  3. 将带有序号的图片打印出来

实现思路也很简单,在屏幕上显示时重写paintEvent函数,利用QPainter完成图片和序号的绘制。打印时只需要将QPainter对应的QPaintDevice切换成QPrinter就可以了。

具体来说就是利用drawEllipse绘制圆圈,利用drawText绘制数字,利用QFontsetPointSizeF设置数字大小,以适配圆圈大小。

问题

这个功能的逻辑不算复杂,在开发时没有什么问题,能够正常显示和打印。

但是在测试阶段发现如下问题:

  1. 屏幕上显示的序号看上去很正常,但打印出的序号数字明显变小了
  2. 换了一台机器运行,序号中的数字变得很大,导致数字只能部分显示。

这两个问题都是字体相对于圆圈大小的问题。

原因

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对应的设备有关。

对于第一个问题 ,屏幕绘制与打印唯一的区别就是QPainterQPaintDevice不同,所以基本可以确定问题出在QPaintDevice上。
第二个问题 基本可以确认是硬件上的原因,进一步推定是屏幕的原因。

一番测试后,基本确定是由于QPaintDevice的DPI不同造成的

尝试给出最小复现代码:

  1. 自定义QPaintDevice,实现不同DPI的QPaintDevice
  2. 利用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();

虽然找到了解决方案,但没能完全明白问题所在。

各位大神有清楚的请多指教。

相关推荐
点云SLAM42 分钟前
C++ 平面拟合原理和最小法实现示例
c++·线性代数·平面·最小二乘法·平面拟合·pca算法
小gpt&1 小时前
01 音视频知识学习(视频)
c++·qt·学习·ffmpeg·音视频
AI+程序员在路上1 小时前
QT与网页显示数据公式的方法
开发语言·qt
Source.Liu1 小时前
【CXX】6.6 UniquePtr<T> — std::unique_ptr<T>
c++·rust·cxx
仟濹2 小时前
【前缀和与差分 二分搜索 C/C++】洛谷 P1083 借教室
c语言·c++·算法
心态与习惯2 小时前
c++ 调用 gurobi 库,cmake,mac
c++·macos·cmake·第三方库·gurobi
Zԅ(¯ㅂ¯ԅ)2 小时前
计算机图形学交互式技术实验(鼠标、拾取操作和菜单)——绘制可用鼠标进行修改颜色的五角星和矩形
c++·visual studio
John_ToDebug2 小时前
chrome源码中非常巧妙、复杂或者不常见的技术手段
c++·chrome·性能优化
七七知享3 小时前
2024 Qiniu 跨平台 Qt 高级开发全解析
开发语言·qt·零基础·操作系统·跨平台·qt5·精通
caoruipeng4 小时前
Windows编程----结束进程
c++·windows·子进程