как избежать проблем с обрезкой растровых изображений при использовании qpainter во время вращения - PullRequest
0 голосов
/ 21 января 2020
label=new QLabel(this);
label->setGeometry(this->width()/2,this->height()/2,label->width(),label->height());
QPixmap myPixmapForNow;
myPixmapForNow.load("C://Users//abc//Documents//QpixMap//hub_needle.png");
label->setMinimumSize(QSize(myPixmapForNow.width(),myPixmapForNow.width()));
label->setAlignment(Qt::AlignCenter);
QPixmap rotated(label->width(),label->width());
QPainter p(&rotated);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.setRenderHint(QPainter::HighQualityAntialiasing);

p.translate(myPixmapForNow.size().width() / 2,
            (myPixmapForNow.size().height() / 2));
qDebug()<<"before rotation width:"<<rotated.size().width()<<"height:"<<rotated.size().width();
p.rotate(arg1);
p.translate(-myPixmapForNow.size().width() / 2,
            -(myPixmapForNow.size().height() / 2));
qDebug()<<"after rotation height:"<<-rotated.size().width()<<"height:"<<-rotated.size().height();[![enter image description here][1]][1]
p.drawPixmap(QRect(0,0,myPixmapForNow.width(),myPixmapForNow.height()), myPixmapForNow);
p.end();
label->setPixmap(rotated);

После вращения

enter image description here

до вращения

enter image description here

1 Ответ

0 голосов
/ 22 января 2020

Я должен признать, что ОП мог бы объяснить проблему немного подробнее. К сожалению, OP не отреагировал на комментарии.

Однако, из любопытства, я попытался разгадать это в небольшой демонстрации. (Мне действительно нравится писать небольшие демонстрационные ролики Qt, особенно с изображениями и изображениями кошек.)


Моим первым предположением было то, что OP боролся с порядком преобразований.

Во время переводов являются коммутативными (изменение порядка не приводит к изменению результата), это не относится к поворотам (и другим преобразованиям).

Однако, после того, как я заключил код OPs в MCVE , я убедился Я сам, что порядок преобразований соответствовал моим ожиданиям - поворот вокруг центра изображения.

Snapshot of testQPainterRotateCenter

Итак, я сосредоточился на названии

как избежать проблем с обрезкой растровых изображений при использовании qpainter при вращении

Причина «проблемы с обрезкой» проста: Чтобы нарисовать повернутое изображение (прямоугольник), для вывода может потребоваться больший диапазон пикселей, чем у оригинала.

Существует две возможности исправить это:

  1. увеличить QPixmap для вывода
  2. масштабируйте результат, чтобы он соответствовал исходному размеру QPixmap.

Таким образом, это оставляет задачу предварительно определить выходной размер повернутого изображения, чтобы либо выходное значение QPixmap соответственно больше или для добавления соответствующего масштабирования.

Ограничивающий прямоугольник повернутого прямоугольника можно рассчитать с помощью trigonometri c функций ( sin, cos , et c.) Вместо этого я решил (для ИМХО более наивного способа) позволить Qt сделать всю работу за меня.

Чтобы достичь этого, преобразование должно быть рассчитывается до создания QPixmap и QPainter. Следовательно, предыдущий

  qPainter.translate(cx, cy);
  qPainter.rotate(ra);
  qPainter.translate(-cx, -cy);

заменяется на:

  QTransform xform;
  xform.translate(cx, cy);
  xform.rotate(ra);
  xform.translate(-cx, -cy);

, который позже может быть применен как:

  qPainter.setTransform(xform);

