如何在 2D 中绘制/变形 QImage?
How to paint / deform a QImage in 2D?
我有一个代码可以操纵 QImage
并将其渲染到 QWidget
上。操作包括典型的齐次变换以及裁剪、应用颜色传递函数等。
现在我必须将图像 变形 成一个四顶点多边形(四边形),不一定是矩形。需要说明的是,我说的不是裁剪图像,而是变形图像。
我知道如何使用 OpenGL(带纹理的四边形)执行此操作,但我想知道是否可以在不切换到 OpenGL 的情况下执行此操作。
Qt 是否有类似 2D 纹理的多边形或任何其他方式来变形 QImage
,就像 PhotoShop 中的自由变换工具一样?
QPainter::transform()
确实是我在评论中推荐的解决方案。我对此并不完全确定,但 QPainter::transform()
甚至覆盖了分别使原始图像矩形变形的绘制图像。 (否则,我已将转换应用于 QImage
本身。)
然而,在制作一个小样本时,我意识到这只是故事的一半。
尽快完成了一个小型 Qt 示例应用程序,但我很难找到正确设置转换的方法。
顺便说一下,我不得不意识到 translate()
、scale()
和 shear()
最多只适用于 3 点变形。 4 点变形也可能引入透视变形。因此,可能还需要 project()
。感觉,我已经达到了我个人数学能力的极限,我 google 了解其他人是如何解决这个问题并发现
OpenCV getPerspectiveTransform()
Calculates a perspective transform from four pairs of the corresponding points.
听起来很有希望。查看 cv::getPerspectiveTransform()
的(不太长的)实现后,我意识到他们制作了一个线性方程并使用求解器产生相应的变换。
因此,我在 google 上进行了另一次搜索,发现了 Martin Thoma Solving linear equations with Gaussian elimination 的恕我直言 straight-forward 实现。 (我不知何故记得我一定在我的数学课上听说过高斯消元法,但那是几十年前的事了,从那以后我在日常工作中再也不需要这个了。)
所以,这就是我为求解器所做的(对 Martin Thoma 的原始代码进行细微的风格更改)– solveLin.h
:
#ifndef SOLVE_LIN_H
#define SOLVE_LIN_H
#include <cassert>
#include <cmath>
#include <vector>
template <typename VALUE>
class MatrixT {
public:
typedef VALUE Value;
private:
std::vector<Value> _values;
size_t _nCols;
public:
explicit MatrixT(
size_t nRows, size_t nCols, const Value &value = Value()):
_values(nRows * nCols, value),
_nCols(nCols)
{ }
explicit MatrixT(
size_t nRows, size_t nCols,
std::initializer_list<Value> values):
_values(/*assert(values.size() == nRows * nCols),*/ values),
_nCols(nCols)
{ }
~MatrixT() = default;
MatrixT(const MatrixT&) = default;
MatrixT& operator=(const MatrixT&) = default;
size_t cols() const { return _nCols; }
size_t rows() const { return _values.size() / _nCols; }
const Value* operator[](size_t row) const
{
assert(row < rows());
return &_values[row * _nCols];
}
Value* operator[](const size_t row)
{
return (Value*)((const MatrixT&)*this)[row];
}
};
/** strongly inspired by (not to say: shamelessly copied from)
* Martin Thoma "Solving linear equations with Gaussian elimination"
* https://martin-thoma.com/solving-linear-equations-with-gaussian-elimination/
*/
template <typename VALUE>
std::vector<VALUE> gauss(MatrixT<VALUE> mat)
{
typedef VALUE Value;
const size_t n = mat.rows();
assert(mat.cols() == n + 1);
for (size_t i = 0; i < n; ++i) {
// search for max. value in this column
Value maxI = std::abs(mat[i][i]);
size_t iMax = i;
for (size_t k = i + 1; k < n; ++k) {
const Value maxK = std::abs(mat[k][i]);
if (maxI < maxK) maxI = maxK, iMax = k;
}
// swap max. row with current row
std::swap_ranges(
mat[i] + i, mat[i] + n + 1,
mat[iMax] + i);
// make all rows below this one 0 in current column
for (size_t k = i + 1; k < n; ++k) {
const Value c = mat[k][i] / mat[i][i];
for (size_t j = i; j < n + 1; ++j) {
if (i == j) mat[k][j] = (Value)0;
else mat[k][j] -= c * mat[i][j];
}
}
}
// solve equation Ax=b for an upper triangular matrix A
std::vector<Value> x(n);
for (size_t i = n; i--;) {
x[i] = mat[i][n] / mat[i][i];
for (size_t k = i; k--;) {
mat[k][n] -= mat[k][i] * x[i];
}
}
// done
return x;
}
#endif // SOLVE_LIN_H
主要应用testQImage4Point.cc
:
#include <QtWidgets>
#include "solveLin.h"
/* strongly inspired by (not to say: shamelessly copied from)
* cv::Mat cv::getPerspectiveTransform(
* const Point2f src[], const Point2f dst[], int solveMethod)
*/
QTransform xform4Point(
const QPoint quad0[4], const QPoint quad1[4])
{
qDebug() << "quad0:" << quad0[0] << quad0[1] << quad0[2] << quad0[3];
qDebug() << "quad1:" << quad1[0] << quad1[1] << quad1[2] << quad1[3];
typedef MatrixT<double> Matrix;
Matrix mat(8, 9, 0.0);
for (size_t i = 0; i < 4; ++i) {
mat[i][0] = mat[i + 4][3] = quad0[i].x();
mat[i][1] = mat[i + 4][4] = quad0[i].y();
mat[i][2] = mat[i + 4][5] = 1.0;
mat[i][6] = -quad0[i].x() * quad1[i].x();
mat[i][7] = -quad0[i].y() * quad1[i].x();
mat[i + 4][6] = -quad0[i].x() * quad1[i].y();
mat[i + 4][7] = -quad0[i].y() * quad1[i].y();
mat[i][8] = quad1[i].x();
mat[i + 4][8] = quad1[i].y();
}
std::vector<double> result = gauss(mat);
return QTransform(
result[0], result[3], result[6],
result[1], result[4], result[7],
result[2], result[5], 1.0);
}
class Canvas: public QWidget {
private:
QImage _qImg;
QTransform _qXform;
QPoint _quadOrig[4];
QPoint _quadXform[4];
int _editMode;
bool _viewXform;
QSize _gripSize;
QPoint *_quadEdit; // pointer to currently edited quad
int _grip; // current grip (in mouse drag)
public:
Canvas();
virtual ~Canvas() = default;
Canvas(const Canvas&) = delete;
Canvas& operator=(const Canvas&) = delete;
public:
const QImage& image() const { return _qImg; }
void setImage(const QImage &qImg);
int editMode() const { return _editMode; }
void setEditMode(int editMode);
bool viewXform() const { return _viewXform; }
void setViewXform(bool enable);
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
virtual void mouseMoveEvent(QMouseEvent *pQEvent) override;
private:
int pickGrip(const QPoint &pos) const;
void drawQuad(QPainter &qPainter, const QPoint quad[4], bool grips);
void drawGrid(QPainter &qPainter);
};
Canvas::Canvas():
QWidget(),
_quadOrig{
QPoint(0.25 * width(), 0.25 * height()),
QPoint(0.75 * width(), 0.25 * height()),
QPoint(0.75 * width(), 0.75 * height()),
QPoint(0.25 * width(), 0.75 * height())
},
_quadXform{
_quadOrig[0], _quadOrig[1], _quadOrig[2], _quadOrig[3]
},
_editMode(0),
_viewXform(true),
_gripSize(7, 7),
_quadEdit(_quadOrig),
_grip(-1)
{ }
void Canvas::setImage(const QImage &qImg)
{
_qImg = qImg;
_quadOrig[0] = QPoint(0.25 * _qImg.width(), 0.25 * _qImg.height());
_quadOrig[1] = QPoint(0.75 * _qImg.width(), 0.25 * _qImg.height());
_quadOrig[2] = QPoint(0.75 * _qImg.width(), 0.75 * _qImg.height());
_quadOrig[3] = QPoint(0.25 * _qImg.width(), 0.75 * _qImg.height());
std::copy(_quadOrig, _quadOrig + 4, _quadXform);
update();
}
void Canvas::setEditMode(int editMode)
{
_editMode = editMode;
_quadEdit
= _editMode == 0 ? _quadOrig
: _editMode == 1 ? _quadXform
: nullptr;
update();
}
void Canvas::setViewXform(bool enable)
{
_viewXform = enable;
update();
}
void Canvas::paintEvent(QPaintEvent *pQEvent)
{
QWidget::paintEvent(pQEvent);
QPainter qPainter(this);
const QTransform qXformOld = qPainter.transform();
if (_viewXform) qPainter.setTransform(_qXform);
qPainter.drawImage(0, 0, _qImg);
qPainter.setPen(Qt::white);
drawGrid(qPainter);
qPainter.setPen(Qt::black);
qPainter.setPen(Qt::DotLine);
drawGrid(qPainter);
qPainter.setPen(Qt::SolidLine);
qPainter.setTransform(qXformOld);
for (int i = 1; i <= 2; ++i) {
switch ((_editMode + i) % 2) {
case 0: // draw orig. quad
qPainter.setPen(Qt::red);
drawQuad(qPainter, _quadOrig, _editMode == 0);
break;
case 1:
// draw transformed quad
qPainter.setPen(Qt::green);
drawQuad(qPainter, _quadXform, _editMode == 1);
break;
}
}
}
void Canvas::mousePressEvent(QMouseEvent *pQEvent)
{
if (pQEvent->button() == Qt::LeftButton) {
_grip = pickGrip(pQEvent->pos());
qDebug() << "hit:" << _grip;
}
}
void Canvas::mouseMoveEvent(QMouseEvent *pQEvent)
{
if ((pQEvent->buttons() & Qt::LeftButton) && _grip >= 0) {
_quadEdit[_grip] = pQEvent->pos();
_qXform = xform4Point(_quadOrig, _quadXform);
qDebug() << "transform:" << _qXform;
update();
}
}
int Canvas::pickGrip(const QPoint &pos) const
{
if (!_quadEdit) return -1;
const QPoint gripOffs(_gripSize.width() / 2, _gripSize.height() / 2);
for (int i = 4; i--;) {
const QRect rect(_quadEdit[i] - gripOffs, _gripSize);
if (rect.contains(pos)) return i;
}
return -1;
}
void Canvas::drawQuad(QPainter &qPainter, const QPoint quad[4], bool grips)
{
qPainter.drawPolygon(quad, 4);
if (grips) {
const QPoint gripOffs(_gripSize.width() / 2, _gripSize.height() / 2);
for (int i = 0; i < 4; ++i) {
qPainter.drawRect(QRect(quad[i] - gripOffs, _gripSize));
}
}
}
void Canvas::drawGrid(QPainter &qPainter)
{
const int w = _qImg.width() - 1, h = _qImg.height() - 1;
const int n = 5;
for (int i = 0; i <= n; ++i) {
const int x = i * w / n, y = i * h / n;
qPainter.drawLine(x, 0, x, h);
qPainter.drawLine(0, y, w, y);
}
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// init GUI
QMainWindow winMain;
winMain.setWindowTitle("4 Point Transform");
Canvas canvas;
winMain.setCentralWidget(&canvas);
QToolBar qToolBar;
QActionGroup qTglGrpEdit(&qToolBar);
QAction qTglFrom("Edit From", &qTglGrpEdit);
qTglFrom.setCheckable(true);
if (canvas.editMode() == 0) qTglFrom.setChecked(true);
qToolBar.addAction(&qTglFrom);
QAction qTglTo("Edit To", &qTglGrpEdit);
qTglTo.setCheckable(true);
if (canvas.editMode() == 1) qTglTo.setChecked(true);
qToolBar.addAction(&qTglTo);
qToolBar.addSeparator();
QActionGroup qTglGrpView(&qToolBar);
QAction qTglOrig("View Original", &qTglGrpView);
qTglOrig.setCheckable(true);
if (!canvas.viewXform()) qTglOrig.setChecked(true);
qToolBar.addAction(&qTglOrig);
QAction qTglXform("View Deformed", &qTglGrpView);
qTglXform.setCheckable(true);
if (canvas.viewXform()) qTglXform.setChecked(true);
qToolBar.addAction(&qTglXform);
winMain.addToolBar(&qToolBar);
winMain.show();
// init image
const int dx = winMain.width() - canvas.width();
const int dy = winMain.height() - canvas.height();
canvas.setImage(QImage("window-cats.jpg"));
winMain.resize(canvas.image().width() + dx, canvas.image().height() + dy);
// install signal handlers
QObject::connect(&qTglFrom, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setEditMode(0); });
QObject::connect(&qTglTo, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setEditMode(1); });
QObject::connect(&qTglOrig, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setViewXform(false); });
QObject::connect(&qTglXform, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setViewXform(true); });
// runtime loop
return app.exec();
}
我制作了一个项目文件在cygwin中编译 – testQImage4Point.pro
:
SOURCES = testQImage4Point.cc
QT += widgets
可以使用以下命令构建和 运行:
$ qmake-qt5 testQImage4Point.pro
$ make && ./testQImage4Point
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQImage4Point.o testQImage4Point.cc
g++ -o testQImage4Point.exe testQImage4Point.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4
因为我必须调试代码,所以我也做了一个 CMakeLists.txt
:
project(QImage4Point)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories(
"${CMAKE_SOURCE_DIR}")
add_executable(testQImage4Point
testQImage4Point.cc
solveLin.h)
target_link_libraries(testQImage4Point
Qt5::Widgets)
# define QT_NO_KEYWORDS to prevent confusion between of Qt signal-slots and
# other signal-slot APIs
target_compile_definitions(testQImage4Point PUBLIC QT_NO_KEYWORDS)
我用来制作 VS2017 解决方案。
这是我按预期运行后代码的运行方式:
我还没有做的事情:应用转换将 QImage
直接转换为另一个 QImage
。我认为这是可能的。对于结果 QImage
,必须在源中查找每个像素,将逆变换应用于坐标。因此,当然,转换后的坐标可能会超出范围。因此,必须处理这种情况(例如返回 pre-defined 边框颜色)。 (这是我在回答类似问题时所做的
,这是一天后偶然得到的。)
我有一个代码可以操纵 QImage
并将其渲染到 QWidget
上。操作包括典型的齐次变换以及裁剪、应用颜色传递函数等。
现在我必须将图像 变形 成一个四顶点多边形(四边形),不一定是矩形。需要说明的是,我说的不是裁剪图像,而是变形图像。
我知道如何使用 OpenGL(带纹理的四边形)执行此操作,但我想知道是否可以在不切换到 OpenGL 的情况下执行此操作。
Qt 是否有类似 2D 纹理的多边形或任何其他方式来变形 QImage
,就像 PhotoShop 中的自由变换工具一样?
QPainter::transform()
确实是我在评论中推荐的解决方案。我对此并不完全确定,但 QPainter::transform()
甚至覆盖了分别使原始图像矩形变形的绘制图像。 (否则,我已将转换应用于 QImage
本身。)
然而,在制作一个小样本时,我意识到这只是故事的一半。
尽快完成了一个小型 Qt 示例应用程序,但我很难找到正确设置转换的方法。
顺便说一下,我不得不意识到 translate()
、scale()
和 shear()
最多只适用于 3 点变形。 4 点变形也可能引入透视变形。因此,可能还需要 project()
。感觉,我已经达到了我个人数学能力的极限,我 google 了解其他人是如何解决这个问题并发现
OpenCV
getPerspectiveTransform()
Calculates a perspective transform from four pairs of the corresponding points.
听起来很有希望。查看 cv::getPerspectiveTransform()
的(不太长的)实现后,我意识到他们制作了一个线性方程并使用求解器产生相应的变换。
因此,我在 google 上进行了另一次搜索,发现了 Martin Thoma Solving linear equations with Gaussian elimination 的恕我直言 straight-forward 实现。 (我不知何故记得我一定在我的数学课上听说过高斯消元法,但那是几十年前的事了,从那以后我在日常工作中再也不需要这个了。)
所以,这就是我为求解器所做的(对 Martin Thoma 的原始代码进行细微的风格更改)– solveLin.h
:
#ifndef SOLVE_LIN_H
#define SOLVE_LIN_H
#include <cassert>
#include <cmath>
#include <vector>
template <typename VALUE>
class MatrixT {
public:
typedef VALUE Value;
private:
std::vector<Value> _values;
size_t _nCols;
public:
explicit MatrixT(
size_t nRows, size_t nCols, const Value &value = Value()):
_values(nRows * nCols, value),
_nCols(nCols)
{ }
explicit MatrixT(
size_t nRows, size_t nCols,
std::initializer_list<Value> values):
_values(/*assert(values.size() == nRows * nCols),*/ values),
_nCols(nCols)
{ }
~MatrixT() = default;
MatrixT(const MatrixT&) = default;
MatrixT& operator=(const MatrixT&) = default;
size_t cols() const { return _nCols; }
size_t rows() const { return _values.size() / _nCols; }
const Value* operator[](size_t row) const
{
assert(row < rows());
return &_values[row * _nCols];
}
Value* operator[](const size_t row)
{
return (Value*)((const MatrixT&)*this)[row];
}
};
/** strongly inspired by (not to say: shamelessly copied from)
* Martin Thoma "Solving linear equations with Gaussian elimination"
* https://martin-thoma.com/solving-linear-equations-with-gaussian-elimination/
*/
template <typename VALUE>
std::vector<VALUE> gauss(MatrixT<VALUE> mat)
{
typedef VALUE Value;
const size_t n = mat.rows();
assert(mat.cols() == n + 1);
for (size_t i = 0; i < n; ++i) {
// search for max. value in this column
Value maxI = std::abs(mat[i][i]);
size_t iMax = i;
for (size_t k = i + 1; k < n; ++k) {
const Value maxK = std::abs(mat[k][i]);
if (maxI < maxK) maxI = maxK, iMax = k;
}
// swap max. row with current row
std::swap_ranges(
mat[i] + i, mat[i] + n + 1,
mat[iMax] + i);
// make all rows below this one 0 in current column
for (size_t k = i + 1; k < n; ++k) {
const Value c = mat[k][i] / mat[i][i];
for (size_t j = i; j < n + 1; ++j) {
if (i == j) mat[k][j] = (Value)0;
else mat[k][j] -= c * mat[i][j];
}
}
}
// solve equation Ax=b for an upper triangular matrix A
std::vector<Value> x(n);
for (size_t i = n; i--;) {
x[i] = mat[i][n] / mat[i][i];
for (size_t k = i; k--;) {
mat[k][n] -= mat[k][i] * x[i];
}
}
// done
return x;
}
#endif // SOLVE_LIN_H
主要应用testQImage4Point.cc
:
#include <QtWidgets>
#include "solveLin.h"
/* strongly inspired by (not to say: shamelessly copied from)
* cv::Mat cv::getPerspectiveTransform(
* const Point2f src[], const Point2f dst[], int solveMethod)
*/
QTransform xform4Point(
const QPoint quad0[4], const QPoint quad1[4])
{
qDebug() << "quad0:" << quad0[0] << quad0[1] << quad0[2] << quad0[3];
qDebug() << "quad1:" << quad1[0] << quad1[1] << quad1[2] << quad1[3];
typedef MatrixT<double> Matrix;
Matrix mat(8, 9, 0.0);
for (size_t i = 0; i < 4; ++i) {
mat[i][0] = mat[i + 4][3] = quad0[i].x();
mat[i][1] = mat[i + 4][4] = quad0[i].y();
mat[i][2] = mat[i + 4][5] = 1.0;
mat[i][6] = -quad0[i].x() * quad1[i].x();
mat[i][7] = -quad0[i].y() * quad1[i].x();
mat[i + 4][6] = -quad0[i].x() * quad1[i].y();
mat[i + 4][7] = -quad0[i].y() * quad1[i].y();
mat[i][8] = quad1[i].x();
mat[i + 4][8] = quad1[i].y();
}
std::vector<double> result = gauss(mat);
return QTransform(
result[0], result[3], result[6],
result[1], result[4], result[7],
result[2], result[5], 1.0);
}
class Canvas: public QWidget {
private:
QImage _qImg;
QTransform _qXform;
QPoint _quadOrig[4];
QPoint _quadXform[4];
int _editMode;
bool _viewXform;
QSize _gripSize;
QPoint *_quadEdit; // pointer to currently edited quad
int _grip; // current grip (in mouse drag)
public:
Canvas();
virtual ~Canvas() = default;
Canvas(const Canvas&) = delete;
Canvas& operator=(const Canvas&) = delete;
public:
const QImage& image() const { return _qImg; }
void setImage(const QImage &qImg);
int editMode() const { return _editMode; }
void setEditMode(int editMode);
bool viewXform() const { return _viewXform; }
void setViewXform(bool enable);
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
virtual void mousePressEvent(QMouseEvent *pQEvent) override;
virtual void mouseMoveEvent(QMouseEvent *pQEvent) override;
private:
int pickGrip(const QPoint &pos) const;
void drawQuad(QPainter &qPainter, const QPoint quad[4], bool grips);
void drawGrid(QPainter &qPainter);
};
Canvas::Canvas():
QWidget(),
_quadOrig{
QPoint(0.25 * width(), 0.25 * height()),
QPoint(0.75 * width(), 0.25 * height()),
QPoint(0.75 * width(), 0.75 * height()),
QPoint(0.25 * width(), 0.75 * height())
},
_quadXform{
_quadOrig[0], _quadOrig[1], _quadOrig[2], _quadOrig[3]
},
_editMode(0),
_viewXform(true),
_gripSize(7, 7),
_quadEdit(_quadOrig),
_grip(-1)
{ }
void Canvas::setImage(const QImage &qImg)
{
_qImg = qImg;
_quadOrig[0] = QPoint(0.25 * _qImg.width(), 0.25 * _qImg.height());
_quadOrig[1] = QPoint(0.75 * _qImg.width(), 0.25 * _qImg.height());
_quadOrig[2] = QPoint(0.75 * _qImg.width(), 0.75 * _qImg.height());
_quadOrig[3] = QPoint(0.25 * _qImg.width(), 0.75 * _qImg.height());
std::copy(_quadOrig, _quadOrig + 4, _quadXform);
update();
}
void Canvas::setEditMode(int editMode)
{
_editMode = editMode;
_quadEdit
= _editMode == 0 ? _quadOrig
: _editMode == 1 ? _quadXform
: nullptr;
update();
}
void Canvas::setViewXform(bool enable)
{
_viewXform = enable;
update();
}
void Canvas::paintEvent(QPaintEvent *pQEvent)
{
QWidget::paintEvent(pQEvent);
QPainter qPainter(this);
const QTransform qXformOld = qPainter.transform();
if (_viewXform) qPainter.setTransform(_qXform);
qPainter.drawImage(0, 0, _qImg);
qPainter.setPen(Qt::white);
drawGrid(qPainter);
qPainter.setPen(Qt::black);
qPainter.setPen(Qt::DotLine);
drawGrid(qPainter);
qPainter.setPen(Qt::SolidLine);
qPainter.setTransform(qXformOld);
for (int i = 1; i <= 2; ++i) {
switch ((_editMode + i) % 2) {
case 0: // draw orig. quad
qPainter.setPen(Qt::red);
drawQuad(qPainter, _quadOrig, _editMode == 0);
break;
case 1:
// draw transformed quad
qPainter.setPen(Qt::green);
drawQuad(qPainter, _quadXform, _editMode == 1);
break;
}
}
}
void Canvas::mousePressEvent(QMouseEvent *pQEvent)
{
if (pQEvent->button() == Qt::LeftButton) {
_grip = pickGrip(pQEvent->pos());
qDebug() << "hit:" << _grip;
}
}
void Canvas::mouseMoveEvent(QMouseEvent *pQEvent)
{
if ((pQEvent->buttons() & Qt::LeftButton) && _grip >= 0) {
_quadEdit[_grip] = pQEvent->pos();
_qXform = xform4Point(_quadOrig, _quadXform);
qDebug() << "transform:" << _qXform;
update();
}
}
int Canvas::pickGrip(const QPoint &pos) const
{
if (!_quadEdit) return -1;
const QPoint gripOffs(_gripSize.width() / 2, _gripSize.height() / 2);
for (int i = 4; i--;) {
const QRect rect(_quadEdit[i] - gripOffs, _gripSize);
if (rect.contains(pos)) return i;
}
return -1;
}
void Canvas::drawQuad(QPainter &qPainter, const QPoint quad[4], bool grips)
{
qPainter.drawPolygon(quad, 4);
if (grips) {
const QPoint gripOffs(_gripSize.width() / 2, _gripSize.height() / 2);
for (int i = 0; i < 4; ++i) {
qPainter.drawRect(QRect(quad[i] - gripOffs, _gripSize));
}
}
}
void Canvas::drawGrid(QPainter &qPainter)
{
const int w = _qImg.width() - 1, h = _qImg.height() - 1;
const int n = 5;
for (int i = 0; i <= n; ++i) {
const int x = i * w / n, y = i * h / n;
qPainter.drawLine(x, 0, x, h);
qPainter.drawLine(0, y, w, y);
}
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// init GUI
QMainWindow winMain;
winMain.setWindowTitle("4 Point Transform");
Canvas canvas;
winMain.setCentralWidget(&canvas);
QToolBar qToolBar;
QActionGroup qTglGrpEdit(&qToolBar);
QAction qTglFrom("Edit From", &qTglGrpEdit);
qTglFrom.setCheckable(true);
if (canvas.editMode() == 0) qTglFrom.setChecked(true);
qToolBar.addAction(&qTglFrom);
QAction qTglTo("Edit To", &qTglGrpEdit);
qTglTo.setCheckable(true);
if (canvas.editMode() == 1) qTglTo.setChecked(true);
qToolBar.addAction(&qTglTo);
qToolBar.addSeparator();
QActionGroup qTglGrpView(&qToolBar);
QAction qTglOrig("View Original", &qTglGrpView);
qTglOrig.setCheckable(true);
if (!canvas.viewXform()) qTglOrig.setChecked(true);
qToolBar.addAction(&qTglOrig);
QAction qTglXform("View Deformed", &qTglGrpView);
qTglXform.setCheckable(true);
if (canvas.viewXform()) qTglXform.setChecked(true);
qToolBar.addAction(&qTglXform);
winMain.addToolBar(&qToolBar);
winMain.show();
// init image
const int dx = winMain.width() - canvas.width();
const int dy = winMain.height() - canvas.height();
canvas.setImage(QImage("window-cats.jpg"));
winMain.resize(canvas.image().width() + dx, canvas.image().height() + dy);
// install signal handlers
QObject::connect(&qTglFrom, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setEditMode(0); });
QObject::connect(&qTglTo, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setEditMode(1); });
QObject::connect(&qTglOrig, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setViewXform(false); });
QObject::connect(&qTglXform, &QAction::triggered,
[&](bool checked) { if (checked) canvas.setViewXform(true); });
// runtime loop
return app.exec();
}
我制作了一个项目文件在cygwin中编译 – testQImage4Point.pro
:
SOURCES = testQImage4Point.cc
QT += widgets
可以使用以下命令构建和 运行:
$ qmake-qt5 testQImage4Point.pro
$ make && ./testQImage4Point
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQImage4Point.o testQImage4Point.cc
g++ -o testQImage4Point.exe testQImage4Point.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
Qt Version: 5.9.4
因为我必须调试代码,所以我也做了一个 CMakeLists.txt
:
project(QImage4Point)
cmake_minimum_required(VERSION 3.10.0)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Qt5Widgets CONFIG REQUIRED)
include_directories(
"${CMAKE_SOURCE_DIR}")
add_executable(testQImage4Point
testQImage4Point.cc
solveLin.h)
target_link_libraries(testQImage4Point
Qt5::Widgets)
# define QT_NO_KEYWORDS to prevent confusion between of Qt signal-slots and
# other signal-slot APIs
target_compile_definitions(testQImage4Point PUBLIC QT_NO_KEYWORDS)
我用来制作 VS2017 解决方案。
这是我按预期运行后代码的运行方式:
我还没有做的事情:应用转换将 QImage
直接转换为另一个 QImage
。我认为这是可能的。对于结果 QImage
,必须在源中查找每个像素,将逆变换应用于坐标。因此,当然,转换后的坐标可能会超出范围。因此,必须处理这种情况(例如返回 pre-defined 边框颜色)。 (这是我在回答类似问题时所做的
,这是一天后偶然得到的。)