- 1. Жоба құрылымы
- 2. CMakeLists.txt
- 3. main.cpp
- 4. EWidget.h
- 5. EWidget.cpp
- 6. ClipScene.h
- 7. ClipScene.cpp
- 8. Қорытынды
QGraphicsScene арқылы кескінді қиюға мүмкіндік беретін шағын қолданбаны жазайық. Бұл жағдайда кескін шаршы алынатын етіп қиылады. Яғни, кескін әрқашан төртбұрышты болуы үшін (оны қызықтырақ ету үшін осы функцияны қосамыз).
Қолданбаға графикалық көрініс қосылады, оған сурет QFileDialog арқылы қосылады. Сондай-ақ кескіннің кесілген бөлігі қосылатын QLabel болады. Кескінді қию QGraphicsRectItem нысаны арқылы орындалады.
Кесу механизмі келесідей:
- Суретті тінтуірдің сол жақ түймешігімен басу таңдау квадратын жасайды.
- LMB басылған кезде шаршыны масштабтаңыз.
- Шығарылған кезде графикалық көріністен кескіннің бір бөлігін кесіп алу арқылы таңдау объектісін жоямыз.
LMB шығарған кезде таңдау параметрлері алынады, атап айтқанда QRect , оның көмегімен кескіннің қажетті аймағын қиып алуға болады.
Қолданба келесідей болады:
Жоба құрылымы
Жоба CMake құрастыру жүйесін пайдаланып CLion IDE ішінде жасалған.
- 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
Таңдау аймағының өлшемі LMB басу нүктесінен өзгертіледі. Мұны істеу үшін келесі әдістерді қайта анықтау керек:
- mousePressEvent
- mouseMoveEvent
- mouseReleaseEvent
Сондай-ақ біз LMB тінтуірінің қозғалысының қысылғанын хабарлайтын айнымалыны жариялаймыз.
Сонымен қатар, біз таңдау ретінде әрекет ететін 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 еще не дошел )