Реклама

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);
}

Заключение

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

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • JaJay
  • 17 декабря 2017 г. 5:16

C++ - Тест 002. Константы

  • Результат 58 баллов
  • Очки рейтинга -2
  • JaJay
  • 17 декабря 2017 г. 4:55

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

  • Результат 93 баллов
  • Очки рейтинга 8
  • JaJay
  • 17 декабря 2017 г. 4:48

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

  • Результат 66 баллов
  • Очки рейтинга -1
Последние комментарии
  • EVILEG
  • 7 декабря 2017 г. 9:47

Django - Урок 011. Добавление комментариев на сайт с Django

Визуальный пример чего? комментариев? При ответе на конкретный комментарий рядом с ником отвечающего будет стрелочка и указание ник другого пользователя. Который будет ссылкой на коммента...

  • Bernar
  • 7 декабря 2017 г. 9:24

Django - Урок 011. Добавление комментариев на сайт с Django

есть визуальный пример ?

  • EVILEG
  • 6 декабря 2017 г. 11:30

Django - Урок 011. Добавление комментариев на сайт с Django

Да, так будет даже лучше, я на сайте уже обновил до такого вида код Вот это уже не нужно if request.method == 'POST': Поскольку Вы и так используете метод post, то есть эта про...

  • Bernar
  • 6 декабря 2017 г. 11:19

Django - Урок 011. Добавление комментариев на сайт с Django

сделал немного по другому class EArticleView(View): template_name = 'knowledge/article.html' comment_form = CommentForm def get(self, request, *args, **kwargs): ...

Сейчас обсуждают на форуме
  • EVILEG
  • 16 декабря 2017 г. 17:23

Пауза в многопоточности

QFuture, который возвращается QtConcurrent::map имеет методы pause() и resume() и теоретически должен поддерживать этот функционал. Но для Qt...

  • Миша
  • 15 декабря 2017 г. 11:26

Как найти в QVector макс и мин

Спасибо

  • Galant
  • 14 декабря 2017 г. 19:58

LPT

Понял! Спасибо!

  • EVILEG
  • 14 декабря 2017 г. 13:38

QCustomPlot можно ли построить прерывистую линию на одном графике?

Во-первых: В pro файле проект по идее достаточно указать следующий define для включения возможности рендеринга через OpenGL DEFINES += QCUSTOMPLOT_USE_OPENGL И во вторых:...

  • EVILEG
  • 13 декабря 2017 г. 8:05

В многопоточности выполнять действие только в одном из потоков

Статическиe методs QThread::currentThread(); и QThread::currentThreadId() могут возвращать указатель на поток и его handle id соответственно. Можете попробовать через как...