Evgenii Legotckoi
July 28, 2017, 1:55 a.m.

Qt/C++ - Tutorial 070. Crop image using QGraphicsScene

Let's write a small application that will allow you to crop the image using QGraphicsScene . In this case, the image will be cropped so that a square is obtained. That is, to make the image always square (Add this functionality to make it more interesting).

In the application, a graphic scene will be added to which an image will be added via QFileDialog . Also there will be QLabel , in which the cut out part of the image will be added. The image will be cropped using the QGraphicsRectItem object.

The cutting mechanism is as follows:

  1. With the left mouse click on the image, a selection box is created.
  2. While holding LMB scalable square.
  3. When releasing, we delete the selection object, cutting out part of the image from the graphic scene.

When the LMB is released, the selection options, namely QRect, will be used to cut out the desired image area.

The application will look like this:


Project structure

The project is created in the IDE CLion using the CMake build system.

  • CMakeLists.txt
  • main.cpp
  • EWidget.h - Application window header file
  • EWidget.cpp - Application window source code file
  • ClipScene.h - Header file graphical scene with cut-out functionality
  • ClipScene.cpp - Source file of the graphic scene

CMakeLists.txt

  1. cmake_minimum_required(VERSION 3.8)
  2. project(ClipImage)
  3.  
  4. set(CMAKE_CXX_STANDARD 17)
  5. set(CMAKE_AUTOMOC ON)
  6.  
  7. find_package(Qt5Core REQUIRED)
  8. find_package(Qt5Gui REQUIRED)
  9. find_package(Qt5Widgets REQUIRED)
  10.  
  11. set(SOURCE_FILES main.cpp EWidget.cpp EWidget.h ClipScene.cpp ClipScene.h)
  12. add_executable(${PROJECT_NAME} ${SOURCE_FILES})
  13.  
  14. target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets)

main.cpp

  1. #include <QApplication>
  2. #include "EWidget.h"
  3.  
  4.  
  5. int main(int argc, char *argv[])
  6. {
  7. QApplication app(argc, argv);
  8.  
  9. EWidget widget;
  10. widget.show();
  11.  
  12. return app.exec();
  13. }

EWidget.h

In the application window, you need to create slots to add an image to the graphic scene, and to add the cropped image to the label in the application.

Widgets and Layouts are also declared here.

  1. #pragma once
  2.  
  3. #include <QtWidgets/QWidget>
  4.  
  5. class QGridLayout;
  6. class QPushButton;
  7. class QGraphicsView;
  8. class QLabel;
  9. class ClipScene;
  10.  
  11. class EWidget : public QWidget
  12. {
  13. using BaseClass = QWidget;
  14.  
  15. Q_OBJECT
  16.  
  17. public:
  18. EWidget(QWidget* parent = nullptr);
  19.  
  20. private slots:
  21. void onAddFile(); // Slot for adding an image to the application
  22. void onClippedImage(const QPixmap& pixmap); // Slot for accepting a cropped application area
  23. private:
  24. QGridLayout* m_gridLayout;
  25. QPushButton* m_pushButton;
  26. QGraphicsView* m_graphicsView;
  27. QLabel* m_clippedLabel; // A label in which the cropped image will be placed
  28. ClipScene* m_clipScene; // The graphical scene in which the image trimming functionality is implemented
  29. };
  30.  

EWidget.cpp

  1. #include "EWidget.h"
  2.  
  3. #include "ClipScene.h"
  4.  
  5. #include <QtWidgets/QGridLayout>
  6. #include <QtWidgets/QPushButton>
  7. #include <QtWidgets/QFileDialog>
  8. #include <QtWidgets/QLabel>
  9. #include <QtWidgets/QGraphicsView>
  10.  
  11. EWidget::EWidget(QWidget* parent) :
  12. BaseClass(parent)
  13. {
  14. m_gridLayout = new QGridLayout(this);
  15. m_graphicsView = new QGraphicsView(this);
  16. m_gridLayout->addWidget(m_graphicsView), 0, 0;
  17. m_pushButton = new QPushButton("Add file", this);
  18. m_clippedLabel = new QLabel(this);
  19. m_gridLayout->addWidget(m_clippedLabel, 0, 1);
  20. m_gridLayout->addWidget(m_pushButton, 1, 0);
  21. m_clipScene = new ClipScene(this);
  22. m_graphicsView->setScene(m_clipScene);
  23.  
  24. // Connections to slots for adding an image to an application and for adding an image to a label
  25. connect(m_pushButton, &QPushButton::clicked, this, &EWidget::onAddFile);
  26. connect(m_clipScene, &ClipScene::clippedImage, this, &EWidget::onClippedImage);
  27.  
  28. resize(640, 480);
  29. }
  30.  
  31. void EWidget::onAddFile()
  32. {
  33. QString imagePath = QFileDialog::getOpenFileName(this, "Open Image File", QString(), tr("Image (*.png *.jpg)"));
  34. m_clipScene->setImage(imagePath);
  35. }
  36.  
  37. void EWidget::onClippedImage(const QPixmap& pixmap)
  38. {
  39. m_clippedLabel->setPixmap(pixmap);
  40. }

