当前位置: 首页 > news >正文

Qt DPI相关逻辑

今天给大家分享下Qt高分屏相关知识,依然从源码开始,本文源码版本qt5.14.16

static qreal qt_effective_device_pixel_ratio(QWindow *window = nullptr)
{if (!qApp->testAttribute(Qt::AA_UseHighDpiPixmaps))return qreal(1.0);if (window)return window->devicePixelRatio();return qApp->devicePixelRatio(); // Don't know which window to target.
}
上面代码是qicon类中,计算缩放比下尺寸的函数,可以清晰看到最开始判断条件是一个flag值,Qt::AA_UseHighDpiPixmaps,如果未开启,就直接返回1.0

原因可能有几个:

  1. Qt 没开启 High-DPI 支持
    Qt 默认是逻辑 DPI = 96,如果你没开启 DPI scaling,那么 qt_effective_device_pixel_ratio(window) 就始终返回 1。

    需要在 main() 里启用:

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
    
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
    

    我来解释下上面三行分别有什么用:
    AA_EnableHighDpiScaling让 Qt 根据系统 DPI 自动对窗口和 UI 元素做缩放(逻辑坐标 → 物理坐标),相当于 Qt 内部帮你把 150% 转换成 1.5 的缩放因子。
    AA_UseHighDpiPixmaps让 Qt 根据 devicePixelRatio 自动选择/生成合适的 pixmap(比如自动加载 @2x 图片,或者内部存两份 pixmap)。
    (Qt::HighDpiScaleFactorRoundingPolicy::PassThrough) 则是选择让缩放比支持小数而不是round取整,例如1.5的缩放比不会返回为2

  2. 传入的 QWindow* window 没有关联物理屏幕
    如果 window == nullptr,或者它还没 show(),Qt 没法取到屏幕的 devicePixelRatio,也会返回默认的 1。

    qDebug() << window->devicePixelRatio();
    qDebug() << window->screen()->devicePixelRatio();
    qDebug() << QGuiApplication::primaryScreen()->devicePixelRatio();
    

    如果这些都是 1,说明 Qt 根本没感知到你的缩放。

  3. Windows 下 DPI 感知等级

    • 如果 exe 的 manifest 里没有启用 DPI awareness(Per Monitor V2 / Per Monitor),Windows 会自动给你的程序做 DPI 虚拟化,Qt 拿到的 DPI 就是 96 → 比例 = 1。
    • 在 Qt 5.6 以后,需要确保 manifest 或者 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling) 打开,否则系统缩放不会反映到 devicePixelRatio。(Qt 的 windeployqt 默认加了)

  • Qt::AA_EnableHighDpiScaling
    让 Qt 根据系统 DPI 自动对窗口和 UI 元素做缩放(逻辑坐标 → 物理坐标),相当于 Qt 内部帮你把 150% 转换成 1.5 的缩放因子。

  • Qt::AA_UseHighDpiPixmaps
    让 Qt 根据 devicePixelRatio 自动选择/生成合适的 pixmap(比如自动加载 @2x 图片,或者内部存两份 pixmap)。

👉 如果没启用,它们都走“老 DPI 模式”:逻辑像素 = 物理像素,devicePixelRatio 就始终是 1


  • 如果你的应用只关心“逻辑 DPI 转换”(比如自己算 px = dpi/96):
    👉 不要启用 AA_EnableHighDpiScaling,否则会有重复计算风险。

  • 如果你希望 Qt 自动帮你缩放 UI 和图标(多数桌面应用):
    👉 启用 AA_EnableHighDpiScaling + AA_UseHighDpiPixmaps,然后 不要再自己做 dpi/96 的换算,只用 QWindow::devicePixelRatio()

  • Qt 5.14+ 的推荐写法

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
    

    这样 Qt 就不会把 1.5 round 到 2.0,而是保留 1.5。


一句话总结

  • 要么完全交给 Qt(开属性,不做 dpi/96 计算);
  • 要么完全自己算(关属性,手动做 dpi/96);
  • 混用就会出现“1.5 → 2.0 → 再除 96 → 翻倍”的情况。

Qt 里 devicePixelRatio 之所以从 1.5 → 2.0,是因为 Qt 默认用了 取整策略(Round to nearest integer)。
Qt 5.14 开始,Qt 提供了一个接口,可以控制缩放因子的取整策略:

QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);

可选策略

  • Qt::HighDpiScaleFactorRoundingPolicy::Round (默认)
    四舍五入,1.5 → 2.0

  • Qt::HighDpiScaleFactorRoundingPolicy::Ceil
    向上取整,1.5 → 2.0

  • Qt::HighDpiScaleFactorRoundingPolicy::Floor
    向下取整,1.5 → 1.0

  • Qt::HighDpiScaleFactorRoundingPolicy::PassThrough
    保留原值,不取整,1.5 → 1.5


用法示例

main() 里,QApplication/QGuiApplication 构造之前调用:

int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);QApplication app(argc, argv);...return app.exec();
}

