- 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 еще не дошел )