Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/api/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ QPainter旋转会阻止正确的QPixmap渲染_C++_Qt_Transform_Qpainter_Qpixmap - Fatal编程技术网

C++ QPainter旋转会阻止正确的QPixmap渲染

C++ QPainter旋转会阻止正确的QPixmap渲染,c++,qt,transform,qpainter,qpixmap,C++,Qt,Transform,Qpainter,Qpixmap,作为错误报告给Qt: 我正在不同位置多次重新绘制QPixmap,通过变换QPainter进行不同的旋转。在某些情况下,QPixmap绘制不正确。下面的GIF显示了我最初发现这个问题时使用的QPixmap,其中包含一个绿色圆柱体,请注意GIF左侧的渲染行为与预期的一样,但是存在一个边界,超出该边界渲染是不正确的。QPixmap内容似乎固定在适当的位置,边缘处的像素似乎在像素地图的其余部分涂抹掉。在GIF中,QPixmap有一个洋红色背景,这是因为QPainter::drawPixmap()使用的

作为错误报告给Qt:


我正在不同位置多次重新绘制
QPixmap
,通过变换QPainter进行不同的旋转。在某些情况下,
QPixmap
绘制不正确。下面的GIF显示了我最初发现这个问题时使用的
QPixmap
,其中包含一个绿色圆柱体,请注意GIF左侧的渲染行为与预期的一样,但是存在一个边界,超出该边界渲染是不正确的。
QPixmap
内容似乎固定在适当的位置,边缘处的像素似乎在像素地图的其余部分涂抹掉。在GIF中,
QPixmap
有一个洋红色背景,这是因为
QPainter::drawPixmap()
使用的
targetRect
也用于分别填充pixmap下方的矩形,这是因为我想检查目标rect是否正确计算

最小可复制示例: 为了简单起见,我只是简单地用洋红像素填充
QPixmap
,带有1像素宽的透明边缘,这样涂抹会导致像素贴图完全消失。它没有显示图像“粘贴”到位,但它清楚地显示了边界,因为在边界之外,像素贴图似乎消失了

我自己也在试验,我相信这完全是由
QPainter
的旋转造成的

旋转角度似乎有影响,如果所有像素点旋转到相同的角度,则边界从模糊对角线(其中模糊表示每个像素点的分离边界不同)更改为锐角90度角(其中锐角表示所有像素点的分离边界相同)

不同角度的范围似乎也起到了一定作用,如果随机生成的角度在一个小的10度范围内,那么边界只是一个稍微模糊的直角,带有一个斜角。当应用不同的旋转次数时,似乎有一个从锐利的直角到模糊的对角线的过程

代码 QtTestBed/pro:

QT += widgets

CONFIG += c++17
CONFIG -= app_bundle

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
        MainWindow.cpp \
        main.cpp

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    MainWindow.h
main.cpp:

#include "MainWindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

这将导致在“查看右侧边”中显示已剪裁的正方形。当移动它们时,正方形似乎卡在原地,而不是边界处的边缘消失,而是距离边界最远的边缘首先消失。

这个问题非常有趣。就我所能测试的而言,您的代码看起来不错,我觉得这是一个Qt错误,我认为您需要将其报告给Qt:。你应该发布一段代码来说明这个问题,你的“更新”编辑的第二段代码很好:它可以很容易地重现这个问题。也许你也应该发布一个小视频来说明当你用鼠标放大/缩小或移动区域时,事情是如何出错的

我尝试了一些替代方案,希望能找到一个解决办法,但没有找到:

  • 试图使用
    QImage
    而不是
    QPixmap
    ,同样的问题
  • 尝试从冻结的png/qrc文件加载pixmap,问题相同
  • 尝试使用
    QTransform
    玩缩放/平移/旋转,同样的问题
  • 尝试了Linux和Windows 10:发现了相同的问题
请注意:

  • 如果不旋转(注释
    paint.rotate(entity.rotation);
    ),则问题不可见
  • 如果您的pixmap是一个简单的单色正方形(只需使用
    pixmap_U600(QColor::fromRgba(0x12345600));
    用单色填充pixmap即可),则问题不再可见。这是最令人惊讶的,看起来像是图像中的一个像素被重新用作背景,把事情搞砸了,但是如果所有的图像像素都是一样的,就不会导致任何显示问题

