Я должен признать, что ОП мог бы объяснить проблему немного подробнее. К сожалению, OP не отреагировал на комментарии.
Однако, из любопытства, я попытался разгадать это в небольшой демонстрации. (Мне действительно нравится писать небольшие демонстрационные ролики Qt, особенно с изображениями и изображениями кошек.)
Моим первым предположением было то, что OP боролся с порядком преобразований.
Во время переводов являются коммутативными (изменение порядка не приводит к изменению результата), это не относится к поворотам (и другим преобразованиям).
Однако, после того, как я заключил код OPs в MCVE , я убедился Я сам, что порядок преобразований соответствовал моим ожиданиям - поворот вокруг центра изображения.
Итак, я сосредоточился на названии
как избежать проблем с обрезкой растровых изображений при использовании qpainter при вращении
Причина «проблемы с обрезкой» проста: Чтобы нарисовать повернутое изображение (прямоугольник), для вывода может потребоваться больший диапазон пикселей, чем у оригинала.
Существует две возможности исправить это:
- увеличить
QPixmap
для вывода - масштабируйте результат, чтобы он соответствовал исходному размеру
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
Вывод :
Повернутое изображение без увеличения, чтобы соответствовать:
Увеличено, чтобы соответствовать:
Увеличено до оригинального размера:
Примечания:
Изначально работая с квадратным изображением 300 × 300 пикселей, я понял, что вращение неквадратного прямоугольника может привести к появлению ограничивающей рамки с другим соотношением сторон, чем у оригинала. Следовательно, может потребоваться дополнительный перевод, чтобы снова выровнять масштабированный вывод в исходной ограничительной рамке. Для иллюстрации этого я переключился на не квадратное примерное изображение размером 300 × 200 пикселей.
С учетом соответствия вычислений переводы до / после поворота фактически устарели. В любом случае результат будет переведен в предполагаемую позицию.
Вместо Qt::gray
«фоновый цвет» (т. Е. Цвет, которым изначально заполнен QPixmap
) может быть задан полностью прозрачно. Я решил придерживаться Qt::gray
для иллюстрации.