- 1. Projektstruktur
- 2. CMakeLists.txt
- 3. main.cpp
- 4. EWidget.h
- 5. EWidget.cpp
- 6. ClipScene.h
- 7. ClipScene.cpp
- 8. Fazit
Lassen Sie uns eine kleine Anwendung schreiben, mit der Sie ein Bild mit QGraphicsScene zuschneiden können. In diesem Fall wird das Bild so beschnitten, dass ein Quadrat entsteht. Das heißt, dass das Bild immer quadratisch ist (lassen Sie uns diese Funktionalität hinzufügen, um es nur interessanter zu machen).
In der Anwendung wird eine grafische Szene hinzugefügt, zu der ein Bild über QFileDialog hinzugefügt wird. Es wird auch ein QLabel geben, dem der ausgeschnittene Teil des Bildes hinzugefügt wird. Das Zuschneiden des Bildes erfolgt mit dem QGraphicsRectItem -Objekt.
Der Schneidemechanismus ist wie folgt:
- Ein Linksklick auf ein Bild erzeugt ein Auswahlquadrat.
- Mit gedrückter linker Maustaste das Quadrat skalieren.
- Beim Loslassen zerstören wir das Auswahlobjekt, indem wir einen Teil des Bildes aus der grafischen Szene ausschneiden.
Wenn Sie die LMB loslassen, werden die Auswahlparameter übernommen, nämlich QRect , mit denen Sie den gewünschten Bereich des Bildes ausschneiden können.
Die Anwendung wird wie folgt aussehen:
Projektstruktur
Das Projekt wurde in der CLion-IDE mit dem CMake-Buildsystem erstellt.
- CMakeLists.txt
- main.cpp
- EWidget.h - Header-Datei des Anwendungsfensters
- EWidget.cpp - Quellcodedatei des Anwendungsfensters
- ClipScene.h - Grafikszenen-Header-Datei mit Clipping-Funktionalität
- ClipScene.cpp - Quellcodedatei für Grafikszenen
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
Im Anwendungsfenster müssen Sie Slots zum Hinzufügen eines Bildes zur Grafikszene sowie zum Hinzufügen eines zugeschnittenen Bildes zu einem Etikett in der Anwendung erstellen.
Auch Widgets und Layouts für das Layout werden hier deklariert.
#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
Die Größe des Auswahlbereichs wird ausgehend vom LMB-Klickpunkt angepasst. Dazu müssen Sie die folgenden Methoden überschreiben:
- mousePressEvent
- MouseMoveEvent
- MouseReleaseEvent
Wir werden auch eine Variable deklarieren, die meldet, dass im Moment die Mausbewegung LMB geklemmt ist.
Außerdem deklarieren wir Zeiger auf QGrpaphicsRectItem , das als Auswahl fungiert, und QGraphicsPixmapItem , das das Bild ist, aus dem der Bereich ausgeschnitten wird.
#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 еще не дошел )