Qt团队提出的解决方案

“通过在画师上启用SmoothPixmapTransform渲染提示,可以轻松解决此问题”


谢谢,我会报告一个错误。看起来我们在调试时有很多相同的想法,很高兴知道我一直在正确的轨道上@特洛伊瑟夫:即使我没有真正解决这个问题……我会得到赏金吗?;-)啊,对不起,我以为你会自动得到赏金,我会想办法给你的,一个汉克斯!您可以使用提交的qtbug的链接更新您的帖子,以便将来的读者可以检查问题是否/何时在Qt中得到修复。@Troyseph:对您来说很好,Qt的人已经提出了一个解决方法:“通过在画师上启用SmoothPixmapTransform渲染提示,可以轻松解决问题”。您的bug似乎有一个很好的活动,希望它能在下一个Qt版本中修复!
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QWidget>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QPainter>

#include <random>

class MainWindow : public QWidget {
    Q_OBJECT
public:
    MainWindow();

    void wheelEvent(QWheelEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* /*event*/) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void resizeEvent(QResizeEvent* /*event*/) override;
    void paintEvent(QPaintEvent* event) override;

private:
    struct PixmapLocation {
        QPointF location_;
        qreal rotation_;
        qreal radius_;
    };

    QPixmap pixmap_;
    std::vector<PixmapLocation> drawLocations_;

    qreal panX_ = 0.0;
    qreal panY_ = 0.0;
    qreal scale_ = 1.0;

    bool dragging_ = false;
    qreal dragX_ = 0.0;
    qreal dragY_ = 0.0;

    QPointF transformWindowToSimCoords(const QPointF& local) const;
    QPointF transformSimToWindowCoords(const QPointF& sim) const;

    static qreal randomNumber(qreal min, qreal max);
};

#endif // MAINWINDOW_H
    #include "MainWindow.h"

MainWindow::MainWindow()
    : pixmap_(30, 50)
{
    setAutoFillBackground(true);

    constexpr int count = 10000;
    constexpr qreal area = 10000.0;
    constexpr qreal size = 44.0;

    for (int i = 0; i < count; ++i) {
        // qreal rotation = 0.0; // No rotation fixes the issue
        // qreal rotation = 360.0; // No rotation fixes the issue
        // qreal rotation = 180.0; // Mirroring also fixes the issue
        // qreal rotation = 90.0; // The boundary is now a corner, and has a sharp edge (i.e. all images dissapear at the same point)
        // qreal rotation = 0.1; // The boundary is now a corner, and has a sharp edge (i.e. all images dissapear at the same point)
        // qreal rotation = randomNumber(0.0, 10.0); // The boundary is still a corner, with a bevel, with a fuzzy edge (i.e. not all images dissapear at the same point)
        qreal rotation = randomNumber(0.0, 360.0); // The boundary appears to be a diagonal line with a fuzzy edge (i.e. not all images dissapear at the same point)

        drawLocations_.push_back(PixmapLocation{ QPointF(randomNumber(-area, area), randomNumber(-area, area)), rotation, size });
    }

    // Make edges transparent (the middle will be drawn over)
    pixmap_.fill(QColor::fromRgba(0x000000FF));

    /*
     * Fill with magenta almost up to the edge
     *
     * The transparent edge is required to see the effect, the misdrawn pixmaps
     * appear to be a smear of the edge closest to the boundary between proper
     * rendering and misrendering. If the pixmap is a solid block of colour then
     * the effect is masked by the fact that the smeared edge looks the same as
     * the correctly drawn pixmap.
     */
    QPainter p(&pixmap_);
    p.setPen(Qt::NoPen);
    constexpr int inset = 1;
    p.fillRect(pixmap_.rect().adjusted(inset, inset, -inset, -inset), Qt::magenta);

    update();
}

void MainWindow::wheelEvent(QWheelEvent* event)
{
    double d = 1.0 + (0.001 * double(event->angleDelta().y()));
    scale_ *= d;
    update();
}

void MainWindow::mouseReleaseEvent(QMouseEvent*)
{
    dragging_ = false;
}