ClipScene.h

The size of the selection area will be changed from the point of pressing the LMB. To do this, you need to override the following methods:

  • mousePressEvent
  • mouseMoveEvent
  • mouseReleaseEvent

Also, we will declare a variable that will report that at the moment the motion of the mouse LMB is clamped.

In addition, we declare pointers to QGrpaphicsRectItem , which will play the selection role, and QGraphicsPixmapItem , which is the image from which the region is cut.

  1. #pragma once
  2.  
  3.  
  4. #include <QtWidgets/QGraphicsScene>
  5.  
  6. class QGraphicsSceneMouseEvent;
  7. class QGraphicsPixmapItem;
  8. class QGraphicsRectItem;
  9.  
  10. class ClipScene : public QGraphicsScene
  11. {
  12. Q_OBJECT
  13. Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)
  14.  
  15. public:
  16. ClipScene(QObject* parent);
  17. QPointF previousPosition() const;
  18.   // Method for replacing an image in QGraphicsScene
  19. void setImage(const QString& filePath);
  20.  
  21. signals:
  22. void previousPositionChanged(const QPointF previousPosition);
  23. void clippedImage(const QPixmap& pixmap); // A signal that transmits the cut out area to the application window to install it in the label
  24.  
  25. public slots:
  26. void setPreviousPosition(const QPointF previousPosition);
  27.  
  28. protected:
  29. virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
  30. virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
  31. virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
  32.  
  33.  
  34. private:
  35. QGraphicsRectItem* m_selection {nullptr};
  36. QGraphicsPixmapItem* m_currentImageItem {nullptr};
  37. QPointF m_previousPosition;
  38. bool m_leftMouseButtonPressed {false};
  39. };

ClipScene.cpp

  1. #include "ClipScene.h"
  2.  
  3. #include <QGraphicsSceneMouseEvent>
  4. #include <QtWidgets/QGraphicsPixmapItem>
  5. #include <QtWidgets/QGraphicsRectItem>
  6.  
  7.  
  8. ClipScene::ClipScene(QObject* parent) : QGraphicsScene(parent)
  9. {
  10.  
  11. }
  12.  
  13. void ClipScene::mousePressEvent(QGraphicsSceneMouseEvent* event)
  14. {
  15. if (event->button() & Qt::LeftButton)
  16. {
  17. // With the left mouse button pressed, remember the position
  18. m_leftMouseButtonPressed = true;
  19. setPreviousPosition(event->scenePos());
  20.  
  21. // Create a selection square
  22. m_selection = new QGraphicsRectItem();
  23. m_selection->setBrush(QBrush(QColor(158,182,255,96)));
  24. m_selection->setPen(QPen(QColor(158,182,255,200),1));
  25. // Add it to the graphic scene
  26. addItem(m_selection);
  27. }
  28.  
  29. QGraphicsScene::mousePressEvent(event);
  30. }
  31.  
  32. void ClipScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
  33. {
  34. if (m_leftMouseButtonPressed)
  35. {
  36. // Form the selection area when moving with the mouse while pressing the LMB
  37. auto dx = event->scenePos().x() - m_previousPosition.x();
  38. auto dy = event->scenePos().y() - m_previousPosition.y();
  39. auto resultDelta = qMax(qAbs(dx), qAbs(dy));
  40. m_selection->setRect((dx > 0) ? m_previousPosition.x() : m_previousPosition.x() - resultDelta,
  41. (dy > 0) ? m_previousPosition.y() : m_previousPosition.y() - resultDelta,
  42. qAbs(resultDelta),
  43. qAbs(resultDelta));
  44. }
  45. QGraphicsScene::mouseMoveEvent(event);
  46. }
  47.  
  48. void ClipScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
  49. {
  50. if (event->button() & Qt::LeftButton)
  51. {
  52. m_leftMouseButtonPressed = false;
  53.  
  54. // When releasing the LMB, we form the cut off area
  55. QRect selectionRect = m_selection->boundingRect().toRect();
  56. clippedImage(m_currentImageItem->pixmap().copy(selectionRect));
  57. delete m_selection;
  58. }
  59. QGraphicsScene::mouseReleaseEvent(event);
  60. }
  61.  
  62. void ClipScene::setPreviousPosition(const QPointF previousPosition)
  63. {
  64. if (m_previousPosition == previousPosition)
  65. return;
  66.  
  67. m_previousPosition = previousPosition;
  68. emit previousPositionChanged(m_previousPosition);
  69. }
  70.  
  71. QPointF ClipScene::previousPosition() const
  72. {
  73. return m_previousPosition;
  74. }
  75.  
  76. void ClipScene::setImage(const QString& filePath)
  77. {
  78. if (m_currentImageItem)
  79. {
  80. this->removeItem(m_currentImageItem);
  81. }
  82. m_currentImageItem = new QGraphicsPixmapItem(QPixmap(filePath));
  83. addItem(m_currentImageItem);
  84. }

