使用Qt5+CMake实现图片的区域选择(附源码)

  • 2019 年 10 月 3 日
  • 筆記

近期研发涉及到了图片的区域选择,找来一些资料一直不能很满意,所以自己实现了一个。
实现步骤如下。源码可以点击ImageAOI获取。
Gitee clone: ImageAOI.
如下资料来自源码的README。


ImageAOI (XLabel): AOI Selection Based on Qt5

Dependency

Qt >= 5.0

Usage

  • Double click to trigger the selector
  • Mouse scrolling t zoom in/out
    Usage

Reference (Appreciation)

ImageCropper


本工具的实现灵感来自ImageCropper。部分源码也做了参考,在此表示非常感。
实现功能如上面图片所示,方面,快捷。操作起来很有快感。

实现步骤

1. 创建一个基于QLabel的类XLabel

注意需要引用#include <QLabel>。此段代码主要在xlabel.h文件中。

class XLabel : public QLabel  {      Q_OBJECT    public:      explicit XLabel(QWidget* parent = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags());      ~XLabel();    }

注意 ·Q_OBJECT·必须不能忘记,否则无法顺利生成。

2. 加入所必须的一些临时变量。

image是图片本身,需要载入。scale为缩放变量。selection_mode是用来决定是否显示区域的变量,双击图片区域激活或取消显示。

private:      QImage image;      float scale;        // area selection      bool selection_mode;      QRectF croppingRect;        bool mouse_pressed;      QPoint start_mouse_pos;      QRectF last_static_rect;      CursorPosition cursor_pos;

3. 加入图片的鼠标事件和绘图事件函数。

函数主要继承自QWidget。只有捕获到事件,才能顺利操作。

protected:      virtual void mousePressEvent(QMouseEvent* event);        virtual void mouseMoveEvent(QMouseEvent* event);        virtual void mouseReleaseEvent(QMouseEvent* event);        virtual void wheelEvent(QWheelEvent* e);        // double click      virtual void mouseDoubleClickEvent(QMouseEvent *event);        void paintEvent(QPaintEvent* _event) override;

4. 实现事件函数。