void MainWindow::mousePressEvent(QMouseEvent* event)
{
    dragging_ = true;
    dragX_ = event->pos().x();
    dragY_ = event->pos().y();
}

void MainWindow::mouseMoveEvent(QMouseEvent* event)
{
    if (dragging_) {
        panX_ += ((event->pos().x() - dragX_) / scale_);
        panY_ += ((event->pos().y() - dragY_) / scale_);
        dragX_ = event->pos().x();
        dragY_ = event->pos().y();
        update();
    }
}

void MainWindow::resizeEvent(QResizeEvent*)
{
    update();
}

void MainWindow::paintEvent(QPaintEvent* event)
{
    QPainter paint(this);
    paint.setClipRegion(event->region());
    paint.translate(width() / 2, height() / 2);
    paint.scale(scale_, scale_);
    paint.translate(panX_, panY_);

    for (const PixmapLocation& entity : drawLocations_) {
        paint.save();
        QPointF centre = entity.location_;
        const qreal scale = (entity.radius_ * 2) / std::max(pixmap_.width(), pixmap_.height());

        QRectF targetRect(QPointF(0, 0), pixmap_.size() * scale);
        targetRect.translate(centre - QPointF(targetRect.width() / 2, targetRect.height() / 2));

        // Rotate our pixmap
        paint.translate(centre);
        paint.rotate(entity.rotation_);
        paint.translate(-centre);

        // paint.setClipping(false); // This doesn't fix it so it isn't clipping
        paint.drawPixmap(targetRect, pixmap_, QRectF(pixmap_.rect()));
        // paint.setClipping(true); // This doesn't fix it so it isn't clipping
        paint.restore();
    }
}

QPointF MainWindow::transformWindowToSimCoords(const QPointF& local) const
{
    qreal x = local.x();
    qreal y = local.y();
    // Sim is centred on screen
    x -= (width() / 2);
    y -= (height() / 2);
    // Sim is scaled
    x /= scale_;
    y /= scale_;
    // Sim is transformed
    x -= panX_;
    y -= panY_;
    return { x, y };
}

QPointF MainWindow::transformSimToWindowCoords(const QPointF& sim) const
{
    qreal x = sim.x();
    qreal y = sim.y();
    // Sim is transformed
    x += panX_;
    y += panY_;
    // Sim is scaled
    x *= scale_;
    y *= scale_;
    // Sim is centred on screen
    x += (width() / 2);
    y += (height() / 2);
    return { x, y };
}

qreal MainWindow::randomNumber(qreal min, qreal max)
{
    static std::mt19937 entropy = std::mt19937();

    std::uniform_real_distribution<qreal> distribution{ min, max };
    //    distribution.param(typename decltype(distribution)::param_type(min, max));
    return distribution(entropy);
}
MainWindow::MainWindow()
    : pixmap_(500, 500)
{
    setAutoFillBackground(true);
    
    constexpr int count = 10000;
    constexpr qreal area = 10000.0;
    constexpr qreal size = 44.0;
    
    for (int i = 0; i < count; ++i) {
        qreal rotation = randomNumber(0.0, 360.0);
        drawLocations_.push_back(PixmapLocation{ QPointF(randomNumber(-area, area), randomNumber(-area, area)), rotation, size });
    }
    
    // Make edges transparent (the middle will be drawn over)
    pixmap_.fill(QColor::fromRgba(0x000000FF));
    
    /*
     * Fill with magenta almost up to the edge
     *
     * The transparent edge is required to see the effect, the misdrawn pixmaps
     * appear to be a smear of the edge closest to the boundary between proper
     * rendering and misrendering. If the pixmap is a solid block of colour then
     * the effect is masked by the fact that the smeared edge looks the same as
     * the correctly drawn pixmap.
     */
    QPainter p(&pixmap_);
    p.setPen(Qt::NoPen);
    constexpr int smallInset = 1;
    const int bigInset = std::min(pixmap_.width(), pixmap_.height()) / 5;
    p.fillRect(pixmap_.rect().adjusted(smallInset, smallInset, -smallInset, -smallInset), Qt::magenta);
    p.fillRect(pixmap_.rect().adjusted(bigInset, bigInset, -bigInset, -bigInset), Qt::green);
    
    update();
}