Qt/C++ - Урок 070. Обрезка изображения с помощью QGraphicsScene

qgraphicsscene, Qt, Image, crop

Напишем небольшое приложение, которое позволит обрезать изображение с помощью QGraphicsScene . При этом обрезка изображения будет производиться так, чтобы получался квадрат. То есть, чтобы изображение было всегда квадратным (Добавим этот функционал, чтобы просто было интереснее).

В приложении будет добавлена графическая сцена, на которую через QFileDialog будет добавляться изображение. Также будет QLabel, в который будет добавляться вырезанная часть изображения. Обрезка изображения будет производиться с помощью объекта QGraphicsRectItem .

Механизм вырезания следующий:

  1. При левом клике мышью по изображению создаётся квадрат выделения.
  2. При зажатой ЛКМ масштабируем квадрат.
  3. При отпускании уничтожаем объект выделения, вырезав с графической сцена часть изображения.

При отпускании ЛКМ будут взяты параметры выделения, а именно 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);
}

Заключение

Ссылка на скачивание проекта

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate
b

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

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

event->accept();
b

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

void ClipScene::mouseDobuleClickEvent(QGraphicsSceneMouseEvent* event)
{
    event->accept();
    QGraphicsScene::mouseDobuleClickEvent(event);
}
b

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

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

b

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

if(m_selection!=nullptr){
            delete m_selection;
        }

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

b

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

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

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

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

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

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

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

b

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Donate

Здравствуйте, уважаемые пользователи EVILEG !!!

Если сайт вам помог, то поддержите разработку сайта финансово, пожалуйста.

Вы можете сделать это следующими способами:

Спасибо, Евгений Легоцкой

Т
11 декабря 2019 г. 16:56
Тома

C++ - Тест 003. Условия и циклы

  • Результат:50баллов,
  • Очки рейтинга-4
АТ
10 декабря 2019 г. 8:06
Анастасия Трощенкова

C++ - Тест 001. Первая программа и типы данных

  • Результат:60баллов,
  • Очки рейтинга-1
АТ
10 декабря 2019 г. 8:02
Анастасия Трощенкова

Qt - Тест 001. Сигналы и слоты

  • Результат:73баллов,
  • Очки рейтинга1
Последние комментарии
9 декабря 2019 г. 3:41
Евгений Легоцкой

Эта ошибка invalid use of incomplete type ‘class Ui::AnotherWindow’ обычно говорит о том, что не найдено определение класса или структуры. Типичная проблема - не подключён заголовочны…
НБ
9 декабря 2019 г. 3:36
Николай Батманов

Ну, не настолько со мной всё полхо...))) Вроде бы. Я ж кнопки отрисовываю.
9 декабря 2019 г. 3:14
Евгений Легоцкой

Добрый день. У вас ui файлов по ходу нет. UI файлы используются для вёрстки в графическом дизайнере.
НБ
9 декабря 2019 г. 3:05
Николай Батманов

Здравствуйте! Полностью скопировал ваш пример к себе, чтобы разобраться. А он не хочет запускаться, дает ошибку: invalid use of incomplete type ‘class Ui::AnotherWindow’ ui(new Ui…
8 декабря 2019 г. 7:23
Евгений Легоцкой

У меня здесь есть одна старая статья с примером векторного редактора. Там есть ответы на ваши вопросы. Поизучайте Qt/C++ - Урок 072. Пример векторного редактора на Qt QGraphicsItem, QG…
Сейчас обсуждают на форуме
12 декабря 2019 г. 11:49
qml_puthon_user

Да, с console.log() я разобрался, счётчик ничего не показывает
12 декабря 2019 г. 9:27
Евгений Легоцкой

Добрый день. Вообще Qt Designer это рисовалка формочек. Вы хотите от него больше, чем в нём есть. Максимум, что вы можете сделать, это накидать всю вёрстку в дизайнере, а потом привязыватьс…
MU
11 декабря 2019 г. 8:27
Maciej Urmański

Thank you! Now works, and this is solution. num_embed = Embed.objects.filter(added_by=recipe.added_by).count()
11 декабря 2019 г. 8:12
Михаиллл

Так работает. Взял этот пример https://api-2d3d-cad.com/face_recognition_with_opencv/ void MainWindow::on_pushButton_4_clicked() //фото определение лица{ // Load Face cascade (.xml…
ТД
10 декабря 2019 г. 4:14
Тимур Досов

Спасибо, работает. А ещё вопрос: как загрузить страницу с динамической подгрузкой контента по скроллингу? Например - [https://ntvplus.ru/tv/]. Пока делаю через костыль - QApplication::s…
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB