- 1. Структура проекта
- 2. CMakeLists.txt
- 3. main.cpp
- 4. EWidget.h
- 5. EWidget.cpp
- 6. ClipScene.h
- 7. ClipScene.cpp
- 8. Заключение
Напишем небольшое приложение, которое позволит обрезать изображение с помощью QGraphicsScene . При этом обрезка изображения будет производиться так, чтобы получался квадрат. То есть, чтобы изображение было всегда квадратным (Добавим этот функционал, чтобы просто было интереснее).
В приложении будет добавлена графическая сцена, на которую через QFileDialog будет добавляться изображение. Также будет QLabel, в который будет добавляться вырезанная часть изображения. Обрезка изображения будет производиться с помощью объекта QGraphicsRectItem .
Механизм вырезания следующий:
- При левом клике мышью по изображению создаётся квадрат выделения.
- При зажатой ЛКМ масштабируем квадрат.
- При отпускании уничтожаем объект выделения, вырезав с графической сцена часть изображения.
При отпускании ЛКМ будут взяты параметры выделения, а именно QRect , с помощью которого можно будет вырезать требуемую область изображения.
Приложение будет выглядеть следующим образом:
Структура проекта
Проект создан в IDE CLion с использованием системы сборки CMake.
- CMakeLists.txt
- main.cpp
- EWidget.h - заголовочный файл окна приложения
- EWidget.cpp - файл исходных кодов окна приложения
- ClipScene.h - заголовочный файл графической сцена с функционалом вырезания
- ClipScene.cpp - файл исходных кодов графической сцены
CMakeLists.txt
cmake_minimum_required(VERSION 3.8) project(ClipImage) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) find_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) find_package(Qt5Widgets REQUIRED) set(SOURCE_FILES main.cpp EWidget.cpp EWidget.h ClipScene.cpp ClipScene.h) add_executable(${PROJECT_NAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets)
main.cpp
#include <QApplication> #include "EWidget.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); EWidget widget; widget.show(); return app.exec(); }
EWidget.h
В окне приложения необходимо создать слоты для добавления изображения на графическую сцену, а также для добавления обрезанного изображения на лейбл в приложении.
Также здесь объявлены виджеты и лейауты для вёрстки.
#pragma once #include <QtWidgets/QWidget> class QGridLayout; class QPushButton; class QGraphicsView; class QLabel; class ClipScene; class EWidget : public QWidget { using BaseClass = QWidget; Q_OBJECT public: EWidget(QWidget* parent = nullptr); private slots: void onAddFile(); // Слот добавления изображения в приложение void onClippedImage(const QPixmap& pixmap); // Слот принимающий обрезанную область приложения private: QGridLayout* m_gridLayout; QPushButton* m_pushButton; QGraphicsView* m_graphicsView; QLabel* m_clippedLabel; // Лейбл, в который будет помещаться обрезанное изображение ClipScene* m_clipScene; // Графическая сцена в которой реализован функционал по обрезке изображения };
EWidget.cpp
#include "EWidget.h" #include "ClipScene.h" #include <QtWidgets/QGridLayout> #include <QtWidgets/QPushButton> #include <QtWidgets/QFileDialog> #include <QtWidgets/QLabel> #include <QtWidgets/QGraphicsView> EWidget::EWidget(QWidget* parent) : BaseClass(parent) { m_gridLayout = new QGridLayout(this); m_graphicsView = new QGraphicsView(this); m_gridLayout->addWidget(m_graphicsView), 0, 0; m_pushButton = new QPushButton("Add file", this); m_clippedLabel = new QLabel(this); m_gridLayout->addWidget(m_clippedLabel, 0, 1); m_gridLayout->addWidget(m_pushButton, 1, 0); m_clipScene = new ClipScene(this); m_graphicsView->setScene(m_clipScene); // Коннекты к слотам для добавления изображения в приложение и для добавления изображения на лейбл connect(m_pushButton, &QPushButton::clicked, this, &EWidget::onAddFile); connect(m_clipScene, &ClipScene::clippedImage, this, &EWidget::onClippedImage); resize(640, 480); } void EWidget::onAddFile() { QString imagePath = QFileDialog::getOpenFileName(this, "Open Image File", QString(), tr("Image (*.png *.jpg)")); m_clipScene->setImage(imagePath); } void EWidget::onClippedImage(const QPixmap& pixmap) { m_clippedLabel->setPixmap(pixmap); }
ClipScene.h
Изменение размеров области выделения будет производиться от точки нажатия ЛКМ. Для этого необходимо переопределить следующие методы:
- mousePressEvent
- mouseMoveEvent
- mouseReleaseEvent
Также объявим переменную, которая будет сообщать о том, что в данный момент движения мыши ЛКМ зажата.
Помимо этого объявим указатели на QGrpaphicsRectItem , который будет играть роль выделения, и QGraphicsPixmapItem , который будет изображением, из которого вырезается область.
#pragma once #include <QtWidgets/QGraphicsScene> class QGraphicsSceneMouseEvent; class QGraphicsPixmapItem; class QGraphicsRectItem; class ClipScene : public QGraphicsScene { Q_OBJECT Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged) public: ClipScene(QObject* parent); QPointF previousPosition() const; // Метод для замены изображения в QGraphicsScene void setImage(const QString& filePath); signals: void previousPositionChanged(const QPointF previousPosition); void clippedImage(const QPixmap& pixmap); // Сигнал, который передаёт вырезанную область в окно приложения для установки его в лейбл public slots: void setPreviousPosition(const QPointF previousPosition); protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; private: QGraphicsRectItem* m_selection {nullptr}; QGraphicsPixmapItem* m_currentImageItem {nullptr}; QPointF m_previousPosition; bool m_leftMouseButtonPressed {false}; };
ClipScene.cpp
#include "ClipScene.h" #include <QGraphicsSceneMouseEvent> #include <QtWidgets/QGraphicsPixmapItem> #include <QtWidgets/QGraphicsRectItem> ClipScene::ClipScene(QObject* parent) : QGraphicsScene(parent) { } void ClipScene::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() & Qt::LeftButton) { // При зажатой левой кнопки мыши, запоминаем позицию m_leftMouseButtonPressed = true; setPreviousPosition(event->scenePos()); // создаём квадрат выделения m_selection = new QGraphicsRectItem(); m_selection->setBrush(QBrush(QColor(158,182,255,96))); m_selection->setPen(QPen(QColor(158,182,255,200),1)); // добавляем его на графическую сцену addItem(m_selection); } QGraphicsScene::mousePressEvent(event); } void ClipScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (m_leftMouseButtonPressed) { // формируем область выделения при движениюю мышью при зажатой ЛКМ auto dx = event->scenePos().x() - m_previousPosition.x(); auto dy = event->scenePos().y() - m_previousPosition.y(); auto resultDelta = qMax(qAbs(dx), qAbs(dy)); m_selection->setRect((dx > 0) ? m_previousPosition.x() : m_previousPosition.x() - resultDelta, (dy > 0) ? m_previousPosition.y() : m_previousPosition.y() - resultDelta, qAbs(resultDelta), qAbs(resultDelta)); } QGraphicsScene::mouseMoveEvent(event); } void ClipScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (event->button() & Qt::LeftButton) { m_leftMouseButtonPressed = false; // При отпускании ЛКМ формируем обрезанную область QRect selectionRect = m_selection->boundingRect().toRect(); clippedImage(m_currentImageItem->pixmap().copy(selectionRect)); delete m_selection; } QGraphicsScene::mouseReleaseEvent(event); } void ClipScene::setPreviousPosition(const QPointF previousPosition) { if (m_previousPosition == previousPosition) return; m_previousPosition = previousPosition; emit previousPositionChanged(m_previousPosition); } QPointF ClipScene::previousPosition() const { return m_previousPosition; } void ClipScene::setImage(const QString& filePath) { if (m_currentImageItem) { this->removeItem(m_currentImageItem); } m_currentImageItem = new QGraphicsPixmapItem(QPixmap(filePath)); addItem(m_currentImageItem); }
Как избежать падения программы при doubleclick? Она падает потому что при двойном щелчке все равно после двойного щелчка срабатывает mouseReleaseEvent в котором при удалении несуществующей области происходит краш, пробовал завернуть в try catch все равно падает. Не создавать же в doubleClick-е пустую область для этого случая, это же костыль...
Попробуйте делать accept события.
не понял, где это делать?
ничего не поменялось, так же падает
если падает внутри сцены, то проверяйте на nullptr, если пустой указатель, то ничего не делайте.
проверяю перед удалением
все равно падает, не падает если только убрать удаление области
Чтобы не падала на doubleclick-e надо после проверки и удаления обнулять m_selection
вот так работает правильно
Я бы так сделал
Иначе у вас будут утечки памяти
Вообще, наверное здесь имеет смысл smart pointer использовать, например std::uтique_ptr здесь подошёл бы
спасибо, до smart pointer еще не дошел )