QT QWebEngine 滚动后渲染?

2023-12-13

使用 WebEngineView 保存网页图像效果很好,但是当我想滚动并保存另一个图像时,生成的图像不会显示网站已滚动(它显示网页的顶部)。

我的问题是:如何在 QWebEngineView 中向下滚动,然后保存显示正确滚动的网页的屏幕截图?

我在网页顶部截取一张屏幕截图,向下滚动约 700 像素,等待 JavaScript 回调触发,然后再截取另一张屏幕截图。 javascript 和回调工作正常(我观察到 QWebEngineView 滚动)。

    this->setScrollPageHandlerFunc([&] (const QVariant &result) {
        saveSnapshotScroll();
    });
    saveSnapshotScroll();
    view->page()->runJavaScript("scrollPage();",this->scrollPageHandlerFunc);

截图代码:

void MainWindow::saveSnapshotScroll()
{

QPixmap pixmap(this->size());
view->page()->view()->render(&pixmap);
pixmap.save(QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

}

JavaScript:

function scrollPage()
{
    var y = qt_jq.jQuery(window).scrollTop();
    qt_jq.jQuery(window).scrollTop(y+708);
}

UPDATE:我发现,如果我将 saveSnapshotScroll() 放在约 100 毫秒或更长的计时器上(即滚动后等待 100 毫秒来保存快照),而不是在页面滚动时立即截取屏幕截图,它会起作用。因此,执行滚动时的 JavaScript 回调与滚动页面的呈现之间存在一些延迟。我不会称其为完整的解决方案,因此我只更新该帖子。我真正想要的是来自 QT 的回调,表示渲染的网页已在屏幕缓冲区中更新。这样的事情存在吗?


当回调的时候runJavaScript被触发则脚本完成。然而,窗户应该重新粉刷(或者至少准备重新粉刷)才能使用QWidget::render(&pixmap).

似乎某些绘制事件对于检测小部件的重新绘制很有用。不幸的是QWebEngineView几乎不捕获任何事件(除了鼠标进入和退出,最近添加的未处理的键盘事件),例如参见“[QTBUG-43602] WebEngineView 不处理鼠标事件”.

几乎所有事件(如鼠标移动或绘画)都由QWebEngineView私有类型的子代表RenderWidgetHostViewQtDelegateWidget这是源自QOpenGLWidget.

有可能捕获新的孩子QWebEngineView类型的QOpenGLWidget并在此子项上安装所有所需事件的事件过滤器挂钩。

该解决方案依赖于未记录的结构QWebEngineView。因此,未来的 Qt 版本可能不支持它。但是,它可用于具有当前 Qt 版本的项目。也许将来会出现一些更方便的接口来捕捉QWebEngineView活动将被实施。

下面的例子实现了这个魔法:

#ifndef WEBENGINEVIEW_H
#define WEBENGINEVIEW_H

#include <QEvent>
#include <QChildEvent>
#include <QPointer>
#include <QOpenGLWidget>
#include <QWebEngineView>

class WebEngineView : public QWebEngineView
{
    Q_OBJECT

private:
    QPointer<QOpenGLWidget> child_;

protected:
    bool eventFilter(QObject *obj, QEvent *ev)
    {
        // emit delegatePaint on paint event of the last added QOpenGLWidget child
        if (obj == child_ && ev->type() == QEvent::Paint)
            emit delegatePaint();

        return QWebEngineView::eventFilter(obj, ev);
    }

public:
    WebEngineView(QWidget *parent = nullptr) :
        QWebEngineView(parent), child_(nullptr)
    {
    }

    bool event(QEvent * ev)
    {
        if (ev->type() == QEvent::ChildAdded) {
            QChildEvent *child_ev = static_cast<QChildEvent*>(ev);

            // there is also QObject child that should be ignored here;
            // use only QOpenGLWidget child
            QOpenGLWidget *w = qobject_cast<QOpenGLWidget*>(child_ev->child());
            if (w) {
                child_ = w;
                w->installEventFilter(this);
            }
        }

        return QWebEngineView::event(ev);
    }

signals:
    void delegatePaint();
};

#endif // WEBENGINEVIEW_H

孩子添加被捕获WebEngineView::event。保存子指针并将事件过滤器安装到该子指针上。在子绘制事件上发出信号WebEngineView::delegatePaint发射于WebEngineView::eventFilter.

信号delegatePaint当某些脚本或由于鼠标悬停或任何其他原因突出显示某些 Web 控件而更改 Web 视图时,始终会发出此消息。

该信号是在实际执行之前从事件过滤器发出的QOpenGLWidget::paintEvent()。因此,看起来只有在完整绘制完成后才需要拍摄页面快照(也许使用异步Qt::QueuedConnection联系)。看来此时在事件过滤器中的时候delegatePaint由于小部件已准备好使用 JavaScript 而被触发render()。但是,由于某些其他原因(例如由于窗口激活)可能会收到绘制事件,这可能会导致警告消息:

QWidget::repaint:检测到递归重绘

所以,还是用比较好Qt::QueuedConnection以避免此类问题。

现在的技巧是使用事件delegatePaint仅当 JavaScript 完成时一次。该部分可以根据实际需要进行调整。

由于某些脚本或加载新图像,页面视图可以随时重新绘制。假设我们需要捕获脚本执行后页面的外观。所以,可以连接delegatePaint发信号给saveSnapshotScroll仅在脚本回调中使用槽并断开该连接saveSnapshotScroll。以下测试循环生成三个不同滚动位置的快照。类似的快照按文件夹组织0, 1 and 2:

void MainWindow::runJavaScript()
{
    // count initialized by 0
    if (++count > 1000)
        return;

    QString script = QString::asprintf("window.scrollTo(0, %d);", 708 * (count % 3));

    view->page()->runJavaScript(script,
        [&] (const QVariant&) {
            connect(view, &WebEngineView::delegatePaint,
                    this, &MainWindow::saveSnapshotScroll,
                    Qt::QueuedConnection);
        }
    );
}

void MainWindow::saveSnapshotScroll()
{
    disconnect(view, &WebEngineView::delegatePaint,
               this, &MainWindow::saveSnapshotScroll);

    QPixmap pixmap(view->size());
    view->render(&pixmap);
    pixmap.save(QString::number(count % 3) + "/" +
                QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

    runJavaScript();
}

在这些情况下,当事件由其他一些窗口交互触发时,可能会得到错误的快照。如果在脚本执行期间未触摸窗口,则结果是正确的。


为了避免处理错误的绘制事件,可以将 Web 视图像素图与以前保存的图像进行比较。如果这些图像之间的差异很小,则意味着应跳过当前的绘制事件,并需要等待下一个绘制事件:

void MainWindow::saveSnapshotScroll()
{
    QSharedPointer<QPixmap> pixmap(new QPixmap(view->size()));
    view->render(pixmap.data());

    // wait for another paint event if difference with saved pixmap is small
    if (!isNewPicture(pixmap))
        return;

    pixmap->save(QString::number(count % 3) + "/" +
              QString::number(QDateTime::currentMSecsSinceEpoch()) + ".png");

    disconnect(view, &WebEngineView::delegatePaint,
               this, &MainWindow::saveSnapshotScroll);

    runJavaScript();
}

bool MainWindow::isNewPicture(QSharedPointer<QPixmap> pixmap)
{
    // initialized by nullptr
    if (!prevPixmap) {
        prevPixmap = pixmap;
        return true;
    }

    // <pixmap> XOR <previously saved pixmap>
    QPixmap prev(*prevPixmap);
    QPainter painter;
    painter.begin(&prev);
    painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination);
    painter.drawPixmap(0, 0, *pixmap);
    painter.end();

    // check difference
    QByteArray buf;
    QBuffer buffer(&buf);
    buffer.open(QIODevice::WriteOnly);
    prev.save(&buffer, "PNG");

    // almost empty images (small difference) have large compression ratio
    const int compression_threshold = 50;
    bool isNew = prev.width() * prev.height() / buf.size() < compression_threshold;

    if (isNew)
        prevPixmap = pixmap;

    return isNew;
}

上述解决方案只是一个示例,它基于Qt提供的工具。可以考虑其他的比较算法。相似度阈值也可以根据具体情况进行调整。如果滚动视图与前一个图像非常相似(例如,在长空白空间的情况下),则这种比较存在限制。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT QWebEngine 滚动后渲染? 的相关文章

  • 很奇怪!调用 window.location 或 location.replace 会重定向到该页面,然后再次返回!

    我处于调试模式 因此我可以看到正在访问哪个页面 当我打电话时window location or window location replace 它会转到该页面 然后返回原始页面 怎么会这样 解决方案是添加 window location
  • std::forward_as_tuple 将参数传递给 2 个构造函数

    我想传递多个参数以便在函数内构造两个对象 以同样的方式std pair
  • 全局未在 ../node_modules/socket.io-parser/is-buffer.js 中定义

    预先感谢您帮助我 我正在尝试在我的一个角度组件中连接套接字 但在浏览器的控制台中它会抛出一个错误 指出 Global 未在 Object node modules socket io parser is buffer js 中定义 这是我的
  • 更改特定字符串的颜色

    有谁知道如果将特定单词输入文本区域 我如何更改它的颜色 例如 如果用户输入 你好我的朋友 它会动态地将 你好 更改为绿色 在google上花了很多时间 找不到任何相关的东西 谢谢 textareas 的设计目的不是选择性着色
  • 是否有相当于 Clang/LLVM 的 .spec 文件,在哪里可以找到参考?

    The gcc驱动程序可以配置为使用特定的链接器 特定的选项和其他细节 例如覆盖系统头 specs files 当前 截至撰写本文时 GCC 版本 4 9 0 的手册此处描述了规范文件 https gcc gnu org onlinedoc
  • selenium-webdriver 与 webdriverjs 有什么区别(以及何时使用)?

    我是一位使用 selenium webdriver 的经验丰富的专业人士 我正在探索有关如何测试 javascript 应用程序的更多选项 我发现了 webdriverJs 不幸的是 我不明白这两者 2 之间有什么区别 有人可以解释一下何时
  • 在 EnvDTE 中调试时捕获 VS 局部变量

    是否可以使用 EnvDTE 进行 vsix Visual Studio 扩展来捕获本地和调试窗口使用的调试数据 或者可以通过其他方法吗 我想创建一个自定义的本地窗口 我们可以修改它以根据需要显示一些较重的内容 而无需为高级用户牺牲原始的本地
  • 我可以让 ungetc 取消阻止阻塞的 fgetc 调用吗?

    我想在收到 SIGUSR1 后使用 ungetc 将 A 字符重新填充到标准输入中 想象一下我有充分的理由这样做 调用 foo 时 stdin 中的阻塞读取不会被收到信号时的 ungetc 调用中断 虽然我没想到它会按原样工作 但我想知道是
  • 如何上传文件 - sails.js

    我可以下载图像和 pdf 但无法下载文档文件 doc pptx odt 下载文档 doc pptx odt 时 仅将其下载为 ZIP XML 文件 我可以做什么 我在用着 填写上传文件文档 https github com balderda
  • Promise 构造函数回调的主体何时执行?

    假设我有以下代码构造一个Promise function doSomethingAsynchronous return new Promise resolve gt const result doSomeWork setTimeout gt
  • 使用restsharp序列化对象并将其传递给WebApi而不是序列化列表

    我有一个看起来像的视图模型 public class StoreItemViewModel public Guid ItemId get set public List
  • 每个数据库多个/单个 *.edmx 文件

    我有一个通过 ADO net 数据服务与数据库交互的项目 数据库很大 近 150 个具有依赖关系的表 该项目几年前开始 当时使用的是数据集 现在我们正在转向实体模型关系 由于我们添加了更多需要使用的表 该模型正在不断增长 这是管理这一切的正
  • 无法从 JSON 请求获取数据,尽管我知道它已返回

    我试图获取从 getJSON 返回的数据 但我无法让它工作 我已经在 search twitter API 上尝试了相同的代码 效果很好 但它不适用于其他网站 我知道数据已返回 因为我在使用检查器时可以找到它 我通过检查器找到的值是 id
  • C++ Streambuf 方法可以抛出异常吗?

    我正在尝试找到一种方法来获取读取或写入流的字符数 即使存在错误并且读 写结束时间较短 该方法也是可靠的 我正在做这样的事情 return stream rdbuf gt sputn buffer buffer size 但如果streamb
  • 角度 ng-repeat 根据条件添加样式

    我在 div 列表上使用 ng repeat 并且在渲染此 div 的 json 中手动添加项目 我需要定位我在 json 中添加的最后一个 div 它会自动在屏幕上渲染 即 couse 光标所在的位置 其余部分保持在相同位置 但没有给出渲
  • 使用 div 或表格来包含链接列更好吗?

    我的页面底部有 3 列链接 每列都放入一个 div 中 所有三个 div 都包装在页面中央的一个大 div 中 这是更适合桌子的东西还是桌子不适合这项工作 您还可以使用 ul http www w3schools com tags tag
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • 如何制作过期/签名视频嵌入网址

    我是新来的 正在学习网络开发等等 我只知道如何将我的视频嵌入网站中 任何菜鸟都可以轻松获得源代码 他们也可以嵌入它 但在许多网站中 视频 src 均使用重定向器链接进行编码 例如 它会在一段时间后过期 在本例中是一天 我了解到这是一个签名网
  • Qt 布局,在小部件大小更改后调整到最小大小

    基本上我有一个QGridLayout里面有一些小部件 最重要的是 2 个标签 我用它们将图像绘制到屏幕上 好吧 如果用户愿意 他可以更改传入图像的分辨率 从而强制标签调整大小 我们假设标签的初始大小是320x240 用户将 VideoMod
  • React 错误:目标容器不是 DOM 元素

    我刚刚开始使用 React 所以这可能是一个非常简单的错误 但我们开始吧 我的html代码非常简单 load staticfiles

随机推荐

  • 对象从函数中的命名空间中消失

    我正在编写一个包装器来按行组合任意数量的数据集 由于有些变量可能具有唯一的变量 因此我首先限制数据中的变量 我的功能是这样工作的 rcombine lt function List Vars List2 lt lapply List sub
  • Python 中的字符串匹配

    我在列表中存储了300K个字符串 每个字符串的长度在10到400之间 我想删除那些作为其他字符串的子字符串的字符串 长度较短的字符串有更高的概率是其他字符串的子字符串 目前 我首先根据长度对这 300K 字符串进行排序 然后使用以下方法 s
  • SSRS 2008 R2 Globals!RenderFormat 导出方法

    有 7 个用于导出 SSRS 2008 报告的内置选项 我想知道在选择导出选项时是否有更简单的方法在 SSRS 中编写以下代码 IIF Globals RenderFormat Name WORD OR Globals RenderForm
  • CMake 引用逃脱难题

    我似乎无法理解 CMake 的转义规则 鉴于 set X A B C add custom target works COMMAND DUMMY 0 X X env grep X COMMENT This works add custom
  • grid.mvc 在控制器中使用过滤结果

    我正在使用 grid mvc http gridmvc codeplex com 用于过滤和排序 有谁知道如何在动作控制器中处理过滤结果 我试图通过 FormCollection 传递隐藏字段 但由于分页 仅传递可见值 或者 mvc 中是否
  • jsfiddle 上的相同代码但无法在我的服务器上运行? [复制]

    这个问题在这里已经有答案了 我很困惑 我只是想测试一个jquery simpleselect 并让它在jquery上正常工作 但是当我将它上传到我的服务器时 完全不起作用 我发誓它的代码是相同的 但也许新的眼光会有所帮助 我在这里缺少什么
  • ClearCase 动态视图中两个版本之间的差异

    比如说 我在 ClearCase 中有两个不同的动态视图 我想知道是否有任何命令可以提供报告 x lines added y lines deleted z lines changed 两个版本之间 是的 您可以使用diffstat生成一个
  • MessageStore 支持 QueueChannel,带有 Spring Integration+ Java Config

    弹簧集成参考指南指的是使用 MessageStore 实现来为 QueueChannel 提供持久性 它被提到了很多次 但所有示例都使用 XML 配置 即
  • 如何减少Android Workmanager的时间?

    我一直在检查其他链接 stackoverflow 以减少工作管理器的时间段 但我发现了以下链接 如何减少WorkManager中Periodic WorkManager的时间 上面的链接说最短时间是 15 分钟 需要立即从移动设备向服务器发
  • 如何将 MS botframework 机器人连接到 Skype for Business

    我发布了一个使用机器人框架构建的机器人 但我不知道如何将其连接到内部公司网络上的 Skype for Business 有谁知道流程是什么吗 Ed 尚不支持 Skype for Business
  • 如何创建一个数据类实现 Spring Security 特定的 UserDetails

    我正在尝试迁移一些spring webfluxkotlin 的示例代码 目前我想转换我的Spring数据蒙戈科特林样本 有一个User 原始 Data Mongo 版本看起来 Data ToString Builder NoArgsCons
  • Docusign 嵌入式签名

    我们正在使用docusign让人们签署在我们网站上注册的同意书 有人向我指出了嵌入式签名 API 据我了解 我必须创建一个我已经完成的信封 我为此使用 Net 示例 通过 API 登录正常 但尝试从 API 获取 URL 时出现以下错误 E
  • 在哪里可以找到 execve() 的源代码?

    你能给我源代码吗execve 系统调用 exec家庭 我正在使用 Linux execve 调用 sys execve sys execve 又调用 do execve 这就是操作的位置 http git kernel org p linu
  • 获取表的列名,该列名始终为空

    我有一个有很多列的表 他们中的一些人总是NULL并且不包含任何值 有没有办法使用 SQL 查询列出这些列 而不是一一测试它们 我想避免 SELECT Col1 from MyTable where Col1 IS NOT NULL SELE
  • Azure Function (Python) w/存储上传触发器因大文件上传而失败

    Azure Function Python 由文件上传到 Azure 存储触发 该功能适用 于高达 120MB 的文件 我刚刚使用 2GB 文件进行负载测试 该函数产生了错误Stream was too long 此限制记录在哪里 我将如何
  • 从 Lambda 访问 Redshift - 避免 0.0.0.0/0 安全组

    我正在尝试从 Lambda 函数访问 Redshift 数据库 当我将 0 0 0 0 0 添加到 Redshift 界面中的安全组连接时 如建议的那样 本文 我能够成功连接 然而 从安全角度来看 我觉得使用 0 0 0 0 0 不太舒服
  • ASP.NET WebApi:如何使用 WebApi HttpClient 执行文件上传的分段发布

    我有一个 WebApi 服务处理来自简单表单的上传 如下所示
  • 使用compareTo和Collections.sort

    我有一个特许经营类别 其中包含所有者 特许经营所有者名称 州 特许经营所在州的 2 个字符串 和销售额 当天的总销售额 public class Franchise implements Comparable
  • 尝试创建无限水平滚动 - 最好使用 jquery 插件

    我希望创建一个类似于 The Killers 网站上使用的效果 http www thekillersmusic com html5 该网站编码的复杂性超出了我的范围 但我喜欢它的工作原理 基本上 想要一个类似水平全景背景的东西 我可以在上
  • QT QWebEngine 滚动后渲染?

    使用 WebEngineView 保存网页图像效果很好 但是当我想滚动并保存另一个图像时 生成的图像不会显示网站已滚动 它显示网页的顶部 我的问题是 如何在 QWebEngineView 中向下滚动 然后保存显示正确滚动的网页的屏幕截图 我