这样在 150% 缩放的屏幕上,QWindow::devicePixelRatio()QPixmap::devicePixelRatio() 就会返回 1.5,而不是 2.0


qreal QScreen::logicalDotsPerInch() const
{Q_D(const QScreen);QDpi dpi = QHighDpiScaling::isActive() ? QHighDpiScaling::logicalDpi(this) : d->logicalDpi;return (dpi.first + dpi.second) * qreal(0.5);
}

这里开启了上述说的的attribute后,返回值变成了96,而不是缩放比的1.5后的144
这个点是 Qt HighDPI 的两个不同层次


为什么启用 AA_EnableHighDpiScaling 后变成了 96

启用 HighDPI scaling 以后,Qt 会把 系统缩放(1.5, 2.0 …)折算进了“scale factor”
而把 逻辑 DPI 重新归一化为 96(也就是 “Qt 坐标空间” 总是 96 DPI)。

这样做的原因是:

  • 保证应用在不同缩放下使用的 逻辑坐标系统是一致的
  • 让开发者不需要关心 DPI,统一用 96 作为“逻辑 DPI”,缩放全部交给 devicePixelRatio

举例:150% 缩放的显示器

  • 没开 HighDPI 属性时

    • QScreen::logicalDotsPerInch() = 144 (96 × 1.5)
    • QWindow::devicePixelRatio() = 1.0

    👉 你必须自己做 dpi / 96.0 的换算。

  • 开了 HighDPI 属性时

    • QScreen::logicalDotsPerInch() = 96
    • QWindow::devicePixelRatio() = 1.5

    👉 逻辑 DPI 固定 96,缩放信息放到 devicePixelRatio 里。


关键点

  • 逻辑 DPI (logicalDotsPerInch):给 UI 计算字体/布局用,Qt 强制规范成 96,避免应用乱掉。
  • devicePixelRatio:才是真正反映缩放比例的参数。

总结
看到 96 并不是 Qt 算错,而是因为开启了 HighDPI scaling,Qt 把缩放从 DPI 拆出去放到了 devicePixelRatio()
所以在这个模式下,不要再依赖 logicalDotsPerInch() 来算缩放,应该用:

qreal scale = window->devicePixelRatio();   // 或 screen->devicePixelRatio()

High-DPI 在 “启用 / 未启用” 两种模式下的行为尽可能全面地整理成:核心结论 → 对照表 → 公式 / 代码检测片段 → 实务建议 / 检查清单

先给核心结论:

  • 启用 Qt 的 High-DPI(Qt::AA_EnableHighDpiScaling / QT_ENABLE_HIGHDPI_SCALING)后,Qt 会把 “缩放” 的信息放到 devicePixelRatio(),并把逻辑 DPI 归一到 96(方便统一逻辑坐标系); 不要同时再用 dpi/96 做二次缩放。 (codebrowser.dev)
  • 非整数缩放(例如 1.5)可能被 Qt 按策略取整(默认 Qt5 是 Round)——可以通过 QGuiApplication::setHighDpiScaleFactorRoundingPolicy() 改为 PassThrough 保留 1.5。 (doc.qt.io)
  • High-DPI 的最终可见效果还受 环境变量(QT_SCALE_FACTOR / QT_AUTO_SCREEN_SCALE_FACTOR / QT_SCREEN_SCALE_FACTORSWindows 的进程 DPI awareness(manifest / Per-Monitor v2) 影响。 (doc.qt.io)

对照表(便于快速查找)

概念 / APIHigh-DPI 关闭(默认老行为)High-DPI 启用AA_EnableHighDpiScaling / Qt 自动缩放)说明 / 取值与使用建议
QScreen::logicalDotsPerInch()返回屏幕实际 DPI(例如 144,96×1.5)返回 96(逻辑 DPI 统一归一),缩放转移到 devicePixelRatio()启用后不要用 logicalDotsPerInch()/96 来算缩放;改用 devicePixelRatio()。 (codebrowser.dev)
QScreen::physicalDotsPerInch()代表显示器物理 DPI(与系统设置相关)仍能从底层平台查询到原始物理 DPI(平台实现差异)若要底层实际 DPI,可查询 platform 接口或 QPlatformScreen
QWindow::devicePixelRatio() / qt_effective_device_pixel_ratio(window)通常 1.0(Qt 未启用高 DPI)返回屏幕缩放因子(1.5 / 2.0 / …),受 rounding policy 影响若启用 High-DPI,以它为准 来换算像素。 (doc.qt.io)
QPixmap::devicePixelRatio()多数情况为 1,除非你手动设置或加载 @2x 资源pixmap 持有自己的 DPR,用于绘制到高 DPR 屏幕(Qt 会为你选或扩展)使用 AA_UseHighDpiPixmaps 可自动管理 pixmap。
QIcon::pixmap()需要你按 DPI 手动选择资源或缩放Qt 会基于 DPR 给合适尺寸/分辨率的 pixmap(如果 Qt 能识别和管理)别既启用 Qt scaling 又在外面再做 dpi/96 的换算,否则重复缩放。
QScreen::logicalDotsPerInchX/Y()= 实际屏幕 DPI= 96(统一逻辑 DPI)同上。
环境变量QT_SCALE_FACTOR / QT_AUTO_SCREEN_SCALE_FACTOR 可影响(在 Qt < 5.6/5.14 行为差异)同上;且 QT_SCALE_FACTOR 会与原生 DPR 相乘,得到最终 DPR推荐用环境变量做临时测试或覆盖,生产把 DPI awareness 放到 manifest/代码里。 (doc.qt.io)
Rounding 策略无(因为通常 DPR=1)Qt 默认(Qt5)将非整数四舍五入(Round);可改为 PassThrough 保留 1.5使用 QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); 保留精确值。 (doc.qt.io)

