使用Qt5+CMake實現圖片的區域選擇(附原始碼)

小獅子發表於2019-08-12

近期研發涉及到了圖片的區域選擇,找來一些資料一直不能很滿意,所以自己實現了一個。
實現步驟如下。原始碼可以點選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: %d\n", (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

相關文章