Я использовал тот факт, что все четыре углы повернутого прямоугольника коснутся ограничивающего прямоугольника. Таким образом, ограничивающий прямоугольник может быть рассчитан путем применения min() и max() к x и y компонентам повернутых углов изображения:

    const QPoint ptTL = xform * QPoint(0, 0);
    const QPoint ptTR = xform * QPoint(w - 1, 0);
    const QPoint ptBL = xform * QPoint(0, h - 1);
    const QPoint ptBR = xform * QPoint(w - 1, h - 1);
    QRect qRectBB(
      QPoint(
        min(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
        min(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())),
      QPoint(
        max(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
        max(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())));

После этого выходной сигнал можно настроить с помощью происхождение и размер qRectBB.

Все демонстрационное приложение testQPainterRotateCenter.cc:

#include <algorithm>

// Qt header:
#include <QtWidgets>

int min(int x0, int x1, int x2, int x3)
{
  return std::min(std::min(x0, x1), std::min(x2, x3));
}

int max(int x0, int x1, int x2, int x3)
{
  return std::max(std::max(x0, x1), std::max(x2, x3));
}

QPixmap rotate(
  const QPixmap &qPixMapOrig, int cx, int cy, int ra,
  bool fitIn, bool keepSize)
{
  int w = qPixMapOrig.width(), h = qPixMapOrig.height();
  QTransform xform;
  xform.translate(cx, cy);
  xform.rotate(ra);
  xform.translate(-cx, -cy);
  if (fitIn) {
    // find bounding rect
    const QPoint ptTL = xform * QPoint(0, 0);
    const QPoint ptTR = xform * QPoint(w - 1, 0);
    const QPoint ptBL = xform * QPoint(0, h - 1);
    const QPoint ptBR = xform * QPoint(w - 1, h - 1);
    QRect qRectBB(
      QPoint(
        min(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
        min(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())),
      QPoint(
        max(ptTL.x(), ptTR.x(), ptBL.x(), ptBR.x()),
        max(ptTL.y(), ptTR.y(), ptBL.y(), ptBR.y())));
    qDebug() << "Bounding box:" << qRectBB;
    // translate top left corner to (0, 0)
    xform *= QTransform().translate(-qRectBB.left(), -qRectBB.top());
    if (keepSize) {
      // center align scaled image
      xform *= w > h
        ? QTransform().translate((w - h) / 2, 0)
        : QTransform().translate(0, (h - w) / 2);
      // add scaling to transform
      const qreal sx = qreal(w) / qRectBB.width();
      const qreal sy = qreal(h) / qRectBB.height();
      const qreal s = std::min(sx, sy);
      xform *= QTransform().scale(s, s);
    } else {
      // adjust w and h
      w = qRectBB.width(); h = qRectBB.height();
    }
  }
  QPixmap qPixMap(w, h);
  qPixMap.fill(Qt::gray);
  { QPainter qPainter(&qPixMap);
    qPainter.setRenderHint(QPainter::Antialiasing);
    qPainter.setRenderHint(QPainter::SmoothPixmapTransform);
    qPainter.setRenderHint(QPainter::HighQualityAntialiasing);
    qPainter.setTransform(xform);
    qPainter.drawPixmap(0, 0, qPixMapOrig.width(), qPixMapOrig.height(), qPixMapOrig);
  } // end of scope -> finalize QPainter
  return qPixMap;
}

// main application
int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // setup data
  const QString file = QString::fromUtf8("cats.jpg");
  QPixmap qPixMapOrig;
  qPixMapOrig.load(file);
  int cx = qPixMapOrig.width() / 2, cy = qPixMapOrig.height() / 2;
  int ra = 0;
  // setup GUI
  QWidget qWin;
  qWin.setWindowTitle(
    file % QString(" (")
      % QString::number(qPixMapOrig.width())
      % " x " % QString::number(qPixMapOrig.height())
      % ") - testQPainterRotateCenter");
  QVBoxLayout qVBox;
  QHBoxLayout qHBox1;
  QLabel qLblCX(QString::fromUtf8("center x:"));
  qHBox1.addWidget(&qLblCX);
  QLineEdit qEditCX;
  qEditCX.setText(QString::number(cx));
  qHBox1.addWidget(&qEditCX, 1);
  QLabel qLblCY(QString::fromUtf8("center y:"));
  qHBox1.addWidget(&qLblCY);
  QLineEdit qEditCY;
  qEditCY.setText(QString::number(cy));
  qHBox1.addWidget(&qEditCY, 1);
  QLabel qLblRA(QString::fromUtf8("rotation angle:"));
  qHBox1.addWidget(&qLblRA);
  QSpinBox qEditRA;
  qEditRA.setValue(ra);
  qHBox1.addWidget(&qEditRA, 1);
  qVBox.addLayout(&qHBox1);
  QHBoxLayout qHBox2;
  QCheckBox qTglFitIn(QString::fromUtf8("Zoom to Fit"));
  qTglFitIn.setChecked(false);
  qHBox2.addWidget(&qTglFitIn);
  QCheckBox qTglKeepSize(QString::fromUtf8("Keep Size"));
  qTglKeepSize.setChecked(false);
  qHBox2.addWidget(&qTglKeepSize);
  qVBox.addLayout(&qHBox2);
  QLabel qLblImg;
  qLblImg.setPixmap(qPixMapOrig);
  qLblImg.setAlignment(Qt::AlignCenter);
  qVBox.addWidget(&qLblImg, 1);
  qWin.setLayout(&qVBox);
  qWin.show();
  // helper to update pixmap
  auto update = [&]() {
    cx = qEditCX.text().toInt();
    cy = qEditCY.text().toInt();
    ra = qEditRA.value();
    const bool fitIn = qTglFitIn.isChecked();
    const bool keepSize = qTglKeepSize.isChecked();
    QPixmap qPixMap = rotate(qPixMapOrig, cx, cy, ra, fitIn, keepSize);
    qLblImg.setPixmap(qPixMap);
  };
  // install signal handlers
  QObject::connect(&qEditCX, &QLineEdit::textChanged,
    [&](const QString&) { update(); });
  QObject::connect(&qEditCY, &QLineEdit::textChanged,
    [&](const QString&) { update(); });
  QObject::connect(&qEditRA, QOverload<int>::of(&QSpinBox::valueChanged),
    [&](int) { update(); });
  QObject::connect(&qTglFitIn, &QCheckBox::toggled,
    [&](bool) { update(); });
  QObject::connect(&qTglKeepSize, &QCheckBox::toggled,
    [&](bool) { update(); });
  // runtime loop
  return app.exec();
}

Файл проекта Qt testQPainterRotateCenter.pro:

SOURCES = testQPainterRotateCenter.cc

QT += widgets

Вывод :

Повернутое изображение без увеличения, чтобы соответствовать:

Snapshot of testQPainterRotateCenter (angle: 30°)

Увеличено, чтобы соответствовать:

Snapshot of testQPainterRotateCenter (angle: 30°, zoom to fit)

Увеличено до оригинального размера:

Snapshot of testQPainterRotateCenter (angle: 30°, zoom to fit, keep size)

Примечания:

Изначально работая с квадратным изображением 300 × 300 пикселей, я понял, что вращение неквадратного прямоугольника может привести к появлению ограничивающей рамки с другим соотношением сторон, чем у оригинала. Следовательно, может потребоваться дополнительный перевод, чтобы снова выровнять масштабированный вывод в исходной ограничительной рамке. Для иллюстрации этого я переключился на не квадратное примерное изображение размером 300 × 200 пикселей.

С учетом соответствия вычислений переводы до / после поворота фактически устарели. В любом случае результат будет переведен в предполагаемую позицию.

Вместо Qt::gray «фоновый цвет» (т. Е. Цвет, которым изначально заполнен QPixmap) может быть задан полностью прозрачно. Я решил придерживаться Qt::gray для иллюстрации.

...