关键公式(如何换算 pixel / logical)

(对照两种模式给出公式,方便直接套用)

  • High-DPI 未启用(你自己处理 DPI)

    • scale = screen->logicalDotsPerInch() / 96.0 (例如 144/96 = 1.5)
    • physical_pixels = logical_pixels * scale
  • High-DPI 启用(交给 Qt)

    • scale = window->devicePixelRatio()screen->devicePixelRatio() (例如 1.5)
    • physical_pixels = logical_pixels * scale
    • 注意:QScreen::logicalDotsPerInch() 在这种模式通常返回 96,不要再用 logicalDotsPerInch()/96 做重复换算。 (codebrowser.dev)

常见坑

  • 同时开启 AA_EnableHighDpiScaling 又手动用 dpi/96:会造成“重复缩放”。
  • 双屏(100% + 150%)且 Qt 默认 Round 策略:你会看到某屏返回 devicePixelRatio() = 2.0(1.5 被 round 到 2),外观比系统其它窗口更“大”。可以改为 PassThrough。 (doc.qt.io)
  • Windows 下如果没有把进程设为 Per-Monitor DPI aware(manifest),Windows 可能会对程序做 DPI 虚拟化,导致 Qt 读到的仍是 96(即 DPR = 1)。务必检查 manifest / dpiawareness。 (微软学习)

推荐的 main() 启用方式(如果你想让 Qt 负责缩放并保留 1.5)

int main(int argc, char **argv)
{// 必须在创建 Q(Core)Application 之前QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);// 保留非整数缩放(不要 round 到 2.0)QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);QApplication app(argc, argv);...return app.exec();
}

说明:把 rounding 政策放在 application 构造前设置。 (doc.qt.io)


诊断小段:打印各项值(把它放到某个窗口 show() 后调用)

把下面拷到程序合适位置(窗口已 show())可以看清楚当前各屏和窗口的实际值:

#include <QDebug>void dumpDpiInfo(QWindow* w = nullptr)
{qDebug() << "QT envs: QT_SCALE_FACTOR=" << qEnvironmentVariable("QT_SCALE_FACTOR")<< " QT_AUTO_SCREEN_SCALE_FACTOR=" << qEnvironmentVariable("QT_AUTO_SCREEN_SCALE_FACTOR");QList<QScreen*> screens = QGuiApplication::screens();for (QScreen* s : screens) {qDebug() << "Screen" << s->name()<< "logicalDPI:" << s->logicalDotsPerInch()<< "physicalDPI:" << s->physicalDotsPerInch()<< "devicePixelRatio:" << s->devicePixelRatio()<< "geometry:" << s->geometry();}if (w) {qDebug() << "Window devicePixelRatio():" << w->devicePixelRatio()<< "qt_effective_device_pixel_ratio:" << qt_effective_device_pixel_ratio(w);}
}

这样可以直接看到:logicalDotsPerInch()devicePixelRatio()、以及环境变量对最终 DPR 的影响。


实务建议(如果在做跨屏/跨平台的生产级 UI)

  1. 二选其一: 要么让 Qt 完全负责(启用 AA_EnableHighDpiScaling + AA_UseHighDpiPixmaps不要再做 dpi/96),要么完全关掉(自己用 logicalDotsPerInch()/96 做缩放)。混用会出错。 (doc.qt.io)
  2. 双屏或非整数缩放场景下,推荐把 rounding policy 设为 PassThrough(保留 1.5)以避免四舍五入导致的视觉差异。 (doc.qt.io)
  3. 在 Windows 上把 DPI awareness 放在 manifest(Per-Monitor V2)或使用 qt.confdpiawareness 参数进行设置,避免被 Windows 自动虚拟化。 (微软学习)
  4. 使用 QT_SCALE_FACTOR / QT_AUTO_SCREEN_SCALE_FACTOR 做临时测试,但生产中以 manifest + AA_ 为主。 (doc.qt.io)