Conclusion

Link to download the project

Do you like it? Share on social networks!

b
  • July 12, 2019, 2:42 p.m.

Как избежать падения программы при doubleclick? Она падает потому что при двойном щелчке все равно после двойного щелчка срабатывает mouseReleaseEvent в котором при удалении несуществующей области происходит краш, пробовал завернуть в try catch все равно падает. Не создавать же в doubleClick-е пустую область для этого случая, это же костыль...

Evgenii Legotckoi
  • July 12, 2019, 5:29 p.m.

Попробуйте делать accept события.

  1. event->accept();
b
  • July 12, 2019, 5:33 p.m.

не понял, где это делать?

Evgenii Legotckoi
  • July 12, 2019, 5:46 p.m.
  1. void ClipScene::mouseDobuleClickEvent(QGraphicsSceneMouseEvent* event)
  2. {
  3. event->accept();
  4. QGraphicsScene::mouseDobuleClickEvent(event);
  5. }
b
  • July 12, 2019, 6:08 p.m.

ничего не поменялось, так же падает

Evgenii Legotckoi
  • July 12, 2019, 6:30 p.m.

если падает внутри сцены, то проверяйте на nullptr, если пустой указатель, то ничего не делайте.

b
  • July 12, 2019, 7:58 p.m.

проверяю перед удалением

  1. if(m_selection!=nullptr){
  2. delete m_selection;
  3. }

все равно падает, не падает если только убрать удаление области

b
  • July 16, 2019, 6:33 p.m.

Чтобы не падала на doubleclick-e надо после проверки и удаления обнулять m_selection

  1. // При отпускании ЛКМ формируем обрезанную область
  2. if (m_selection!=nullptr){ // добавил условие
  3. QRect selectionRect = m_selection->boundingRect().toRect();
  4. clippedImage(m_currentImageItem->pixmap().copy(selectionRect));
  5. delete m_selection;
  6. }
  7. m_selection = nullptr; // и обнуление переменной

вот так работает правильно

Evgenii Legotckoi
  • July 16, 2019, 6:34 p.m.

Я бы так сделал

  1. // При отпускании ЛКМ формируем обрезанную область
  2. if (m_selection!=nullptr){ // добавил условие
  3. QRect selectionRect = m_selection->boundingRect().toRect();
  4. clippedImage(m_currentImageItem->pixmap().copy(selectionRect));
  5. delete m_selection;
  6. m_selection = nullptr; // и обнуление переменной
  7. }

Иначе у вас будут утечки памяти

Evgenii Legotckoi
  • July 16, 2019, 6:35 p.m.

Вообще, наверное здесь имеет смысл smart pointer использовать, например std::uтique_ptr здесь подошёл бы

b
  • July 16, 2019, 6:38 p.m.

спасибо, до smart pointer еще не дошел )

Comments

Only authorized users can post comments.
Please, Log in or Sign up
  • Last comments
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
  • A
    Oct. 19, 2024, 5:19 p.m.
    Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html