这里分为两部分,鼠标事件用于更新变量。绘图事件中变量的基础上,完成绘制和渲染。

  • 鼠标事件如下
  void XLabel::mousePressEvent(QMouseEvent* event)  {      if (Qt::LeftButton == event->button())      {          mouse_pressed = true;          start_mouse_pos = originPoint(event->pos());          last_static_rect = croppingRect;      }        updateCursorIcon(originPoint(event->pos()));  }    void XLabel::mouseMoveEvent(QMouseEvent* event)  {      auto origin_p = originPoint(event->pos());        if (!mouse_pressed)      {          cursor_pos = cursorPosition(croppingRect, origin_p);            updateCursorIcon(origin_p);      }      else if (cursor_pos != CursorPositionUndefined)      {          QPointF mouseDelta;          mouseDelta.setX(origin_p.x() - start_mouse_pos.x());          mouseDelta.setY(origin_p.y() - start_mouse_pos.y());            int dx = WIDGET_MINIMUM_SIZE.width() / 2;          int dy = WIDGET_MINIMUM_SIZE.height() / 2;          //          if (cursor_pos != CursorPositionMiddle)          {              QRectF newGeometry =                  calculateGeometry(                      last_static_rect,                      cursor_pos,                      mouseDelta);                if (!newGeometry.isNull())              {                    // boudary check                  if (newGeometry.x() < image.width() - dx && newGeometry.y () < image.height() - dy                      && newGeometry.width() + newGeometry.x() > dx                      && newGeometry.height() + newGeometry.y() > dy)                  {                      if (newGeometry.width() >= WIDGET_MINIMUM_SIZE.width() && newGeometry.height() >= WIDGET_MINIMUM_SIZE.height())                      {                          croppingRect = newGeometry;                      }                  }                }          }          else          {              auto new_pt = last_static_rect.topLeft() + mouseDelta;              if (new_pt.x() < image.width() - dx && new_pt.y() < image.height() - dy                  && croppingRect.width() + new_pt.x() > dx                  && croppingRect.height() + new_pt.y() > dy)              {                  croppingRect.moveTo(last_static_rect.topLeft() + mouseDelta);              }          }            update();        }      }    void XLabel::mouseReleaseEvent(QMouseEvent* event)  {      mouse_pressed = false;      updateCursorIcon(originPoint(event->pos()));        // single-click signal      //emit clicked(int(0.5 + event->x()/scale), int(0.5+event->y()/scale));      printf("[ %d, %d] n", (int)event->x(), (int)event->y());  }    void XLabel::wheelEvent(QWheelEvent * e)  {      int numDegrees = e->delta() / 8;      int numSteps = numDegrees / 15;        float k = numDegrees > 0 ? 1.09 : 0.90;        k = k *scale;        setScale(k );    }    void XLabel::mouseDoubleClickEvent(QMouseEvent * event)  {        selection_mode = !selection_mode;      update();  }
  • 绘图事件如下,核心的绘制都在此处。
void XLabel::paintEvent(QPaintEvent * _event)  {      QLabel::paintEvent(_event);        if (image.isNull()) return;      int width = image.width();          //printf("Selection: %dn", (int)selection_mode);          QPixmap rawImage = QPixmap::fromImage(image);      QPainter widgetPainter;      widgetPainter.begin(&rawImage);        // Image boundary      QRectF rect(2, 2, image.width()-4, image.height()-4);      QPen pen0(Qt::darkRed);      if (selection_mode)      {          pen0 = QPen(Qt::darkGreen);      }      pen0.setWidth(4);      widgetPainter.setPen(pen0);      widgetPainter.drawRect(rect);          if (selection_mode)      {          if (croppingRect.isNull()) {              const int width = image.width() / 2  - 4;              const int height = image.height() / 2 - 4;              croppingRect.setSize(QSize(width, height));                float x = (image.width() - croppingRect.width()) / 2-2;              float y = (image.height() - croppingRect.height()) / 2-2;              croppingRect.moveTo(x, y);          }            //qDebug() << "H: " << croppingRect.height();              // 1. bg color          widgetPainter.setBrush(QBrush(QColor(0, 0x6f, 0, 120)));          QPen pen;          pen.setColor(Qt::yellow);          pen.setWidth(2);          widgetPainter.setPen(pen);            widgetPainter.drawRect(croppingRect);          // pos          widgetPainter.setPen(Qt::red);          QFont font;          font.setPointSize(croppingRect.width() >240 ? 16 : 10);          widgetPainter.setFont(font);          auto tl = croppingRect.topLeft();          widgetPainter.drawText(QPoint(tl.x() + 6, tl.y() + 24), QString("(%1, %2, %3, %4)").arg(croppingRect.x()).arg(croppingRect.y())              .arg(croppingRect.width()).arg(croppingRect.height()));            // 2. corner boxes          {              widgetPainter.setPen(Qt::green);              widgetPainter.setBrush(QBrush(Qt::white));              const int edgelen = 8;                // Вспомогательные X координаты              int leftXCoord = croppingRect.left() - edgelen/2+1;              int centerXCoord = croppingRect.center().x() - edgelen / 2;              int rightXCoord = croppingRect.right() - edgelen / 2+1;              // Вспомогательные Y координаты              int topYCoord = croppingRect.top() - edgelen / 2+1;              int middleYCoord = croppingRect.center().y() - edgelen / 2;              int bottomYCoord = croppingRect.bottom() - edgelen / 2+1;              //              const QSize pointSize(edgelen, edgelen);              //              QVector<QRect> points;              points                  // левая сторона                  << QRect(QPoint(leftXCoord, topYCoord), pointSize)                  << QRect(QPoint(leftXCoord, middleYCoord), pointSize)                  << QRect(QPoint(leftXCoord, bottomYCoord), pointSize)                  // центр                  << QRect(QPoint(centerXCoord, topYCoord), pointSize)                  << QRect(QPoint(centerXCoord, middleYCoord), pointSize)                  << QRect(QPoint(centerXCoord, bottomYCoord), pointSize)                  // правая сторона                  << QRect(QPoint(rightXCoord, topYCoord), pointSize)                  << QRect(QPoint(rightXCoord, middleYCoord), pointSize)                  << QRect(QPoint(rightXCoord, bottomYCoord), pointSize);              //              widgetPainter.drawRects(points);          }                // 3. center dash lines          {              QPen dashPen(Qt::white);              dashPen.setStyle(Qt::DashLine);              widgetPainter.setPen(dashPen);                widgetPainter.drawLine(                  QPoint(croppingRect.center().x(), croppingRect.top()),                  QPoint(croppingRect.center().x(), croppingRect.bottom()));              // ... горизонтальная              widgetPainter.drawLine(                  QPoint(croppingRect.left(), croppingRect.center().y()),                  QPoint(croppingRect.right(), croppingRect.center().y()));          }          }            widgetPainter.end();      this->setPixmap(rawImage.scaledToWidth(scale*width));    }

4. 实现主函数,完成一个界面,测试此类是否可行。

实现一个最简单的QLabel,完成显示,并加入XLabel显示到界面上,完成鼠标拖动测试。

#include <QApplication>  #include <QDialog>  #include <QVBoxLayout>  #include <QLabel>    #include "src/xlabel.h"      int main(int argc, char *argv[])  {      QApplication a(argc, argv);        QDialog wgt;      wgt.setMouseTracking(true);      QVBoxLayout* layout = new QVBoxLayout(&wgt);      wgt.setLayout(layout);        auto xlabel = new XLabel(&wgt);      layout->addWidget(xlabel);      layout->addStretch();        QImage img("../logo_s.png");      xlabel->setImageMap(img);        wgt.resize(640, 480);      wgt.show();          return a.exec();  }

完整代码可访问ImageAOI
Gitee clone: ImageAOI.