快速检查清单(遇到 DPR=1 或逻辑 DPI=96 但期望看到 144 时按这个排查)

  • main() 是否在 QApplication 前设置了 AA_EnableHighDpiScaling?(若没设置,Qt 可能不启用 high-dpi)
  • QWindow* 是否有效且已经 show()?(若未 show,屏幕信息可能不可用)
  • 是否设置了 rounding policy 导致 1.5 被 round 到 2?(PassThrough 可保留 1.5)
  • Windows 下 exe 的 manifest 是否设置了合适的 DPI awareness?(Per-Monitor v2 推荐)
  • 有没有同时自己做 dpi/96 的手动换算?(若 Qt 已处理,就不要再做)

启用高 DPI 缩放后,图标变模糊,基本上就是 资源不足(低分辨率图标被放大) 或者 缩放策略不理想 导致的。Qt 和 Windows 都提供了几类解决方法,我整理一下常见做法和适用场景。


1. 提供高分屏资源(首选方案)

这是最根本、最推荐的方案。

  • QIcon 多分辨率加载

    • Qt 支持 QIcon::addFile(":/icons/foo.png", QSize(16,16)) 加入不同尺寸的资源。
    • Qt 会在渲染时根据 devicePixelRatio 选取最合适的那张。
    • 推荐提供:16×16, 24×24, 32×32, 48×48, 64×64, 128×128。
  • @2x 命名规则(类似 iOS Retina)

    • Qt 会自动识别 icon.pngicon@2x.png,在 devicePixelRatio=2.0 的屏幕上优先使用 @2x 图。
    • Qt 5.6+ 支持,Qt 5.14+ 配合 AA_UseHighDpiPixmaps 更完善。

2. 启用高 DPI pixmap 管理

  • main() 里加:

    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
    

    这样 QIcon::pixmap() 会考虑 devicePixelRatio,自动选用合适的分辨率。


3. 控制缩放算法(避免模糊)

Qt 默认的缩放算法是 平滑插值,对小图标放大会变糊。可以通过 Qt::TransformationMode 调整:

  • 平滑缩放(默认,Qt::SmoothTransformation):抗锯齿,但容易糊。
  • 快速缩放Qt::FastTransformation):更锐利,可能有锯齿,但在小图标上反而更清晰。

示例:

QPixmap pix = icon.pixmap(size * devicePixelRatio, mode, state);
pix.setDevicePixelRatio(devicePixelRatio);
QPixmap sharp = pix.scaled(size, Qt::KeepAspectRatio, Qt::FastTransformation);

4. 使用矢量图标(SVG/Icon Fonts)

避免位图放大的根本办法:

  • QSvgRenderer + QIcon:直接加载 .svg 资源,缩放时无损。
  • Icon Font(FontAwesome, Material Icons):随 DPI 缩放,无模糊。

Qt 提供 QIcon::fromTheme() 也可以加载系统矢量图标。


5. Windows / 系统级别设置

  • 在 Windows manifest 声明 Per-Monitor DPI Aware v2,否则系统可能对应用做位图缩放 → 模糊。

  • 在 Qt 里确认启用了 AA_EnableHighDpiScaling,并设置

    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
    

    避免 1.5 → 2.0 的取整放大导致图标更糊。


6. 程序内的补救算法

如果现有资源有限,可以在程序里做一些图标渲染优化:

  • 在小图标上用 FastTransformation,在大图上用 SmoothTransformation
  • 对缩放后的 QPixmap 做一次锐化滤镜(Sobel/Laplacian 卷积),但这只是补救。
  • 部分项目会用 MipMap 思路:事先生成多级别分辨率的图标,运行时按 DPR 选择。

✅ 建议实践路线

  1. 优先准备多分辨率资源(含 @2x),或直接用 SVG
  2. 在 Qt 层启用 AA_UseHighDpiPixmaps,让 Qt 自动挑资源。
  3. 对必须缩放的小图标,手动选择 Qt::FastTransformation,必要时锐化。
  4. 确保 manifest 正确声明 DPI Awareness,避免系统层“二次模糊”。

QIcon 管理类(比如 HiDpiIconManager),自动封装:

  • 从资源里加载多分辨率图标
  • 根据 DPR 选择最优资源
  • 必要时做一次锐化缩放

这样只要写 HiDpiIconManager::load(":/icons/foo"),就能自动拿到高清图标,不用每次考虑 DPR

缩放比 = 1.5 的情况下,Qt 会怎么挑资源?


1. Qt 的资源命名规则

Qt 支持类似 iOS 的 @Nx 命名规则

  • icon.png → 基础资源,认为是 @1x
  • icon@2x.png → 为 2 倍缩放屏幕准备
  • icon@3x.png → 为 3 倍缩放屏幕准备

2. Qt 的匹配逻辑

devicePixelRatio = X 时,Qt 会选择最接近的整数资源:

  • 1.0 → 使用 icon.png
  • 1.25, 1.5, 1.75 → Qt 仍然选择 icon.png(1x 图),然后 按 1.5 缩放算法拉伸
  • 2.0 → 使用 icon@2x.png
  • 3.0 → 使用 icon@3x.png

也就是说:
👉 非整数缩放(1.25、1.5、1.75)时,Qt 不会去找 icon@1.5x.png,而是从最近的整数资源(通常 @1x@2x)里缩放。


3. 为什么会模糊

  • 如果只有 icon.png(1x),在 150% 时 Qt 会把它放大 → 模糊。
  • 如果有 icon@2x.png,Qt 可能会在 1.5 的情况下选择它,然后再缩小 → 效果会好很多(比放大更清晰)。

4. 常见解决方案

  • 提供 @2x 资源:哪怕缩放比是 1.5,Qt 也能用高分图再缩小 → 更清晰。
  • 矢量图 (SVG):Qt 会按缩放比重新渲染,避免模糊。
  • 开启 AA_UseHighDpiPixmaps:让 Qt 知道应该优先找 @2x 图。
  • 算法优化:Qt 默认用平滑缩放(bilinear),如果需要可以改为 Qt::FastTransformation 或更高质量的 Qt::SmoothTransformation

📌 总结

  • 1.5 倍缩放 下,Qt 默认仍然用 icon.png 并缩放。
  • 如果额外提供了 icon@2x.png,Qt 会更倾向于用高分图再缩小,显示更清晰。
  • 最推荐的方案是 准备 @2x 图直接用 SVG 矢量图

1. SVG 本身的特点

理论上 SVG 是矢量图,按道理在缩放时不会模糊(它应该无限清晰)。
但是实际情况取决于 SVG 的内容

  • 如果路径(<path><polygon> 等)用的是矢量描述 → 缩放时清晰 ✅
  • 如果内部嵌入了 <image> 或者某些近似栅格化的效果(如滤镜、半透明像素对齐不整)→ 放大时可能模糊 ❌

SVG 内容里:

  • 主体都是 <path><polygon>,是矢量的。
  • 但是关键在于:坐标体系是 32x32,然后 Qt 在 1.5 倍时请求的是 48x48 像素图像

2. 为什么还是模糊

Qt 处理 SVG 时大概流程是:

  1. QSvgRenderer 把矢量渲染到 QImage / QPixmap 上。

  2. 输出尺寸 = 目标 QPixmap 大小(比如 48x48)。

  3. 问题:QSvgRenderer 默认用浮点坐标渲染,最后 Qt 会把它光栅化 → 变成像素。

    • 如果缩放比例不是整数(比如 1.5),那么很多直线/边框会落在 半个像素 上。
    • 结果 Qt 会做抗锯齿(抗锯齿 = 插值 = 模糊感)。

所以根本原因是:
👉 SVG 是矢量的,但光栅化到 48×48 时,不可避免要插值和抗锯齿 → 看上去发虚。


3. 常见解决方法

方法 A:禁用抗锯齿,让边界硬对齐
QPainter p(&pixmap);
p.setRenderHint(QPainter::Antialiasing, false);
p.setRenderHint(QPainter::TextAntialiasing, false);
p.setRenderHint(QPainter::SmoothPixmapTransform, false);
renderer.render(&p);

这样线条会锐利,但可能有锯齿。


方法 B:渲染到更高分辨率,再缩小

比如目标是 48x48,可以先渲染 96x96,然后缩小 → 细节更多,更接近 @2x 的清晰度:

QPixmap pix(96, 96);
pix.fill(Qt::transparent);QPainter p(&pix);
renderer.render(&p);pix = pix.scaled(48, 48, Qt::KeepAspectRatio, Qt::SmoothTransformation);

相当于“超采样抗锯齿”,很多 UI 库(包括 macOS)用的就是这个 trick。


方法 C:使用整数坐标和尺寸

如果 SVG viewBox 是 32x32,那么最好让缩放比 = 整数倍(32→64,32→96),避免落在半像素。
但这就和 DPI 缩放的 1.25、1.5、1.75 冲突了 → 只能靠 oversampling 来弥补。


方法 D:优先考虑 SVG + IconEngine

Qt 里 QIcon::fromThemeQIcon::addFile(svg),Qt 会根据 devicePixelRatio 自动挑更合适的分辨率绘制。
但如果比例是 1.5,它仍然会渲染到 48x48,然后触发上面说的半像素问题 → 本质依旧是 oversampling 方案更靠谱。


总结

  • SVG 本身没问题,模糊是因为 1.5 倍缩放导致路径落在半像素,Qt 抗锯齿插值后发虚

  • 解决办法:

    1. 关掉抗锯齿(锐利但有锯齿感)。
    2. 或者用 oversampling 渲染更大尺寸再缩小(推荐)。
    3. 如果能强制用整数倍缩放(2x、3x),那就最清晰。

QOpenGlWidget尺寸问题

上述所说的设置主要影响 QPainter / QWidget 绘制的像素比例,但 不会直接改变 QOpenGLWidget 的尺寸,原因如下:


关键点

  1. QWidget 的尺寸 (QSize)
    QWidget::size() 返回的单位始终是 逻辑像素,即跟随 Qt 的 DPI 缩放策略,而不是物理像素。

  2. OpenGL 的 framebuffer 尺寸
    对于 QOpenGLWidget,真正的渲染区域大小由 QOpenGLWidget::devicePixelRatioF() 决定。
    实际 OpenGL 渲染用的分辨率 = widget->size() * widget->devicePixelRatioF()

  3. 高 DPI 时的现象
    即使你在 Qt 中启用了高 DPI scaling,QOpenGLWidget::size() 看起来没变(逻辑像素),但 framebuffer 实际会变大。
    正确获取物理像素的方法是:

    QSize framebufferSize = openglWidget->size() * openglWidget->devicePixelRatioF();
    

建议解决办法

如果你需要在 OpenGL 里得到 正确的像素大小,不要直接用 width()/height(),而要用 framebuffer 的真实尺寸:

void MyOpenGLWidget::resizeGL(int w, int h)
{qreal dpr = devicePixelRatioF();glViewport(0, 0, int(w * dpr), int(h * dpr));
}

或者直接:

QSize framebufferSize = size() * devicePixelRatioF();
glViewport(0, 0, framebufferSize.width(), framebufferSize.height());

✅ 总结:
QOpenGLWidget 的逻辑尺寸没变是 正常的
在高 DPI 模式下,应该使用 devicePixelRatioF() 结合逻辑大小,才能得到真实的渲染分辨率。


鼠标事件捕捉的是逻辑坐标,对不上缩放后的内容

OpenGL 渲染用的是物理像素坐标,而 QWidget 的事件坐标用的是逻辑像素坐标

也就是说:

  • QMouseEvent::pos() / QMouseEvent::x(), y()逻辑坐标(受 DPI 缩放影响)
  • glViewport()、OpenGL 绘制 → 物理坐标(真实像素数)

所以当你在 resizeGL 里用了 w * devicePixelRatioF() 设置 viewport 后,鼠标点的位置就和渲染不匹配了。


🔧 解决办法

你需要在鼠标事件中,把逻辑坐标转为物理坐标(乘上 devicePixelRatioF()):

void MyOpenGLWidget::mousePressEvent(QMouseEvent *event)
{qreal dpr = devicePixelRatioF();QPoint logicalPos = event->pos(); // 逻辑像素QPointF physicalPos = logicalPos * dpr; // 转为物理像素qDebug() << "Mouse logical:" << logicalPos<< " physical:" << physicalPos;// 用 physicalPos 去对应 OpenGL 里的坐标系
}

同样适用于 mouseMoveEvent / mouseReleaseEvent 等。


void QOpenGLWidget::resizeGL(int width, int height) 
{makeCurrent(); int ratio = QApplication::desktop()->devicePixelRatio(); glViewport(0, 0, (GLint)width*ratio, (GLint)height*ratio); 
}

这里放大了尺寸不会会存在修改QOpenGLWidget的width和height;
resizeGL(int width, int height) 里:

  • widthheight 参数是 Qt 传递给 OpenGL 的逻辑窗口大小(单位是 Qt 的 device independent pixel,受高 DPI 缩放影响)。

  • 里面写的

    int ratio = QApplication::desktop()->devicePixelRatio();
    glViewport(0, 0, (GLint)width * ratio, (GLint)height * ratio);
    

    只是告诉 OpenGL “实际要渲染到多少像素大小的 framebuffer”。这不会反过来去修改 Qt 的 widget 大小。

换句话说:

  • QWidget 的 width() / height() 由 Qt 控件系统管理,只会因为用户调整窗口、布局管理等改变,不会因为你调用 glViewportglOrtho 而改变。
  • 乘以 ratio 只是把 OpenGL 的渲染区域扩展到物理像素级别(比如 Retina 屏上,一个 widget 的 width=100ratio=2,那么实际渲染区域是 200 像素宽)。这是 渲染层面的放大,而不是 Qt widget 尺寸的修改

所以 widget 在 Qt 看来大小不变(resizeEventwidth()height() 都不会受影响),只是 OpenGL 绘制用的视口坐标范围变大了。


要验证很简单,可以在 resizeGL 里打印:

qDebug() << "widget size:" << this->width() << this->height()<< "viewport:" << width*ratio << height*ratio;

你会发现 widget size 一直是逻辑大小,而 viewport 是物理像素大小。


如果存在OpenGLwidget小于绘制尺寸,这样也可以绘制成功吗?

结论是:会存在 QOpenGLWidget 的逻辑大小 < 你设置的 glViewport 尺寸,但依然能绘制成功

原因在于:


1. QOpenGLWidget 的实际 framebuffer 尺寸

  • Qt 内部会根据 逻辑大小 × devicePixelRatioQOpenGLWidget 创建一个 FBO(framebuffer object)。

  • 例如:

    • widget 逻辑大小:200 × 150
    • ratio:2(Retina 屏)
    • Qt 实际分配的 FBO:400 × 300

这保证了设置的

glViewport(0, 0, width*ratio, height*ratio);

和实际 FBO 一致,所以绘制不会出问题。


2. 如果“手动”设置 viewport 大于实际 FBO 尺寸

比如写:

glViewport(0, 0, width*ratio*2, height*ratio*2);

那超出 FBO 范围的区域不会被显示,OpenGL 仍然会绘制,但 超出的部分会被裁剪掉

所以看起来“也能绘制成功”,只是你只能看到 FBO 有效区域的内容。


3. widget 的逻辑大小 vs 绘制大小

  • 逻辑大小 (QWidget::width/height):由 Qt 管理,决定了控件在窗口中的布局占用。
  • 绘制大小 (OpenGL FBO 尺寸):Qt 根据逻辑大小和 ratio 自动分配。
  • glViewport 尺寸:你自己指定的绘制坐标范围,如果和 FBO 一致,就完美匹配。

✅ 所以,QOpenGLWidget 即使逻辑尺寸小(比如 200×150),只要正确用 devicePixelRatio,OpenGL 渲染结果都会显示出来;
❌ 如果 viewport 超过 FBO 尺寸,OpenGL 仍然会“画”,但超出区域被丢弃,看不到。


如果实际widget大小是200 × 150,但是绘制内容是400*300,那么在绘制内容的右下角点击,会出现没点击到widget吗

1. Widget 的边界永远是 200×150(逻辑像素)

  • Qt 的事件系统完全基于 逻辑坐标系
  • 也就是说,QOpenGLWidget 的矩形区域就是 200×150,鼠标只有在这个范围内才会把事件交给它。
  • 如果你用 glViewport(0,0,400,300),虽然 OpenGL 渲染了 400×300 的内容,但实际显示在屏幕上的可见矩形区域仍然只是 200×150 的逻辑大小对应的物理像素矩形

2. 高 DPI 下 Qt 的处理

  • devicePixelRatio = 2 时:

    • Widget 的逻辑大小:200×150
    • Qt 自动给它分配的 framebuffer 大小:400×300 物理像素
  • 所以虽然你告诉 OpenGL 要画 400×300,实际上正好对应了 Qt 分配的 FBO 尺寸。

  • 结果就是:你点击右下角(逻辑坐标 199,149),对应到物理坐标 (398,298),正好是 OpenGL 渲染区域的右下角。


3. 为什么“不会点到外面”

因为 Qt 的窗口系统只会把 200×150 的逻辑矩形 区域交给这个 widget,它之外根本没有你的 OpenGL 内容。
所以即使 OpenGL 里你写了 400×300 的 viewport,它也不会超出 widget 的矩形——Qt 已经帮你把物理像素和逻辑像素匹配好了。


在这里插入图片描述

✅ 结论

  • 不会出现“点到 widget 外面”的情况
  • 在高 DPI 下,200×150 的逻辑 widget 就对应 400×300 的物理 FBO。
  • 你点击右下角时,鼠标事件给你的是 (199,149)(逻辑坐标),乘上 ratio=2 后刚好是 OpenGL 的 (398,298)

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d9620bca86d84f579d515482ce296e10.png
有一个继承自 QOpenGLWidget 的类 SubOpenGLWidget,在 mousePressEvent 里拿到 QMouseEvent*,想把 逻辑坐标(Qt 提供的)转换成物理坐标(OpenGL FBO 用的) 再转发。


关键点

  • QMouseEvent::pos()逻辑坐标,范围是 widget 的 width() × height()
  • OpenGL 的 glViewport / glOrtho物理坐标,范围是 width*devicePixelRatio × height*devicePixelRatio
  • 你需要在 mousePressEvent 里做一次映射。

推荐实现

你可以封装一个小工具函数,比如:

QPointF SubOpenGLWidget::mapToPhysical(const QPoint& logicalPos) const
{qreal ratio = devicePixelRatioF(); // 推荐用这个,支持浮点和多屏return QPointF(logicalPos.x() * ratio,logicalPos.y() * ratio);
}

然后在事件函数里这样用:

void SubOpenGLWidget::mousePressEvent(QMouseEvent* pEvent)
{ODA_ASSERT_ONCE(pEvent->isAccepted());bool bAccepted = false;if (!m_pView.isNull()){// 转换逻辑坐标 -> 物理坐标QPointF physPos = mapToPhysical(pEvent->pos());// 可以选择构造一个新的 QMouseEvent 转发出去QMouseEvent physEvent(pEvent->type(),physPos,                   // 物理坐标pEvent->button(),pEvent->buttons(),pEvent->modifiers());emit m_pView->mousePress(m_pView, &physEvent, bAccepted);}
}

注意事项

  1. 不要直接改原来的 pEvent,因为 Qt 事件对象属于事件循环管理。最好新建一个临时的 QMouseEvent(如上)。
  2. 一定要用 devicePixelRatioF() 而不是老的 QApplication::desktop()->devicePixelRatio(),因为后者在多显示器 / 不同 DPI 下不准确。

👌 那我帮你写一个完整的 SubOpenGLWidget::event(QEvent* ev),覆盖所有 涉及 pos 坐标的输入事件:鼠标、滚轮、拖拽、触摸。这样以后所有位置相关的事件都能自动转成 物理坐标 再转发。


完整实现

bool SubOpenGLWidget::event(QEvent* ev)
{switch (ev->type()){// --- 鼠标相关 ---case QEvent::MouseButtonPress:case QEvent::MouseButtonRelease:case QEvent::MouseButtonDblClick:case QEvent::MouseMove:{QMouseEvent* me = static_cast<QMouseEvent*>(ev);QPointF physPos = mapLogicalToPhysical(me->pos(), this);QMouseEvent physEvent(me->type(),physPos,                         // 物理坐标me->button(),me->buttons(),me->modifiers());return QOpenGLWidget::event(&physEvent);}// --- 滚轮 ---case QEvent::Wheel:{QWheelEvent* we = static_cast<QWheelEvent*>(ev);QPointF physPos = mapLogicalToPhysical(we->position(), this);QWheelEvent physEvent(physPos,we->globalPosition(),we->pixelDelta(),we->angleDelta(),we->buttons(),we->modifiers(),we->phase(),we->inverted(),we->source());return QOpenGLWidget::event(&physEvent);}// --- 拖拽 ---case QEvent::DragEnter:case QEvent::DragMove:case QEvent::Drop:{QDropEvent* de = static_cast<QDropEvent*>(ev);QPointF physPos = mapLogicalToPhysical(de->posF(), this);QDropEvent physEvent(physPos,de->possibleActions(),de->mimeData(),de->mouseButtons(),de->keyboardModifiers(),de->type());return QOpenGLWidget::event(&physEvent);}// --- 触摸 ---case QEvent::TouchBegin:case QEvent::TouchUpdate:case QEvent::TouchEnd:{QTouchEvent* te = static_cast<QTouchEvent*>(ev);QList<QTouchEvent::TouchPoint> physPoints;physPoints.reserve(te->touchPoints().size());for (const QTouchEvent::TouchPoint& tp : te->touchPoints()){QTouchEvent::TouchPoint physTp(tp);physTp.setPos(mapLogicalToPhysical(tp.pos(), this));physTp.setStartPos(mapLogicalToPhysical(tp.startPos(), this));physTp.setLastPos(mapLogicalToPhysical(tp.lastPos(), this));physPoints << physTp;}QTouchEvent physEvent(te->type(),te->device(),te->modifiers(),te->touchPointStates(),physPoints);return QOpenGLWidget::event(&physEvent);}default:break;}// 没有处理的交给基类return QOpenGLWidget::event(ev);
}

辅助函数

inline QPointF SubOpenGLWidget::mapLogicalToPhysical(const QPointF& logicalPos, const QWidget* w) const
{qreal ratio = w->devicePixelRatioF();return QPointF(logicalPos.x() * ratio,logicalPos.y() * ratio);
}

说明

  1. 鼠标类:按键、移动、双击都转成物理坐标。
  2. 滚轮类:只转换 position()globalPosition() 保持不变。
  3. 拖拽类:用 QDropEvent::posF()(Qt 5.14+ 才有),低版本可以用 pos()
  4. 触摸类:所有 TouchPointposstartPoslastPos 全部转换。

http://www.dtcms.com/a/403945.html

相关文章:

  • 约束优化问题的常用解决办法及优缺点、轨迹规划中应用
  • 电子元器件基础知识day1
  • 【C++游记】C++11特性
  • 光子、光量子、量子三者的关系
  • 网站更改目录做301承德信息网络有限公司
  • Pytorch中stack()方法的总结及理解
  • 网站建设需要那种技术开一个网站多少钱
  • 在windows系统如何使用docker将nginx容器化部署
  • 【异世界历险之数据结构世界(二叉搜索树)】
  • 宁夏建设银行网站好的兼职做调查网站
  • SQLMap数据库枚举靶机(打靶记录)
  • 镇江建设工程质量监督局网站虹口 教育 网站建设
  • stm32移植elog
  • 揭阳市网站建设徐州市建设局网站
  • 讯飞起点阅读器京东式开售,后kindle时代机会在哪里?
  • 2018/07 JLPT听力原文 问题四
  • 旅游网站开发说明书网站建设费用应按几年摊销
  • Redis数据持久化
  • wampserver搭建网站鹤山区网站建设
  • 河南省建设厅网站考试成绩查询东莞人才网求职
  • 【数据结构前置知识】泛型
  • Flink SourceOperator和WaterMark
  • 容器化 Djiango 应用程序
  • 营销网站建设企划案例网站建设业务越做越累
  • Java EE、Java SE 和 Spring Boot
  • 两学一做专题网站wordpress 用户密码的加密算法
  • 手写数据结构-- avl树
  • MySQL-事务日志
  • SpringBoot旅游管理系统
  • 永州市城乡建设规划局网站湖南大型网站建设公司